From surprise to rule to skill: how an agent contract teaches itself¶
The agent contract in this repo is not a separate policy document that someone finished before the project started. It is built from incidents.
A ticket hits a gap. The run record names it. If the gap can recur, the project turns it into work. If the same shape shows up across several runs, a retro promotes it into a standing rule, a lint, or a skill. That is the loop this post is about.
Here is the short version:
The important part is not the exact set of rules in this repo. The useful part is the promotion path. A local surprise becomes harder to forget each time it moves up the ladder.
1. Start with the run record¶
Every ticket run is supposed to leave a sibling run record beside the ticket, for example:
The format lives in workflow/docs/authoring/RUN_RECORD.md. The useful fields
for learning are plain:
## Surprises / judgment callsrecords a decision the ticket did not spell out.## Dead endsrecords what failed first and why.## Prevention opportunitiesrecords the class-level prevention the agent considered.## Project agent-rule changesrecords whether the run changed the provider rule surface.## Feedback artifact changesrecords whether the run changed a skill, memory file, or control-plane artifact.
That last pair matters. The run record is not only "what changed." It also asks whether the lesson should move somewhere more durable.
A concrete example from the current rules is "Never hunt the filesystem for
executables." The rule says an autonomous run may use the activated venv or a
bare command on PATH; if the environment is missing, the run must execute
bash scripts/dev.sh setup instead of searching /usr/local, /opt, or a user
profile for another binary. The history doc records why: a nearby executable can
carry different dependencies and make verification stop matching the repo's
intended environment.
That is exactly the kind of lesson a run record should preserve first: specific, observable, and reusable.
2. Turn recurring gaps into tickets¶
A run-record note is easy to lose. A backlog ticket is not.
This repo has a hard rule for that transition: when an agent identifies work
that deserves its own ticket, it must author that ticket in the same turn. It
cannot end with "we should file a follow-up later." The follow-up gets a real
file under tickets/backlog/, scoped by ## Touches, and wired with
## Depends on.
There is a stricter version for fix tickets. If a tooling or workflow defect
blocks existing tickets, the fix ticket must be added to the ## Depends on of
every ticket it unblocks. Prose such as "run this after the fix" is not enough,
because the scheduler reads dependencies, not intent.
This is the first promotion. The lesson moves from a paragraph inside one run record to schedulable work the orchestrator can see.
3. Use retros to find the pattern¶
One ticket can identify one gap. A retro can see whether the gap is a pattern.
The feedback retro template in workflow/docs/authoring/FEEDBACK_RETRO.md tells
the retro agent to read sibling run records, review deferred and escalated
items, and answer four questions. One of those questions is explicitly about
"Candidates for new load-bearing agent rules." The template also requires every
deferred or escalated item to be dispositioned instead of silently dropped.
That is the pump in the system. A run record says "this surprised me." A retro asks "did this surprise happen often enough, or cost enough, that the project should change?"
The retro-seed-verification skill is a good small example. Its rule is simple:
before a retro carries a bug seed forward into the next cluster, re-check the
current code or rerun the cited test. The skill exists because a past retro
carried stale claims forward and created ghost work. The current skill names the
failure directly and tells the next retro author how to avoid it.
4. Promote stable lessons into rules and lints¶
Some lessons need prose. Some need a gate. The durable cases usually need both.
CLAUDE.md and AGENTS.md are the provider-facing rule files. They tell agents
what to do. workflow/project.config.json lists review checks such as
lint-run-records, lint-devblog-mirror, lint-bug-commit-order,
lint-bug-repro-evidence, and lint-no-direct-random. Those checks make parts
of the contract mechanical.
The split is deliberate:
- The rule explains the behavior and the reason.
- The lint catches the violation when an agent forgets or misunderstands.
docs/overview/AGENT_RULES_HISTORY.mdpreserves the incident that made the rule worth keeping.
The randomness rule is a straightforward example. The rule says gameplay code
must not import Python's random directly; randomness is resolved through the
server-side randomness service or AI helper. The gate lint-no-direct-random
scans the relevant Python trees for direct random usage. The history doc explains
why the rule exists and what the lint can and cannot catch.
The doc-propagation rule is another example. The history doc records the
SPECTATOR to OBSERVER rename leaving about 160 stale current references
after the first code change. The standing rule now says a canonical doc edit that
renames a concept must search and update related current docs in the same turn.
That rule came from a concrete miss, not a style preference.
Tempo keeps the same pattern visible in the Go port. The project config in
tempo/assets/bootstrap/project.config.json carries the bootstrap review-check
contract, and tempo/lint_gates.go registers vendored lint-gate modules such as
lint-devblog-mirror, lint-run-records, and the bug-ticket gates. The Python
workflow and the Go port are not allowed to drift silently where a matching
surface exists.
5. Use skills for task-local recall¶
A lint fires late. A rule file can be long. A skill is for guidance that should arrive when the task starts.
Skills in this repo live under .claude/skills/<slug>/SKILL.md. Each one has a
frontmatter name: and description:. The review rule for skills says the
description must name the task keywords, file pattern, command shape, framework,
or operating system that should trigger the skill. In other words, a skill is not
just another note. It is written to be loaded at the moment a matching task is
being done.
That makes skills useful for workflow quirks:
boardgameui-dev-workflowreminds agents to usebash scripts/dev.shfor project checks andbash workflow/scripts/dev.shfor workflow commands.retro-seed-verificationreminds retro authors to re-check stale carry-forward seeds.tickle-vscode-scmcaptures the local SCM-panel refresh step after commits.
These are not universal architecture laws. They are specific operating lessons. Putting them in skills keeps them close to the task that needs them.
What this buys¶
The ladder gives each lesson the smallest durable home that fits it.
A one-off observation stays in a run record. Work that should happen becomes a ticket. A repeated pattern becomes a retro finding. A global behavior becomes an agent rule and, when possible, a lint. A task-local habit becomes a skill.
That shape is practical. It avoids two bad outcomes:
- Important lessons trapped in old run records.
- Every annoyance promoted into a permanent global rule.
The retro is the filter. The lint is the enforcement. The skill is the local reminder.
The limits¶
This does not make the system automatic.
Run records still have to be written. Tickets still have to be scoped. Retros still have to read the evidence and reject weak candidates. Lints still have to be kept in sync with the rule they enforce. Skills still have to be updated when the workflow changes.
The current blog refresh ticket is a small example of the same discipline. The previous version of this post used broad claims about the contract "teaching itself." The refresh ticket points back to the writing standard in the blog index: plain prose, concrete examples, and fresh facts verified against the repo. So the fix is not just "make this post sound better." The fix is to ground each claim in the current rule files, templates, review checks, Tempo surfaces, and skill files.
That is the honest version of the loop. The project learns only when a specific surprise is written down, promoted deliberately, and kept current after it becomes part of the contract.