CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# agent-smith — AI Agent Instructions
## Context Files (read in this order)
1. **Every** `.agentsmith/contexts/<name>/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/<name>/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/<name>.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 <path>`), 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 <tree> commit`, `git -c <name>=<value>
commit`, `git --git-dir=<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 <file>`, `-t <template>`,
`-C`/`-c <rev>`, `--amend --no-edit`.
- **Passed through, loudly:** a bare commit or an editor amend (no message exists yet),
`-F -`, and a message the shell builds from a command substitution such as
`-m "$(cat message.txt)"` — its text never reaches the gate. Run the checks by hand.
- **Not covered at all:** a commit made outside the Bash tool.
- **Afterwards:** every recognised spec commit leaves one line in `.claude/spec-gate.log`
of the shared checkout — verdict (`passed`/`blocked`/`not-gated`), spec id, the tree, and
the commit's parent. A spec commit with no line never met the gate.
- **Joining a line to a commit:** that last field is the tree's HEAD when the hook ran, not
the resulting commit's parent. For an ordinary commit they are the same sha; for an
`--amend` they are not — the amended commit's parent is one older. Any check that pairs
ledger lines with commits has to allow for that, or it reports false alarms on every
amended spec commit.
## Key Rules
- **English only** — all code, comments, docs, exceptions, logs, commit messages. Specs and repo docs are English even when the conversation is German.
- **No customer names** — never write customer, project, or target identifiers into any artifact in this repo (see `[[feedback_no_customer_names]]`).
- **No over-engineering** — only build what the spec requires, nothing more.
- **Tests** — every new public method gets at least one test.
- **Follow each context's principles.md** — these are constraints, not suggestions.