# agent-smith — AI Agent Instructions ## Context Files (read in this order) 1. **Every** `.agentsmith/contexts//context.yaml` (glob `contexts/*/context.yaml`) — read it down to `state:` for architecture, stack and integrations PER stack. `state.done` is an index, one line per finished spec with a pointer to it: search it for the area you touch, never read it front to back 2. **Every** `.agentsmith/contexts//principles.md` — code quality rules per stack (ALWAYS follow) 3. `.agentsmith/specs/active/*.yaml` — the spec being implemented (its `applies_to:` names the dominant context) 4. `.agentsmith/decisions/*.yaml` — past decisions, one YAML per spec or run (read the active spec's file and its `requires:` chain) 5. `.agentsmith/memory/MEMORY.md` — the experiential-memory index, one line per recorded fact; recall detail from `.agentsmith/memory/.md` on demand when a line touches your task ## Spec Directory Structure ``` .agentsmith/specs/ done/ # completed specs (historical reference) active/ # spec currently being worked on planned/ # upcoming specs with requirements ``` ## Experiential Memory (remember / recall) `.agentsmith/memory/` holds typed Markdown facts: one file per memory with frontmatter `name` (slug = filename), `description` (one line), and `metadata.type` (`feedback` = how the operator wants the agent to work; `project` = goals/constraints/state not derivable from code or git; `reference` = external pointers). `MEMORY.md` is the index — one line per memory, content never in the index. A memory is a **hint with a track record, never a rule**. No entry waits for a person's ratification: its standing is decided by the runs that use it and changes over time. - **Recall before re-deriving**: when the index hints at a fact you are about to work out from scratch, read the entry file instead. - **Remember sparingly**: store what code and git cannot already tell the next agent — one fact per file; check the index for an existing entry and update rather than duplicate; delete a memory that turns out wrong; link related memories as `[[slug]]` (a cited slug requires its committed definition). Write no `status:` — nothing ratifies a memory. - **Say what you relied on**: a spec's `hand_forward` names the memories it acted on in `relied_on`; `dispute_memory` names one that led you wrong and why. What became of the spec (done confirms, failed strains) and a person's not-met judgement of its criteria become the memory's evidence. - **Standing**: evidence fades with a 90-day half-life. An entry is `holding` while its evidence is positive, `unconfirmed` otherwise, and `disputed` once a contradiction leaves it negative — strains alone never dispute. A master's index lists each entry with its record, holding first, and leaves disputed entries out; the spec record step rewrites the labels in `MEMORY.md`. - **Memory vs decision**: a decision records a CHOICE (`decisions/`); a memory records a transferable FACT, held as a hint. Never duplicate one into the other. - **Project memory** (2026-11-10-fcd7a..c): an installation with a database also keeps memories on the PROJECT (`ProjectMemories`, keyed to the project's config entity), under the reserved label `@project` — no repository name may start with `@`. A design turn whose poster holds runs.control and a coding run (`remember` with repository `@project`) write there; each entry carries its provenance (conversation turn or run and spec, and author). It is cited `@project/slug`, listed first and quoted in every index, and deleting an entry deletes its ledger rows, so a re-created slug starts with no record. ## Minting a Spec Id A spec id is minted from the clock, never from a count: **today's UTC date plus four random hex digits** — `{yyyy-MM-dd}-{xxxx}`, e.g. `2026-08-24-8a3f`. Read the date off the machine you are on and take the four digits at random. Nothing else is consulted. Minting therefore needs no knowledge of what anyone else has taken. A worktree cut this morning, a sandboxed agent with no network, and two agents working in parallel all mint safely — the four hex digits give a 16-bit keyspace against a same-day collision. "What is the highest number so far?" is a question none of them can answer, and answering it wrongly is how two specs end up sharing one id. The suffix's **fixed width** marks where the id ends and the label begins: `.agentsmith/specs/planned/2026-08-24-8a3f-spec-id-offline-minting.yaml`. **Specs cut from one piece of work form a series.** They share ONE minted number and differ only by a trailing lowercase letter — `2026-08-24-8a3fa`, `2026-08-24-8a3fb`, `2026-08-24-8a3fc` — so a file listing shows the kinship that a shared date alone does not. Three rules keep that readable: - **The letter is appended, never dashed.** A slug may legitimately begin with a one-letter word (`2026-08-24-8a3f-a-spec-id-can-be-minted-offline` is the example `SpecSeriesIdTests` pins), so in a dashed form nothing could tell a series letter from the first word of a label. Appended, position decides: four hex digits, then either a dash or the letter. - **The base number is not itself a spec.** A series is `8a3fa` upward; plain `8a3f` names nothing, so nothing has to decide whether it means the series or a member. - **A series is minted in one go, and a later addition mints anew.** Appending a fifth member means knowing that `a` through `d` are taken — exactly the coordination the date-minted shape removed, and two agents extending one series would both reach for the same letter. A spec that turns up later gets its own number and says in prose what it follows. A series letter and a successor letter are the same mechanism and are not told apart; the counter namespace carried both meanings and nothing ever depended on the difference. **Counter ids (`p0042`, `p0057a`, `p0131c-pre`) are a closed namespace.** Every one of them stays valid forever and an id is never renamed. The namespace is closed to NEW ids only. Counter ids run four to six digits, six because ids minted from a ticket number (`p19106a`) live in the same namespace. **An id is frozen; a file name is a pointer.** Every `requires:` edge, every record line and every commit message cites the ID, which is why moving a spec file breaks nothing but the `-> .agentsmith/specs/…` pointer in `context.yaml` — and that pointer moves with the file (`SpecRecord_EveryPointer_ResolvesToAFile` proves every one resolves). Specs from p0400 upward and every date-minted spec were relabelled once, in 2026-09-07-4e6a; the cut is where the measurement turned (mean slug words 4.0 over p0350–p0399, 5.8 over p0400–p0449, 8.2 over p05xx, 7.6 over the dated specs). That was a one-off migration boundary, not a rule: the RULE is scoped by namespace and shape, never by an ordering, because a date-minted id sorts below every counter id as text. A deferred successor is named in prose by what it does — a random suffix cannot be reserved in advance, so `requires:` names only specs that already exist. ## Naming a Spec A spec file is `{id}-{label}.yaml`. The label is a **topic label of 2 to 5 words**, at most **50 characters**, and the `goal:` is one sentence of at most **200 characters**. The CLAIM lives in the goal — it already states it, with room, punctuation and grammar that a fifty-character slug has none of. The reasoning goes in `decisions:`. The label is **area-first**: the leading word names the subject area, the rest narrows it — `checkpoint-partial-restore`, `account-base-ref-search`, `scope-refusal`, `handback-question-case`, `derivation-read-only-tools`. Kin share the leading word, so a directory listing groups them and `ls specs/done/account-*` finds them without a shared id prefix. A label for a counter id must not begin with `pre`, which the id regex would swallow as a `-pre` tail. **Labels may repeat; the id is the identity.** Two specs on one topic may carry one label — the goal says which is which — so uniqueness is not enforced. A label is not a sentence: `the-diagram-cannot-lie` and `one-gate-not-two` were relabelled to `flow-diagram-evidence` and `gate-removed`. A relabel of a spec whose record entry is already over the 400-character cap may not lengthen the entry — the pointer is part of it. `SpecNameRuleTests` enforces the word ceiling and the character bound over the DATE-MINTED namespace; the closed counter namespace is out of scope by construction, and the scoping is a namespace rather than an ordering. The product still mints a sentence slug from the goal (`SpecIdFactory.Slug`, and a second one in `WriteSpecRecordHandler`) — bounding it is `2026-09-07-e9a2` (phase-slug-product-bound). ## Implementation Workflow (follow this order for every spec) 1. **Write the spec first** — create `.agentsmith/specs/planned/{id}-label.yaml` (top-level key `spec: {id}`) with goal, `applies_to:`, steps, and definition of done BEFORE writing any code. No exceptions. Mint `{id}` per **Minting a Spec Id** above. 2. **Move to active** — move the spec file from `planned/` to `active/` when starting work. 3. **Plan first** — explore codebase, design approach, get user approval before coding. 4. **Implement step by step** — contracts/models first, then implementation, then wiring, then tests. 5. **Build after each step** — fix errors immediately, don't accumulate them. 6. **Run the tests of what you changed** — while iterating, run the affected classes (`dotnet test --no-build --filter "FullyQualifiedName~…"`, `pnpm test `), zero failures before moving on. The full suite runs once, in the spec gate at the spec commit, and a red suite blocks that commit. When the gate cannot check the commit — it reports `not-gated` or passes it through (a bare commit, an editor amend, `-F -`, `-m "$(…)"`), or the commit is made outside the Bash tool — run the full `dotnet test AgentSmith.sln --filter "Category!=LiveLLM"` and the dashboard tests by hand before committing. 7. **Log decisions** — one YAML per spec at `.agentsmith/decisions/{id}.yaml`; each entry: what was chosen, what alternatives existed, and why. 8. **Update state** — move the spec from `planned`/`active` to `done` in the relevant context's `context.yaml`. The `state.done` entry is an INDEX LINE, **max 400 characters** and enforced by `SpecRecordLengthRatchetTests`: what shipped, in what area, and the `-> .agentsmith/specs/done/…` pointer. The reasoning goes in the spec the pointer names and in `decisions/{id}.yaml` — an entry that repeats its spec is a second copy that will disagree with the first. 9. **Move to done** — move the spec file from `active/` to `done/`. 10. **Commit** — one commit per spec, descriptive message, on the work branch `spec/{id}`. Work and integration branches are named `spec/{id}`, never `phase/{id}`. ## Spec Gate — what it covers `.claude/hooks/spec-gate.sh` runs as a PreToolUse hook on every Bash call and gates a `git commit` whose message names a spec: the hook's own tests, the dashboard's build and tests, the backend build, the full suite, CLI dry-runs and harness presets must be green, or the commit is blocked. Read this before a definition of done leans on it. - **Two shapes, chosen by core count** (2026-10-09-7f48). On eight or more cores (`getconf _NPROCESSORS_ONLN`) the checks run in two lanes: hook tests then dashboard, beside build then tests, dry-runs and presets at once. The test assemblies run as separate processes and AgentSmith.Tests as five, chosen by the `TestProcess` trait — `env-1`..`env-3` for the environment-mutating collection (environment variables are per process, so the shards keep that collection's guarantee), `serial` for the collections that run alone, and the body as the NEGATION of those four, so an untagged class still runs. `TestProcessTraitRuleTests` fails a class whose trait and collection disagree. Every job is awaited by its own pid, output is printed in a fixed order, and ONE ledger line names every failed step, comma-separated. Below eight cores the gate is the old sequence — one `dotnet test` over the solution, the first failure blocks — because the split puts process-spawning tests beside timing-bounded ones on the same machine. `SPEC_GATE_CORES` overrides the probe. Measured on a 12-core Mac: 84 s median against 360-530 s before. - **The gate proves itself.** `.claude/hooks/test_*.py` — the command detection and the message resolver — run from the tree being gated (first in the sequence, first in the dashboard lane), so a commit that changes the hook is proven by the hook it ships rather than by the copy the session started with. They cost about ten seconds. A gate they drive skips this step instead of running the tests that invoked it, and their ledger is redirected, so nothing they do reaches `.claude/spec-gate.log`. A tree carrying no hook tests says so and moves on. - **The dashboard is always checked.** `pnpm install --frozen-lockfile`, `pnpm gen:hub-events`, `pnpm test` and `pnpm build` in `src/dashboard` — before any .NET check in the sequence, beside them in the lanes — its own CI workflow is path-filtered on `src/dashboard/**`, so a backend-only payload change never proved the half that renders it. A tree with no `src/dashboard/package.json` has nothing to check and says so; a tree that has one and no `pnpm` **blocks**, because a silent skip is indistinguishable from a pass. - **The generated mirror is checked, the generated outputs are not.** `gen:hub-events` compares the C# event contracts under `src/backend/AgentSmith.Contracts/Events/` against the dashboard's committed TypeScript mirror and writes nothing; a contract that outgrew its mirror blocks the commit and the failure names the missing event. The token and openapi generators need no step of their own — `pnpm build` already runs them, and neither could detect drift, because what they emit is gitignored rather than committed. Whether the step runs is read from `src/dashboard/package.json`, not from pnpm's exit code: a tree that declares no `gen:hub-events` script predates the check and is skipped with a line of its own. The `passed` ledger line still says only `dashboard`, so it never claims a step that may not have run. - **The one gate here.** `gate: project` in `.agentsmith/contexts/default/context.yaml` tells the spec-first plugin (2.7.0 and later) that this repository runs its own gate, so the plugin's hook steps aside in one line and the checks above run once per spec commit. - **One copy serves everyone.** `$CLAUDE_PROJECT_DIR` is the launching session's project directory, so a subagent in its own worktree runs the *shared checkout's* script — an edit to the gate takes effect only once it lands in that working tree. It still gates the tree the commit runs in, resolved from the call's `cwd`, a leading `cd` and git's own `-C` — one reading of the command decides both which tree is built and which message is read. - **Gated command shapes:** `git commit`, after a leading `cd` or a shell separator, and with git's global options in between — `git -C commit`, `git -c = commit`, `git --git-dir= commit`. Until 2026-09-21-9ae2 only `commit` straight after `git` was recognised, so the path-addressed form an agent naturally writes was skipped in silence. Still NOT gated: a command that merely mentions the shape (`echo`, `grep`), and another subcommand — `git … log`, `git commit-graph write`. - **Gated message forms:** `-m`, repeated `-m`, a heredoc, `-F `, `-t