@spechycom/archon
v0.20.0
Published
Readme
@spechycom/archon
A deterministic context compiler for AI coding agents.
Your project knowledge lives in a DAG of small, single-purpose documents
instead of one giant CLAUDE.md. The agent says "I'm about to touch
src/api/users.ts" — archon returns exactly the rules wired to that path,
nothing else, over MCP. Same request + same knowledge version = same bytes,
every time.
Contents
| | | |---|---| | Get started | Why · How it works · Quick start · Installation · Building the knowledge base | | Daily use | What a session looks like · Capturing decisions · Team workflows | | Reference | Tools — agent surface · Tools — admin surface · CLI · Prompts & resources | | Concepts | Knowledge model · Delivery budget · Determinism | | Beyond the DAG | Semantic embeddings · Code awareness · Episodic memory · Document discovery · Graph visualization · Quality tooling | | Plumbing | Editor adapters · Hooks · Agent Skills · Status & limitations · Development |
Why
A single instruction file (CLAUDE.md, .cursorrules, AGENTS.md) has
three failure modes, and archon is built against each one:
| Single-file failure | What archon does instead |
|---|---|
| Every rule rides in every prompt, most of them irrelevant | Only documents reachable from the target files are compiled |
| Token cost grows with the file, forever | An allocator inlines what fits and defers the rest as file references |
| No record of what the agent actually read | Every compile is written to an audit log, queryable by compile_id |
The core retrieval path is deliberately dumb: no embeddings, no LLM, no randomness — four resolution steps over SQLite. Everything smarter (semantic suggestions, symbol graphs, episodic memory) is layered on top as additive, fail-open extras that can never change or break the deterministic result.
How it works
compile_context resolves the target files against the knowledge DAG in
four deterministic steps:
target_files ──▶ 1. path_requires glob match (picomatch) per file
2. layer_requires file → layer (layer_rules) → docs
3. command_requires exact match on the `command` argument
4. doc_depends_on transitive closure over matched docs
│
▼
ordering: specificity ↓, priority ↑, doc_id ↑
│
▼
intent_tags expansion (optional, additive)
│
▼
relevance filter (optional, needs `plan`)
│
▼
delivery allocator → inline / deferred / omitted
│
▼
audit row + snapshot_id + knowledge_versionThe read path only ever sees status = 'approved' rows, and cycles in
doc_depends_on are rejected at insert time — step 4 always terminates.
Quick start
Run the setup wizard from your project root:
npx @spechycom/archon@latest setupInteractive, no flags to memorize: pick your agent(s) (Claude Code, Cursor, Codex), it registers both MCP servers for you (
archonagent surface +archon-admin), detects your stack and previews a knowledge template (or starts empty), deploys the editor adapters, and offers an optional local-embeddings step. One command, one screen at a time, a summary before anything is written.Keep coding normally. The wizard already deployed the editor adapters:
CLAUDE.md/AGENTS.md/ Cursor rules now instruct the agent to call archon before touching any file. You never call a tool by hand.Run the same command again any time to update. An existing
.spechy/is detected automatically and the wizard switches to update mode — version bump, template upgrade, adapter redeploy, symbol reindex, and (if enabled) an embeddings refresh, all idempotent.
Requires Node.js >= 18. Windows is supported — the wizard is written to
behave the same in PowerShell/cmd.exe as in a POSIX shell — but it's new
and hasn't had a field validation pass yet; if something looks wrong,
please file an issue. The
SQLite database is created and migrated on first start — plain
DELETE-journal mode, one .db file, no -wal/-shm sidecars.
Can't or don't want to run the wizard (CI, a locked-down shell, hand control over every file)? See Manual installation below — it's the same result, one step at a time.
Installation
Two surfaces, deliberately two separate MCP server processes:
- agent — read-only, cannot mutate knowledge. 9 tools. This is what your coding agent gets.
- admin — everything including the write path, 37 tools total. Keep it
away from the coding agent's session: an agent holding
spechy_approve_proposalcan approve its own proposals, which defeats the human-approval boundary (P-3, INV-6).
The split is enforced twice: admin tools are not registered on the agent
surface (invisible to tools/list, "unknown tool" at the protocol level),
and every admin service method independently asserts the surface as its
first statement — even a caller bypassing the MCP server entirely cannot
mutate canonical knowledge from the agent surface.
Always pass --project-root explicitly: an MCP client's cwd is not
predictable, and file_path imports are sandboxed to this root.
The setup wizard (see Quick start) does everything below
for you — registration for both surfaces, per-client. Read on only if you
want to do it by hand, or the wizard isn't an option in your environment.
Manual installation
Claude Code
Option 1 — edit .mcp.json (project root; for Cursor the same JSON
goes in .cursor/mcp.json):
// .mcp.json — only the agent surface belongs here
{
"mcpServers": {
"archon": {
"command": "npx",
"args": [
"-y", "@spechycom/archon",
"--surface", "agent",
"--db", ".spechy/archon.db",
"--project-root", "/absolute/path/to/project"
]
}
}
}Commit this file — that's what MCP's project scope is for. Do not add
archon-admin to .mcp.json: the file has no per-agent scoping, so
every server in it connects to the coding agent's session. The admin entry
goes in your personal (local) scope instead:
claude mcp add-json archon-admin '{
"command": "npx",
"args": ["-y", "@spechycom/archon", "--surface", "admin", "--db", ".spechy/archon.db", "--project-root", "'"$PWD"'"]
}' --scope localOption 2 — CLI for both: same claude mcp add-json shape with
--scope project for archon (agent). Run /mcp inside Claude Code
afterwards to confirm both servers connected.
(Context cost of mounting admin anyway is small where tool schemas load on demand — measured once at ~426 tokens — so keep it out for the boundary, not the tokens.)
Codex
codex mcp add archon -- npx -y @spechycom/archon --surface agent --db .spechy/archon.db --project-root "$PWD"
codex mcp add archon-admin -- npx -y @spechycom/archon --surface admin --db .spechy/archon.db --project-root "$PWD"Or write the same two entries into ~/.codex/config.toml directly. Codex
has no per-agent scoping either — only add archon-admin where you control
who drives the session. codex mcp list confirms registration.
Windows
"$PWD" is bash/zsh syntax. On Windows, edit .mcp.json /
config.toml directly (plain JSON/TOML, no shell involved) and use forward
slashes in --project-root (C:/Users/you/project) — Node resolves them
fine and you skip \\ escaping. From PowerShell, "$($PWD.Path)" replaces
"$PWD"; from cmd.exe, paste the absolute path literally. WSL counts as
Linux.
Flags
| Flag | Default | Notes |
|---|---|---|
| --surface | agent | agent or admin |
| --db | .spechy/archon.db | Relative paths resolve against --project-root; parent dir auto-created |
| --project-root | process.cwd() | Always pass it explicitly |
| --templates | — | Overrides the local templates directory used by the init_* tools |
Building the knowledge base
Three entry points, by what you're starting from:
A — new project, bootstrap from a template
Packaged templates: Laravel, Next.js, Python, Go (the last two match on language alone). Vite is recognized as a signal but has no bundled template yet. Via the admin surface:
spechy_init_detect— read-only: inspects manifests (package.json,composer.json, …) and directory shape, scores the templates, returns a preview +preview_hash. Optionalplaceholdersoverride template values (Next.jsapp_rootis auto-detected betweensrc/app/app). No fit, or empty on purpose →skip_template: true.spechy_init_confirm({ preview_hash })— bootstraps canonical knowledge from the previewed template and deploys the editor adapters. The hash is process-scoped; a stale one is rejected.- Add your real project rules with
spechy_import_doc(see C), then verify with onespechy_compile_contextcall against a covered path.
The spechy-setup Agent Skill walks an agent through
exactly this.
B — existing docs to migrate
Don't init from a template — bulk-import what you have. The
spechy-bulk-import Agent Skill drives the analyze
pipeline: spechy_analyze_import_batch splits each file into import units
by ## heading and infers kind/edge_hints,
spechy_execute_import_plan turns the reviewed plan into pending proposals
sharing one bundle_id, and spechy_approve_proposal_bundle lands the
whole batch as a single snapshot. Nothing enters canonical knowledge
without that approval step.
C — one document at a time
{
"doc_id": "api-constraint",
"title": "API layer constraints",
"kind": "constraint",
"content": "Never call fetch directly from components. Always go through src/api/client.ts.",
"edge_hints": [{ "edge_type": "path_requires", "source_value": "src/api/**" }]
}spechy_import_doc (admin) with inline content or a repo-relative
file_path. Write documents small and single-purpose — a document is
the unit of retrieval, so one covering three topics gets pulled in for all
three. The tool warns above 4096 bytes, at 2+ ## headings, and when no
edge_hints are given (an edge-less document is unreachable from any
compile). Re-importing a doc_id overwrites content but accumulates
edges. A fresh database tells you so: compile_context returns a
"Project not initialized" warning instead of silently returning nothing.
Alternatively, keep rules in a YAML seed file (documents[] with
file/patterns/depends_on, plus layer_rules[]) committed to git:
spechy-archon seed spechy.seed.yamlWhat a session looks like
You don't call tools yourself — the deployed adapter text instructs the agent. What that adds up to, scenario by scenario:
Before touching a file the agent calls spechy_compile_context with
the files and a one-line plan, reads what comes back inline, opens
deferred documents' source_path itself, and obeys notices.
"Did this change break a written rule?" — spechy_verify_done with the
session's diff mechanically checks changed files against every approved
constraint carrying a ```verify YAML block (forbid_import,
require_companion), and suggests likely-affected test files. No human has
to spot the violation first.
"What does changing these files touch?" — spechy_impact({ files })
lists every approved document the DAG maps those files to, with the exact
edge as evidence. It answers which rules apply; verify_done answers
was a rule broken.
"How does the code get from A to B?" — spechy_search_logic_flow
BFS-searches the tree-sitter call graph for the shortest call path between
two symbols. See Code awareness.
"Didn't we try this before?" — spechy_recall returns past runs:
files touched, plans, session notes. Never injected automatically — asked
for when the work has a past. compile_context's notices independently
flag failed/reverted attempts from the last 90 days on the same files.
The agent missed a rule you know exists — it (or you) files
spechy_observe with event_type: "compile_miss"; a wrong-content rule
gets "review_correction". Neither touches canonical knowledge — an admin
triages later (spechy_process_observations → proposals → approve/reject).
The spechy-triage Agent Skill has the decision tree.
Reviewing / scaffolding / explaining — the three bundled prompts cover "review these files against their rules", "scaffold with template documents included", and "explain the rules for this file to a human".
A doc's source file drifted — spechy_sync_docs (admin) checks
file-anchored documents against their sources and opens an update_doc
proposal per drifted one; dry_run: true previews.
A packaged template got a new version — compile_context notices tell
you proactively; spechy_check_upgrade shows the diff,
spechy_apply_upgrade turns it into pending proposals (never overwriting
documents you've modified), and spechy_sync_init_manifest re-baselines
after the bundle is resolved — skip that last call and the same upgrade is
offered forever.
Health checks — spechy_workspace_status for a read-only dashboard;
spechy-archon doctor for the full scan (isolated docs, dead edges,
missing/stale sources, cycles, oversized docs, near-duplicate rules,
co-change suggestions).
Capturing decisions (standing rules)
Tell the agent something meant to outlive the turn — "from now on, don't
run tests unless I say so" — and the adapter text has it call
spechy_observe with event_type: "manual_note" and ask you to confirm.
Your next prompt commits it: the UserPromptSubmit hook reads your
reply, and a confirmation approves the rule into the knowledge base and
writes it to .spechy/rules.md — a plain-text, git-tracked catalogue
you can read or hand-edit (run sync-rules-file after editing directly).
Approving the same proposal from the admin surface lands in the same place.
If the new rule collides with an existing one, observe returns
conflict_candidates and the agent offers keep both / replace / cancel —
persisted via spechy_resolve_conflict.
To retire a decision outright, call spechy_delete_decision (admin
surface) with its doc_id — no counter-rule or conflict dance needed. It
is a soft-delete: the rule is deprecated (its edges follow, the knowledge
version bumps, a snapshot is written) and disappears from every compile,
but the record stays in history and snapshots. There is deliberately no
hard-delete: snapshots are immutable (INV-3).
Rules scoped to a file/layer/command come back through normal compiles. Behaviour rules with no file to attach to are never loaded unconditionally: the hook matches your prompt's wording against each rule and injects only what looks relevant — deliberately noise-limited (a rule already mentioned this session isn't re-injected, and there's a hard per-session cap).
Team workflows
Cheapest first:
- Standing rules sync for free — commit
.spechy/rules.md. A teammate who pulls picks the rules up on their next session; the hook absorbs whatever's on disk into its own database before rendering over it, so a pulled rule is never silently lost. - Commit a
spechy.seed.yaml, everyone runsspechy-archon seedafter pulling — reviewable as a normal PR diff. Best for structural knowledge. spechy-archon export <file>/import <file>— portable JSON snapshot of the whole database. One-off syncs, pre-risky-operation backups, machine moves.importfully replaces the target DB.- Rebuild per environment from the same sources (
spechy-bulk-import). - Commit the
.dbitself as a last resort — works, but every pull that changes it swaps the file under the running server (which reopens the connection by itself, at the cost of one warning-carrying compile).
If the team diverges (knowledge_version mismatch), compiles stop matching
between people — see docs/TROUBLESHOOTING.md.
More role-by-role workflows: docs/USAGE.md.
Tools — agent surface
Tool names take their prefix from one place
(src/config/adapter-config.ts), appearing as spechy_compile_context etc.
9 tools, all read-only against canonical knowledge:
| Tool | One-liner |
|---|---|
| compile_context | The main event — compile the rules for a set of target files |
| verify_done | Mechanically check a diff against verify-block constraints |
| impact | Which approved documents do these files map to, and via which edges |
| search_logic_flow | Shortest call path between two code symbols |
| get_compile_audit | Full audit record for a past compile_id |
| recall | Episodic index of past runs, plans and session notes |
| get_known_tags | Approved intent-tag catalog + tag_catalog_hash for caching |
| observe | Report a gap/decision/outcome — never mutates knowledge itself |
| workspace_status | Read-only dashboard: versions, backlogs, row counts |
spechy_compile_context
| Param | Type | Notes |
|---|---|---|
| target_files | string[] (min 1) | Required. Repo-relative paths about to be touched |
| target_layers | string[] | Skip layer classification, use these directly |
| command | string | Feeds command_requires; "scaffold" also unlocks templates |
| plan | string | Free text; enables relevance scoring |
| min_relevance | 0..1 | Drops documents below the threshold (needs plan) |
| intent_tags | string[] | Cross-cutting tags (e.g. "auth") resolved via tag_mappings into an additive expanded section; [] opts out |
| max_inline_bytes | int > 0 | Inline budget, default 8192 |
| content_mode | auto \| always \| metadata | Default auto — see Delivery budget |
| include_debug | boolean | Adds debug_info: near-miss edges, layer classification, budget drops |
| agent_id | string | Recorded in the audit log |
Response: compile_id, snapshot_id, knowledge_version,
base.documents[] (each with doc_id, title, kind, delivery,
content_bytes, content_hash, plus content when inline, source_path
when file-backed, relevance when a plan was given, omit_reason when
dropped), base.templates[], base.resolution_path[], optional expanded,
warnings[], notices[].
warnings vs notices: warnings are part of the deterministic result —
same input, same version, same warnings. Notices report time-varying
operational state: pending proposals, an observation backlog, a stale
adapter deploy, a newer bundled-template version, an empty knowledge base,
and failed/reverted attempts from the last 90 days touching the same files.
Because they're time-varying, notices are never written into the audit log.
debug_info.near_miss_edges is the one to look at when nothing matched: it
lists patterns that exist but didn't match, so you can tell a wrong glob
from a wrong path.
spechy_verify_done
Takes diff (unified diff text) and/or changed_files — at least one.
Checks the changed files/layers against every approved constraint
document carrying a fenced ```verify YAML block
(forbid_import: string[], require_companion: string[]); documents
without the block are silently skipped. Returns violations[]
(forbidden_import / missing_companion, each with the trigger file and
evidence), plus best-effort affected_tests[] — a one-hop static-import
scan combined with a git co-change signal, "run these first", never "only
run these".
Results are cached content-addressed in the database (keyed on input +
knowledge_version), with a small ephemeral .spechy/last-verify.json
pointer; the PreCompact hook re-injects the last verdict after context
compaction so a verified change isn't re-litigated. Inspect/prune with
spechy-archon verify-cache --status [--prune-stale].
spechy_impact
{ files: string[] } → every approved document reachable via
path_requires/layer_requires (all kinds, not just constraints), plus
second-order documents through doc_depends_on — each with
evidence[] (edge type, id, pattern, matched file) and a
via_dependency_chain[] (empty = direct match). command_requires is out
of scope in v0.
spechy_search_logic_flow
from_symbol/to_symbol (name or symbol_id) + optional max_depth
(default and hard cap 10). BFS over the symbol graph's calls edges only —
imports/type references/extends/implements are not execution steps. Returns
every path at the shortest distance (paths: string[][]), depth: null
when unreachable, and ambiguous_from/ambiguous_to (BFS doesn't run)
when a name matches multiple symbols. Language coverage:
| Language | Extensions | class | function | method | interface | extends | implements |
|---|---|---|---|---|---|---|---|
| TypeScript | .ts .tsx | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| JavaScript | .js .jsx .mjs .cjs | ✅ | ✅ | ✅ | — | — | — |
| PHP | .php | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (incl. trait use) |
| Python | .py | ✅ | ✅ | ✅ | — | ✅ | — |
| Go | .go | ✅ (struct) | ✅ | ✅ (receiver) | ✅ | — (no inheritance) | — (implicit) |
Only file-local qualified-name matches resolve to a concrete
to_symbol_id; cross-file references stay name-only. The index is built by
spechy-archon reindex-symbols (full-repo scan via git ls-files,
unchanged files skipped by content hash, cross-file edges resolved in one
batch pass) — installed as a SessionStart hook so it refreshes itself.
spechy_observe
The agent's one write-shaped tool — it records observations, never canonical knowledge:
| event_type | Required payload |
|---|---|
| compile_miss | target_files[], review_comment (+ optional missing_doc, target_doc_id) |
| review_correction | target_doc_id, proposed_content, review_comment |
| pr_merged | changed_files[] (+ optional pr_title) |
| manual_note | note (+ optional hints[], abstract) |
| document_import, doc_gap_detected | free-form object |
compile_miss answers with a triage_hint ("1 near-miss edge exists for
src/api/** — check whether target_files should match it").
manual_note/document_import payloads are secret-scanned (AWS/PEM/
GitHub/Slack/OpenAI key patterns + entropy) — a hit warns, never blocks.
spechy_recall
Filters: files, run_id, recent_runs (default 10, max 50), free-text
search, gaps: true for accumulated near-miss counts. Returns an
index — pointers, not content; follow a compile_id into
get_compile_audit for detail. Search ranks episodic hits by a blended
score (½ text, ¼ recency, ¼ graph proximity) and penalizes hits whose
knowledge has since been superseded. Deliberately never injected into
compile_context. Over-budget results say truncated: true.
The rest
get_compile_audit(compile_id) returns the recorded request, resolved doc
ids, delivery stats, budget utilization, drops, near-misses, layer
classification and duration — errors are logged too, so a failed compile is
still auditable. get_known_tags lists the approved intent-tag catalog
with a tag_catalog_hash worth caching. workspace_status reports
knowledge_version, latest snapshot, recent compile regions, unresolved
counts, pending proposals and canonical row counts — and writes nothing,
not even a compile-log row.
Tools — admin surface
The admin surface has all 37 tools: the 9 above plus 28 write-path and curation tools.
Importing & maintaining documents
| Tool | Notes |
|---|---|
| import_doc | Direct import as approved — one transaction: content + edges + version bump + snapshot. Warns on >4096 bytes, 2+ ## headings, missing edge_hints, likely secrets. Edges accumulate across re-imports |
| analyze_doc / analyze_import_batch | Read-only: split source files into suggested import units (by ## heading), infer kind/tags/edge_hints, flag overlap with approved docs (and between items, for the batch form) |
| execute_import_plan | Turns a reviewed plan into pending new_doc/add_edge proposals sharing a bundle_id. Atomic — one collision rolls back the whole call |
| sync_docs | Drift check for file-anchored documents against their sources (whole-file hash or section/line anchor); opens update_doc proposals, dry_run previews |
| set_doc_tags | Replaces a document's tag_mappings entry. Outside the canonical DAG: no version bump, no snapshot, no approval |
| get_document / get_edge | Full row by id regardless of status — for chasing ids surfaced by doctor |
Proposals & observations — the human-approval pipeline
| Tool | Notes |
|---|---|
| list_proposals | One status per call (pending default); each carries computed secret_scan_warnings |
| get_proposal | Proposal + evidence_observation_ids[], or null |
| approve_proposal | Applies the mutation, bumps knowledge_version, writes a snapshot; modifications shallow-merge over the payload first |
| reject_proposal | Marks rejected with a comment; never touches knowledge |
| preflight_proposal_bundle | Read-only dry run of a bundle: order, dependency/conflict validation, simulated outcome |
| approve_proposal_bundle | Applies a whole bundle in dependency order as one version bump + one snapshot; validation failure rolls back everything |
| process_observations | Runs every analyzer over unanalyzed observations → { proposed, skipped, errors[] } |
| list_observations / archive_observations | Triage helpers; archiving means "triaged", not "resolved" |
| resolve_conflict | Persists the keep-both / supersede / cancel answer to a conflict_candidates hit |
| delete_decision | Deletes a captured decision (standing rule) directly — soft-delete: deprecates the document (edges cascade, version bump, snapshot), history stays intact. Rejects non-standing documents |
Init, upgrade & operations
| Tool | Notes |
|---|---|
| init_detect / init_confirm | Template bootstrap with preview + process-scoped hash (see Building the knowledge base) |
| check_upgrade / apply_upgrade / sync_init_manifest | Template upgrade as pending proposals; approve document-first (edge approval doesn't verify its target exists), then re-baseline the manifest |
| deploy_adapters | Same as the CLI deploy-adapters — callable by an agent when notices flag a stale adapter file |
| doctor / get_stats | Health scan and row counts + doc-usage stats (never_delivered hints at recall({ gaps: true }) for per-edge reasons) |
| review_edge_scopes / apply_edge_scopes | Scope review for path_requires edges whose glob matches most of the project. Deciding a rule's true scope is a semantic judgement, so archon asks the connected agent instead of guessing; every glob the agent returns is validated against the real file list, and the outcome is a bundle of remove_edge + add_edge proposals a human still has to approve (see ADR 0057) |
| maintenance | Read-only gap-coverage preview of what process_observations would keep/drop |
Detailed parameter shapes for the init/upgrade family:
docs/adr/0013-init-and-templates.md.
CLI
All commands share --db / --project-root.
spechy-archon setup # interactive setup/update wizard (agent
# registration, init, adapter deploy, repair)
spechy-archon seed <file> # load a YAML seed file
spechy-archon stats # row counts at a glance
spechy-archon doctor # full workspace health scan
spechy-archon export <file> # JSON backup of the whole database
spechy-archon import <file> # restore (fully replaces the DB)
spechy-archon deploy-adapters # write adapter files + skills + hooks
# [--targets claude,cursor,codex] [--dry-run]
spechy-archon sync-rules-file # absorb hand-edits to .spechy/rules.md
# (diffed add/update/deprecate, never a wipe)
spechy-archon sync-rules # sync the shared rules package into documents
# [--check-latest] asks npm for newer versions
# (network, fail-open; runs on post-merge)
spechy-archon reindex-symbols # full-repo tree-sitter symbol reindex
spechy-archon graph [<file>] # interactive HTML view of the knowledge base
# (default .spechy/graph.html)
spechy-archon eval [--replay] [--json]
# delivery-quality report from the audit log;
# --replay re-runs logged requests on today's code
spechy-archon bench [--full] [--external] [--json]
# retrieval/DAG/resource benchmark; --full adds
# LLM-judge A/B (needs ANTHROPIC_API_KEY);
# --external adds 15 real-commit cases,
# reported separately from the internal corpus
spechy-archon embed-docs # compute/update Ollama embeddings
# [--model <name>] [--ollama-url <url>]
spechy-archon verify-cache --status [--prune-stale]
# inspect / prune the verify_done cache
spechy-archon prune-log --older-than <days>
# fold old audit rows into aggregates, then
# delete them — no default on purpose
spechy-archon maintenance # gap-coverage preview; --reclaim-observations
# [--stale-after-days <n>] re-queues analyzed-
# but-never-cited observations (default 1 day)
spechy-archon webui # local browser admin panel (127.0.0.1 only)
# [--port <n>] [--no-open]An unknown command or flag prints usage and exits 1 instead of silently
starting the server. The hook forms (sync-rules --hook,
verify-cache --hook, reindex-symbols --hook) are installed for you by
deploy-adapters — see Hooks.
Prompts & resources
The MCP server also exposes three prompts (visible in Claude Code's /
menu) and one resource:
| Prompt | Args | What it does |
|---|---|---|
| spechy-review | target_files (comma-separated) | Compile context for the files, then review them against exactly what came back |
| spechy-scaffold | target_files | Compile with command: "scaffold" so template documents are included, then follow them |
| spechy-explain | target_file | Read-only: summarize the matched documents for a human, propose nothing |
spechy://guide/workflow is a markdown operating guide covering the
read/write/approve cycle — the same content underlying
docs/USAGE.md.
Knowledge model
Documents — doc_id, title, kind (guideline / pattern /
constraint / template / reference), content, content_hash,
status, source_path. template is special-cased by the allocator.
Edges — how a document becomes reachable:
| edge_type | Source | Matching |
|---|---|---|
| path_requires | glob pattern | picomatch against each target file |
| layer_requires | layer name | file → layer via layer_rules, or target_layers |
| command_requires | command name | exact string equality |
| doc_depends_on | doc_id | transitive pull-in; cycles rejected at insert |
Layer rules — path_pattern → layer_name; most specific match wins per
file, unmatched classifies as null.
Specificity is computed from the pattern, never stored by hand: per /
segment, ** adds 0, a *-containing segment adds 1, a literal adds 2 —
so src/api/client.ts (6) outranks src/api/** (4) outranks src/** (2).
Only path_requires edges have specificity. Final ordering is
specificity ↓, priority ↑, doc_id ↑ — no ties left to iteration order.
Delivery budget
Every resolved document ends up inline (content in the response),
deferred (metadata + source_path, the agent reads the file itself) or
omitted (policy):
- Templates are omitted unless
command === "scaffold"orcontent_mode === "always". - Documents without a
source_pathexist only in the database — there is nothing on disk to defer to, so they always inline. If their total exceedsmax_inline_bytes, the compile returns abudget_exceedederror naming the offenders instead of silently truncating. - Everything else competes for the remaining budget, ordered by class
(template → document → expanded), then relevance ↓, priority ↑,
doc_id↑. Underauto, a file-backed document over 4096 bytes never becomes an inline candidate;metadatadefers everything;alwaysmakes every file-backed document a candidate.
Relevance (when a plan is given) is the fraction of plan terms found in
the document — word-boundary and camelCase-aware, purely lexical. It orders
and optionally filters; it is not semantic search (that's
embeddings).
Determinism
| | |
|---|---|
| INV-1 | The read path only sees status = 'approved' rows |
| INV-2 | doc_depends_on cannot form a cycle |
| INV-3 | Snapshots are immutable once written |
| INV-4 | knowledge_version increases monotonically |
| INV-5 | Every compile is auditable (inputs, outputs, snapshot). Two documented narrowings: an unwritable DB reports the missed audit write in warnings rather than failing the call, and prune-log folds old rows into aggregates before deleting them |
| INV-6 | The agent surface cannot mutate canonical knowledge — admin tools aren't registered on it at all |
| P-1 | Same input + same knowledge_version = same output |
| P-3 | Every mutation of canonical knowledge requires human approval |
Reasoning behind each: docs/INVARIANTS.md and
docs/adr/.
Semantic embeddings
On by default, zero configuration, fully local. The deterministic DAG result never depends on this — embeddings only add suggestions for rules about the same topic in different words (a "billing" rule surfacing for a "payments" plan). Runs against a local Ollama server; nothing leaves your machine.
If Ollama isn't running when needed, archon starts it itself and shuts it
down after — it never touches an instance it didn't start. Startup gets up
to 30 seconds; past that (or with Ollama not installed at all) the call
falls back to the plain deterministic result with a note in warnings. No
request ever fails because of embeddings.
To get value from it:
ollama pull granite-embedding:278m # once, ~560MB, multilingual
spechy-archon embed-docs # re-run after editing rules;
# only re-embeds what changedSPECHY_EMBED_MODEL and SPECHY_OLLAMA_URL customize which model/server
— they don't turn the feature on or off.
Code awareness
Three layers, all read-only and fail-open:
- Symbol graph —
reindex-symbolsparses the repo with tree-sitter (TS/JS/PHP/Python/Go) intosymbols/symbol_edges;search_logic_flowanswers path questions over it. - Impact analysis —
impactmaps changed files to the rule surface they enter. - Mechanical verification —
verify_doneenforces machine-checkable constraint rules against a diff and suggests affected tests, with a compaction-surviving result cache.
Details under Tools — agent surface.
Episodic memory
Every server run is recorded (files touched, plan, best-effort session id);
turn outcomes land as session_note observations. recall queries this
history on demand, and compile notices proactively mark files with recent
failed/reverted attempts. History is kept out of compile_context's
result on purpose — the past is a pointer, not a prompt tax.
Document discovery
New doc-like files added to the repo after your initial import don't slip
through: the SessionStart hook diffs git ls-files against approved
documents and pending proposals, announces "N new document candidates" at
session start, and points at the spechy-bulk-import skill. Discovery
itself proposes nothing — the import still goes through pending proposals
and human approval (P-3).
Graph visualization
spechy-archon graph # writes .spechy/graph.html
spechy-archon graph out.html # or whereverOne interactive HTML file: approved documents, every edge type, embedding-similarity links, and the pending backlog (proposals/observations) as ghost nodes. No server needed; it loads D3 from a CDN, so viewing needs internet. Good for spotting isolated clusters, over-connected hubs, and where the backlog accumulates.
Quality tooling
eval— delivery-quality report from the audit log: missed and unnecessary deliveries, budget waste;--replayre-runs logged requests against their own snapshot with today's code.bench— labeled retrieval/DAG/resource corpus;--externaladds 15 real-commit cases reported separately (so "100% on internal fixtures" can't hide "60% on real tasks");--fulladds an LLM-judge generation A/B (needsANTHROPIC_API_KEY, skipped without it).doctor— the health scan, also callable as an admin tool.prune-log— audit-log retention with the fold-then-delete rule; auto-generated session notes are pruned, hand-written ones kept.
Editor adapters
deploy-adapters writes a managed rule block into the files editors
actually read, and installs the bundled skills and hooks:
spechy-archon deploy-adapters --db <path> --project-root <path> [--targets claude,cursor,codex] [--dry-run]| Target | Files |
|---|---|
| claude | CLAUDE.md + .claude/skills/*/SKILL.md + .claude/settings.json hooks + git hooks |
| cursor | .cursor/rules/spechy-process.mdc + .cursor/skills/*/SKILL.md |
| codex | AGENTS.md + .codex/skills/*/SKILL.md |
CLAUDE.md/AGENTS.md get a block wrapped in <!-- spechy:start --> /
<!-- spechy:end --> — everything outside it is yours and never touched.
Missing/duplicated markers error out rather than guessing at a rewrite. The
Cursor file is entirely spechy-owned (<!-- spechy:managed -->); an
existing file without that marker is treated as hand-written and reported
as a conflict (which exits 0 — a warning, never a blocker).
On every compile the server re-renders each deployed target with the
running version's config and compares hashes recorded in adapter_meta; a
mismatch (e.g. after upgrading archon) surfaces as a notice. This does
not detect hand-edits after deploy — the check reads adapter_meta, not
the file (known limitation, see
docs/adr/0012-adapters.md).
The rule block itself is the entire enforcement mechanism — MUST/NEVER instructions the agent reads every session. There is no technical gate that forces an agent to compile first.
Hooks
Installed by deploy-adapters (claude target). All fail-open: an erroring
hook logs to stderr and exits 0 — never blocks a session, commit or merge.
Claude Code hooks (marker-managed entries in .claude/settings.json;
your own hooks are left alone):
| Event | Command | Purpose |
|---|---|---|
| SessionStart + UserPromptSubmit | sync-rules --hook | Absorb .spechy/rules.md edits, inject relevant behaviour rules, commit confirmed rule captures — see Capturing decisions |
| SessionStart | reindex-symbols --hook | Refresh the symbol index + run document discovery |
| PreCompact | verify-cache --hook | Re-inject the last verify_done verdict after compaction |
Git hooks (managed blocks in .git/hooks/):
| Hook | Command | Purpose |
|---|---|---|
| pre-commit | sync-rules | Keep shared rules-package content synced into documents |
| post-merge | sync-rules --check-latest | Same, plus one fail-open registry check for a newer rules package |
Agent Skills
Three Agent Skills ship in skills/ and deploy
with the adapters:
| Skill | Use when |
|---|---|
| spechy-setup | Setting up archon in a project, configuring MCP, bootstrapping knowledge |
| spechy-bulk-import | Bulk-importing existing documentation via the analyze pipeline + bundle approval |
| spechy-triage | Triaging observations, reviewing compile misses, maintaining knowledge quality |
Source lives flat (skills/spechy-setup.md); deploy rewrites cross-links
and drops each at <target>/skills/<name>/SKILL.md with a
<!-- spechy:managed-skill --> marker — a hand-written file at that path
without the marker is never overwritten, only reported as a conflict.
Status & limitations
Everything documented above is shipped. Known gaps, current as of this writing:
list_proposalsfilters one status per call — no combined query.archive_observationsdoesn't check evidence — archiving an observation already cited by a proposal is allowed on purpose ("triaged" ≠ "resolved").apply_upgradedoesn't finalize itself — callsync_init_manifestafter resolving the bundle, or the same upgrade is offered forever.intent_tagsresolve nothing untilset_doc_tagspopulatestag_mappings— nothing does that automatically; an SLM tagger port exists (src/core/read/tagger.ts) but isn't wired in.- Four packaged templates (Laravel, Next.js, Python, Go). Vite is a
recognized signal without a bundled template. Everything else:
bulk-import or manual, via
skip_template: true. - Adapter staleness detection reads
adapter_meta, not the files — hand-edits after deploy go unnoticed. search_logic_flowv1 scope — Python decorators and Go generics/defined non-struct types are not parsed; Go extends/implements are deliberately empty (no inheritance; interface satisfaction is structural).
Development
pnpm install
pnpm test # vitest
pnpm check # tsc --noEmit && biome check .
pnpm build # rm -rf dist && tsc -p tsconfig.build.jsonRun a local build without publishing:
node dist/main.js --surface admin --db /tmp/archon.db --project-root "$PWD"Layout: src/core/store (schema, repository, snapshot, seed, migrations),
src/core/read (compiler, allocator, relevance, embedder, verify, impact,
symbol graph), src/core/memory (search, recall, eval, bench, prune),
src/core/init (detection, templates, bootstrap/upgrade),
src/core/automation (observation analyzers, secret scan),
src/adapters (editor targets + hooks), src/mcp (server, services),
src/config/adapter-config.ts (tool prefix — change it here, never as a
string literal).
License
Unpublished license terms — internal Spechy project.
