Rule System
Audience: Tier 2/3 (team adopter, framework contributor).
This chapter is the schema-level reference for the v0.5.0 rule system: where rule files live, how scope is derived, how the inheritance chain resolves, and which frontmatter fields are valid. It is the spec the resolver (aidlc-graph.ts compile) and the doctor rule-drift check read against. Rules are the feedforward half of the control loop; Sensor System covers the deterministic-verification half they pair with. For the user-facing walkthrough — the learning-loop ritual, the ANZ worked example, and how a confirmed learning lands in a rule file — see Rules and the Learning Loop in the User Guide.
Layout
Rules live in the active space memory layer at aidlc/spaces/<active-space>/memory/ (one hand-editable set at the workspace root, read by every harness via its native include — Claude @-import stub, Kiro CLI agent resources, Kiro IDE always-included steering with live file references, Codex AIDLC_RULES_DIR) with neutral scope-named files:
aidlc/spaces/<active-space>/memory/
├── org.md
├── team.md
├── project.md
└── phases/
├── ideation.md
├── inception.md
├── construction.md
└── operation.md
The layout consolidates team-authored harness config (formerly in a separate practices namespace) and self-learning guardrails (formerly two-deep) into a single active space memory directory at aidlc/spaces/<active-space>/memory/.
Filename-derived scope
Rule files do not carry a scope: frontmatter field. Scope is derived from the filename:
| Filename pattern | Scope |
|---|---|
org.md |
org |
team.md |
team |
project.md |
project |
phases/<phase>.md |
phase (phase value = filename) |
Org, team, project, and phase rules carry no path-scoping frontmatter — pull authoring puts the relationship on the stage side. Org / team / project apply to every stage as universal defaults (filename-derived); the matching phase rule attaches because the stage's frontmatter phase: <name> field is the pull import for phases/<name>.md.
Five-layer inheritance
Rules resolve through a five-layer chain at workflow start:
org → team → project → phase → stage
Org carries framework defaults; team and project layers extend with team-affirmed and project-specialised content. Phase is orthogonal — it attaches the matching phases/<name>.md rule because the stage already declared phase: <name> in its frontmatter (the same authoring direction as requires_stage and consumes). Stage rules are reserved-for-future-use; when authored, each aidlc-stage-<slug>.md will attach via the stage's slug: declaration. Cross-link to 01-architecture.md § Configuration layers for the broader two-axis configuration model this chain operationalises.
The compile output (stage-graph.json per-stage rules_in_context field) bakes the resolved chain into each stage node. Runtime never walks the chain — the compile owns resolution.
Strict-additive runtime model
Every applicable rule appears in rules_in_context. Org, team, and project rules concatenate; nothing drops at runtime. The phase rule attaches when the rule's filename matches the stage's phase: declaration - no glob filter, no concrete-path synthesis. Before run-stage, the engine reads each substantive rule file from the active space and emits its text through one or more bounded load-steering directives. Continuation tokens carry an integrity MAC keyed by a random, machine-local secret under the active intent's gitignored .aidlc-* runtime state, or under the clone-local session runtime before an intent exists; the token binds the stage, workflow state, rule bundle, route, and next part and cannot be re-signed from the public project path. A fresh next lazily mints the key when needed. There is no size-based path fallback. Empty templates are dropped, while unreadable or invalid required rules stop with repair guidance. Delivery repeats every stage, so a learning admitted mid-workflow reaches the next stage.
The §13 Learnings Ritual asks the orchestrator to screen for conflicts (a narrower scope contradicting broader policy) at the memory gate before a learning reaches the resolver. The check is section-level: when a proposed dated learning entry is about to be written to memory/project.md (or memory/team.md), the orchestrator compares it against memory/org.md's matching heading via an LLM check; if a conflict is found, the user revises, skips, or escalates (there is no override path). Only conflict-clear or user-escalated selections reach aidlc-learnings.ts persist, which does not read org.md or independently judge the comparison. The check is therefore an audit aid, not a deterministic enforcement boundary. Practices-discovery's affirmation gate is the other admission gate, but its promotion is a deterministic section-replace (aidlc-state.ts practices-promote) legitimised by the user's affirmation — it does not run the automated org-conflict check. Post-write drift between org and team/project content is surfaced separately by the doctor's rule-drift row (below).
This design replaces the earlier enforcement: enforced keyword and overrides: block model. Both keywords are removed from the schema. Frontmatter parsing rejects them via the unknown-key tolerance policy below — they pass through silently rather than throwing, but the resolver ignores them.
The doctor rule-drift check surfaces post-write drift on demand: when org rules change after team or project content has already landed on disk, doctor deterministically finds ## headings that the team/project practice files (memory/team.md, memory/project.md) share with a populated org heading and surfaces each overlap as an advisory candidate — file, section, and the quoted org sentence — rendered Rule drift: N team/project rule(s) overlap org policy (review for contradiction). Overlaps from a stale file are removed from that live count and reported separately as Rule drift: N stale-suppressed. Doctor itself runs no LLM: detection is byte-reproducible heading/string work. The contradiction verdict — the same section-level LLM check the admission gates run — is the consuming orchestrator's at observation-time, non-blocking. The human then revises, escalates, or accepts the surfaced drift.
pairing: field
Rules MAY pair with a deterministic sensor from .claude/sensors/aidlc-*.md:
---
pairing: aidlc-required-sections
---
# or
pairing: feedforward-only
---
Valid values:
feedforward-only— explicit declaration that the rule has no sensor companion (the framework cannot verify it deterministically).<sensor-id>— must match an existing.claude/sensors/aidlc-<id>.mdid:field.
The doctor paired-coverage row counts paired-vs-feedforward-only rules and surfaces unpaired rules as a coverage gap (see Rule-drift detection).
Lifecycle fields
Every rule file MAY declare file-level lifecycle metadata:
---
status: active
stale_after: 2026-12-31
---
statusaccepts exactlyactive,deprecated, ordraft.deprecatedmakes the file stale;activeanddraftdo not.stale_aftermust be a real calendar date inYYYY-MM-DDform. A file becomes stale only when the current UTC date is strictly greater than that value, so thestale_afterday itself remains live.- Lifecycle applies to the whole file, not individual headings. A stale team/project file's org-heading overlaps move from the live rule-drift count into the separate passing
stale-suppressedrow.
Both fields are optional, and invalid values stop rule loading with an error that names the file. They are valid on org, team, project, and phase rule files, although the Tier-1 deterministic consumer is deliberately limited to suppressing team/project overlaps in /aidlc --doctor; lifecycle metadata does not remove any rule from the strict-additive runtime bundle.
Rule-drift detection
/aidlc --doctor ships two advisory rows that observe rule/sensor state. Both are read-only and always pass — neither changes the health-check exit code. (As of v0.6.10 --doctor is cold-safe: the GUARDRAIL_LOADED audit row the paired-coverage check emits is written only when the active intent's audit/ shard already exists. On a fresh shell with no intent yet, doctor prints the rows but emits nothing and creates no files.)
- Rule drift — for each team/project practice file (
memory/team.md,memory/project.md), doctor finds##headings that also appear under a populated heading inmemory/org.mdand surfaces each overlap as a candidate pair: file, section, and the first org sentence quoted verbatim. Headings whose org body is empty (e.g. the framework-default## Forbidden,## Mandated,## Corrections, which hold only HTML comments) do not count — the overlap must carry content on both sides. Live overlaps render in the existing review-for-contradiction row; overlaps from files whose lifecycle is stale render in one separateRule drift: N stale-suppressedadvisory row with the same inline detail. The count N in each row is the number of structural candidate pairs, not LLM-confirmed contradictions: doctor detects deterministically; the contradiction verdict is the orchestrator-LLM's at observation time (see the strict-additive section). - Paired sensor coverage — for each rule carrying
pairing: <sensor-id>, doctor strips theaidlc-prefix and confirms the named sensor exists in at least one stage's resolved sensor set (sensors_applicable). The row readsPaired sensor coverage: P/(M-X) guardrails paired (X feedforward-only), where M is the rules carrying apairing:value, X is the feedforward-only rules (which never need a sensor), and P is the rules whose named sensor resolves; unpaired rules (sensor id named but bound by no stage) are listed inline. This is a file-existence check, not a semantic one — it confirms the binding resolves, not that the sensor fits the rule. The row emits theGUARDRAIL_LOADEDaudit event once per run on an initialized project (the emit is suppressed on a pristine project with noaudit.md— see the cold-safe note above).
Forward-compat policy
Rule frontmatter is forward-compatible by additive extension:
- New fields land as additive — existing rule lines never need rewriting.
- Consumers MUST tolerate unknown frontmatter keys (ignore + pass through). This is how legacy
enforcement:/overrides:keys (if present in user-extension overlays) load without error. - Rule body conventions (sections like
## Forbidden,## Mandated) follow the existing guardrail conventions. - The Learnings Ritual (the memory gate) writes each confirmed learning as a practice directly into the space memory files —
memory/project.md(default) andmemory/team.md(one-click promote; no org-scope write) — as a dated entry under its diary-heading topic. A confirmed learning is a practice: these are the same files practices-discovery affirms, not a separate*-learnings.mdsurface. The resolver sorts them on the clean integer chainSCOPE_PRIORITY(org:0, team:1, project:2, phase:3) — there are no fractional 1.5 / 2.5 tiers. The orchestrator performs the admission conflict-check described under## Strict-additive runtime modelabove (single proposed dated entry vsmemory/org.md's matching section; revise / skip / escalate, no override path), thenaidlc-learnings.ts persistwrites the selections it receives without re-running that comparison.
Pre-v0.5.0 rule files load unchanged after the rename + flatten lands; migration is path-only.
Next Steps
- Sensors — the deterministic half a rule can pair with via
pairing:. See Sensor System. - The compile boundary — how
rules_in_contextis resolved once at workflow start and read off the graph node throughout the workflow. See Plane Architecture. - The learning loop in practice — the
memory.mddiary, the approval-gate ritual, and the ANZ worked example. See Rules and the Learning Loop in the User Guide. - The two-axis configuration model — the broader routing principle this five-layer chain operationalises. See Architecture § Configuration layers.