Rules and the Learning Loop
Rules are the standing decisions your team carries into every workflow — the "always do it this way" that you would otherwise re-explain at the start of each run. As a harness engineer you author them in two ways: by editing a rule file directly, or by letting the learning loop promote an in-workflow correction into a durable rule for you. This chapter is the authoring-side companion to the Rules and the Learning Loop chapter in the User Guide, which covers the loop conceptually and walks the banking example end to end. Here the focus is operational: which file you edit for what, how the strict-additive model behaves when you stack a rule, and how to run the loop so a one-time fix becomes a standing rule.
Rules are the feedforward half of the control loop — prose the agent reads before it works. Sensors are the feedback half — deterministic checks that fire after a write. A rule can name a sensor as its companion; the two halves are designed to pair.
The five layers and which file you edit
Rules live as Markdown files under core/memory/ (the authored source for the space memory layer), one file per scope.
There is no scope: frontmatter field — this implementation derives scope from
the filename, so the file you pick is the scope you author at:
| You want a rule that applies to… | Edit | Scope |
|---|---|---|
| every project at your organization | org.md |
org (framework default) |
| every project your team runs | team.md |
team |
| this one project | project.md |
project |
| every stage in one phase | phases/<phase>.md |
phase |
A plugin can bring house rules too. They land as one section of their own at the end of the file the plugin names (or the one chosen at install) and of the phase file they are for, and the team's own rules stay in the sections above it. See Authoring a Plugin.
The four phase files are phases/ideation.md, phases/inception.md,
phases/construction.md, and phases/operation.md (initialization is
bootstrap-only and ships no rule file). A fifth layer — per-stage rules
(aidlc-stage-<slug>.md) — is reserved for a future release; you cannot author
one yet.
Two judgment calls drive which file you reach for:
org.mdis framework-shipped. It carries the defaults every project inherits — trunk-based development, the walking-skeleton policy, the testing posture per scope. Treat it as upstream. Most harness engineers leave it alone and author at team or project.- Project scope is for durable deviation, not for everything. Reach for
project.mdonly when this project stably departs from team-wide practice — a monorepo that rebases where the team squashes, a legacy project that skips the test floor. If the rule would help every project your team runs, it belongs inteam.md.
Each file is plain prose under topical headings — ## Way of Working,
## Testing Posture, ## Deployment, ## Code Style, and so on. You add a rule
by adding a bullet under the heading it belongs to. The full filename-to-scope
table and the resolver mechanics are the normative contract:
Rule System § Filename-derived scope
and § Layout.
Holding Guard Policy for the whole repo
One heading in these files is read as a structured setting rather than prose:
## Guard Policy. It decides how far the guards stand aside for work in this
space, in two respects. What happens when an input changes after a human approved
or confirmed something (a plan edited after approval, a reviewed document edited
after its review, an output saved without the current summary confirmation):
strict reopens that approval, while relaxed and off record the change once,
tell the human in one line, and continue. Workspace source that moved after a
plan was approved is recorded and continued under every value. And which authority fences hold:
strict lowers none, relaxed lowers plan-approval and review-freeze, and
off lowers those two plus state-transition and reviewer-scope.
human-presence is never lowered by the word. By default each intent takes its
scope's value. To lower it from chat, a person asks in their own words and the
conductor runs the setter, or types the switch (/aidlc --guard-policy relaxed
or guard policy off) and the agent runs the same setter for that line. To hold strict for everyone, put one line under the
heading in team.md (or org.md, or project.md for one project):
## Guard Policy
Mode: strict
A Mode: strict in any layer wins over the scope default and over the intent's
own line; a chat or flag flip to relaxed or off is then refused with a
sentence naming this file, and the next governed check on a running intent records
the change as a GUARD_POLICY_SET row naming the file as its source. Mode:
relaxed and Mode: off set the policy for every intent whose value came from
its scope, the narrowest layer that declares one winning (project.md, then
team.md, then org.md); a value the person set for one intent keeps it.
Leaving the section empty changes nothing. Any other value is a
validation error naming the file and the three allowed values. The line is read
with the same Field: value grammar as ## Testing Posture, commented-out lines
included: a Mode: inside an HTML comment declares nothing.
A layer holding strict refuses /aidlc config set guard.<fence> off, naming
the memory file in the refusal, and forces any fence lowered earlier back on
while that line stands. The persisted Guards Off entry remains and takes
effect again only after the memory line no longer holds strict. A machine-wide
kill switch still takes precedence; turning a fence on remains allowed.
The retired heading ## Change Control is still read for one release, so a space
whose memory files predate the rename keeps working; a section under the new
heading wins when a file carries both.
Strict-additive: layers stack, none silently overrides
The chain resolves through four layers today, with a fifth reserved:
org -> team -> project -> phase (stage: reserved, not authorable yet)
The model is strict-additive. Every applicable rule appears in the agent's
context at once — nothing is dropped or suppressed at runtime. Org defaults, team
practices, and project specialization concatenate; the matching phase rule
attaches because the stage already declares its phase: in frontmatter (the same
pull-authoring direction a stage uses to import everything else). Which files apply
to each stage is baked into its graph node when the graph is compiled, and the
runtime never re-walks the chain; the selected files' current text is read
before each stage delivery (see Learnings apply from the next stage delivery). See
Rule System § Strict-additive runtime model
and § Five-layer inheritance.
What this means for you as an author: there is no overrides: block and no
enforcement: keyword to set. A narrower layer does not quietly win over a
broader one. Instead, every layer you write is present in the agent's view
simultaneously. That changes how you author — you state a rule positively at the
scope where it should apply, and you trust it to stack with the layers above it
rather than reaching for a switch to suppress them.
Conflicts are checked before learning writes, not resolved at runtime
Because nothing overrides at runtime, a rule that contradicts a broader-scope
rule would be a problem the resolver could not untangle. The learning-loop
protocol addresses that by asking the orchestrator to run a
section-level LLM check before a selection reaches the deterministic writer.
When a team- or project-scope learning is being added under a given ## Heading,
the orchestrator compares the proposed text against org.md's same heading. If
it finds a contradiction, it surfaces the org sentence and offers three choices
— revise, skip, or escalate to the org-rule owner. Only
conflict-clear or user-escalated selections proceed. Project-tier writes check
against org only, since team-versus-project differences are legitimate project
specialization, not a policy violation.
The deterministic aidlc-learnings.ts persist writer does not read org.md or
independently enforce this comparison; it accepts the selections it receives.
The check is an audit aid, not an enforcement boundary. Practices-discovery is
the other admission gate, but it does not run the org-conflict check: its
promotion is a deterministic section-replace legitimised by user affirmation.
Project rules are appended under Mandated or Forbidden only when the same
text is absent from that section, ignoring generated (affirmed YYYY-MM-DD)
suffixes. Re-affirming an unchanged rule keeps its existing text and date.
Two read-only /aidlc --doctor rows surface state after the fact: a
rule-drift
row flags headings where team or project content overlaps a populated org heading
(a candidate contradiction for a human to verify), with lifecycle-stale files
reported separately as stale-suppressed, and a paired-coverage row reports how
many rules name a sensor that actually resolves. Both are advisory and never
change the exit code. See
Rule System § Rule-drift detection.
Operating the learning loop
The other way a rule gets authored is that you never open a file — you correct an agent during a workflow, confirm the correction at a gate, and the framework writes it for you. That is the learning loop. Most stage runs add nothing, which is healthy; the loop comes in only when the person corrected the agent in a stage, or asked it to keep something for next time.
The mechanics are covered in the User Guide chapter. The harness-engineer view is what the loop produces and where it lands:
- The diary records during the stage. The conductor (the live
/aidlcsession running the active stage) keeps an observation log at<record>/<phase>/<stage>/memory.md(under the intent's record dir,aidlc/spaces/<space>/intents/<YYMMDD>-<label>/), with entries under four headings — Interpretations, Deviations, Tradeoffs, Open questions. It is auto-created and maintained for you; never hand-edit it. It is the stage's record of the agent's reasoning, and nothing in it is offered to the person. - A correction is asked about once. Before the approval gate of a stage
where the person corrected the agent (Request Changes, or a correction in
their own words while it worked) in a way that would hold next time, the
agent asks "You asked me to
. Keep that for next time?" with Yes, for this project,Yes, for the teamandNo. With nothing corrected, nothing is asked. A note they ask outright to keep is kept with no question. The User Guide shows what it looks like. - It writes what they keep. A kept correction lands as a dated practice line: the person never picks a file path or a heading; their pick decides the file and the agent routes the heading.
Where a kept learning lands
A confirmed learning is a practice: it lands in the same space memory files
practices-discovery affirms (aidlc/spaces/<active-space>/memory/team.md /
memory/project.md), with no separate *-learnings.md surface. The person's
pick decides the file:
| The person's pick | Lands in | Audit event |
|---|---|---|
| Yes, for this project (and a note they asked to keep) | aidlc/spaces/<active-space>/memory/project.md |
RULE_LEARNED |
| Yes, for the team | aidlc/spaces/<active-space>/memory/team.md |
RULE_LEARNED |
| No | nothing | none |
The default scope is project, the narrowest. Yes, for the team widens
a learning to memory/team.md when the lesson applies beyond this project. There is no widen-to-org path: org practices are framework-
shipped or organization-authored through a separate process, so the loop never
writes at org scope. Keeping the default narrow stops one project's surprise from
becoming an organization-wide rule by accident. The learning loop and
practices-discovery write into these same files by two different lifecycles: the
loop appends one dated entry at a time, practices-discovery affirms whole sections.
The resolver sorts the files on the clean integer chain org → team → project → phase.
When the learning is a check, not a rule (SENSOR_PROPOSED)
Sometimes the thing worth keeping is a recurring manual check — you keep eyeing
a stage's output for the same gap. When you confirm that kind of learning at the
gate, the framework treats it as a sensor binding rather than a rule. It does a
two-write install atomically: it scaffolds a sensor manifest under
.claude/sensors/ and appends the new sensor's id to the originating stage's
sensors: import list, leaving a SENSOR_PROPOSED audit row. That is the one
sanctioned edit to a stage file outside a framework release — only the import list
grows; the stage body is never touched. From there you flesh out the manifest by
hand using Sensors — the loop scaffolds the binding; you author
the check.
Learnings apply from the next stage delivery
A learning captured at a gate lands in an already-selected rule file
(project.md or team.md), and the engine re-reads the selected files' text
before each stage delivery. So the rule reaches the agent at the next
applicable stage of the current run, and every later workflow. What waits is
the selection itself: a brand-new rule file enters the directory walk, and a
new Sensor binds, only when the stage graph is next compiled.
The same holds when you author by hand: editing team.md mid-workflow reaches
the next stage delivery of the run in progress (it does not retroactively
change stages already run). Sensor configuration is different from rule text:
part of it lives in the compiled graph and part is read again at fire time, and
the split differs between the write hook, the gate path, and install forms. To
make a Sensor edit — a new Sensor or a change to an already-bound one — take
effect predictably, recompile the stage graph. One exception no recompile can
reach: on a native install the bundled Sensors' workers are selected by id, so
editing a bundled manifest's command runs nothing different — a changed check
there needs its own Sensor id.
Next
Sensors — author a deterministic check and bind it to the stages
that should run it, including the manifest the loop scaffolds when you confirm a
SENSOR_PROPOSED learning.