Context Loading

September 2, 2026 · View on GitHub

Domain: _shared

Overview

The context loading convention defines how fab skills load project context before execution. It is implemented in $(fab kit-path)/skills/_preamble.md as a shared preamble read by all skills. The convention uses a layered approach: always-load essentials, change-specific artifacts, and selective domain memory loading.

Requirements

Always Load Layer (Descriptive — Skill File Wins)

The always-load layer is the default every skill inherits unless the skill's own Context Loading section says otherwise — the skill file wins (260611-zc9m; the contract is descriptive, not exhaustive — a self-exempting skill does not contradict the preamble). Override is opt-in, not opt-out-by-silence: a skill with no Context Loading section still defaults to the full layer.

The exception set is rule-derived, never enumerated in the preamble (d9rs): _preamble.md §1 does not name the exception skills — the authoritative source for any override is the skill file itself (its ## Context Loading section, or an explicit context note near its header, e.g. fab-proceed.md's "skips preflight/context loading itself" note). The preamble keeps only illustrative examples (/fab-setup and /docs-hydrate-memory skip the layer entirely; /fab-operator loads a reduced 3-file set). Why rule-derived, not enumerated: see § Design Decisions → "Exception Set Rule-Derived, Never Enumerated". See § Exception Skills below for the shipped set.

Skills on the default path read seven files as baseline context:

  1. fab/project/config.yaml — project configuration: identity and paths, provider interactive_command/native/headless_command capabilities and role fills, the agent depth/profile overrides, dispatch.mode plus pane width/reaping preferences, and optional stage hooks
  2. fab/project/constitution.md — project principles and constraints (MUST/SHOULD/MUST NOT rules)
  3. fab/project/context.md — free-form project context: tech stack, conventions, architecture (optional — no error if missing)
  4. fab/project/code-quality.md — coding standards for apply/review: principles, anti-patterns, test strategy (optional — no error if missing)
  5. fab/project/code-review.md — review policy: severity definitions, scope, rework budget (optional — no error if missing)
  6. docs/memory/index.md — documentation landscape (which domains exist; a domain may contain sub-domains, surfaced in that domain's index via a ## Sub-Domains table — see Selective Domain Loading)
  7. docs/specs/index.md — specifications landscape (pre-implementation design intent, human-curated)

This gives the agent awareness of project settings, constraints, project context, coding standards, review policy, the documentation landscape, and the specifications landscape before generating any artifact.

The only universal helper beyond the 7 project files is _preamble.md. Additional helpers are declared per-skill via the helpers: frontmatter field — see Skill Helper Declaration (Opt-In) below. Naming conventions and run-kit (rk) recipes are inlined into _preamble.md (§ Naming Conventions, § Run-Kit (rk) Reference). Common fab commands are inlined into _preamble.md § Common fab Commands so most skills do not need _cli-fab.

Skill Helper Declaration (Opt-In)

Skills declare additional helper files via the helpers: frontmatter list. Allowed values (eight): _generation, _review, _cli-fab, _cli-external, _cli-agents, _srad, _pipeline, _intake. The agent MUST read .claude/skills/{helper}/SKILL.md for each declared helper after reading _preamble and before executing the skill body.

Stage-conditional loading (260611-zc9m): a skill MAY instead load a helper at its point of use via an explicit in-body read instruction (e.g., "read .claude/skills/_review/SKILL.md before entering Review Behavior"). Frontmatter helpers: declares unconditional pre-body loads; in-body read instructions declare conditional ones — a helper loaded this way is intentionally absent from the frontmatter list, so the frontmatter contract stays honest. /fab-continue is the sole current user: _generation at apply entry / intake-active regeneration, _review at Review Behavior entry (see pipeline/execution-skills.md).

Current mapping:

Skill(s)helpers:
fab-new, fab-draft[_generation, _srad, _intake] (consumers declare underlying helpers rather than inheriting transitively — the _pipeline precedent)
fab-continue[_srad] (+ point-of-use in-body reads of _generation/_review)
fab-ff, fab-fff[_generation, _review, _srad, _pipeline] (orchestrator-level rework edits plan.md sections directly, so _generation stays unconditional; _pipeline is the shared ff/fff pipeline bracket and constitutes the wrappers' entire body, so its load is unconditional by construction (szxd))
fab-adopt[_srad, _generation, _review, _pipeline]
fab-clarify[_srad]
code-reorg, code-dedupe[_srad] (report-only analysis skills — SRAD grades report-item confidence; no _intake/_generation)
fab-operator[_cli-agents, _cli-fab, _cli-external] (_cli-agents carries the agent-CLI interaction primitives the operator's spawn/pre-send/peek steps reference — see runtime/agent-primitives.md)
All others (19 skills)omitted / [] (load only _preamble)

_naming and _cli-rk are NOT allowed values — their content is inlined into _preamble. _preamble itself is implicit and never listed. /fab-proceed declares no helpers: (it dispatches _intake as a subagent prompt — the subagent reads the helper) (3xaj). The internal helpers _generation, _review, _pipeline, and _intake themselves carry no helpers: frontmatter — they reference what they need in-body and rely on the consumer (or dispatched subagent) having loaded it.

One shared helper per pipeline phase (3xaj). The four internal orchestration/mechanics helpers decompose the workflow symmetrically — each is a shared body parameterized by call-site-specific knobs, with call-site tails staying in the consumer files:

PhaseHelperKnob(s)Consumers
artifact mechanics_generationfab-new, fab-draft, fab-continue, fab-ff, fab-fff, fab-adopt
review mechanics_review{mode}fab-continue, fab-ff, fab-fff, fab-adopt
pre-intake orchestration_intake{questioning-mode}fab-new, fab-draft, fab-proceed
post-intake orchestration_pipeline{driver}, {terminal}fab-ff, fab-fff, fab-adopt

_intake (3xaj) is the pre-boundary counterpart to the post-boundary _pipeline (szxd): intake is the single context-bearing boundary in the pipeline; everything up to and including intake creation runs in the main session context (pre-boundary: _intake), everything after runs as dispatched subagents over the intake artifact (post-boundary: _pipeline). Both extractions mirror the same shape (shared body + one-or-two knobs + call-site tails). See pipeline/planning-skills.md § The _intake Shared Create-Intake Procedure for the full pre-boundary decomposition.

Preflight Script for Change Context

Skills that operate on an active change resolve the change context by running fab preflight [change-name] via Bash. The command accepts an optional positional argument as a change name override. When provided, it resolves the change using case-insensitive substring matching against folder names in fab/changes/ (excluding archive/) instead of reading .fab-status.yaml. The override is transient — .fab-status.yaml is never modified. When no argument is provided, it reads .fab-status.yaml.

The matching supports full folder names, partial slug matches, and 4-char random IDs (e.g., zq9x). Exact match takes priority; single partial match resolves directly; multiple matches or no match produce a non-zero exit with a descriptive error.

The command validates project initialization, the change directory, and .status.yaml, then outputs structured YAML with id, name, change_dir, stage, display_stage, display_state, progress, plan, and confidence fields. On non-zero exit, the agent stops and surfaces the stderr error message. On success, the agent uses the stdout YAML instead of re-reading .status.yaml.

Since preflight validates config.yaml and constitution.md existence, skills using preflight don't need separate existence checks for these files — they only need to read them for content.

The 4-step validation sequence (check current, check directory, check .status.yaml, check config/constitution) is documented in _preamble.md as reference for what the command validates internally.

Generic fab-Command Failure Rule

_preamble.md § Common fab Commands "Key behaviors" carries a generic failure rule covering every fab invocation, not just preflight: any fab command that exits non-zero → STOP and surface stderr — resumability handles the re-run. The rule is unconditional — there is no guard-marked exemption class (ye8r): fab log command can never trip the rule through internal failure because it owns its best-effort contract in Go — it always exits 0 given valid usage, surfacing internal failures as a stderr warning only (a cobra arg-count error is a usage error that exits 2 before RunE), so no shell guard is needed. The rule defers to explicit per-skill handling where a skill intentionally branches on a non-zero exit by design (e.g., fab-proceed's active-change probe, fab-discuss's context probe, git-pr's already-shipped check, fab-archive's archive-state check) — those carve-outs are unaffected. This closes the gap where only preflight (§2 step 2) had a stated non-zero-exit STOP and a mid-pipeline failure of any other fab command (e.g., fab status finish) had no defined handling, risking skills proceeding with silently diverged state.

Selective Domain Loading

When operating on an active change, skills selectively load relevant memory files based on the change's scope. An Affected Memory entry is either flat ({domain}/{name}) or sub-domained ({domain}/{sub-domain}/{name} — used after an over-wide domain has been split by docs-reorg-memory). Loading is an up-to-3-hop walk:

  1. Read the intake's Affected Memory section to identify relevant domains (and sub-domains)
  2. Domain index: for each referenced domain, read docs/memory/{domain}/index.md — its ## Sub-Domains table lists any sub-domains the domain contains
  3. Sub-domain index (only if the entry is sub-domained): when the referenced file lives in a sub-domain (3-part {domain}/{sub-domain}/{name} form), read docs/memory/{domain}/{sub-domain}/index.md next
  4. File: read the specific memory file referenced — docs/memory/{domain}/{name}.md for a flat entry, or docs/memory/{domain}/{sub-domain}/{name}.md for a sub-domained entry
  5. If a referenced domain, sub-domain, or file doesn't exist yet, note this and proceed without error (it will be created during hydrate)
  6. Do not load unrelated domains — keeps context focused and efficient

A flat domain is just the degenerate 2-hop case (domain index → file); the sub-domain index hop is taken only when the Affected Memory entry carries the 3-part form. This matches _preamble.md § Memory File Lookup (uliv). The always-load layer loads root + domain indexes; its description acknowledges that a domain may contain sub-domains.

This applies to all skills operating on an active change, not just spec-writing skills.

Standard Subagent Context

When orchestrator skills (/fab-ff, /fab-fff, and the prefix-step orchestrator /fab-proceed, which dispatches the _intake Create-Intake Procedure (3xaj), /fab-switch, and /git-branch as prefix steps before delegating (d9rs)) or middle agents (/fab-continue) dispatch subagents via the Agent tool, the subagent prompt MUST instruct the subagent to read a standard set of project files before executing its task. This is defined in _preamble.md § Standard Subagent Context and is distinct from the Always Load layer (which is for the parent agent itself).

The standard subagent context includes:

Required (subagent reports error if missing):

  • fab/project/config.yaml
  • fab/project/constitution.md

Optional (skip gracefully if missing):

  • fab/project/context.md
  • fab/project/code-quality.md
  • fab/project/code-review.md

This is a subset of the Always Load layer — it includes the 5 fab/project/** files but excludes docs/memory/index.md and docs/specs/index.md (which are navigation aids for the parent agent, not project principles needed by subagents).

Nested dispatch: When a subagent dispatches its own sub-subagent, the inner prompt MUST also include the standard subagent context instruction. The same 5 files are loaded at every nesting level.

Relationship to Always Load: The Always Load layer is what the parent agent reads. The Standard Subagent Context is what the parent agent instructs its subagents to read. The parent does not re-pass docs/memory/index.md or docs/specs/index.md to subagents — those are for the parent's own domain awareness.

Continuation carve-out (tv3g): the obligation binds every subagent dispatch. A continuation — the orchestrator resuming an already-running apply worker with a further instruction rather than spawning a new one, whether it is reached by name (apply-{id}, native arm) or by delivering into its still-live pane (fab dispatch deliver … --prompt-file, pane arm) — is not a dispatch, and deliberately does not re-carry the 5 files: the worker already holds them, which is the whole point of continuing it. A continuation still carries the other two dispatch-prompt obligations (return a result; end with the terminal fab status refresh <change>) and re-states the block contract. See pipeline/execution-skills.md § Shared Pipeline Bracket for where continuation is used and its mandatory fresh-dispatch fallback.

Per-Stage Model Resolution

Per-stage model selection is wired into the sub-agent dispatch seam (l3ja) (_preamble.md § Subagent Dispatch → Per-Stage Model Resolution is the canonical contract). The seam resolves one of six roles (default/operator/doing/review/hydrate/fast) per the fixed stage→role mapping, and every stage it resolves is a Tier-2 (workers) role — so agent.workers is the knob it consults. A stage resolves the same role regardless of which caller drives it (/fab-continue, /fab-ff, /fab-fff, /fab-proceed) — the caller-invariance invariant. Immediately before dispatching each pipeline stage's sub-agent, the dispatching skill — the orchestrators /fab-ff, /fab-fff, /fab-proceed, and /fab-continue's own sub-agent dispatch — runs fab agent <stage> -o yaml and passes the resolved profile into the Agent dispatch:

  • Output is structured YAML carrying provider, full model, model_alias, effort, composed command, provenance, and an optional dispatch: mapping. fab agent -o yaml resolves dispatch.mode through the shared pane → native → headless descent. Native is represented by an absent dispatch: key; pane/headless include a labelled rung and their profile-substituted command. Dispatch sites branch only on key presence: absent invokes the native Agent tool, present invokes fab dispatch. The mapping is surfaced for compliance and never executed by the skill; fab dispatch start re-resolves the same ladder. CLI commands always contain the full model ID while model_alias supplies the Agent-tool seam.
  • Automatic adapter resolution is preference-bounded. Pane requires tmux and interactive_command, native requires native: true, and headless requires headless_command. Selection begins at dispatch.mode, never ascends, and fails only when no reachable rung exists. fab dispatch start|restart perform the real tmux probe and may continue descending when tmux is unreachable. If their automatic selection lands on native, they fail before any dispatch-state write and direct the caller back to native dispatch; if start's lands on pane, it fails the same way and directs the caller to fab dispatch open, since start launches only the headless arm. Explicit --pane/--headless, timeout, and server semantics retain precedence and hard-error on missing prerequisites. Successful automatic output reports either mode: <rung> (preferred) or mode: <rung> (descended: <reasons>).
  • Recovery policy — bounded, orchestrator-owned, composed OVER the five states (mnri). Neither fab dispatch mode has in-harness supervision: a native sub-agent is retried on 5xx and its death is harness-reported, while a CLI or pane worker that exhausts its provider's internal retry simply stops and one wedged at an error banner reads running forever. So the observation wiring carries its own bounded recovery, composed over the five states — no state added or renamed, and the result-file contract and prompt obligations untouched:
    • Restart is tier 2's only recovery verb — never a nudge. A stage dispatch carries no irreplaceable conversational state (fab checkpoints it into artifacts, so a relaunched worker resumes from the last [x]), which makes fab dispatch restart <change> <stage> cheap and deterministic; it relaunches from the persisted {stage}-prompt.md, so the orchestrator needs no prompt in context, and it re-derives the mode from the current environment (a pane dispatch orphaned by a dead tmux server lands headless). See runtime/dispatch.md § fab dispatch restart.
    • Budget: exactly ONE restart per stage dispatch, held in the orchestrator's CONTEXT — no on-disk counter or history (last-attempt-only preserved; the worst case after a context loss is one extra restart).
    • orphaned spends it automatically, after which the orchestrator re-arms wait (whose ~2s liveness tick is what surfaced the death almost immediately). failed gets no automatic restart (a deterministic failure — bad config, a real test failure, a 124 timeout — would loop), though the orchestrator MAY read the fab dispatch logs --tail N output and spend the same budget on a clearly-transient signature (provider 5xx / overload). failed (no-result) always escalates and never restarts — a contract violation needs eyes.
    • Peek on suspicion: on every timeout-return of fab dispatch wait — a running state printed after the --timeout 300 bound expired, the same ~5-minute cadence — take a read-only peek — fab dispatch logs --tail 40 (headless) or fab pane capture [-L <server>] <pane> (pane) — and classify three ways: (a) progressing ⇒ re-arm wait; (b) parked / dead-ended ⇒ fab dispatch kill + restart within the same budget; (c) awaiting genuine human input ⇒ notify without killing. --timeout is the peek cadence carrier, not a poll interval.
    • Escalation = surface the per-mode evidence + a rk notify gated on command -v rk (fail-silent per the rk universal rule) + stop per the stage's existing failure path. Escalation introduces no new state and no new transition — it is the stage's ordinary stop, reached only once recovery is exhausted.
    • Against a WORKER the pipeline's verb set is exactly peek / kill / restart / notify / stop / reap — no send-keys, ever. reap is not a recovery verb: kill is recovery (any state, ungated, spent by classification (b) within the restart budget), while reap is hygiene — it fires only on the done success path, is knob-gated on dispatch.reap_done, and can never terminate a running/orphaned/failed/failed (no-result) dispatch, so nothing in this Recovery policy changes because of it. Its timing is stage-aware (the apply pane survives to be resumed into — see pipeline/execution-skills.md), which is wiring rather than policy. Nudging and answering a delivered worker stay the operator's and the user's affordances: a pipeline nudge channel would fork the cross-adapter contract, since a native worker has none. The single carve-out is the pre-delivery pane — between fab dispatch open and a verified fab dispatch deliver the pane holds no stage context, so it is not yet a worker and the readiness gate's judgment rounds may type into it; fab dispatch ready/deliver are the sanctioned mechanical senders and both refuse a mid-stage worker in code. The pane subset carries the same policy (orphaned gets the same one restart) while failed/failed (no-result) stay unreachable there rather than newly handled. The contract boundary is fixed in docs/specs/harness-adapters.md § Recovery is orchestrator policy over these states.
  • The dispatch-prompt obligations bind ALL THREE adapters. The result-file obligation, the standard-subagent-context instruction, and the terminal fab status refresh <change> epilogue are composed identically whatever adapter dispatches the stage; only the delivery mechanism varies — the dispatched prompt itself (native), the command's stdin (headless CLI), or a prompt file plus a one-line pointer typed in after spawn by fab dispatch deliver and verified against the screen (pane). On the pane adapter the result file is the sole completion signal, so that obligation is load-bearing there rather than merely contractual. The standard-subagent-context obligation binds every dispatch specifically: a continuation message to an already-running named worker carries the result and refresh obligations only (§ Standard Subagent Context). See _preamble.md § Dispatch-Prompt Obligations.
  • Empty model ⇒ omit the dispatch model param entirely (inherit the orchestrator/session model — today's behavior). Empty effort ⇒ omit the effort instruction (see § The two halves dispatch through two seams below).
  • The resolver maps <stage> (or role) → fixed role → {provider, model, effort}, then selects an adapter from that provider's independent capabilities. Provider precedence is invocation flag → role override → depth knob → built-in claude; model/effort precedence is invocation flag → role override → provider role fill → provider default fill → empty. Stage mapping and role depth are fixed; agent, providers, and dispatch.mode are the persistent configuration surfaces.

User-directed per-stage overrides ride the SAME single call — and bind the NATIVE arm only. When the user directs a provider/model/effort for specific stages ("run review on codex", "apply on sonnet this once"), the dispatch site adds override flags to its existing single fab agent <stage> -o yaml call — --provider <name> and/or --model <id> / --effort <level>, the top rung of the fill precedence (see runtime/providers-and-profiles.md § fab agent). A --provider swap re-derives model and effort from the named provider's own per-role fills, so swapping to a provider that ships none resolves an empty model — pair it with --model when the stage should run a specific one. Nothing else about the seam changes: one resolve call per stage, the same two seams for a native dispatch, the same branch on dispatch: key presence, the same compliance-visibility obligation (an override makes what was resolved even more worth surfacing). There is no new dispatch machinery and no persistent state — an override is per-invocation, so "use codex for the next N stages" means passing the same flags on those N resolve calls.

An invocation-time override reaches only the native Agent-tool arm and CANNOT move a stage onto CLI dispatch. The fab dispatch launch verbs take no override flags — they re-resolve the stage from config themselves — so an overridden profile is silently discarded there (the headless arm errors on the unoverridden provider's missing headless_command; open composes the unoverridden provider's interactive_command). A dispatch: mapping that appears only because of an override is therefore not actionable, and the two remedies are not interchangeable: dispatching natively with the overridden profile is executable only for a within-claude --model/--effort override, because the Agent tool's model param is a Claude-alias enum (opus/sonnet/haiku/fable) and a non-Claude model has no native seam to ride. That leaves a config override (agent.workers, agent.profiles.<role>.provider, or a providers: entry) as the sole executable path for a cross-provider --provider override — it is what dispatch start's own re-resolution sees.

The two halves dispatch through two seams (model param + prompt instruction) (m3d4). The resolved profile has a model half and an effort half, and Claude Code consumes them through different seams: the model rides the Agent tool's model parameter (empty ⇒ omit/inherit); the effort is injected as an explicit imperative line in the dispatched subagent prompt — Operate at `<effort>` reasoning effort for this task. — because the Agent tool has no effort parameter. Empty effort ⇒ omit the instruction (mirroring the empty-model omit rule). The effort-via-prompt seam is imperfect and not reliably honored: the session-level reasoning effort dominates, so a sub-agent dispatched from a high-effort session generally keeps running at the session's effort whatever its prompt asks for (a known Claude Code limitation — GitHub issues #64033 and #39220 — not a fab bug). It is nonetheless the only per-sub-agent effort seam that exists; a first-class per-sub-agent effort param on the Agent tool would close it cleanly and is the residual harness ask (docs/specs/stage-models.md § Skill wiring — out of fab's control, not built).

Effort asymmetry — the two arms are not equally reliable. Model differentiation works on both dispatch arms. Effort differentiation is trustworthy only on the CLI armsfab dispatch headless and pane, and the operator launcher — where the resolved effort rides --effort (or the provider's equivalent) inside a composed command line the harness reads as configuration. On the native Agent-tool arm it rides a prompt instruction and is therefore advisory. Practical consequence: a project that genuinely needs a cheaper-thinking stage reaches for a cheaper model (or runs that stage through fab dispatch), not a lower effort on a natively-dispatched stage. See docs/specs/stage-models.md § Effort asymmetry.

Compliance visibility (m3d4). Each dispatch site MUST surface the resolved YAML — at minimum provider, model, model_alias, effort, and dispatch: presence — in the orchestrator's own step output and carry the applicable model/effort values into the dispatch prompt. A skipped structured-resolution call (the sub-agent silently inherits the session profile) or a mis-resolved role is therefore visible rather than silent. There is no code-level guard fab can install (dispatch is harness-internal), so visibility is the available seam; the canonical contract also notes an all-empty resolution is itself worth surfacing/asserting rather than dispatching blind.

Harness-adapter boundary (Claude Code). The resolution (stage→role→{model, effort}) is provider-neutral; injecting it into the actual dispatch is harness-specific. For Claude Code the model adapter is the Agent tool's model parameter and the effort adapter is the subagent-prompt instruction above. One concrete harness detail: the Agent tool's model param is a hard enum of short aliases (opus/sonnet/haiku/fable), not the full versioned id (claude-opus-4-8) in the YAML model key — so Agent-tool dispatch reads model_alias from fab agent <stage> -o yaml, which carries the deterministic Go-side translation (prefix-matched so dated variants resolve; empty for non-Claude IDs, which have no native seam); no agent hand-maps the id. Named explicitly as the Claude-Code adapter, not as universal truth; the coupling is not new (fab's entire subagent-dispatch design is already Claude-Code-shaped), so per-stage selection is exactly as portable as fab's existing dispatch. (The operator launcher path is the deliberate exception — it resolves the operator-role profile in-process and composes the full model through spawn.WithProfile: for a templated provider interactive_command (one carrying {model}/{effort} — the built-in claude default is templated; 260703-gvxd) it substitutes the resolved profile into the placeholders, and for a non-templated (plain-form) interactive_command it appends --model <full-id> --effort <level> at the END — see configuration.md § providers and runtime/operator.md.)

Review resolves once, like every stage (single review agent) (pag2). The review stage dispatches a single review agent. The dispatcher resolves fab agent review -o yaml once and applies the resolved model + effort-prompt instruction to that one agent — no different from any other stage's single resolution. There are no nested reviewers or a mechanical merge to spread the profile across, so review is unexceptional here.

Per-stage selection applies on every dispatched post-intake stage (fgxx). Per-stage selection is a property of dispatched sub-agent runs. /fab-continue (a one-stage sequencer) resolves fab agent <stage> -o yaml and dispatches every post-intake stage's block, and the /fab-ff//fab-fff orchestrators do the same in their full lane — so structured resolution applies uniformly across apply/review/hydrate regardless of caller. The one post-intake foreground path is the orchestrators' light lane (≤ 5 plan tasks, or --light): apply task execution and hydrate (and, for /fab-fff, ship/review-pr) run inline in the orchestrator's own context — no dispatch and no YAML stage resolution (see pipeline/execution-skills.md § Shared Pipeline Bracket); review stays dispatched in both lanes. Intake is pre-boundary — it runs in the main session, so no dispatch resolves a role for it. The residual advisory case covers any stage executed with no dispatch at all (inline light-lane stages, or a stage skill genuinely run standalone): fab cannot switch the session model mid-run, so such execution MAY note "this stage is configured for X; you're on Y" but MUST NOT attempt to switch. The effort half of the profile is injected via the subagent-prompt instruction (see § The two halves dispatch through two seams above); the lone remaining residual is a first-class per-sub-agent effort param on the Agent tool, a harness ask outside fab's control.

Two non-stage dispatch seams also resolve roles (caller-invariance). Beyond the post-intake pipeline stages, two additional dispatch sites resolve a role by name so no dispatch runs at the merely-inherited session model:

  • /fab-proceed prefix steps — each prefix-step dispatch resolves a role by name (the resolver accepts a role name positionally): /fab-switch and /git-branch resolve fab agent fast -o yaml; the _intake create-intake dispatch resolves fab agent default -o yaml. (Intake itself stays advisory-only on the foreground /fab-new path, which no resolution can govern.) This is why fast is multi-referent — it governs the ship stage AND these prefix-step dispatches.
  • /fab-continue's ship and review-pr rows — these delegate to /git-pr / /git-pr-review and resolve fab agent ship -o yaml / fab agent review-pr -o yaml before dispatching, surfacing the required YAML keys and applying the two seams — mirroring /fab-fff's full-lane Steps 4–5 exactly (in the light lane those steps run inline with no YAML stage resolution). /git-pr / /git-pr-review still self-manage their own fab status transitions; only the model/effort seam is added.

Both close the caller asymmetry so a stage/step resolves the same role regardless of caller.

This subsection documents where the resolution call sits and how the profile is consumed (a dispatch-seam concern, parallel to Standard Subagent Context above). The config schema (the two depth knobs, agent.profiles, the fixed mapping) and the design rationale (no-validation, fixed-mapping-vs-budget) live in configuration.md.

SRAD Protocol (via the _srad Helper)

The SRAD autonomy framework lives in the dedicated _srad.md helper (zc9m), declared via frontmatter helpers: by the planning skills — fab-new, fab-draft, fab-continue, fab-ff, fab-fff, fab-clarify — and by the report-only analysis skills code-reorg/code-dedupe for report-item confidence grading. It is not part of the always-load layer: _preamble.md carries only a ~3-line pointer (§ SRAD Autonomy Framework (pointer)), so non-planning skills do not pay for the framework. The framework defines:

  • SRAD scoring table — four dimensions evaluated on a continuous 0–100 scale per decision point
  • Fuzzy-to-grade mapping — composite score via weighted mean (w_S=0.20, w_R=0.30, w_A=0.30, w_D=0.20) (4yi8), mapped to indicative-only grades via half-open bands: composite ≥ 80 Certain, 50 ≤ c < 80 Confident, 20 ≤ c < 50 Tentative, else Unresolved (the bands align with the demerit penalty-curve knees; the grade is derived from the composite and never read by the score formula) (4yi8)
  • No Critical Rule, no hard-fail (4yi8) — there is deliberately no "R < 25 AND A < 25 forces Unresolved" override and no "any Unresolved row → 0.0" short-circuit; blocking is emergent from the demerit penalty curve (a composite < 20 row penalizes ≥ 2.0), and reversibility is carried by R's 0.30 weight rather than a separate rule
  • Confidence grades — Certain, Confident, Tentative, Unresolved with corresponding artifact markers
  • Worked examples — three examples in a compact one-liner style
  • Artifact markers<!-- assumed: ... --> for Tentative, <!-- clarified: ... --> for resolved assumptions
  • Assumptions Summary Block — standard format with required Scores column for per-dimension data; all four grades (Certain, Confident, Tentative, Unresolved) recorded

The companion confidence-scoring internals — the .status.yaml confidence: schema, the score formula (the demerit model (4yi8): score = clamp(5.0 − Σ penalty(composite), 0, 5), no coverage factor and no expected_min in the score path), and the status-template notes — live in _cli-fab.md § fab score (extended) (zc9m). Agents never compute the score: fab score (Go) does, reading intake.md as the sole scoring source. _preamble.md § Confidence Scoring keeps only the Gate Threshold (single flat-3.0 intake gate via fab score --check-gate --stage intake) and Invocation (who scores, when (d9rs): /fab-new and /fab-draft persist the intake score after generation — both through the shared _intake Step 7; /fab-clarify re-persists it in both modes — Suggest Step 7 and Auto Mode step 4 — not just suggest mode). The preamble's Bulk Confirm subsection is likewise a one-sentence pointer — fab-clarify.md (Step 2, Suggest Mode) is the sole authority for the trigger and semantics (see pipeline/clarify.md).

Next Steps Convention (State Table, Scoped MUST)

The _preamble.md preamble defines a state-keyed Next Steps Convention that skills use to derive their Next: output lines. The MUST is scoped (260611-zc9m): it applies unless the skill's own Output or Key Properties section defines a different ending — the skill file wins, mirroring the §1 context-loading contract. The exemption basis is a skill-file-declared ending, not a "pipeline-state skill" classification (/git-pr advances ship and /git-pr-review runs review-pr transitions, yet both declare their own completion output; /fab-discuss's ready signal and /fab-operator's status frame are the other current exemptions). The convention includes:

  1. State Table — 10 states (none, initialized, intake, apply, review pass, review fail, hydrate, ship, review-pr pass, review-pr fail) each mapping to available commands and a default
  2. State derivation rules — how to determine the current state from config.yaml existence, .fab-status.yaml, and .status.yaml progress map
  3. Lookup procedure — determine state, look up in table, output default first
  4. Activation preamble — when a skill creates/restores a change without activating it (/fab-draft always, /fab-archive restore without --switch), the Next: line includes a /fab-switch {name} instruction before state-derived commands (/fab-new auto-activates and does not need it)

No skill duplicates or maintains its own suggestion logic — skills on the default path derive from this single canonical table.

Exception Skills

The exception set is declared by the skill files themselves (the preamble never enumerates it) (d9rs). The shipped override set (d9rs), per each skill's own ## Context Loading section (or header context note):

  • /fab-setup — bootstraps structure, doesn't need project memory
  • /fab-switch — navigation only (requires no always-load files) (zc9m)
  • /fab-status — read-only status display, minimal context
  • /docs-hydrate-memory — ingests/generates memory content, doesn't pre-load the landscape (carries an explicit ## Context Loading override section) (d9rs)
  • /fab-help — uses no context at all
  • /fab-archive — none beyond preflight (fab change archive reads intake.md and the backlog itself)
  • /docs-hydrate-specs, /docs-reorg-memory, /docs-reorg-specs, /docs-distill-memory — load their own doc-tree working sets (memory/spec indexes + files); no config, constitution, or active change (/docs-distill-memory reads each target domain's topic files + $(fab kit-path)/reference/fkf.md; a no-arg invocation first runs a read-only heuristic survey across all domains, then loops every flagged domain sequentially — one domain per approval unit — see distill)
  • /fab-proceed — skips preflight/context loading itself, delegating all pipeline context loading to /fab-fff (header context note)

Partial exception: /fab-operator loads only config.yaml, constitution.md, and context.md (260611-zc9m — code-quality, code-review, and both doc indexes serve artifact generation/review, which the operator never does, and a long-lived session re-pays every loaded file after each reload — compaction, /clear, or restart). See runtime/operator.md.

Special case: /fab-discuss is not an exception — it loads the full 7-file always-load layer. However, it is the only skill whose entire purpose is to surface that layer. Other skills load the always-load layer as a preamble to generating or validating artifacts; fab-discuss loads it as its primary output, presenting an orientation summary for exploratory discussion sessions. It does not run preflight, does not require an active change, and does not advance any stage. Its skill file points at _preamble.md §1 rather than restating the 7-file list, keeping only its do-not-run-preflight / no-change-artifacts deltas (zc9m).

Design Decisions

The CLI Adapter Observes by Blocking Wait, Run at the Harness's Notify-on-Exit Seam

Decision: The dispatch:-present arm observes its worker with a single blocking fab dispatch wait <change> <stage> --timeout 300, preferably launched as a background command so the harness re-invokes the orchestrator when it exits (in Claude Code, Bash run_in_background); a harness with no such seam runs the identical command as a plain foreground blocking call. A running return means the bound expired and IS the peek-on-suspicion moment; every other state routes into the unchanged five-state handling. Why: The native Agent-tool adapter was already push — the harness wakes the orchestrator when a background sub-agent finishes — so the turn cost was specific to the CLI path, where fab dispatch sits outside the harness. Converting that path back to push needed no new channel, only the existing background-command seam plus a verb that blocks instead of returning immediately: one blocking call collapses what used to be hundreds of fab dispatch status turns into one wake-up when something actually happens. Bounding it at 300s reproduces the former "peek every 10th poll at 30s" cadence exactly, so the recovery policy's timing is preserved while the turns that used to count it are gone. Documenting the foreground fallback keeps docs/specs/harness-adapters.md honest as a cross-harness contract — it must not assume a Claude-Code-only seam — and even degraded it is a 10× turn reduction. Rejected: A worker-initiated push into the orchestrator's pane via tmux send-keys — delivery is documented-flaky (the printed-prompt trap), it would fork the cross-adapter contract (a native worker has no such channel), and it inverts the pipeline's own never-send-keys rule, which forbids typing at a worker for the same reasons in the other direction. An unbounded wait (drops peek-on-suspicion entirely — a worker parked at an error banner reads running forever). A shorter bound (reintroduces turn cost for no policy benefit). Leaving the poll loop in place (its cost scales with stage duration, and pane dispatch — the adapter that exists for long watchable stages — paid it worst). Introduced by: 260806-mkfj-dispatch-wait-event-driven

The Descent Ladder Preserves the dispatch: Key-Presence Branch

Decision: fab agent <stage> -o yaml and fab dispatch start|restart share one preference-bounded adapter selection rule. Native omits dispatch:; pane and headless include the corresponding labelled rung and provider command. Skills keep the same two-way key-presence branch, observation loop, recovery policy, and prompt obligations. Why: The resolver value is visibility, not an executable handoff: fab dispatch start re-resolves against current tmux reachability. Keeping the transport detail behind the existing presence seam adds pane/native/headless selection without multiplying branches across every orchestrator skill. Rejected: A third resolver line, skill-side config interpretation, executing the resolver's value directly, or separate ladders for resolution and process launch. Introduced by: 260808-yilt-dispatch-mode-descent-ladder

Tier 2's Recovery Verb Is Restart, Never a Nudge

Decision: The orchestrator→worker tier (tier 2) recovers a stuck or dead stage worker by fab dispatch restart — bounded at exactly one restart per stage dispatch, held in the orchestrator's own context — and by nothing else. The pipeline's verb set is exactly peek (read-only), kill, restart, notify, stop; it never sends keystrokes to a worker. Peek-on-suspicion classifies a still-running result-less worker three ways, and only the parked/dead-ended class is killed — a worker awaiting genuine human input is notified about, never answered and never killed. Why: The operator→session tier (tier 1) nudges because a session carries irreplaceable conversational state. A stage dispatch is the inverse: fab checkpoints stage state into artifacts (plan.md task checkboxes, the result-file contract, idempotent stages), so a relaunched worker resumes from the last [x] and restart is deterministic and nearly lossless. Send-keys delivery is separately documented-flaky (the printed-prompt trap, the probe-then-retype choreography — see runtime/operator.md), and a pipeline nudge channel would fork the cross-adapter contract, since a native Agent-tool worker has no such channel at all. Boundedness is what keeps recovery from becoming liveness-at-all-costs: an unbounded loop against a provider that is 5xx-ing platform-wide only burns tokens. Keeping the budget in context rather than on disk preserves last-attempt-only, at a worst case of one extra restart after a context loss. Rejected: An orchestrator→worker send-keys/nudge channel (fragile TUI delivery, a cross-adapter contract fork, and a duplicate of the operator's job). A supervisor daemon, timer, or background sweep (the only clock is the one inside a foreground fab dispatch wait the orchestrator itself invoked — fab's no-magic-background-work posture). An on-disk attempt counter or history in {stage}.yaml (a second source of truth needing its own reset semantics; breaks last-attempt-only). Auto-answering a worker's prompt (steering stays human). Introduced by: 260806-mnri-dispatch-worker-lifecycle-supervision

failed Recovery Is Orchestrator Judgment; orphaned Is a Rule

Decision: orphaned spends the single restart budget automatically. failed gets no automatic restart, but the orchestrator MAY read the fab dispatch logs --tail N output and spend that same budget on a signature it can positively read as transient (provider 5xx / overload / rate-limit exhaustion); anything else stops. failed (no-result) always escalates and never restarts. Why: The two states carry different information. orphaned means no exit code was ever recorded — a death (reboot, kill -9, a closed tmux window), transient by nature, so a rule is safe. failed carries a real exit code and is usually deterministic (bad config, a genuine test failure, a 124 timeout), so any automatic rule either loops on deterministic failures or under-recovers on transient ones. Because the orchestrator is an agent rather than a script, it can read a log tail — so encoding judgment at that seam beats encoding a rule that must be wrong in one direction. failed (no-result) is a contract violation (clean exit, no result file), which needs eyes rather than retries. Recovery composes over the existing states: none is added, renamed, or re-tabled, and the result-file contract and prompt obligations are untouched. Rejected: Auto-restarting failed too (loops on deterministic failures). A provider-5xx pattern matcher in Go (fab would own a rotting per-provider signature list — and the classification seam already sits in an agent that can just read the text). Treating failed (no-result) as recoverable (a contract violation looped is a contract violation hidden). Introduced by: 260806-mnri-dispatch-worker-lifecycle-supervision

model_alias — Deterministic Go-Side id→alias Translation at the Agent-Tool Seam

Decision: The Agent-tool model half reads model_alias from fab agent <stage> -o yaml, which emits the Claude-Code short alias (opus/sonnet/haiku/fable) alongside the full model. The two Claude-Code surfaces deliberately diverge: provider CLI commands compose the full model ID, while the Agent tool's model param is a hard JSON-schema enum (["sonnet","opus","haiku","fable"]) that rejects full IDs. The mapping lives in agent.ModelAlias (internal/agent, alongside the role tables + resolution): prefix-matched (claude-opus-opus, etc.) so dated variants (claude-haiku-4-5-20251001haiku) resolve; an empty/non-Claude alias means no native model seam. The deprecated line projection retains --alias as its frozen compatibility adapter. Why: This replaces the prompt-side hand-mapping of PR #413 (m3d4) — the prose instruction "the orchestrator maps the resolved id → alias at the dispatch seam," which told every dispatching agent to translate the id by hand on each dispatch. The live failure this fixes was an agent fumbling exactly that hand-map (it passed claude-opus-4-8 verbatim into the Agent tool's model param, hitting "Invalid tool parameters"). Encoding the map in Go at the harness-adapter boundary stage-models.md already names makes the failure mode impossible to reproduce. The shipped per-role fills are deliberately NOT stored as aliases (rejected — see below), so the provider-neutral full-ID default and the drift-guarded spec tables stay untouched. Rejected: (a) Keeping the #413 prompt-only hand-mapping (brittle; the failure mode that prompted this change). (b) Storing the shipped per-role fills / the two drift-guarded stage-models.md tables as aliases (breaks provider-neutrality, weakens the Fable version-pin discipline, and forces a coordinated edit across the Go map + spec tables + config comments + migration — pushing a harness quirk into the provider-neutral core; TestDocTablesMatchAgentMaps stays unaffected by keeping full IDs canonical). (c) Threading a bool into formatAgentProfile (couples the formatter to a flag it doesn't need — the empty-model branch already does the right thing because ModelAlias("")""). (d) Making --alias a Claude-only validator that errors on non-Claude IDs (would break the provider-neutral pass-through). Introduced by: 260613-yky7-resolve-agent-alias-flag

Preamble Context Diet — Consumer-Specific Content Moves to Opt-In Homes

Decision: Content in the always-loaded _preamble.md that serves only a subset of skills (or no live skill) is relocated to opt-in homes, with short pointers left behind: the SRAD framework → new _srad.md helper (declared by the 6 planning skills); confidence-scoring schema/formula/template → _cli-fab.md § fab score (extended) (preamble keeps Gate Threshold + Invocation); Bulk Confirm → one-sentence pointer (fab-clarify.md Step 2 is sole authority); the dormant [AUTO-MODE] Skill Invocation Protocol → fab-clarify.md (its sole referencer; Auto Mode retained — user decision: move, not delete); Operator Spawning Rules → _cli-external.md wt section (one repo-targeting rule, duplicate dropped). The §1 always-load contract and the Next:-line MUST become descriptive with a skill-file-wins override, and the helper model gains stage-conditional in-body loading (used by /fab-continue for _generation/_review). Preamble: 32,790 → 22,313 B (−32.0%); every non-planning skill saves the full ~10.5KB per invocation; relocated content is paid only by its consumers. Why: The preamble was 2–26x the body of the skill being run and roughly a third of it served a small subset of skills. Duplicated copies (bulk-confirm trigger, spawning rules, restated context lists in fab-proceed/fab-discuss) had already drifted once. The existing helpers: mechanism plus fab-kit's listSkills auto-deploy (internal/skills.go; lived in sync.go until the 260612-tb6f split) meant the reduction needed zero Go changes and zero semantic loss — content moves, it doesn't disappear. Rejected: Deleting the dormant [AUTO-MODE]/Auto-Mode pair (user chose move-over-delete — preserves behavior). Prose compression alone (saves far less, leaves the wrong-audience placement problem). An explicit exempt-skill list for the Next:-line MUST (goes stale with every new skill; a skill-file-declared-ending basis is self-maintaining — and the "pipeline-state skill" basis contradicted its own examples, since /git-pr//git-pr-review do advance pipeline state). Introduced by: 260611-zc9m-preamble-context-diet

Exception Set Rule-Derived, Never Enumerated in the Preamble

Decision: _preamble.md §1 never names the exception skills. The authoritative source for any always-load override is the skill file itself — its ## Context Loading section or an explicit context note near its header; the preamble keeps only illustrative examples. Why: An enumerated exception list drifts: the preamble's list named four skips while the shipped override set was larger (/fab-help, /fab-archive, /docs-hydrate-specs, /docs-reorg-memory, /docs-reorg-specs, and /fab-proceed all declare their own context behavior). A rule keyed on the skill file is self-maintaining — a new self-exempting skill needs no preamble edit. Every exception skill carries its own override for the rule to key on (/docs-hydrate-memory carries an explicit ## Context Loading section for exactly this reason). Rejected: Keeping the preamble enumeration in sync by hand — it had already drifted once when the rule replaced it. Introduced by: 260612-d9rs-docs-reality-sweep

A Live Consumer-Set Enumeration Is Swept as a CLASS, Not as a File List

Decision: Adding a consumer to a shared helper obliges a sweep whose unit is the class of prose constructs the addition invalidates, not the set of files a reviewer named. The class is defined behaviorally: a file enumerates a consumer set whenever it lists which skills do a thing the new consumer does — not merely when it lists helpers: declarations. It spans name lists, count words ("all six declaring skills", "the remaining two"), coverage notes that assert a subset is accounted for by other means, parallel twin tables (the phase-symmetry table above exists byte-parallel in memory-docs/templates.md — every row moves together, including rows for helpers other than the one that prompted the edit), and ASCII flow diagrams (a diagram line is live prose and drifts identically). The binding procedure is enumerate-the-class-first, adjudicate every hit with explicit reasoning, sweep the parallel restatement of every line edited, then self-verify by re-running the greps. Append-only log.md/log.seed.md and dated finding archives are frozen historical records and are excluded. (4v91) Why: A coverage-note omission is a live functional defect, not a stale count — a skill declaring helpers: [_srad] that finds no posture, interruption budget, or escape valve for itself in _srad.md has an actual behavioral hole, and a skill reading that it places no <!-- assumed: --> artifact markers leaves its own Tentative rows invisible to /fab-clarify's scan. One canonical sentence commonly has five or six live homes across src/kit/skills/, docs/specs/, and docs/memory/, each in different wording, so the sweep must grep the claim and not the phrase — the score-persister claim alone appears as "persist the intake score" / "Computation" / "Computed by" / "who scores, when" / "writes the intake score". A file-tracking sweep cannot terminate, because the defect is a property of the construct class rather than of any file. Rejected: Fixing the reported site list and shipping — three consecutive attempts did exactly that and the identical defect survived one hop out each time (name lists → count words → a byte-parallel spec copy). Name-matching rather than behavioral membership — it wrongly edits near-miss enumerations (an ID-collision set a natural-language-input consumer never joins; a change-creation inventory keyed on activation behavior). Introduced by: 260728-4v91-add-fab-dedupe-skill

External Sub-Domain Addressing (Up-to-3-Hop Selective Load)

Decision: When an over-wide domain is split into sub-domains, the sub-domain file is addressed externally — the Affected Memory contract, the always-load layer, and selective loading all gain a {domain}/{sub-domain}/{file} form. Selective domain loading becomes an up-to-3-hop walk: domain index → (only if the entry is sub-domained) sub-domain index → file. A flat domain stays the degenerate 2-hop case (no sub-domain index hop, byte-identical to pre-change behavior). Why: External addressing makes sub-domains first-class and navigable — there is no "find-the-file-anywhere-under-the-domain" resolver ambiguity (the failure mode of the Internal/duplicate-truth-file alternative). loom's historical External-style index churn was the hand-edited-index problem, eliminated by generated sub-domain indexes (fab memory-index writes them too) (tciy), so External's only historical downside is moot; its upside (explicit, navigable addressing) stands. Rejected: Internal addressing (sub-domain files resolved by search under the domain) — re-introduces resolver ambiguity and a duplicate-truth-file failure mode. A flat-only model (never sub-dividing) — leaves over-wide domains (e.g. fab-workflow at 20 files > ~12) with no structural escape valve. Introduced by: 260607-sx7a-reorg-memory-shape-rebalance

Smart Loading for All Skills on Active Changes

Decision: Expanded "Memory Lookup" from spec-writing-only to all skills operating on an active change. Why: Agents need domain awareness for planning, implementation, and review — not just spec writing. Rejected: Per-skill opt-in — too much maintenance overhead and easy to miss new skills. Introduced by: 260207-q7m3-separate-hydrate-smart-context

Always Load docs/specs/index.md

Decision: Added docs/specs/index.md to the "Always Load" layer as a 4th baseline file. Why: Gives every skill awareness of the specifications landscape (pre-implementation design intent) alongside the documentation landscape. The index is lightweight and human-curated, so context cost is minimal. Rejected: Loading design index only when relevant — same inconsistency risk as with memory/index.md. Introduced by: 260207-bb1q-add-specs-index

Always Load docs/memory/index.md

Decision: Added docs/memory/index.md to the "Always Load" layer alongside config.yaml and constitution.md. Why: Gives every skill baseline awareness of the documentation landscape. The index is lightweight (a table of domains), so the context cost is minimal. Rejected: Loading only when needed — would require each skill to independently decide, leading to inconsistency. Introduced by: 260207-q7m3-separate-hydrate-smart-context

Flatten Helper Include Tree

Decision: Collapse the helper always-load set from {_preamble, _cli-fab, _naming, _cli-rk} to {_preamble} only. Inline _naming and _cli-rk into _preamble — the rk content lives in _preamble.md § Run-Kit (rk) Reference, where the silent-fail-when-rk-missing design is preserved verbatim, and centralizing the visual-display recipe there (rather than baking it into visual-explainer) keeps the capability available to any skill (mgsm). Add a new per-skill helpers: frontmatter field listing the additional helpers each skill needs (_generation, _review, _cli-fab, _cli-external). Inline the 6 most-used fab commands into _preamble § Common fab Commands. Compress _cli-fab from 773 lines to ≤300. Why: Two root causes. (1) Universal "also read" fanout from _preamble shipped ~1324 lines of helper content that 15 of 24 skills didn't use. (2) Agents silently skipped 2nd-layer "also read" directives — pointer-based loading was non-deterministic. Replacing the fanout with explicit, frontmatter-declared helpers is auditable, grep-able, and reliable (agents read frontmatter before body). Inlining the smallest helpers and the commonest commands eliminates the 2nd layer for most skills entirely. Rejected: (a) Splitting _preamble further — deepens the tree, worsens skip-rate. (b) Relying on prompt caching — doesn't fix correctness when pointers are silently skipped. (c) Full inline of _cli-fab — adds ~500 lines to universal load. (d) Renaming _-prefix to visible names (backlog [84bh]) — addresses visibility but not fanout; the structural fix covers it. (e) Decentralizing the rk visual-display recipe into visual-explainer only — forces other skills to duplicate logic or use visual-explainer as a middleman (mgsm). Introduced by: 260418-or0o-flatten-skill-helpers

Standard Subagent Context as Centralized Template

Decision: Added a Standard Subagent Context subsection to _preamble.md § Subagent Dispatch, listing the 5 fab/project/** files that every subagent must read. Skills reference this template instead of maintaining ad-hoc file lists. Why: Each skill that dispatched subagents maintained its own context list, creating silent quality gaps (forgotten files) and drift risk (new files not propagated). Centralizing in _preamble.md ensures all subagents — at any nesting depth — inherit project principles automatically. Rejected: Including docs/memory/index.md and docs/specs/index.md in subagent context — these are navigation aids for the parent agent, not project principles needed by subagents. Introduced by: 260318-dzze-standard-subagent-context