# nagent_review_v3_20260619 — Mike Acton's nagent, the 24-commit evolution + case studies

**Status:** Draft (Phase 1 setup complete; cluster sections pending)
**Initialized:** 2026-06-19
**Owner:** Tier 1 Orchestrator (sole author; Tier 2 executing per `plan_v3.md`)
**Spec pair:** `spec_v3.md` + `plan_v3.md` (in the same track directory)
**Lineage:** Supersedes `nagent_review_v2_3_20260612.md` (4,969 lines, the v2.3 canonical review). v2.3 is preserved as historical.
**Source state:** `macton/nagent@a1f0680` (2026-06-18 23:51:28 UTC) + the two case-study repos at `main`.

> **Reading guide.** v3 covers the 24 new nagent commits on `macton/nagent@main` between `eb6be32a` (2026-06-12) and `a1f0680` (2026-06-18), and the two case-study repos that didn't exist at v2.3 baseline: [`macton/pep-copt`](https://github.com/macton/pep-copt) and [`macton/differentiable-collisions-optc`](https://github.com/macton/differentiable-collisions-optc). The 11 clusters are: Campaigns (§1), Conversation safety net (§2), Hooks (§3), Project-local roots (§4), Provider expansion (§5), Delegation rewrite (§6), Robustness (§7), Operating rules (§8), Case-study methodology (§9), PEP case study (§10), Collisions case study (§11).

> **Lineage note.** v2.3's 14-pattern analysis stands; v3 does not delete it. Where v3 updates a v2.3 pattern, the cluster section calls out the update explicitly. Where v3 introduces a new pattern, the cluster section cites the v2.3 pattern it does NOT replace (if any).

## §0 TL;DR

v3 covers the **24-commit nagent evolution** between `eb6be32a` (v2.3 baseline, 2026-06-12) and `a1f0680` (v3 baseline, 2026-06-18), plus two case-study repos that didn't exist at v2.3: [`macton/pep-copt`](https://github.com/macton/pep-copt) (PEP image compression, 2.04× speedup aggregate, byte-identical output, 24-image benchmark) and [`macton/differentiable-collisions-optc`](https://github.com/macton/differentiable-collisions-optc) (Convex Primitive Collision Detection, 101.06× speedup on committed input, distance-tolerance match contract). **Three entirely new first-class subsystems** land: Campaigns (§1, plans as operable artifacts), Conversation safety net (§2, checkpoints + rebuild), Hooks (§3, per-turn ground-truth injection). The case-study methodology (§9) is itself a new abstraction — the 5-element pattern (prompts + harness + log + freeze + subject) with a parameterizable match contract. Updates to existing patterns: Together is added as a sixth provider (§5) with per-model token-cap rebuild triggers; delegation rewrite fixes a recursion bug (§6) and names "decompose or isolate, never offload"; robustness commits harden the loop (§7) against four specific failure modes (non-protocol output, duplicate tags, ordering, scratch collisions); operating-rules gain Q9 (§8) for "sampling justifies replacing the machine." The total v3 cluster count is **11** (§1-§11) covering 24 commits + 2 case-study repos + 1 cross-cutting methodology cluster.

## §1 Campaigns

**Source:** nagent `24cf16d`, `199a36b`, `f3ec090`, `c1d2cad`, `6443d70`, `7a7e242` (`bin/nagent-campaign`, `bin/helpers/nagent_campaign_lib.py`, `bin/helpers/nagent_distill_lib.py:228-260` + `:793-979`, `bin/nagent-distill:107-200`, `prompts/campaign-decompose.md`, `prompts/campaign-item.md`, `prompts/knowledge-merge.md`, `prompts/knowledge-graduate.md`, `prompts/create-readme.md:248-251`, `issues/0002-campaign-system.md`, `issues/0004-conversation-safety-net.md`, `tests/test_nagent_campaign.py`, `tests/test_nagent_distill.py`, `README.md:474-484` + `:900-908`)
**One-liner:** Plans become operable artifacts. The plan is data (YAML), the driver is deterministic code, the model's non-determinism is relocated and bounded to narrow judgments.
**Pattern summary:** Campaigns make the plan a first-class artifact: an inspectable, editable, durable spine that survives the conversation that created it. The artifact is a YAML tree on disk (`.nagent/campaigns/{slug}/index.yaml` + per-item `item.yaml` + per-item conversation); the driver is `bin/nagent-campaign` doing one bounded pass and exiting; the model's non-determinism is relocated to the narrow judgment of proposing items (decomposition) and reporting (status), and bounded by an explicit review gate. This extends the "durable work, disposable workers" principle (v2.3 Pattern 1) by making "durable work" an explicit artifact instead of a process convention, and extends "conversations are editable state" (v2.3 Pattern 3) by adding a new editable dimension parallel to conversations: the plan tree itself.

#### §1.1 What Campaigns Adds

Campaigns introduce a new lifecycle boundary between planning and execution. Before campaigns, nagent's loop was implicit: a conversation's "what to do next" was the model's judgment, re-made every turn. With campaigns, the plan is a tree on disk that the model can read (it's part of initial context) and write to (via the proposal file), but cannot edit silently (the review gate is explicit). The four pieces of the campaigns abstraction are:

1. **Artifact** — the YAML tree at `.nagent/campaigns/{slug}/index.yaml` (campaign-level) + per-item `item.yaml` (one per leaf task) + per-item `conversation` (the conversation that produced / is working the item). The artifact is the state of record; the conversation is ephemeral.
2. **Driver** — `bin/nagent-campaign update` runs a deterministic 6-phase pass: merge → check → propose → review gate → dispatch → report. One pass, one exit. The driver is the only mutator of the tree; workers read it, return data, but do not write to it.
3. **Invariants** — four load-bearing rules from `issues/0002-campaign-system.md:139-164`: (a) one pass then exit (the driver never loops); (b) one writer for the tree (the driver); (c) review gate not cap (proposals accumulate, a human or threshold decides); (d) schema is the whole schema (the YAML is a complete description; the code does not maintain a parallel mental model).
4. **Context surfaces** — three places the campaigns pattern appears in initial context: every project conversation gets a "Campaigns" block (the tree is visible); dispatched item workers get the worker contract (the item's `item.yaml` + the parent campaign's `index.yaml`); campaign-level conversations are ordinary conversations with the campaign as subject (the tree is read, not written).

This decomposition is itself data-oriented — the campaign's behavior is the artifact's shape, not code branching on state. The model never has an "is this campaign active" boolean to check; it reads the YAML and the state is the file.

#### §1.2 The Driver Phases

The `update` command runs six phases. Each phase is a pure operation on the tree + a bounded external call (LLM for `propose`, LLM for `report`):

1. **Merge** — collect structured results from in-flight item workers, update their `status` from `in-progress` to `done` / `failed` / `question` based on the result files. Pure code; no LLM call.
2. **Check** — run the executable test of `completion: [condition]` entries. For `condition` types that are LLM-judged (e.g., "the README explains X"), the judge is bounded to one short LLM call per condition, with the judgment in a sidecar file. No multi-turn model reasoning.
3. **Propose** — for items that are too large (the `decompose:` field on the item, or a heuristic on item age/size), call the LLM with `prompts/campaign-decompose.md` to produce a `proposal.yaml` with sub-items. The LLM proposes; the user (or threshold) decides.
4. **Review gate** — for `proposal.yaml` files that exceed `auto_confirm_max_items` or `auto_confirm_max_depth`, surface them to the user. Below the thresholds, auto-confirm. The gate is explicit: a `proposal.yaml` either gets accepted by the gate or it doesn't; there is no "the model assumed it was OK" path.
5. **Dispatch** — pick up to N unblocked items (where N is `dispatch_max_concurrent` or a default), launch each as a `--campaign-item` worker with the worker contract. Workers return data; they do not write the tree.
6. **Report** — produce a tree summary (status counts, tokens spent, questions raised). The report is a single LLM call with the full tree as context, gated to a small output budget.

A code-shape sketch using survey grammar (per the format commitment §5.1):

```
campaign := { name: string, status: active|paused|done,
              completion: [condition], items: [item] }
item     := { id: string, status: todo|proposed|in-progress|done|failed|question,
              blocked_by: [id], conversation: path,
              decompose: { when: heuristic, into: [sub_item] } }
update {slug} {
  merge       // collect structured results, update statuses (pure code)
  check       // run executable test: conditions; bounded judge for judge:
  propose     // decompose big items -> proposal.yaml, status proposed
  review_gate // auto-confirm within thresholds; report scope of pending
  dispatch    // bounded N unblocked items, each as --campaign-item worker
  report      // tree summary + questions + tokens spent
}
```

The `{ssdl}` shape tag for the campaign tree is `[M]` (mutable aggregate, hand-edited by humans) — the artifact is the state of record, the worker contract returns data, the driver is the only mutator. The lineage to v2.3's harvest pattern is direct: workers produce data (harvest-JSON in v2.3; `result.json` here), code merges into the tree (regenerate_digest in v2.3; driver merge phase here).

#### §1.3 The Invariants

From `issues/0002-campaign-system.md:139-164`, the four invariants that hold the abstraction together:

1. **One pass then exit.** The driver never loops. It does one bounded pass and exits. If the result of the pass is "more work to do", the user (or a cron, or a hook) runs `update` again. This is what makes the driver cheap to reason about: it cannot deadlock, cannot recurse, cannot "hang" waiting for the model. It's a function of (tree, in-flight results) → (updated tree, dispatched workers, report).
2. **One writer for the tree.** The driver is the only thing that writes `.nagent/campaigns/{slug}/`. Workers read it, return data, do not write. The user can edit it (that's the point of "the artifact is editable"), but the model cannot edit it without going through a proposal. This eliminates the "two writers race on the same file" class of bugs.
3. **Review gate not cap.** Proposals accumulate. A human (or a threshold) decides whether to accept them. The model never "assumes" a proposal is accepted; the gate is explicit. This is what makes the abstraction safe for long-running campaigns: the model cannot silently expand the plan.
4. **Schema is the whole schema.** The YAML tree is a complete description of the campaign. The code does not maintain a parallel mental model (e.g., "we track active items in memory and the YAML is just a snapshot"). The YAML is the truth; the code is a function of the YAML.

The fourth invariant is the load-bearing one for the data-oriented framing: the campaign's behavior is the artifact's shape, not code branching on state. The model never has an "is this campaign active" boolean to check; it reads the YAML and the state is the file.

#### §1.4 Per-Commit Detail

The six commits that built the campaigns subsystem, in dependency order:

1. **`24cf16d` — Add the campaigns driver.** Adds `bin/nagent-campaign` (the CLI entry point) + `bin/helpers/nagent_campaign_lib.py` (the driver implementation, ~400 lines). Also adds the initial context block (`prompts/campaign-decompose.md` + `prompts/campaign-item.md`) so the model knows how to propose and dispatch. The 6-phase `update` command lands here. The worker contract is finalized in this commit: a `--campaign-item` worker gets the item's YAML, the parent campaign's index, and a tight output budget; it returns a result file (the structured outcome) and an optional question file (the narrow judgment).
2. **`199a36b` — Add the issue file that fully specifies the system.** Adds `issues/0002-campaign-system.md` (326 lines). This is the "long form spec as a file" pattern from v2.3 — the design is in the repo, not in a wiki or a chat. The issue file lists the layout, the invariants (the four above), the driver phases, the costs (token budget per phase), and the done criteria. This is the document the driver implementation in `24cf16d` was built to.
3. **`f3ec090` — Wire the merge/graduate passes to the campaign lifecycle.** Adds `bin/nagent-distill --merge` + `--graduate` CLI surface (lines 107-200) and the supporting `bin/helpers/nagent_distill_lib.py:228-260` (finished-campaign-as-harvest-source) + `:793-979` (`run_merge` + `run_graduate`). The merge pass takes the per-item results, the per-conversation knowledge files, and the campaign's own artifacts, and rewrites each category file with provenance preserved (the lineage to v2.3's harvest is direct). The graduate pass takes "proven playbooks" (knowledge that has been used N times) and drafts them as non-executable `{name}.draft` files invisible to tool discovery until the user reviews them. The two prompts (`prompts/knowledge-merge.md` + `prompts/knowledge-graduate.md`) are short and tight: merge is 19 lines, graduate is 26.
4. **`c1d2cad` — Update the README to teach the merge + graduate passes.** Adds `README.md:474-484` (the merge/graduate teaching) and a key sentence to `prompts/create-readme.md:248-251` that codifies the "graduate proven playbooks" principle: "Proven playbooks stay prose that must be re-read and re-trusted every time. Therefore: graduate them into self-describing tools and prompts — knowledge becomes capability, gated by review." This is the design rationale: knowledge graduates into capability, but only after review. The "gated by review" clause is the same review-gate invariant as the proposal gate.
5. **`6443d70` — Rework the conversation safety net issue file.** This is not strictly a campaigns commit, but it lands in the same window. Reworks `issues/0004-conversation-safety-net.md` to reflect the new wall-clock checkpoints + burst guard (the §2 cluster covers this in detail). The connection to campaigns: a long-running campaign can have conversations that exceed the model's context window; the safety net is what catches the case where the campaign's "I am still working on this" assumption breaks down. Also deletes `issues/0003-distill-passes.md` (its content shipped in `f3ec090`) — the issue file pattern is self-pruning: closed issues get deleted when their work merges.
6. **`7a7e242` — File the deferred follow-ups as issue files.** Adds `issues/0001-retry-attempts-persist-raw-invalid-output.md` + `issues/0002-invalid-output-sidecars-are-never-collected.md`. Two known rough edges in the driver that are not blocking but are filed for future work. The issue numbering restarts at 0001/0002 because the closed issues were deleted — so the "issue files" pattern is self-pruning and the numbering reflects "currently-open issues", not "issues ever filed".

#### §1.5 Manual Slop Implications

The Manual Slop equivalents of the campaigns pattern are partial. The closest analog is the per-track `plan.md` + `state.toml` + `metadata.json` triplet in `conductor/tracks/{track_id}/`. The per-track `plan.md` is the editable plan; `state.toml` is the machine-readable progress; `metadata.json` is the spec-derived scope. But the Manual Slop analog lacks three of the four campaigns invariants:

1. **No "one writer for the tree" guarantee.** The `plan.md` is hand-edited by the user, hand-edited by Tier 2 (with `edit_file` or `set_file_slice`), and read by Tier 3 workers. There is no `bin/nagent-campaign` equivalent that mediates writes. The "two writers race" class of bugs is real (e.g., Tier 2 edits `plan.md` while Tier 3 worker is reading it).
2. **No "one pass then exit" driver.** The MMA WorkerPool's `ConductorEngine` (in `src/multi_agent_conductor.py`) is the closest analog — it manages ticket execution with auto-queue / step-mode — but it does not have the 6-phase pass structure. It loops; the driver does not.
3. **No explicit review gate.** Manual Slop's HITL flow is the modal confirm (`_predefined_callbacks` + `_gettable_fields` in `src/app_controller.py`); nagent's gate is the `proposal.yaml` file with `auto_confirm_max_items`/`auto_confirm_max_depth` thresholds. The Manual Slop gate is a yes/no per worker spawn; the nagent gate is a threshold over a batch of proposals.

The Manual Slop patterns that already align with campaigns:
- **Per-track `state.toml`** (e.g., `conductor/tracks/nagent_review_20260608/state.toml`) is a partial `[M]` mutable aggregate. It has phase + task entries with `status` + `commit_sha` fields. The analog is partial: the `state.toml` is read by the conductor but the writing discipline is "Tier 2 Tech Lead hand-edits after each commit", not "the driver is the only writer".
- **The `_predefined_callbacks` Hook API** (in `src/app_controller.py:531-617`) is the closest analog to the campaign's context surfaces. The Hook API exposes any App method as a `custom_callback` action, which is how external automation (the ApiHookClient) drives the app. The campaigns analog: the initial-context block is the Hook API's surface; the worker contract is the `custom_callback` payload.
- **The MMA WorkerPool's tier-3 workers** (in `src/multi_agent_conductor.py` + `scripts/mma_exec.py`) already follow the spirit of campaigns (structured result, no direct tree mutation) but lack a documented worker contract + review gate. The `WorkerPool` spawns workers with `mma_exec.py --role tier3-worker`; the worker returns its result via the file system; the `ConductorEngine` picks up the result and updates the ticket. This is the campaigns pattern at the tier-3 layer, but it is not generalized to the per-track layer.

The gap Manual Slop could close: a per-track `conductor/tracks/{track_id}/campaign.yaml` + a `bin/conductor-campaign update` driver that does the 6-phase pass. The driver would: merge Tier 3 worker results into `state.toml`, check completion conditions, propose decomposition of large tasks, gate the proposals through the existing HITL flow, dispatch unblocked tasks to the WorkerPool, and report. This would be a significant new feature — the closest existing analog is the `MMA Dispatcher Loop` in `src/multi_agent_conductor.py:280-340`, but it's scoped to the MMA queue, not the per-track plan.

**Note on YAML format (per the user's directive, expanded in v3.1 §12):** the campaigns artifact format is YAML. Manual Slop would use a different format — markdown with frontmatter (per the project's TOML precedent in `conductor/presets.py` + `conductor/personas.py`) or a custom DSL. The data shape is the same (tree of items with status, blocked_by, conversation); the format is markdown, not YAML. See v3.1 §12 for the full rationale.

#### §1.6 Honest Gaps

1. **The decompose prompt is not deep-dived.** `prompts/campaign-decompose.md` is the LLM prompt that proposes item decomposition. The v3 cluster notes its existence and its role, but does not analyze the prompt's structure (how it instructs the LLM to produce a `proposal.yaml` with sub-items, what the schema constraints are, what the "small enough to dispatch" heuristic is). A future v3.1 deep-dive (or a v4) would read the prompt in full and characterize the prompt-as-spec pattern.
2. **The worker contract is not deep-dived.** The `--campaign-item` worker gets a specific input shape (the item's YAML, the parent campaign's index, a tight output budget) and returns a specific output shape (a result file, an optional question file). The v3 cluster notes the contract's existence and the merge phase's handling of the output, but does not enumerate the full worker contract surface (what fields are required vs optional, what the output schema is, what happens when a worker returns a malformed result).
3. **The judge condition type is not deep-dived.** The `completion: [condition]` field supports an LLM-judged condition type (e.g., "the README explains X"). The judge is a bounded one-shot LLM call with the judgment in a sidecar file. The v3 cluster notes the existence of the judge but does not analyze the judge's prompt structure, the sidecar schema, or the failure modes (what happens when the judge returns "I cannot determine"?).
4. **The `auto_confirm_max_items` and `auto_confirm_max_depth` thresholds are not enumerated.** The review gate's thresholds are mentioned but the v3 cluster does not document what the recommended values are, what the cost model is, or how a user would tune them for their use case. A v4 would document the threshold tuning procedure.
5. **The dispatch concurrency limit is not enumerated.** The `dispatch_max_concurrent` field is mentioned (the driver picks up to N unblocked items), but the v3 cluster does not document the recommended N, the cost model, or the failure handling (what happens when a dispatched worker crashes without returning a result? does the driver time out and re-dispatch? does the item stay `in-progress`?).
6. **The interaction with the conversation safety net is not deep-dived.** The §2 cluster covers the safety net (wall-clock checkpoints + burst guard) and notes that a long-running campaign can have conversations that exceed the model's context window. The v3 cluster does not document the specific interaction: does the campaign driver check for context-window-exceeded conditions during the merge phase? does the dispatch phase refuse to launch a worker when the context window is already full? does the report phase surface context-window warnings to the user? A v4 would map the safety net's hooks into the campaign driver's phases.

#### §1.7 Code-Shape Sketch

The campaign tree, in survey-grammar SSDL notation, with shape tags:

```
campaign := { name: string,                        # [S] string concatenation
              status: active|paused|done,          # [I] inspectable enum
              completion: [condition],             # [M] mutable list
              items: [item],                       # [B] boundary (the dispatch list)
              proposal: proposal_yaml? }           # [M] mutable, pending review

item     := { id: string,                         # [S]
              status: todo|proposed|in-progress|done|failed|question,  # [I]
              blocked_by: [id],                    # [B] dependency edge
              conversation: path,                  # [B] path to conversation file
              decompose: { when: heuristic, into: [sub_item] }?,  # [M] optional
              result: result_json? }               # [M] populated by merge phase

condition := { type: executable|judge,             # [I]
               spec: string,                       # [S] the test or the judge prompt
               satisfied: bool }                   # [I] populated by check phase

result_json := { status: done|failed|question,     # [I]
                 summary: string,                  # [S]
                 question: question? }            # [M] optional

update {slug} {                                      # driver entry point
  merge       // collect result.json files, update item statuses (pure code)
  check       // run executable test: conditions; bounded judge for judge:
  propose     // decompose big items -> proposal.yaml, status proposed
  review_gate // auto-confirm within thresholds; report scope of pending
  dispatch    // bounded N unblocked items, each as --campaign-item worker
  report      // tree summary + questions + tokens spent
}
```

The shape tag map: `[I]` for inspectable enums and booleans (the model's understanding is the file's value), `[S]` for string concatenations (the model's understanding is the file's content), `[B]` for boundaries (the model's understanding is the file's edge), `[M]` for mutable aggregates (the model's understanding is the file's state). The campaign tree is a `[M]` aggregate: it is the state of record, hand-edited by humans, written by the driver, read by workers.

**Source-read citations:**
- `bin/nagent-campaign` — new CLI entry point (24cf16d)
- `bin/helpers/nagent_campaign_lib.py` — driver implementation (24cf16d)
- `issues/0002-campaign-system.md:1-326` — full spec: layout + invariants + driver phases + costs + done criteria (199a36b)
- `bin/helpers/nagent_distill_lib.py:228-260` — finished-campaign-as-harvest-source (f3ec090)
- `bin/helpers/nagent_distill_lib.py:793-979` — `run_merge` + `run_graduate` (f3ec090)
- `bin/nagent-distill:107-200` — `--merge` + `--graduate` CLI surface (f3ec090)
- `prompts/knowledge-graduate.md:1-26` — graduation LLM prompt (f3ec090)
- `prompts/knowledge-merge.md:1-19` — merge LLM prompt (f3ec090)
- `README.md:474-484` — merge + graduate teaching (c1d2cad)
- `README.md:900-908` — `nagent-campaign` CLI examples (24cf16d)
- `prompts/create-readme.md:248-251` — graduation rationale (c1d2cad)
- `issues/0001-retry-attempts-persist-raw-invalid-output.md` + `issues/0002-invalid-output-sidecars-are-never-collected.md` — two deferred follow-ups, filed as issue files (7a7e242)
- `issues/0004-conversation-safety-net.md` (reworked at 6443d70) — wall-clock checkpoints + burst guard
- `prompts/campaign-decompose.md:1-N` — decomposition LLM prompt (24cf16d)
- `prompts/campaign-item.md:1-N` — worker contract prompt (24cf16d)
- `bin/nagent-campaign:1-N` — CLI argument parsing + subcommand dispatch (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:update()` — the 6-phase driver entry (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:merge_phase()` — collect results, update statuses (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:check_phase()` — run conditions (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:propose_phase()` — decompose big items (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:review_gate_phase()` — threshold-based accept (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:dispatch_phase()` — bounded worker launch (24cf16d)
- `bin/helpers/nagent_campaign_lib.py:report_phase()` — tree summary + tokens (24cf16d)
- `tests/test_nagent_campaign.py` — driver unit tests (24cf16d)
- `tests/test_nagent_distill.py:merge_*` + `:graduate_*` — merge/graduate tests (f3ec090)
- `README.md:450-500` — campaigns teaching section (24cf16d + c1d2cad)
- `README.md:880-920` — campaigns CLI examples + cost model (24cf16d)
- `issues/0002-campaign-system.md:139-164` — the 4 invariants (199a36b)
- `issues/0002-campaign-system.md:159-191` — the 6 driver phases (199a36b)
- `issues/0002-campaign-system.md:193-260` — costs (tokens per phase) + done criteria (199a36b)
- `issues/0002-campaign-system.md:262-326` — open questions + future work (199a36b)

**Decision candidate:** NEW Candidate 17 (HIGH). "Campaign-style plan-as-data for the conductor": add a `.conductor/campaigns/{slug}/` layout with `index` + per-task `task` + per-task conversation artifacts; add a deterministic driver (1 pass, then exit) that mirrors `nagent-campaign update`'s 6 phases. The artifact format is markdown + frontmatter, not YAML (per the v3.1 §12 YAML avoidance observation). See `decisions.md` Candidate 17.
**Cross-refs:** §2 Conversation safety net (the safety net that decomposition cannot bound); §9 Case-study methodology (the 5-element pattern that the campaigns driver partially implements); §12 YAML avoidance (the format choice for the campaign artifact).
**Pattern history:** NEW in v3. v2.3 had the implicit "what to do next is the model's judgment" loop. v3 makes the plan a first-class artifact.
## §2 Conversation safety net

**Source:** nagent `38d3d4f`, `6426a67` (`bin/nagent:1455-1687` + `:1840-1881` + `:2463-2677` + `:2819`, `bin/helpers/nagent_distill_lib.py:587-654` + `:851-862`, `config.example.json:3-7`, `prompts/checkpoint-conversation.md`, `README.md:653-668` + `:323-332`, `issues/0004-conversation-safety-net.md`, `tests/test_nagent_safety.py`, `tests/test_nagent_distill.py`)
**One-liner:** A conversation that outgrows its window gets caught, not killed. Checkpoints are a separate one-call writer, not the working model; rebuild is a deterministic string assembly that runs a synchronous checkpoint first; saves are instant because the summary is extracted from the checkpoint's already-paid-for Intent line, not a new LLM call.
**Pattern summary:** The safety net is a four-piece composition: trigger, writer, rebuild, provenance. The trigger is wall-clock + burst guard, both computed from data on disk; the writer is a separate one-call LLM call (not the working model); the rebuild is a deterministic string assembly that runs the writer synchronously first; the provenance is the deterministic header that lets the writer find the delta on the next pass. Failure widens the fallback (4× tail on writer error) rather than blocking. Saves are instant because the summary is extracted from the checkpoint's already-paid-for Intent line, not a new LLM call — the cost moves from the hot path to the maintenance path. This extends the "the loop" principle (v2.3 Pattern 5) with failure-recovery semantics, extends "large files as explicit artifacts" (v2.3 Pattern 11) with checkpoints as an explicit working-state artifact editable between triggers, and extends "repo history as data" (v2.3 Pattern 7) with deferred-cost summaries where the LLM cost is visible (dry-run reports) and bounded (per-pass), not paid up-front.

#### §2.1 What the Safety Net Adds

The safety net introduces a failure-recovery layer between the conversation and the model's context window. Before the safety net, a conversation that grew past the model's window was a hard failure: the model lost coherence, the user lost work, and the recovery was "start over". With the safety net, the conversation is a recoverable artifact: checkpoints are written to a separate file, the rebuild procedure is deterministic, and the failure mode is "fall back to a wider tail" instead of "lose the conversation".

The four pieces of the safety net abstraction:

1. **Trigger** — wall-clock + burst guard, both computed from data on disk. `bin/nagent:1519-1539` implements `checkpoint_due` and `rebuild_due` as pure functions of (last checkpoint timestamp, current conversation size, config). The trigger is data, not code branching on state. The cadence reasoning is explicit: "time and context consumption are uncorrelated in exactly the wrong direction" (`issues/0004-conversation-safety-net.md:30`). Token-percentage triggers were "an approximation of an approximation" — three numbers in units `ls -l` can verify are the data-grounded alternative.
2. **Writer** — a separate one-call LLM call (`bin/nagent:1547-1587` — `write_checkpoint`). The writer is NOT the working model. It is a fresh one-shot call with a tight prompt (`prompts/checkpoint-conversation.md`) that produces a deterministic-structured output (## Intent | ## Next action | ## Constraints | ...). The writer's output is user-editable: the checkpoint file is a markdown file the user can hand-edit between triggers.
3. **Rebuild** — a deterministic string assembly (`bin/nagent:1590-1662` — `rebuild_conversation`) that runs the writer synchronously first. The rebuild is "initial context + {checkpoint} + tail" — no LLM call beyond the synchronous checkpoint. The deterministic assembly is what makes the rebuild safe to reason about: it cannot fail in a way the user cannot predict.
4. **Provenance** — the deterministic header (`updated:`, `conversation_chars:`) that lets the writer find the delta on the next pass. The header is the contract between checkpoints: the writer reads it, computes the delta, writes the new checkpoint with an updated header.

The "sync checkpoint first" invariant is the load-bearing one. A naive rebuild that trusted the most-recent checkpoint's freshness would fail on the exact conversation the safety net is meant to save (a conversation that grew past `rebuild_at_kb` between scheduled checkpoints). The rebuild runs the writer synchronously, and on writer failure widens the tail 4× (`bin/nagent:1610-1612`) — failure as data, not failure as control flow. The rebuild is "blockable by a provider outage" would be the wrong failure mode.

#### §2.2 The Writer and the Checkpoint Format

The checkpoint is a markdown file with a deterministic header and a fixed-structure body. The header is two fields:

```
updated: <ISO 8601 timestamp>
conversation_chars: <integer>
```

The body is the writer's LLM output, constrained to a fixed schema (`prompts/checkpoint-conversation.md`):

```
## Intent
<one sentence: what the user is trying to achieve>

## Next action
<one sentence: what the next model turn should do>

## Constraints
<bullet list: things the next model turn must NOT do>

## Open questions
<bullet list: things the next model turn should ask>
```

The schema is the whole schema. The code does not maintain a parallel mental model (e.g., "we track the intent in a separate field"). The markdown file is the truth; the code is a function of the markdown file.

The writer is a one-shot LLM call, not the working model. This matters for two reasons:

1. **Cost visibility.** The writer's LLM cost is paid once per checkpoint, not once per turn. A conversation with 100 turns and 4 checkpoints pays 4 writer calls; the alternative (the working model re-summarizing on every turn) would pay 100 re-summary calls. The cost moves from O(turns) to O(checkpoints).
2. **Non-deterministic working model does not pollute the checkpoint.** The working model is mid-conversation, mid-reasoning; its output is shaped by the current turn's context. The writer is a fresh one-shot with the full conversation as input; its output is shaped by the prompt's schema, not the current turn's state. The checkpoint is stable across reads.

A code-shape sketch using survey grammar:

```
checkpoint := { updated: timestamp,                  # [S] string
                conversation_chars: int,             # [I] inspectable
                body: ## Intent | ## Next action | ## Constraints | ## Open questions }  # [B] boundary

write_checkpoint { conversation, llm, now } {
  delta = conversation[meta.conversation_chars:]   # [S] string slice
  if len(delta) < min_delta_chars { return nil }   # too small to summarize
  prompt = format(prompts.checkpoint-conversation.md, delta)  # [S] string format
  body   = llm.call(prompt)                        # [B] boundary to LLM
  write checkpoint.updated = now
  write checkpoint.conversation_chars = len(conversation)
  write checkpoint.body = body
}
```

The `[B]` boundary tag marks the single LLM call in the writer. Everything else is pure data manipulation: string slicing, string formatting, file writes. The writer is "an LLM call wrapped in deterministic I/O".

#### §2.3 The Trigger Logic

The trigger is a pure function of (last checkpoint timestamp, current conversation size, config). `bin/nagent:1519-1539` implements two functions:

1. **`checkpoint_due(meta, conversation_chars, now, settings)`** — returns true if either:
   - `elapsed_minutes(now, meta.updated) > settings.checkpoint_interval_minutes` AND `conversation_chars > meta.conversation_chars + new_chars_threshold`
   - `conversation_chars - meta.conversation_chars > settings.checkpoint_max_new_kb * 1024`
   - `meta is nil` AND `conversation_chars > settings.rebuild_at_kb * 1024` (first checkpoint, when the conversation has already grown past the rebuild threshold)
2. **`rebuild_due(meta, conversation_chars, settings)`** — returns true if `meta is nil` OR `conversation_chars > settings.rebuild_at_kb * 1024`.

The three config numbers are in `config.example.json:3-7`:

```json
{
  "safety_net": {
    "checkpoint_interval_minutes": 10,
    "checkpoint_max_new_kb": 32,
    "rebuild_at_kb": 192
  }
}
```

All three are in units `ls -l` can verify: minutes, kilobytes, kilobytes. Token-percentage triggers were rejected as "an approximation of an approximation" (`issues/0004-conversation-safety-net.md:30-44`) — the 3-number config is the data-grounded alternative. The user can `ls -l` the conversation file and know whether the trigger will fire, without having to estimate the model's token-percentage consumption.

#### §2.4 The Rebuild Procedure

The rebuild is "initial context + {checkpoint} + tail" — a deterministic string assembly (`bin/nagent:1590-1662` — `rebuild_conversation`). The procedure:

1. **Sync checkpoint first.** Run `write_checkpoint(conversation, llm)` synchronously. This catches the case where the most-recent scheduled checkpoint is stale (the conversation grew past `rebuild_at_kb` between scheduled checkpoints). The sync checkpoint is the "freshness" guarantee.
2. **Widen tail on writer failure.** If the writer call fails (provider outage, rate limit, malformed response), widen the tail 4× — `bin/nagent:1610-1612`. Failure as data, not failure as control flow. The rebuild cannot fail in a way that loses the conversation.
3. **Archive the old conversation.** Move the conversation file to `archive/{timestamp}-{slug}/conversation` so the user has the pre-rebuild state.
4. **Write the new initial context.** Build the new initial context from the system prompt + the checkpoint's body + the tail of the conversation. The tail is the last `REBUILD_TAIL_CHARS` characters of the conversation (default 64KB, `bin/nagent:1463`).
5. **Reset the checkpoint's `conversation_chars`.** The new conversation's size becomes the new "fresh window" for the next rebuild.

A code-shape sketch:

```
rebuild { conversation, llm, now, settings } {
  try write_checkpoint(conversation, llm, now)
  recover {
    tail_chars = REBUILD_TAIL_CHARS * 4             # widen 4x on failure
    audit msg "checkpoint writer failed; using widened tail"
  } else tail_chars = REBUILD_TAIL_CHARS

  archive_path = archive/{now}/{slug}/conversation
  move conversation -> archive_path
  new_conversation = initial_context + checkpoint + conversation[-tail_chars:]
  write conversation = new_conversation
  reset meta.conversation_chars = len(new_conversation)
  reset meta.updated = now
}
```

The `{ssdl}` shape tag for the rebuild is `[S]` (string concatenation). The only LLM call is the sync checkpoint. Everything else is deterministic I/O.

#### §2.5 The Instant-Saves Change (6426a67)

The instant-saves change is a smaller, sharper version of the same idea: the cost of an LLM summary is moved from the hot path (every save) to the maintenance path (`nagent-distill --apply` backfill + `--summarize-conversation` on demand).

Before `6426a67`, every conversation save did an implicit LLM call to produce the summary. This had two costs:
1. **Hot-path latency.** A save was a multi-second LLM call, not a millisecond file write.
2. **Cost opacity.** The LLM cost was paid on every save, even when the user was just checkpointing progress.

After `6426a67`, the summary is extracted from the checkpoint's already-paid-for Intent line (the `## Intent` section of the most recent checkpoint). The summary is the artifact's own data — no new LLM call. The `summary_source: extracted | llm` provenance in the index is what makes this safe: the user can see which entries have been upgraded (via `--summarize-conversation`) and which are still extracted. The backfill pass (`bin/helpers/nagent_distill_lib.py:587-654` + `:851-862`) reports its cost in the dry-run summary, so the cost is visible before it is paid.

The "summary_source: extracted" provenance is a data-grounded trace of where the summary came from. The user can see at a glance: "this entry's summary was extracted from the checkpoint's Intent line; if I want an LLM-generated summary, I can run `--summarize-conversation` on it".

#### §2.6 Per-Commit Detail

The two commits that built the safety net subsystem:

1. **`38d3d4f` — Add the safety net machinery.** Adds `bin/nagent:1455-1687` (the `run_safety_net` + `checkpoint_due` + `rebuild_due` + `write_checkpoint` + `rebuild_conversation` functions), `bin/nagent:2819` (the `safety_settings=load_safety_settings(...)` wiring into `run_agent_loop`), `config.example.json:3-7` (the 3 safety-net config numbers), `prompts/checkpoint-conversation.md` (the writer LLM prompt), `README.md:653-668` (Part VI safety-net teaching), and `tests/test_nagent_safety.py` (the test file). This is the "structural" commit — it adds the abstraction, the trigger, the writer, the rebuild, the config, the prompt, the tests. The `safety_settings` wiring is the integration point: the safety net is now part of the main loop, not a separate opt-in feature.
2. **`6426a67` — Add the instant-saves change.** Adds `bin/nagent:1840-1881` (the `extract_conversation_summary` function), `bin/nagent:2463-2677` (the `--summarize-conversation` CLI surface), `bin/helpers/nagent_distill_lib.py:587-654` (the `_summary_backfill_candidates` + `_backfill_saved_summaries` functions), `bin/helpers/nagent_distill_lib.py:851-862` (the backfill wired into the distill apply path), and `README.md:323-332` (Part II instant-saves teaching). This is the "cost-moves" commit — it changes the summary source from "implicit LLM call on every save" to "extracted from the checkpoint's already-paid-for Intent line". The `_summary_backfill_candidates` function is the dry-run entry point: it returns the list of entries that would benefit from an LLM summary, with the estimated cost. The user sees the cost before paying it.

The two commits together implement the safety net as a structural pattern (not a persona-driven "watch-dog"). The trigger is data, the writer is a one-shot LLM call, the rebuild is deterministic, the provenance is in the file header. The pattern survives a provider outage (tail widens 4×), a model mid-conversation (writer is separate from working model), and a user mid-edit (checkpoint is user-editable markdown).

#### §2.7 Manual Slop Implications

The Manual Slop equivalents of the safety net are partial. The closest analog is the per-discussion write path in `src/discussion.py` (or similar) + the per-take branching in `src/project_manager.py:branch_discussion` + `promote_take`. The discussion history is a per-file artifact (`logs/sessions/{session_id}/discussion.jsonl` or similar), and the discussion index is a separate file. But the Manual Slop analog lacks three of the four safety-net invariants:

1. **No "sync checkpoint first" guarantee.** Manual Slop's discussion save path does not have a separate writer + rebuild procedure. A discussion that exceeds the model's context window is a hard failure (the next turn cannot see the full history).
2. **No "widen tail on failure" fallback.** Manual Slop's failure modes are exception-based, not data-widening. A provider outage during a save would raise an exception, not widen the fallback.
3. **No `summary_source: extracted | llm` provenance.** Manual Slop's discussion index does not record where each entry's summary came from. The user cannot tell which entries have been LLM-summarized vs extracted from the entry's own data.

The Manual Slop patterns that already align with the safety net:
- **`Result[T]` discipline** (per `conductor/code_styleguides/error_handling.md`) — failure widens the fallback instead of blocking. This is the same pattern as the safety net's "widen tail 4×" on writer failure.
- **`promote_take` + `branch_discussion`** (in `src/project_manager.py`) — the per-take branching is a form of "checkpoint" (each take is a snapshot of the discussion at a point in time). The user can rewind to a previous take, which is the same as reloading from a checkpoint.
- **The 3-layer MCP security model** (per `docs/guide_mcp_client.md`) — the Allowlist → Validate → Resolve layers are a form of "structural safety net" (failures are caught at the boundary, not in the middle of an LLM call).

The gap Manual Slop could close: a per-discussion safety net that writes checkpoints on a wall-clock cadence, runs a sync checkpoint before any rebuild, widens the tail on writer failure, and records the summary provenance. This would be a significant new feature — the closest existing analog is the per-take branching, but it's user-driven (the user explicitly creates a take), not automatic (the safety net fires on a schedule).

**Note on the 3-number config pattern:** the safety net's `checkpoint_interval_minutes`, `checkpoint_max_new_kb`, `rebuild_at_kb` config is a model Manual Slop should follow. Operations should be configurable in units `ls -l` can verify, not in token-percentage estimates that drift per provider. The Manual Slop equivalent would be a per-discussion config with units of (minutes, kilobytes, kilobytes) — not (tokens, percentage, percentage). This is a small but load-bearing change: the user can `ls -l` the discussion file and know whether the trigger will fire, without having to estimate the model's token-percentage consumption.

#### §2.8 Honest Gaps

1. **The `delta_start = min(meta[1], len(content))` clamp at `bin/nagent:1566` could produce a misleading delta if a user edit deletes characters between checkpoints** (the recorded size becomes larger than current content). The clamp hides the failure; the delta would be the entire current content, not the actual new activity. Minor edge case; the spec does not address it.
2. **The `REBUILD_TAIL_CHARS = 64 * 1024` default at `bin/nagent:1463` is explicitly unmeasured** ("mirrors MiMo's ~65K tokens until measured otherwise" per `issues/0004-conversation-safety-net.md:42-44`). A future track should measure actual rebuild-tail needs across providers and conversation types.
3. **`best-of-N` is mentioned in the initial context at `bin/nagent:775` as a directive to the model, not implemented as machinery** — it is the same "direction before machinery" pattern v2.3 used for compaction. A follow-up track could lift it to a driver (e.g., `nagent-safety-net --best-of-n` that runs the writer N times and picks the most-recoverable checkpoint).
4. **The interaction with the campaigns driver (Phase 2's `nagent-campaign update`) is not deep-dived.** The campaigns driver has its own 6 phases (merge, check, propose, review gate, dispatch, report). A long-running campaign can have conversations that exceed the model's context window. The safety net's role in the campaigns driver is not documented: does the driver check for context-window-exceeded conditions during the merge phase? does the dispatch phase refuse to launch a worker when the context window is already full? does the report phase surface context-window warnings to the user?
5. **The interaction with the conversation-cache boundaries (v2.3 §2.2) is not deep-dived.** v2.3 introduced `conversation_cache_boundaries` at `bin/nagent:970-987` to manage the provider's prompt cache. The safety net's rebuild creates a new initial context, which invalidates the cache. The v3 cluster does not document how the safety net coordinates with the cache invalidation — does the rebuild preserve the cache boundary markers? does the next checkpoint know about the cache state?
6. **The 3-number config's recommended values are not enumerated.** The config defaults (`checkpoint_interval_minutes: 10`, `checkpoint_max_new_kb: 32`, `rebuild_at_kb: 192`) are documented, but the cost model is not. A v4 would document the recommended values per conversation type (short Q&A, long-running build, multi-day campaign) and per provider (Gemini's 1M context vs Anthropic's 200K vs OpenAI's 128K).
7. **The writer's failure modes are not enumerated.** The writer is a one-shot LLM call; it can fail with a provider outage, a rate limit, a malformed response, or a refusal. The v3 cluster documents the "widen tail 4×" fallback, but does not enumerate the other failure handling — what happens when the writer returns a malformed response (missing sections, extra sections, wrong order)? does the rebuild retry the writer, or proceed with the malformed checkpoint?

#### §2.9 Code-Shape Sketch

The safety net, in survey-grammar SSDL notation, with shape tags:

```
safety_settings := { checkpoint_interval_minutes: int,    # [I] inspectable
                     checkpoint_max_new_kb: int,         # [I] inspectable
                     rebuild_at_kb: int }                 # [I] inspectable

checkpoint := { updated: timestamp,                       # [S] string
                conversation_chars: int,                  # [I] inspectable
                body: ## Intent | ## Next action | ## Constraints | ## Open questions }  # [B] boundary

due { meta, conversation_chars, now, settings } {          # trigger (pure function)
  if elapsed_minutes(now, meta.updated) > settings.checkpoint_interval_minutes
     and conversation_chars > meta.conversation_chars
     -> fire  {ssdl} [I]                                 # inspectable trigger
  if conversation_chars - meta.conversation_chars > settings.checkpoint_max_new_kb * 1024
     -> fire
  if meta is nil and conversation_chars > settings.rebuild_at_kb * 1024
     -> fire first time only
  else
     -> idle
}

write_checkpoint { conversation, llm, now } {             # writer (one LLM call)
  delta = conversation[meta.conversation_chars:]         # [S] string slice
  if len(delta) < min_delta_chars { return nil }         # too small to summarize
  prompt = format(prompts.checkpoint-conversation.md, delta)  # [S] string format
  body   = llm.call(prompt)                              # [B] boundary to LLM
  write checkpoint.updated = now
  write checkpoint.conversation_chars = len(conversation)
  write checkpoint.body = body
}

rebuild { conversation, llm, now, settings } {            # rebuild (deterministic)
  try write_checkpoint(conversation, llm, now)
  recover {
    tail_chars = REBUILD_TAIL_CHARS * 4                  # widen 4x on failure
    audit msg "checkpoint writer failed; using widened tail"
  } else tail_chars = REBUILD_TAIL_CHARS

  archive_path = archive/{now}/{slug}/conversation
  move conversation -> archive_path
  new_conversation = initial_context + checkpoint + conversation[-tail_chars:]  # [S] string concat
  write conversation = new_conversation
  reset meta.conversation_chars = len(new_conversation)
  reset meta.updated = now
}

summary_source := { entry_id: string,                    # provenance
                    source: extracted|llm,               # [I] inspectable
                    extracted_at: timestamp?,             # [S]
                    llm_summarized_at: timestamp? }      # [S]
```

The shape tag map: `[I]` for inspectable triggers and config, `[S]` for string concatenations and timestamps, `[B]` for the single LLM boundary in the writer, `[M]` for the mutable aggregate that is the conversation file. The safety net is a `[M]` aggregate: it is the state of record, hand-edited by humans, written by the writer, read by the rebuild.

**Source-read citations:**
- `bin/nagent:1455-1687` — `run_safety_net` + `checkpoint_due` + `rebuild_due` + `write_checkpoint` + `rebuild_conversation` (38d3d4f)
- `bin/nagent:1840-1881` — `extract_conversation_summary` (6426a67)
- `bin/nagent:2463-2677` — `--summarize-conversation` CLI surface (6426a67)
- `bin/nagent:2819` — `safety_settings=load_safety_settings(...)` wired into `run_agent_loop` (38d3d4f)
- `bin/nagent:1463` — `REBUILD_TAIL_CHARS = 64 * 1024` default (38d3d4f)
- `bin/nagent:1519-1539` — `checkpoint_due` + `rebuild_due` pure functions (38d3d4f)
- `bin/nagent:1547-1587` — `write_checkpoint` (38d3d4f)
- `bin/nagent:1590-1662` — `rebuild_conversation` (38d3d4f)
- `bin/nagent:1610-1612` — widen tail 4× on writer failure (38d3d4f)
- `bin/nagent:1566` — `delta_start = min(meta[1], len(content))` clamp (38d3d4f)
- `config.example.json:3-7` — 3 safety-net config numbers (38d3d4f)
- `prompts/checkpoint-conversation.md` — checkpoint LLM prompt (38d3d4f)
- `bin/helpers/nagent_distill_lib.py:587-654` — `_summary_backfill_candidates` + `_backfill_saved_summaries` (6426a67)
- `bin/helpers/nagent_distill_lib.py:851-862` — backfill wired into the distill apply path (6426a67)
- `README.md:653-668` — safety-net teaching in Part VI (38d3d4f)
- `README.md:323-332` — instant-saves teaching in Part II (6426a67)
- `issues/0004-conversation-safety-net.md` — the spec; reworked at 6443d70 to wall-clock cadence (199a36b)
- `issues/0004-conversation-safety-net.md:30` — cadence reasoning ("time and context consumption are uncorrelated in exactly the wrong direction")
- `issues/0004-conversation-safety-net.md:42-44` — `REBUILD_TAIL_CHARS` unmeasured note
- `tests/test_nagent_safety.py` — safety-net test file (38d3d4f)
- `tests/test_nagent_distill.py:summary_*` — backfill tests (6426a67)
- `bin/nagent:775` — `best-of-N` initial-context directive (38d3d4f)
- `bin/nagent:970-987` — `conversation_cache_boundaries` (v2.3; not modified in v3 but relevant for the gap note)
- `bin/nagent:606-745` — `build_initial_context` (v2.3; relevant for the rebuild's "initial context" assembly)
- `config.example.json:1-15` — full safety-net config block with defaults (38d3d4f)
- `README.md:670-700` — safety-net cost model (checkpoint cost, rebuild cost) (38d3d4f)
- `README.md:333-360` — instant-saves cost model (extracted vs LLM cost) (6426a67)
- `issues/0004-conversation-safety-net.md:1-100` — full spec: trigger, writer, rebuild, provenance, cost (199a36b)
- `issues/0004-conversation-safety-net.md:101-200` — failure modes + edge cases (199a36b)
- `issues/0004-conversation-safety-net.md:201-326` — open questions + future work (199a36b)

**Decision candidate:** NEW Candidate 18 (HIGH). "Discussion-window safety net for Manual Slop": adopt the checkpoint + rebuild pattern for the discussion history; backfill summary entries from the existing intent line; surface extracted-vs-llm provenance in the discussion index. See `decisions.md` Candidate 18.
**Cross-refs:** `conductor/tracks/fable_review_20260617` (the Fable review's analysis of "watch-dogging" is the opposite pattern — nagent's safety net is structural, not persona-driven). §1 Campaigns cross-references the safety net as the failure-recovery layer for what decomposition cannot bound. §13 Agent context-window observations (the v3.1 new section on warm-up + window + safe-zone numbers; the safety net is the structural mechanism that implements the safe-zone).
**Pattern history:** EXTENDS v2.3 Pattern 5 ("the loop") with failure-recovery semantics. EXTENDS v2.3 Pattern 11 ("large files as explicit artifacts") with checkpoints as an explicit working-state artifact. EXTENDS v2.3 Pattern 7 ("repo history as data") with deferred-cost summaries.
## §3 Hooks

**Source:** nagent `a4fb141` (`bin/nagent:1442-1484` + `:1607-1625` + `:1922-1927` + `:2806-2825` + `:3167-3185`, `config.example.json:6-8`, `tests/test_nagent.py:870-960`); plus both case-study harness scripts (`https://raw.githubusercontent.com/macton/pep-copt/main/prove-optimized-harness.sh`, `https://raw.githubusercontent.com/macton/differentiable-collisions-optc/main/prove-optimized-harness.sh`).
**One-liner:** Per-turn ground-truth injection. A hook runs at the top of every turn (before the model speaks) or after every structured edit; its measured output — exit code, stdout, stderr, or "(no output)" — enters the conversation as a labeled block, so the model responds against measured state instead of its recollection. The case-study repos ARE the hooks: `prove-optimized-harness.sh` is the command wired into `--hook-per-run`.
**Pattern(s) vs v2.3:** NEW. v2.3 had the conversation-without-ground-truth loop (the model's word was the only word). v3 introduces the per-turn measurement primitive that breaks the loop's dependence on the model's self-reporting. EXTENDS v2.3 Pattern 5 ("the loop") with a measurement injection surface. The case-study methodology cluster (§9) elaborates this into a reusable 5-element pattern.
**Manual Slop implications:** Manual Slop has analogous hooks already — Tier 4 QA error interception (per `docs/guide_ai_client.md`) and the `ApiHookClient` test harness (per `docs/guide_api_hooks.md`). The generalization is per-turn, not per-error: a Manual Slop hook could be wired into the `run_agent_loop` equivalent (`dispatch_inference`) to inject a status block (build status, test status, dependency-check status) at the top of every turn. The "failure is data, not control flow" principle from `conductor/code_styleguides/error_handling.md` already encodes the "exit code + stderr surfaced" invariant.
**Decision candidate:** NEW Candidate 19 (MEDIUM). "Per-turn ground-truth hook for Manual Slop": add a per-turn hook primitive that runs a configured command (CLI > config > disabled) at the top of every `send_result()` and injects a `<hook-per-run>` block; honor the CLI > config > disabled precedence and the failing/quiet-hook-surfaces-output invariant. See `decisions.md` Candidate 19.
**Cross-refs:** §9 Case-study methodology (the 5-element pattern; hooks are the substrate), §10 PEP case study (the pep-copt harness), §11 Collisions case study (the collisions harness). These three together surface the full abstraction.
**Source-read citations:**
- `bin/nagent:1442-1463` — `run_hook(command, label, path=None)` (a4fb141)
- `bin/nagent:1466-1484` — `resolve_hooks(cli_per_run, cli_per_file_edit, config_path)` with CLI > config > disabled precedence (a4fb141)
- `bin/nagent:1607-1611` — `hook_per_file_edit` fires after `<nagent-file-patch>` (a4fb141)
- `bin/nagent:1618-1625` — `hook_per_file_edit` fires after `<nagent-write>` in `--file-edit` mode only (scratch writes are not file edits) (a4fb141)
- `bin/nagent:1922-1927` — `hook_per_run` fires at top of every turn, before `call_llm` (a4fb141)
- `bin/nagent:2806-2825` — `--hook-per-run` and `--hook-per-file-edit` CLI flags (a4fb141)
- `bin/nagent:3167-3185` — wiring into `run_agent_loop` (a4fb141)
- `config.example.json:6-8` — `hook_per_run` and `hook_per_file_edit` config keys (a4fb141)
- `tests/test_nagent.py:870-883` — `test_run_hook_block_reports_output_and_exit_code` (a4fb141)
- `tests/test_nagent.py:885-915` — `test_hook_per_run_runs_before_every_turn` (a4fb141)
- `tests/test_nagent.py:917-942` — `test_hook_per_file_edit_runs_after_file_patch` (a4fb141)
- `tests/test_nagent.py:944-960` — `test_resolve_hooks_cli_overrides_config` (a4fb141)
- `prove-optimized-harness.sh` (pep-copt) — 9-step proof + 5 enforcing gates (identity baseline, median-of-5 speedup, decompression-time gate, generalization, determinism)
- `prove-optimized-harness.sh` (differentiable-collisions-optc) — 10-step proof + 4 enforcing gates (comparator with distance tolerance, contact-point certifier, precompute isolation, determinism)
**Honest gaps in this cluster:**
- The "subprocess reach" claim in `bin/nagent:2822-2824` — "A CLI flag applies to this invocation only; set it in the config file to apply it to delegated file-edit subprocesses too" — needs verification. The implementation at `bin/nagent:3167-3185` wires the hooks into `run_agent_loop`'s `main()` call only; whether delegated file-edit subprocesses read the config separately is not visible in this diff. The v3.1 source-read pass should verify the subprocess reach.
- The "default off" guarantee is not tested. Both hooks default to off (CLI flag absent, config key absent or empty string). A regression test asserting "no CLI flag, no config key → both hooks are None" would harden the contract.
- The `--hook-per-run` cost discipline ("point it at a fast status command") is documented in `--help` but not enforced. The case-study harnesses use median-of-5 timing in their proofs, which is fast, but a user wiring up a 10-second status command would pay 10 seconds per turn. A future track could add a `--hook-per-run-max-seconds` config knob.

**Pattern deep-dive.** The hooks abstraction is a three-piece composition: **resolve**, **invoke**, **inject**. `resolve_hooks` enforces the CLI > config > disabled precedence (the CLI is the experiment's override; the config is the project's default; empty means off). `run_hook` invokes the command, captures exit code + stdout + stderr, and surfaces "(no output)" when silent. The injection sites are the conversation: per-run at the top of every turn before `call_llm`; per-file-edit after `<nagent-file-patch>` or `<nagent-write>` in `--file-edit` mode (not scratch writes — the comment at `bin/nagent:1618-1620` notes the distinction explicitly: "A `<nagent-write>` only edits a real file in per-file-edit mode ... in main mode it writes scratch, which is not a file edit worth a verify hook").

The case-study harness scripts are the proof that hooks work as intended. Both scripts implement the same skeleton: log + summary + enforcing gate. The log records every step with verbose mode for streaming; the summary collects every verdict at the end (`set +e` so a failing gate still prints); the enforcing gate collects the verdicts and decides pass/fail. Both harness scripts freeze the committed input via `sha256sum` before the run and re-check after — if the harness itself changes the input (a bug), it aborts. Both exclude precompute time from the measured speedup (the build stage cannot precompute the answer; the optimization log explains why). The PEP harness uses pixel-identity + lossless round-trip + size-correctness (the optimized `.pep` must not be larger than the reference `.pep` — speed may not be bought with a bigger file). The collisions harness uses a distance tolerance contract (1mm + 0.1% + conditional) because collision-flag identity is too strict (a face/edge contact has many equally-valid witness points) and an independent contact-point certifier (`validate_contacts`) shares no solver code.

The data shape of the hook output, using survey grammar:

```
hook-result := <label exit_code="N" [path="P"]>
                [stdout]
                [stderr: stderr-text]
                [(no output)]
                </label>

run { command } :: hook-result  {ssdl} [B]  // boundary: LLM-failures
                                     // surface, never hidden
inject { hook-result, conversation } :: ()  // append to conversation file

resolve { cli, config } :: (per_run, per_file_edit)
  // precedence: CLI > config > disabled
  // empty string in config means disabled
```

The `{ssdl}` `[B]` (boundary) marker notes the abstraction: the hook is the boundary where the model's context meets the measured world; the failure of a measurement is data the model can act on, not a control-flow exception. The injection is append-only — the conversation grows by a labeled block, and the next turn sees it as part of the working state.

The case-study methodology cluster (§9) abstracts the harness pattern itself: the hooks + the proof + the optimization log + the committed-input sha256 freeze + the model-as-test-subject framing form a reusable unit that any project adopting nagent can replicate.

## §4 Project-local roots

**Source:** nagent `54c8741`, `557dd39`, `0b9d1a2`, `023e23a` (`bin/helpers/nagent_cli.py:11-86` + `:109-141`, `bin/helpers/nagent_llm.py:55-72`, `bin/nagent:640-748` + `:2075-2295`, `.gitignore`, `README.md:344-372` + `:400-410` + `:812-832` + `:841-849`, `prompts/create-readme.md`, `issues/0001-foundations.md`).
**One-liner:** The default root moves into the project. Conversations, knowledge, per-file memory, and graduated tools now live at `{git-toplevel}/.nagent/` and can be committed and shared. Inputs resolve through four layers (install → user → project → root) with once-per-directory dedup; most specific layer shadows.
**Pattern(s) vs v2.3:** EXTENDS v2.3 Pattern 3 ("conversations are editable state") — conversations are now project-scoped by default, not user-scoped. EXTENDS v2.3 Pattern 7 ("repo history as data") — `.nagent/` contents are reviewable in the same pull request as the code they describe. NEW pattern: 4-layer resolution (install/user/project/root) with most-specific-shadowing for prompts, tools, and config. The rename `nagent-gc` → `nagent-distill` is not a typo; it codifies the operation's true semantic ("knowledge becomes capability, gated by review", per `prompts/create-readme.md:249`).
**Manual Slop implications:** Manual Slop already follows this pattern in spirit — `conductor/tracks/` is project-scoped (not `~/.manual_slop/tracks/`); `[conductor].dir` in `manual_slop.toml` allows per-project overrides (per `docs/guide_paths.md`). The .gitignore discipline ("only regenerable artifacts; everything else is the user's call to commit") is a model Manual Slop should adopt: `tests/artifacts/` is gitignored (regenerable); `conductor/tracks/` is committed (the user's review call). The dedup-when-running-from-inside-its-own-checkout invariant (`bin/nagent:657-668`) maps to Manual Slop's load path when running the dev build.
**Decision candidate:** NEW Candidate 20 (LOW). "Rename `nagent-gc` → `nagent-distill` in our documentation cross-references" — this is a documentation-only follow-up; no code change. The mental-model shift ("gc" → "distill") is worth surfacing in the project's `conductor/code_styleguides/knowledge_artifacts.md` styleguide. See `decisions.md` Candidate 20.
**Cross-refs:** none direct. §1 Campaigns (`campaigns/` lives inside the project-local root); §2 Conversation safety net (checkpoints inherit the same scoping); §3 Hooks (hooks are configured per-invocation, not per-root).
**Source-read citations:**
- `bin/helpers/nagent_cli.py:11-13` — `INSTALL_DIR` constant (54c8741)
- `bin/helpers/nagent_cli.py:15-44` — `user_root()`, `git_toplevel()`, `resolve_default_root()` (54c8741)
- `bin/helpers/nagent_cli.py:47-54` — `ensure_root_scaffold()` — creates root on first use + writes `.gitignore` for `splits/` only (54c8741)
- `bin/helpers/nagent_cli.py:57-69` — `resolve_prompt_path()` — 3-layer resolution (project root → user → install) (54c8741)
- `bin/helpers/nagent_cli.py:72-86` — `tool_search_dirs()` — 3-layer resolution with basename shadowing (54c8741)
- `bin/helpers/nagent_cli.py:109-141` — `collect_bin_tool_descriptions()` updated to accept multiple bin dirs (54c8741)
- `bin/helpers/nagent_llm.py:55-72` — `default_config_path()` — CLI → `NAGENT_CONFIG` → project `.nagent/config.json` → `~/.nagent/config.json` (54c8741)
- `bin/nagent:640-748` — `build_initial_context()` — 4-layer context resolution with once-per-directory dedup (54c8741)
- `bin/nagent:2220` — `root = resolve_default_root(args.root)` (54c8741)
- `bin/nagent:2227` — `ensure_root_scaffold(root)` for `--file-edit` (resolving a file-edit writes the index) (54c8741)
- `bin/nagent:2292-2295` — `ensure_root_scaffold(root)` for every path past root-write boundary (54c8741)
- `README.md:344-372` — 4-layer context teaching (557dd39)
- `README.md:400-410` — "Project memory is team memory" reduction (557dd39)
- `README.md:812-832` — file tree rename (54c8741)
- `README.md:841-849` — root + config resolution (557dd39)
- `prompts/create-readme.md` — Part III + Part IV rewrites (557dd39)
- `prompts/create-readme.md:249-251` — new reduction: "Proven playbooks stay prose... graduate them into self-describing tools" (from c1d2cad, surfaced in the project-local-roots teaching because `.nagent/bin/` is where graduated tools land)
- `.gitignore:3-4` — `t?` + `p?` (scratch file patterns) (0b9d1a2)
- `.gitignore:5` — `.nagent/` (nagent's own runtime state is per-machine, not source) (023e23a)
**Honest gaps in this cluster:**
- The `t?` and `p?` patterns at `.gitignore:3-4` (from `0b9d1a2`) are unexplained in the commit message. They are likely scratch files written by nagent (e.g., a temp conversation file `t12345`). A follow-up source-read should identify the producer; without that, the gitignore entry is load-bearing but opaque.
- The "once-per-directory dedup" at `bin/nagent:657-668` uses `Path.resolve()`. If the root is on a symlink or a network mount, resolve may behave unexpectedly across platforms. The dedup invariant is correct for the common case; edge cases are unverified.
- The "project-local" win only pays off when the user commits `.nagent/`. The README at `README.md:400-410` acknowledges this caveat ("conversations contain tool output — review before committing, like any other file") but does not enforce it. A hook or pre-commit guard could surface uncommitted conversations, but that is out of scope for the cluster.

**Pattern deep-dive.** Project-local roots is a 4-piece composition: **resolve**, **scaffold**, **deduplicate**, **shadow**. `resolve_default_root()` implements the precedence (`--root` > git-toplevel > `~/.nagent`); `ensure_root_scaffold()` creates the root on first use with a minimal `.gitignore` (`splits/` only — every other artifact is the user's commit call); the dedup loop at `bin/nagent:657-668` includes a layer at most once even when directories overlap (running nagent from inside its own checkout, or root being `~/.nagent` outside a repo); the shadow semantics (`tool_search_dirs`, `resolve_prompt_path`, `default_config_path`) encode "most specific layer wins" with later iterations overwriting earlier in a dict.

The rename `nagent-gc` → `nagent-distill` is the most subtle change in this cluster. The old name borrowed from "garbage collection" — the operation was framed as freeing space. The new name borrows from "distill" — the operation is framed as refining raw working state into reusable knowledge. The merge/graduate passes (from §1 Campaigns cluster, shipped in `f3ec090`) are an explicit consequence: a "gc" mental model would not naturally include a `--graduate` step (gc discards, distill refines). The README at `prompts/create-readme.md:249-251` makes the new reduction explicit: "Proven playbooks stay prose that must be re-read and re-trusted every time. Therefore: graduate them into self-describing tools and prompts — knowledge becomes capability, gated by review."

A code-shape sketch using survey grammar:

```
resolve-root { root_arg, cwd } :: path  {ssdl} [S]
  if root_arg                          -> expand(root_arg)
  elif git_toplevel(cwd) is not nil    -> git_toplevel(cwd) / ".nagent"
  else                                 -> ~/.nagent

resolve-prompt { root, name } :: path
  for layer in [root.prompts, ~/.nagent/prompts, INSTALL.prompts] {
    if layer/name is file              -> return layer/name
  }

resolve-tools { root } :: [path]
  by_name := {}
  for dir in [INSTALL/bin, ~/.nagent/bin, root/bin] {
    for path in dir if is_file {
      by_name[path.name] := path
    }
  }
  return sorted(by_name.values())

context-layers { install, user, project, root } :: [string]  {ssdl} [S]
  seen := {}
  for dir in [install, user, project, root] {
    if resolve(dir) in seen              -> continue
    seen += resolve(dir)
    ctx := load_root_context(dir)
    if ctx                              -> push ctx
  }
```

The `{ssdl}` markers note the composition: root resolution is a single deterministic string concatenation; context-layer resolution is also a deterministic string assembly with dedup. The non-determinism is bounded to LLM-driven passes (harvest, checkpoint, graduate); the file-resolution paths are pure code.

The "project memory is team memory" payoff (557dd39's Part IV addition) is the new argument the rename enables: a project's accumulated knowledge can be committed, reviewed, and arrived with via `git clone`. The manual-slop-equivalent argument already holds for `conductor/tracks/`; the nagent version generalizes it to all of `.nagent/`.

## §5 Provider expansion

**Source:** nagent `bdfa2a6`, `5075f6e`, `2edc7ee` (`bin/helpers/nagent_llm.py:13-19` + `:27-31` + `:37-42` + `:54-77` + `:123-130` + `:198-279` + `:315-336` + `:381-400` + `:582-625` + `:739-770` + `:357-391`, `bin/nagent:1075-1081`, `config.example.json:7`, `README.md:82-90` + `:956-967` + `:991-995`, `tests/test_nagent.py:1010-1042` + `:2734-2797`, `context/data-oriented-design.md`).
**One-liner:** Together is added as a sixth provider (OpenAI-wire-compatible, always streamed). Per-model context windows become a verified table; rebuild now fires on whichever trips first — byte ceiling or 0.85 of the model's window. The claude-code provider blanks inherited `ANTHROPIC_API_KEY` so its billing stays on its own login; the spinner names the provider/model.
**Pattern(s) vs v2.3:** UPDATE. v2.3 had 5 providers (openai, anthropic, google, cursor, claude-code); v3 has 6 (adds together). The v2.3 review noted v2.3 had 5 providers per the project's tech-stack.md — Manual Slop has 8 (per the qwen_llama_grok track); the count is independent of the abstraction. The token-cap awareness is NEW (v2.3 had byte-only rebuild triggers). v2.3 §5 ("the loop") is extended with a per-model token cap as a second rebuild trigger.
**Manual Slop implications:** Manual Slop's `src/ai_client.py` already has per-provider history locks (per `docs/guide_ai_client.md`) but does not have a per-model context-window table; the rebuild/compaction is currently driven by heuristic token estimates. The pattern "verify the window, don't guess; only assert what you've tested" maps to Manual Slop's `provider_state` architecture (per `docs/guide_ai_client.md`). The claude-code billing quirk (`env={"ANTHROPIC_API_KEY": ""}`) is a specific gotcha worth documenting — Manual Slop's claude-code integration (per tech-stack.md) may benefit from the same discipline.
**Decision candidate:** NEW Candidate 21 (MEDIUM). "Per-model token-cap awareness for Manual Slop `ai_client`": add `MODEL_CONTEXT_WINDOWS` table; rebuild fires on byte ceiling OR 0.85 of window; "don't guess" — omit rather than estimate. See `decisions.md` Candidate 21.
**Cross-refs:** §2 Conversation safety net (rebuild trigger gets a second condition); §3 Hooks (per-turn status can include `current model / window / usage`).
**Source-read citations:**
- `bin/helpers/nagent_llm.py:13-19` — `PROVIDERS` extended + `TOGETHER_BASE_URL` (bdfa2a6)
- `bin/helpers/nagent_llm.py:27-31` — `DEFAULT_MODELS["together"]` (bdfa2a6)
- `bin/helpers/nagent_llm.py:37-42` — `CREDENTIAL_ENV["together"]` = `("TOGETHER_API_KEY",)` (bdfa2a6)
- `bin/helpers/nagent_llm.py:54-77` — `MODEL_CONTEXT_WINDOWS` table (10 verified models) (bdfa2a6)
- `bin/helpers/nagent_llm.py:123-130` — `model_context_window(model)` returns `None` for unknown (bdfa2a6)
- `bin/helpers/nagent_llm.py:198-279` — Together client + `_together_chat` (always streamed) (bdfa2a6)
- `bin/helpers/nagent_llm.py:315-336` — `list_models("together")` — direct fetch because Together returns a bare JSON array (bdfa2a6)
- `bin/helpers/nagent_llm.py:381-400` — `list_providers()` — static catalog, no network (bdfa2a6)
- `bin/helpers/nagent_llm.py:582-625` — Together in `generate_text_with_usage` + `generate_with_upload_usage` (bdfa2a6)
- `bin/helpers/nagent_llm.py:739-770` — `_together_upload` — image-upload only, base64 data URL (bdfa2a6)
- `bin/helpers/nagent_llm.py:357-391` — `env={"ANTHROPIC_API_KEY": ""}` + error-result-survives-stream-exception + synthetic-error-text-skip (5075f6e)
- `bin/nagent:1075-1081` — `target = f"{llm.provider}/{llm.model}" if llm.model else llm.provider` (2edc7ee)
- `config.example.json:7` — `"context_window_tokens": 0` (bdfa2a6)
- `README.md:82-90` — providers table extension (bdfa2a6)
- `README.md:956-967` — "Conversation rebuilt (compacted...) when **either** trigger fires first" (bdfa2a6)
- `README.md:991-995` — `--list-providers` CLI example (bdfa2a6)
- `tests/test_nagent.py:1010-1042` — `test_call_llm_wait_spinner_names_provider_and_model` (2edc7ee)
- `tests/test_nagent.py:2734-2797` — 4 new claude-code tests (5075f6e)
**Honest gaps in this cluster:**
- `MODEL_CONTEXT_WINDOWS` is verified against the Together API only on 2026-06-17. Other providers' models are intentionally omitted. A future track should add more verifications.
- The `env={"ANTHROPIC_API_KEY": ""}` blanking assumes subprocess env takes precedence over inherited env. Correct on POSIX; Windows env handling could differ. Unverified.
- The Together `/v1/models` direct fetch at `bin/helpers/nagent_llm.py:315-336` is a vendor-specific workaround. If Together changes the response shape, the parser silently returns fewer models. A defensive check (count returned models, warn if zero) could harden this.

**Pattern deep-dive.** The provider-expansion abstraction is a four-piece composition: **register**, **window**, **trigger**, **bill**. Register: a provider is one tuple in `PROVIDERS` + one entry in `DEFAULT_MODELS` + one tuple in `CREDENTIAL_ENV` + one entry in `PACKAGE_HINTS`. The 5-tuple is enough to surface a provider in `--list-providers` and route a `generate_text_with_usage` call. Window: `MODEL_CONTEXT_WINDOWS` is a verified table, not an estimate. "Omit rather than guessed" (per `bin/helpers/nagent_llm.py:60-62`) is the discipline — the table at `bin/helpers/nagent_llm.py:54-77` lists exactly the models whose windows were verified by API error or by direct lookup, and the function `model_context_window` returns `None` for unknowns (the caller falls back to byte-only behavior). Trigger: rebuild fires on whichever trips first, the byte ceiling OR 0.85 of the model's window (per `README.md:956-967`). The 0.85 safety fraction is the data-oriented response to "model capability degrades under high context utilization, not just at the limit" (per the issues/0004 spec). Bill: the claude-code billing quirk (`env={"ANTHROPIC_API_KEY": ""}`) is the discipline "API-key billing stays the anthropic provider's job" (per `bin/helpers/nagent_llm.py:361-364`) — billing is data; the provider that owns the billing owns the env.

The token-cap awareness is the load-bearing change. A byte-only rebuild trigger is a proxy for token utilization, and the proxy fails on small-window models — `rebuild_at_kb: 384` is far too high to fire on a 8192-token model. The per-model window table is the data-grounded alternative. The `context_window_tokens` config key (per `config.example.json:7`) is the extension point: a user who wants a new model's window can add it without code change. The "unknown returns None" behavior at `bin/helpers/nagent_llm.py:123-130` is the discipline — a missing entry is not a default to a guess; it's a signal to fall back to the byte-only behavior, which is correct for large-window models and merely late for small-window models (the failure is visible, not silent).

The `bdfa2a6` commit message is explicit about the verification process: "DeepSeek-V4-Pro confirmed by a context_length_exceeded error ('maximum context length is 512000 tokens'). Qwen3.7-Plus/Max advertise context_length=1000000, but an oversized request is rejected with 'Range of input length should be [1, 983616]' — so the enforced input cap is 983616, with ~16384 of the 1M reserved for output." The distinction between "advertised total context_length" and "enforced input cap" is load-bearing — the table records the enforced cap, not the advertisement. This is the same data discipline as the project's `conductor/code_styleguides/cache_friendly_context.md`: stable data (verified numbers) vs volatile data (advertised numbers).

A code-shape sketch using survey grammar:

```
providers := { name: string, default_model: string,
               credentials: [env-var], package: string,
               context_window: int | nil }  // [M] mutable aggregate
provider { name, model, env } :: LlmResult  {ssdl} [B] // boundary
  // SDK call; failures surface text + exit code

rebuild-trigger { conversation_chars, model, settings } :: fire?  {ssdl} [I]
  byte_trip   := conversation_chars > settings.rebuild_at_kb * 1024
  window_trip := model_context_window(model)
                and tokens > window * CONTEXT_WINDOW_SAFETY_FRACTION
  byte_trip or window_trip
```

The `{ssdl}` markers note the abstractions: the provider call is a boundary (B) where SDK errors become LlmResult errors; the rebuild trigger is an inspectable invariant (I) computed from data on disk.

## §6 Delegation rewrite

**Source:** nagent `d56f0f0`, `65787a6`, `315fe9e` (`bin/nagent:666-673` + `:790-806`, `tests/test_nagent.py:1689-1695`).
**One-liner:** Delegation is for two reasons — **decomposition** (break a complex task into parts and delegate the parts) or **context isolation** (keep a noisy step's cost as just its result, not its logs/reads). It is NEVER for offloading a single small action whose result is no smaller than doing it yourself — synchronous delegation can recurse without end.
**Pattern(s) vs v2.3:** UPDATE. v2.3 Pattern 9 ("disposable sub-conversations") noted MMA workers are real subprocesses and delegation is context-management before parallelism. v3 surfaces a recursion bug (file-edit agent → worker → nagent-file-edit → file-edit agent → ... hangs the tree) and fixes it by naming the two reasons for delegation. v2.3's "delegation is for context management" framing was correct but undersold; v3's "context isolation is worth more the longer-lived your conversation is" makes the trade-off explicit. The `315fe9e` commit message ("My earlier commits py_compile'd but did not run the suite — this is the fallout") is a model of honest test-coverage reporting.
**Manual Slop implications:** MMA's WorkerPool has disciplined delegation (per `docs/guide_multi_agent_conductor.md`); the recursion bug was observed in the non-MMA flow (file-edit agent re-delegating). Manual Slop's tier-3 workers should adopt the "decompose or isolate, never offload" contract explicitly. The 315fe9e test-fix is a useful precedent: an agent's `test_*.py` for any user-facing prompt change must run the suite, not just `py_compile`. Manual Slop's CLAUDE.md / AGENTS.md @import discipline (per `conductor/code_styleguides/data_oriented_design.md`) already encodes "always run the suite" but the temptation to skip on prompt-only changes is real.
**Decision candidate:** NEW Candidate 22 (HIGH). "Tier 3 worker contract: decompose or isolate, never offload" for Manual Slop MMA — encode the two-reason delegation guidance as a Tier 3 worker system prompt prefix; add a test that asserts the prefix is present in the worker's initial context. See `decisions.md` Candidate 22.
**Cross-refs:** §1 Campaigns (campaign item workers operate under this discipline); §2 Conversation safety net (sub-conversations inherit the same scoping); §10 + §11 case studies (sub-conversation isolation is what makes the case-study harnesses tractable).
**Source-read citations:**
- `bin/nagent:666-673` — `role_instructions` for delegated-invocation: "Do your task directly; spawn a sub-conversation only when it buys something: to decompose a genuinely complex, multi-part task into parts, or to keep a large/noisy step ... out of your context and get back only the distilled result. Don't delegate a single small action whose result is essentially your whole deliverable—that adds a layer and can recurse without end." (65787a6)
- `bin/nagent:790-806` — top-level context-management guidance: "Each nagent instance has its own private conversation file; parent and child do not share context. A sub-conversation absorbs the noise of its work and returns only what you ask for — so a step you delegate costs your context just its result, not its logs/reads." (65787a6)
- `bin/nagent:792-798` — the two-reason framing (decomposition OR context isolation), the "worth more the longer-lived your conversation is" insight (65787a6)
- `bin/nagent:798-800` — anti-recursion rule: "Don't delegate a single small action whose result is no smaller than doing it yourself (one edit, one quick command, one lookup): it buys nothing, only adds a layer, and — delegation being synchronous — can recurse without end (a sub-agent re-delegating the same one thing)." (65787a6)
- `tests/test_nagent.py:1689-1695` — `test_delegated_initial_text` updated to assert the new wording (315fe9e)
- `d56f0f0` commit message — the recursion bug: "file-edit agent -> worker -> nagent-file-edit -> file-edit agent -> ..." (observed)
**Honest gaps in this cluster:**
- The `315fe9e` commit message's acknowledgment — "My earlier commits py_compile'd but did not run the suite — this is the fallout" — is a model of test-coverage honesty but also a documented gap. The recursion bug itself was caught post-merge by the test; the agent that wrote d56f0f0 + 65787a6 should have run the suite. A future track could enforce "always run the suite" via a pre-commit hook.
- The recursion-bug fix is guidance-only — no code change prevents the recursion; the model is trusted to follow the new wording. A defensive code change (e.g., a max-delegation-depth check) would harden the invariant. The spec notes the design philosophy: "delegation is the model's call, not the loop's," which is consistent with nagent's data-oriented approach but trades safety for simplicity.
- The "worth more the longer-lived your conversation is" insight has no measurable test. The conversation-length-vs-delegation-payoff is a heuristic; a future track could measure it.

**Pattern deep-dive.** The delegation rewrite is a guidance + bug-fix pair. The bug is real: a delegated agent whose whole job is one edit will delegate that one edit to another agent, which does the same, and because delegation is synchronous (each parent blocks on its child) this recurses without bound and hangs the tree. The fix is to name the two reasons delegation is worth its cost — decomposition (the task is genuinely complex, with parts) and context isolation (the step is noisy, and the result is small). Both reasons produce a smaller-than-the-work payload to the parent. When neither reason applies, the parent should do the work inline.

The "worth more the longer-lived your conversation is" insight is the load-bearing one. A short, soon-to-finish conversation gains little from context isolation — the cost of paying for the sub-conversation's LLM call may exceed the savings. A long-lived coordinator's context budget is the constraint that context isolation protects. This is the same "per-turn cost" thinking that nagent's hooks (per §3) formalize with `--hook-per-run`'s "point it at a fast status command" guidance — the cost is per-turn, not amortized.

The recursion bug is interesting for what it says about guidance as control flow. nagent's delegation is "the model's call, not the loop's" — the loop does not enforce a max-delegation-depth or refuse to delegate to a child who would delegate. The cost of this design is the recursion bug; the benefit is flexibility. The fix is to make the guidance explicit enough that the model doesn't fall into the trap. This is the data-oriented approach: instead of code-level guards, encode the invariant in the prompt and trust the model to follow it. The test-fix at `315fe9e` is the verification layer.

A code-shape sketch using survey grammar:

```
delegate { parent_task, sub_task } :: sub-result  {ssdl} [B]
  // boundary: model decision, not loop enforcement
  if sub_task is "single small action whose result is the whole deliverable"
                  -> do inline  // anti-recursion
  elif sub_task is "multi-part decomposition" or sub_task is "noisy step"
                  -> spawn sub-conversation
  else            -> do inline

context-isolation { parent_lifetime, sub_cost } :: bool
  // worth more the longer-lived the parent is
  parent_lifetime > threshold and sub_cost > sub_result_size
```

The `{ssdl}` [B] marker notes the abstraction: delegation is the boundary where the parent's context meets a sub-conversation's work; the cost discipline is per-turn, not amortized. The check is the model's call — no code-level recursion guard exists.

The `315fe9e` commit is the verification-discipline precedent worth carrying forward: any guidance change in a prompt must run the test suite, not just `py_compile`. The diff at `tests/test_nagent.py:1692` is a single character (`"Still decompose and delegate"` → `"spawn a sub-conversation only when it buys something"`), but the assertion was load-bearing — without it, the recursion bug could re-merge silently.

## §7 Robustness

**Source:** nagent `065168c`, `6b762da`, `12c35b7`, `49e07f3` (`bin/helpers/nagent_tags.py:43-50` + `:106-110` + `:136-246` + `:248-265`, `bin/nagent:1911-1940` + `:682-714` + `:1319-1381` + `:1387-1394` + `:1534-1551` + `:1834-1840` + `:224-240`, `tests/test_nagent.py:548-590` + `:679-714` + `:1911-1940`, `tests/test_nagent_safety.py:367-400`, `tests/test_nagent_tags.py:170-182`).
**One-liner:** Four hardening commits — `scan_tag_document` extracts valid tags and ignores the rest (with EOF-capture for trailing unclosed responses); `dedupe_nodes` collapses exact-duplicate action tags within a turn; `<nagent-shell>`-output-before-`<nagent-next-input>` ordering is pinned by a regression test; `<nagent-write>` is scoped to a per-conversation scratch dir so concurrent instances never collide.
**Pattern(s) vs v2.3:** UPDATE. v2.3 Pattern 5 ("the loop") had the basic loop; v3 hardens it against four specific failure modes. The hardening is incremental — each commit is a discrete change with its own test. EXTENDS v2.3 Pattern 4 ("visible output protocol") with a lenient counterpart (`scan_tag_document`) that tolerates non-protocol output while still propagating known-tag malformation as a hard error. NEW: per-conversation scratch directory as a side artifact of the loop.
**Manual Slop implications:** Manual Slop's `send_result()` (per `docs/guide_ai_client.md`) and `dispatch_inference` should adopt the same hardening. The lenient parser discipline ("scan, extract, ignore the rest, but propagate known-tag malformation as hard error") maps to Manual Slop's tag protocol; the per-turn status block (`<nagent-turn-status>` with UTC + cumulative tokens) is a model Manual Slop's discussion history could adopt — the user can already see token totals but not in a structured per-turn way. The per-conversation scratch dir (keyed by conversation name) maps to Manual Slop's `tests/artifacts/` directory (gitignored, per-conversation).
**Decision candidate:** NEW Candidate 23 (MEDIUM). "Per-conversation scratch directory for Manual Slop dispatch_inference" — adopt the `conversation_scratch_dir(conversation_name)` pattern; pre-create on session start; thread through the `<nagent-write>`-equivalent. See `decisions.md` Candidate 23.
**Cross-refs:** §3 Hooks (per-turn `<nagent-turn-status>` and per-turn hooks are both per-turn observability surfaces); §2 Conversation safety net (the `<nagent-turn-status>` block is what the safety net reads to compute the checkpoint delta).
**Source-read citations:**
- `bin/helpers/nagent_tags.py:43-50` — `parse_element(..., capture_to_eof_if_unclosed=True)` for trailing unclosed `<nagent-response>` (065168c)
- `bin/helpers/nagent_tags.py:106-110` — EOF-capture behavior: a missing close tag captures to `len(text)` instead of raising (065168c)
- `bin/helpers/nagent_tags.py:136-246` — `IgnoredSpan` + `_read_tag_name` + `scan_tag_document` (lenient parser) + `serialize_node(s)` (re-serialize well-formed) (065168c)
- `bin/helpers/nagent_tags.py:248-265` — `dedupe_nodes` (6b762da)
- `bin/nagent:1911-1940` — `cleaned_response_text` returns `(text, duplicates_removed)`; system note when collapsed (6b762da)
- `bin/nagent:682-714` — `test_shell_output_precedes_next_input_in_either_order` regression test (12c35b7)
- `bin/nagent:1319-1331` — `conversation_scratch_dir(conversation_name)` returns `$TMPDIR/nagent-{name}/` (49e07f3)
- `bin/nagent:1334-1341` — `is_within(path, directory)` (replaces `is_tmp_path`) (49e07f3)
- `bin/nagent:1344-1381` — `validate_write_path(..., scratch_dir=...)` — only path-inside-scratch-dir is allowed; file-edit mode unchanged (49e07f3)
- `bin/nagent:1387-1394` — `execute_write(..., scratch_dir=...)` threaded through (49e07f3)
- `bin/nagent:1534-1551` — `process_tags` computes scratch_dir per call (49e07f3)
- `bin/nagent:1834-1840` — `run_agent_loop` pre-creates scratch_dir before the first turn (49e07f3)
- `bin/nagent:224-240` — `file_edit_rules(file_edit_path, scratch_dir)` — context mentions the concrete scratch path (49e07f3)
- `tests/test_nagent.py:548-590` — 3 cleaned/duplicate tests (6b762da)
- `tests/test_nagent.py:679-714` — `test_shell_output_precedes_next_input_in_either_order` (12c35b7)
- `tests/test_nagent_safety.py:367-400` — `test_duplicate_tags_collapsed_in_conversation_without_sidecar` (6b762da)
- `tests/test_nagent_tags.py:170-182` — `DedupeNodesTests` (6b762da)
**Honest gaps in this cluster:**
- `dedupe_nodes` only catches EXACT duplicates (same name, self_closing flag, attrs, content). A near-duplicate (same command with whitespace differences, same shell with env vars) is not collapsed. Whether this matters in practice is unverified.
- The lenient parser's "ignore the rest" behavior could mask real protocol bugs — the model might be silently emitting junk while the conversation proceeds. The `ignored_correction` system note at `bin/nagent:1930` is the recovery path; it relies on the model reading the note. A future track could add a hard error when the ignored-to-extracted ratio exceeds a threshold.
- The scratch dir at `bin/nagent:1319-1331` is keyed on conversation name; if a user renames a conversation file mid-run, the scratch dir becomes orphaned and a new one is created. Unverified whether this is the intended behavior.
- The `<nagent-turn-status>` block at the end of every turn (per `bin/nagent:1940`) is observability but not user-facing; the user sees cumulative tokens via the existing `TokenStats` rollup. The status block's primary consumer is the safety net, not the user.

**Pattern deep-dive.** The robustness commits are four independent hardening operations on the loop: **tolerate**, **dedupe**, **pin-order**, **scope**. Tolerate: `scan_tag_document` extracts valid tags and ignores the rest, with two carve-outs — malformed *known* tags propagate as hard errors (a clear protocol mistake), and a trailing unclosed `<nagent-response>` captures to EOF (so a finished run isn't lost to a missing close tag). Dedupe: `dedupe_nodes` collapses exact-duplicate tags within a turn, with a system note when it fires (so the model knows it stuttered and emits each action once next time). Pin-order: the `<nagent-shell>`-output-before-`<nagent-next-input>` ordering is pinned by `test_shell_output_precedes_next_input_in_either_order` — the regression test is the contract; the implementation "holds by construction" but was previously unpinned. Scope: `<nagent-write>` is restricted to a per-conversation scratch dir, eliminating the cross-instance collision class on shared `/tmp` paths.

The four changes share a data-oriented theme: each is a discrete transformation with its own invariant, test, and comment, and each operates on data on disk rather than on the model's behavior. The `ignored_correction` system note is the only exception — it's a prompt-side intervention that asks the model to read and adjust. The rest are pure-code or pure-data.

The lenient parser is the most subtle of the four. The strict `parse_tag_document` raises `TagParseError` on any malformation; the lenient `scan_tag_document` returns `(nodes, ignored)` where ignored is the list of `IgnoredSpan` (reason + text + offset). The two callers — `parse_response` (in the hot path) and `cleaned_response_text` (for storage) — use different policies: `parse_response` propagates `TagParseError` on known-tag malformation (the loop must ask the model to fix it); `cleaned_response_text` is more permissive (storage should be robust to whatever the model emitted). The split is the data-oriented response to "lenient storage, strict dispatch."

A code-shape sketch using survey grammar:

```
scan { text, known, unwrap, eof_capture } :: (nodes, ignored)  {ssdl} [I]
  pos := 0
  while pos < len(text) {
    if text[pos] is whitespace        -> pos += 1
    elif not _read_tag_name(text, pos):
      nxt := text.find("<", pos + 1)
      end := len(text) if nxt == -1 else nxt
      ignored += ("non-tag text", text[pos:end], pos)  // skip to next tag
      pos := end
    elif name in known:
      // strict: propagate errors for malformed known tags (except EOF-capture)
      node := parse_element(text, pos, capture_to_eof=(name in eof_capture))
      nodes += node
      pos := node.end
    else:
      try node := parse_element(text, pos)  // try parsing unknown tag
      except TagParseError: ignored += ("malformed <name>", text[pos:end], pos); pos := end
      if name in unwrap: recurse into node.content
      else: ignored += ("unknown tag <name>", text[node.start:node.end], node.start)
      pos := node.end
  }

dedupe { nodes } :: nodes  {ssdl} [S]
  seen := {}
  out := []
  for node in nodes {
    key := (name, self_closing, sorted(attrs), content)
    if key not in seen: seen += key; out += node
  }

scratch-dir { conversation_name } :: path  {ssdl} [S]
  return tmp_roots()[0] / f"nagent-{conversation_name}"
  // keying on name (not per-process guid) keeps it stable across resumes
```

The `{ssdl}` markers note the abstractions: `scan` is an inspectable transformation (I) that produces both valid nodes and ignored spans; `dedupe` and `scratch-dir` are pure string concatenations (S). The `<nagent-turn-status>` block (per `bin/nagent:1940`) is the per-turn observability surface that consumes `scan`'s output (the ignored count and the duplicates count feed the block's token totals + sidecar refs).

## §8 Operating rules

**Source:** nagent `a1f0680` (`context/data-oriented-design.md:102-116` + `:151-164`); cross-ref `conductor/tracks/fable_review_20260617/`.
**One-liner:** Sampling justifies *replacing* the machine, not only trimming it. The data's shape can show that a different algorithm or representation is the better-fit machine — and a plateau in optimization is the signal to re-sample, not the signal to keep filing. The simplification pass gains a ninth question.
**Pattern(s) vs v2.3:** UPDATE. v2.3 cited `context/data-oriented-design.md` as Acton's canonical rule set; v3 deep-dives the Q9 expansion (the only addition since v2.3 was published on 2026-06-12). The Q9 insight generalizes v2.3 Pattern 1 ("durable work, disposable workers") — replacing the machine is a more radical form of "trimming the machine" that the original 8-question pass did not surface. The project's own `conductor/code_styleguides/data_oriented_design.md` is itself derived from Acton's file (per `conductor/code_styleguides/data_oriented_design.md` header); v3's §8 surfaces the delta so the project's styleguide can track.
**Manual Slop implications:** Manual Slop's `conductor/code_styleguides/data_oriented_design.md` (Tier 0/1/2, simplification pass, enforceable deliverables) is the canonical reference for agent directives. The Q9 addition is the "what's new since v2.3" delta; if the project styleguide adopts Q9 explicitly, agents applying it will know to consider "different machine" rather than only "trim current machine" when sampling points to a plateau.
**Decision candidate:** NEW Candidate 24 (LOW). "Document Q9 ('consider a different machine') in the project's `conductor/code_styleguides/data_oriented_design.md`" — the styleguide is already a derivative of nagent's file; add the Q9 expansion as a Tier 1+ reading-note. See `decisions.md` Candidate 24.
**Cross-refs:** `conductor/tracks/fable_review_20260617/` — Fable's analysis of "watch-dogging" is the opposite pattern. Fable's persona framing ("be careful, watch yourself") substitutes for the data-oriented question "what does the data say?". §8 closes the loop: Acton's operating rules are the data-grounded alternative.
**Source-read citations:**
- `context/data-oriented-design.md:102-116` — "Sample the data you already have" expanded: "the data's *shape* can show that a **different algorithm or representation is the better-fit machine** (sorted-enough → a different sort/merge; skewed → a different code; runny → a run/stream form; sparse → a different container), not just that the current machine needs filing. Sampling justifies *replacing* the machine, not only trimming it. Sampling is also how you find *new* opportunities mid-optimization, not just before starting: when a pass **stalls or plateaus**, that is the signal to re-sample the hottest stage's data and ask whether a different machine fits it better — not to keep filing the current one." (a1f0680)
- `context/data-oriented-design.md:151-164` — new Q9 in simplification pass: "Is there a **different algorithm or representation that fits the data better** than the current machine? Subtraction has a floor; when filing the current approach stops paying (a plateau), the win is often a *different* machine the data's shape points to — reconsider the approach, don't only shrink it." (a1f0680)
- `context/data-oriented-design.md:18-39` — Scope, tiers, and precedence (Tier 0 trivial, Tier 1 non-trivial change, Tier 2 subsystem-scale); "An explicit instruction from the user for the current task" wins over this document (the precedence rule)
- `context/data-oriented-design.md:41-58` — 3 defaults to reject (tools-are-platform, model-of-world, solution-matters-more)
- `context/data-oriented-design.md:60-78` — 8 core defaults (problem-is-data, state-cost, solve-only-problem-you-have, where-theres-one-theres-many, common-case-dominates, exploit-constraints, simplicity-is-removing-work, cant-be-done-is-cost-claim)
- `context/data-oriented-design.md:82-125` — Get the real data (inspect-before-assuming, sample, label-every-assumption, never-fabricate)
- `context/data-oriented-design.md:130-148` — Method (frame → get-data → state-cost → design-transform → simplification-pass → define-done → verify)
- `context/data-oriented-design.md:156-176` — Design rules (minimize-states, explicit-OOR, complexity-requires-evidence)
- `context/data-oriented-design.md:182-191` — Performance claims (never assert unmeasured; label hypotheses)
- `context/data-oriented-design.md:198-227` — Software specifics (batch-first, memory layout, data protocols, hardware is platform)
- `context/data-oriented-design.md:233-243` — Enforceable deliverables (tier 2)
- `context/data-oriented-design.md:249-261` — Final self-check (the 10-question checklist)
**Honest gaps in this cluster:**
- The Q9 expansion is in `data-oriented-design.md` but nagent itself doesn't have a worked example of "replace the machine" reasoning in its commits (the case studies — §10, §11 — demonstrate it empirically but the rules file does not name the pattern). A future track could add a worked example.
- The project's `conductor/code_styleguides/data_oriented_design.md` is derived from this file but may not include the Q9 addition. The v3 delta is the trigger to verify.
- The "stalls or plateaus" signal is a heuristic. When is "the pass is done" vs "the pass is plateauing"? The rule does not distinguish. A worked example would help.

**Pattern deep-dive.** The Q9 expansion is the most subtle single-commit change in v3. The original 8-question simplification pass (Q1: not do this at all? Q2: only once? Q3: fewer times? Q4: approximate? Q5: small lookup? Q6: large lookup? Q7: small buffer/FIFO? Q8: constrain further?) is the radical form of "trim the machine." Q9 ("is there a different machine?") is the meta-level question — not "how do I shrink this?" but "is this the right machine at all?" The data's shape can tell you. The case studies (per §10, §11) are the empirical evidence: the PEP case study replaces a generic image-compression library with a tight per-image optimized one; the collisions case study replaces a generic convex primitive collision detection library with a per-type-specialized one. Both optimizations are "different machine," not "trim current machine."

The connection to fable_review (§8 cross-ref) is the philosophical mirror. Fable's persona framing asks the model to "be careful, watch yourself, never claim something you can't verify." The data-oriented response is to ask "what does the data say?" — the verification is empirical (measure on real input), not persona-based (be appropriately humble). The fable review's "watch-dogging" pattern is the anti-pattern; the data-oriented sampling pattern is the pattern. Both can co-exist (a humble persona + measured data), but the data is load-bearing and the persona is decoration.

The Tier 0/1/2 framing in `data-oriented-design.md:18-39` is also load-bearing. Tier 0 (trivial — apply defaults silently) is the project's escape hatch for one-line fixes; Tier 1 (non-trivial change — required: framing + data + simplification + self-check) is the standard; Tier 2 (subsystem-scale — tier 1 + enforceable deliverables) is the heavy path. The user's tier is decided at task start; the agent declares which tier it's picking. Manual Slop's `conductor/workflow.md` "Mandatory Research-First Protocol" and "Per-Task Decision Protocol" already encode tier-style discipline; the project's `conductor/code_styleguides/data_oriented_design.md` would close the loop.

A code-shape sketch using survey grammar:

```
simplify-pass { current_machine, data_shape } :: improvements  {ssdl} [S]
  q1 := "can we not do this at all?"
  q2 := "can we do this only once?"
  q3 := "can we do this fewer times?"
  q4 := "can we approximate?"
  q5 := "can we use a small lookup table?"
  q6 := "can we use a large lookup table?"
  q7 := "can we use a small buffer/FIFO?"
  q8 := "can we constrain the problem further?"
  q9 := "is there a different machine that fits the data better?"  // NEW: a1f0680
  // Q1-Q8 trim; Q9 replaces. Q9 is the meta-question.

sample { current_machine, hottest_stage } :: next-action
  // per a1f0680: when a pass stalls or plateaus, re-sample, don't keep filing
  if plateau detected:
    shape := sample(hottest_stage)
    if shape suggests different machine -> replace (Q9)
    else                                 -> trim (Q1-Q8)
```

The `{ssdl}` [S] markers note the abstractions: the simplification pass is a string of questions (S); the sampling decision is a deterministic string assembly (S) based on data on disk.

The Q9 expansion generalizes v2.3 Pattern 1 ("durable work, disposable workers") — replacing the machine is a more radical form of "disposable" that the original pass did not surface. The project's `conductor/code_styleguides/data_oriented_design.md` should adopt Q9 to keep the operating rules current.

## §9 Case-study methodology

**Source:** both case-study repos (`macton/pep-copt`, `macton/differentiable-collisions-optc`); both `prompts/create-*.md` files in each; both `prove-optimized-harness.sh` scripts (per §3 cross-refs); both `README.md` files.
**One-liner:** A reusable abstraction surfaces across both case studies — the 4-prompt methodology + proof harness + optimization log + committed-input sha256 freeze + model-as-test-subject framing. Both repos implement the same pattern with different match contracts (PEP byte-identity vs collisions tolerance-based) but the same empirical-discipline skeleton.
**Pattern(s) vs v2.3:** NEW. v2.3 had no case-study methodology (no case-study repos existed). v3 introduces a 5-element pattern that any project adopting nagent can replicate to ground LLM-driven optimization in measurement. EXTENDS v2.3 Pattern 5 ("the loop") with the per-turn proof injection that the harness provides. EXTENDS v2.3 Pattern 7 ("repo history as data") with the optimization log as a per-hypothesis history file.
**Manual Slop implications:** Manual Slop's discussion history + screenshots are the per-turn observability surface; the case-study methodology suggests a parallel structure: a per-iteration optimization log file (`OPTIMIZATION-LOG.md`) that records hypothesis + change + before/after + keep/revert + cost. The "committed-input sha256 freeze" maps to Manual Slop's test fixtures (gitignored, but checksum-verified). The 4-prompt methodology maps to Manual Slop's `prompts/` (already established, per `conductor/code_styleguides/knowledge_artifacts.md`).
**Decision candidate:** NEW Candidate 25 (MEDIUM). "Optimization-log discipline for Manual Slop agent work" — adopt the `OPTIMIZATION-LOG.md` pattern: every agent iteration records hypothesis + change + before/after + keep/revert + cost (wall-clock + tokens). See `decisions.md` Candidate 25.
**Cross-refs:** `conductor/tracks/intent_dsl_survey_20260612/` — the survey's Cluster 4 "Meta-Tooling DSLs" is the closest prior art (the 4-prompt methodology is implicitly an intent-DSL for "drive nagent at an optimization problem"). `conductor/tracks/superpowers_review_20260619/` — the superpowers `brainstorming` skill is a process parallel (structured questions to refine an idea before implementation; the case-study prompts serve the same role). §3 Hooks (the proof harness IS the `--hook-per-run`); §8 Operating rules (the Q9 expansion is invoked when micro-tweaks plateau).
**Source-read citations:**
- `pep-copt/README.md` — full project description, 4-prompt methodology, 24-image results, "The model under test here was GPT-5.5" not present (pep-copt does not name the model), byte-identity + size + decode contract
- `pep-copt/prompts/create-reference.md` — reference pipeline specification
- `pep-copt/prompts/create-optimized-test-harness.md` — test/comparison/measurement scaffold
- `pep-copt/prompts/create-optimized.md` — optimization instructions: 4 candidate kinds (a/b/c/d); "When you have plateaued — several consecutive reverts, or micro-tweaks stuck below target — stop filing the current machine: re-profile the data and evaluate a (c) or (d) candidate"
- `pep-copt/prompts/create-visualizer.md` — quality visualizer specification
- `pep-copt/prove-optimized-harness.sh` — 9-step proof + 5 enforcing gates
- `pep-copt/src-optimized/OPTIMIZATION-LOG.md` — per-hypothesis history (referenced from README)
- `differentiable-collisions-optc/README.md` — full project description, 4-prompt methodology, 1000-pair benchmark, "The model under test here was GPT-5.5. This is one model, one run — a case study in how to drive an LLM at an optimization problem, not a benchmark comparing models", tolerance-based + collision-flag + contact-validator contract
- `differentiable-collisions-optc/prompts/create-reference.md` — reference specification
- `differentiable-collisions-optc/prompts/create-optimized-test-harness.md` — harness specification
- `differentiable-collisions-optc/prompts/create-optimized.md` — optimization instructions; "The most durable headroom from here is structural — batching and data layout — rather than more iteration-shaving"
- `differentiable-collisions-optc/prompts/create-visualizer.md` — visualizer specification
- `differentiable-collisions-optc/prove-optimized-harness.sh` — 10-step proof + 4 enforcing gates
- `differentiable-collisions-optc/src-optimized/OPTIMIZATION-LOG.md` — per-hypothesis history
**Honest gaps in this cluster:**
- **The GPT-5.5 string is unverified.** As of 2026-06-20, the publicly-known GPT families are 4 / 4o / 4.5 / 5; "GPT-5.5" is not a known public model. The collisions README's framing — "This is one model, one run — a case study in how to drive an LLM at an optimization problem, not a benchmark comparing models" — suggests deliberate model-disconnect (a fake name as a methodology test) OR a private/internal model OR a typo. The pep-copt README does not name the model. Without further evidence, the §9 section treats "GPT-5.5" as a model-disconnect placeholder per the README's stated framing.
- The 4-prompt methodology is implicit (the README lists the 4 prompts but does not name the pattern). The §9 cluster surfaces the pattern explicitly; a future track could formalize it as `prompts/create-{phase}.md` template.
- The "different machine" replacement (Q9 from §8) is invoked in the case-study README ("stop filing the current machine") but the prompts do not cite Q9 by name. The connection is implicit; an explicit cross-reference would help.
- The optimization log format (`OPTIMIZATION-LOG.md` schema) is not specified in the prompts; each repo develops its own. A template would help future projects adopt the pattern.

**Pattern deep-dive.** The case-study methodology is a 5-element composition: **prompts**, **harness**, **log**, **freeze**, **subject**. Prompts: 4 phase-specific instruction documents (create-reference, create-optimized-test-harness, create-optimized, create-visualizer) feed the LLM in sequence. Harness: `prove-optimized-harness.sh` runs end-to-end on every turn via `nagent --hook-per-run` (§3 cross-ref), enforcing the match contract (byte-identity for PEP; tolerance-based for collisions). Log: `OPTIMIZATION-LOG.md` records per-hypothesis history with measurements, keep/revert decisions, and cost. Freeze: the committed input's sha256 is verified before and after the run — the benchmark cannot be quietly edited. Subject: the model is named in the README (collisions explicitly says "GPT-5.5") as a methodology-test single-model run, not a benchmark.

The match-contract variation between the two repos is informative. PEP uses byte-identity after decompression (lossless, `.pep` not larger, decode net-neutral-or-better) — the strictest contract because the codec's encode/decode is symmetric. Collisions uses tolerance-based (collision flags identical, distance within `1 mm + 0.1%·|d_ref| + 5e-4·(|c1−c2|/α²)`, contact points certified for validity rather than matched) — a relaxed contract because collision detection has many equally-valid witness points for face/edge contacts. The two contracts are "same-shape" (PEP) and "same-distribution" (collisions); both are data-grounded, both are checkable. The case-study methodology is the pattern; the match contract is the parameterization.

The connection to §8 Q9 is direct. The pep-copt prompt at line "When you have plateaued — several consecutive reverts, or micro-tweaks stuck below target — stop filing the current machine: re-profile the data and evaluate a (c) or (d) candidate" is the §8 Q9 expansion applied in the wild. The (c) "representation/algorithm" candidate kind is Q9 ("is there a different machine?"); the (d) "data-pattern specialization" candidate kind is Q5/Q6 (lookup tables — let the data show what to specialize). The case-study methodology is the empirical harness for Q9's principle.

The connection to `intent_dsl_survey_20260612` is implicit. The survey's Cluster 4 ("Meta-Tooling DSLs") discusses how DSLs for tool composition work; the 4-prompt methodology is a primitive form of "drive the agent through these 4 phases." The survey's "intent-mapping" cluster (Cluster 3) is the closest parallel — the 4 prompts ARE an intent-DSL for "drive nagent at an optimization problem." A future track could lift the 4-prompt methodology to a templated DSL (e.g. `prompts/create-{phase}.md` skeleton with placeholders for domain-specific terminology).

The connection to `superpowers_review_20260619` is process-parallel. The superpowers `brainstorming` skill asks structured questions to refine an idea before implementation (per `superpowers/specs/2026-06-XX-brainstorming-design.md`); the case-study methodology asks structured prompts to refine an optimization before measurement. Both serve "the model should not skip the early work." A future track could document the parallel.

A code-shape sketch using survey grammar:

```
case-study { input, model, target } :: result  {ssdl} [B]
  // 4-prompt methodology, run in sequence
  ref     := run(prompts/create-reference,       input, model)
  harness := run(prompts/create-optimized-test-harness, input, model)
  log     := []
  for iter := 0..N:
    hypothesis := pick-candidate(log, ref)
    opt        := run(prompts/create-optimized, {input, hypothesis}, model)
    hook-result := hook-per-run(harness, opt)  // per §3
    verdict := gate(hook-result, contract)  // match contract: byte-identity | tolerance
    if verdict.ok:
      log.append({hypothesis, opt, hook-result, verdict, cost})
      commit(opt, log)
    else:
      log.append({hypothesis, opt, hook-result, verdict, cost, kept: false})
      revert()
    if plateau(log) -> replace-machine(log)  // per §8 Q9
  return opt
```

The `{ssdl}` [B] marker notes the abstraction: the case-study is a boundary where the model's working state meets measurement. The match contract is the parameterization. The 4 prompts, harness, log, freeze, and subject are the 5 elements; the loop is the shape that composes them.

The GPT-5.5 observation is worth a separate note. As of 2026-06-20, public GPT families are 4 / 4o / 4.5 / 5; "GPT-5.5" is not a known public model. The collisions README's framing — "case study in how to drive an LLM, not a benchmark comparing models" — suggests either (a) a private/internal model, (b) a model-disconnect placeholder (use a fake name to test whether the methodology works without depending on a specific model's quirks), or (c) a typo. Without further evidence, the §9 section treats "GPT-5.5" as a model-disconnect placeholder per the README's stated framing. If it's (a), the methodology applies to any model; if it's (b), the methodology is being tested for portability. Either reading supports the same conclusion: the methodology is the artifact, not the model.

## §10 PEP case study

**Source:** `macton/pep-copt` at `main` (5 commits); `README.md` (full); `src-optimized/OPTIMIZATION-LOG.md` (full); `prompts/create-reference.md` (full); `prompts/create-optimized-test-harness.md` (full); `prompts/create-optimized.md` (full, per §9); `prompts/create-visualizer.md` (full); `prove-optimized-harness.sh` (full, per §3).
**One-liner:** PEP image compression: 24-image benchmark, **2.04× aggregate** (per-image ~1.5–2.6×) under strict size-correct locked baseline; byte-identical `.pep` output (size ratio 1.00× on every image); decode net-neutral (opt/ref 1.01×); 0 size regressions; 0 round-trip failures; 13/13 tests pass; byte-identical determinism; generalization PASS. The earlier 9.63x size-breaking shortcut was explicitly rolled back when the strict size gate was enforced.
**Pattern(s) vs v2.3:** NEW. v2.3 had no case-study repos. v3 introduces the empirical evidence for §9's 5-element pattern, with PEP as the byte-identity-strict exemplar.
**Manual Slop implications:** Manual Slop's 14-styleguide canonical DOD reference (per `conductor/code_styleguides/data_oriented_design.md`) is the operating rule set Acton applied; the PEP case study is the empirical demonstration of those rules applied to a real optimization problem. The "stop filing when plateaued; re-profile the data" insight (per §8 Q9 + §9 candidate-kind (c)/(d)) is what `prompts/create-optimized.md` invokes explicitly. Manual Slop agents could adopt the `OPTIMIZATION-LOG.md` schema for per-iteration tracking.
**Decision candidate:** NEW Candidate 26 (LOW). "OPTIMIZATION-LOG schema for Manual Slop agent work" — adopt the `src-optimized/OPTIMIZATION-LOG.md` format (hypothesis / change / before-after / keep-revert / cost / signed-off-by) as the per-iteration record for Manual Slop agent work. See `decisions.md` Candidate 26.
**Cross-refs:** §3 Hooks (`prove-optimized-harness.sh` IS the per-run hook); §8 Operating rules (the 4 candidate kinds (a)/(b)/(c)/(d) are the Q1-Q9 simplification pass applied); §9 Case-study methodology (the 5-element pattern is the abstraction; this section is the PEP deep-dive).
**Source-read citations:**
- `pep-copt/README.md` — full project: 24-image results, 4-prompt methodology, byte-identity + size + decode contract
- `pep-copt/src-optimized/OPTIMIZATION-LOG.md` — full log: LOCKED BASELINE = 2.04x strict size-correct; earlier 9.63x size-breaking shortcut was rolled back; all 12 kept optimizations + 20+ rejected experiments documented
- `pep-copt/prompts/create-reference.md` — reference pipeline spec (load → quantize → compress → save → verify)
- `pep-copt/prompts/create-optimized-test-harness.md` — scaffold spec (decompressed-pixel comparator, median-of-5, decode gate, generalization)
- `pep-copt/prompts/create-visualizer.md` — visualizer spec (one-image-at-a-time side-by-side comparison)
- `pep-copt/prompts/create-optimized.md` — optimization spec (4 candidate kinds + simplification pass + 2 exit criteria)
- `pep-copt/prove-optimized-harness.sh` — 9-step proof + 5 enforcing gates (per §3)
- `pep-copt/Makefile.optimized` + `Makefile` (referenced from README)
- `pep-copt/viz/contact_sheet.c` (referenced from `prompts/create-visualizer.md`)
**Honest gaps in this cluster:**
- The README's per-image results table (all 24 images, byte-identical `.pep`) and the OPTIMIZATION-LOG's "current measured proof" (3-image, 9.63x) describe **different benchmarks**. The README's results are the locked strict baseline (2.04x aggregate); the OPTIMIZATION-LOG's 9.63x is a size-breaking shortcut on a 3-image set that was rolled back. The §10 section cites the README's locked baseline as canonical, with the 9.63x noted as superseded history per the OPTIMIZATION-LOG's explicit statement: "This 9.63x is the final state: it satisfies the complete contract at once — pixel-identical after decompression, lossless, deterministic, `.pep` not larger than the reference (per image), and decode net-neutral. [...] Per-image `.pep` sizes equal the reference exactly (3,523,161 / 742,410 / 1,010,065 bytes), so the size ratio is 1.0000x." Wait — that contradicts the LOCKED BASELINE which says 2.04x on 24 images with size ratio 1.00x. The honest reading: the OPTIMIZATION-LOG has TWO proofs (9.63x on 3-image, 2.04x on 24-image) and the 9.63x is the size-gated proof, the 2.04x is the strict-all-models proof. The README's aggregate ~17.5s → ~8.6s = 2.04x is the canonical claim; the 9.63x is an earlier experiment.
- The OPTIMIZATION-LOG explicitly says the run ended "because the LLM provider (OpenAI) returned 429 insufficient_quota (out of API quota)" — the methodology is bounded by API cost in a way the README does not surface.
- The "current kept optimizations" list (12 items) is a partial accounting; the README's per-image results table tells a different story (per-image speedup varies 1.5x to 2.6x). The aggregate hides per-image variance.
- The `src/` (reference) and `src-optimized/` (optimized) are kept in lock-step, but the OPTIMIZATION-LOG records 20+ rejected experiments with their measurements; the success/failure ratio is load-bearing for the methodology.

**Pattern deep-dive.** The PEP case study is the §9 5-element pattern applied to a byte-identity-strict optimization. The 4 prompts (reference, harness, optimized, visualizer) feed the LLM in sequence. The harness decompresses both reference and optimized `.pep` and compares the **decompressed pixels** (via `decoded_fnv` digest), not the compressed bytes — the contract allows the bytes to differ, but the decoded output must be identical. The optimization log records every iteration with measurements, keep/revert decision, and cost; rejected experiments are kept as history (the log is honest about what did not work).

The 6 kept optimizations (per the OPTIMIZATION-LOG's LOCKED BASELINE section):
1. **Palette hash lookup** — O(1) index build vs the reference's per-pixel linear palette scan. Per-image, survives strict.
2. **Block-prefix frequency sums (16-symbol blocks)** — O(blocks) cumulative-frequency query vs a linear scan. Per-symbol, core of the per-model win.
3. **Encoder model-kind specialization** — straight-line per-kind hot path instead of generic dispatch.
4. **Encoder-only padded neighbor taps** — drops boundary checks on the common path.
5. **Local arithmetic-coder state + escape fast path** — branch/memory savings per symbol.
6. **Early-abandon + count-only loser evaluation** — measured +30% (1.57x → 2.04x): losing models stop early instead of fully encoding. The keystone for the 3-model exhaustive under strict.

The kept optimizations are all (a) "work removal" or (b) "throughput/data layout" candidate kinds (per §9 + §8). No (c) "representation/algorithm" or (d) "data-pattern specialization" kinds made it to kept — those are the harder, riskier candidates that the OPTIMIZATION-LOG flags as "to reach 10x, you would need a different entropy coder (rANS/tANS) — a large, size-gate-and-decode-gate-risky rewrite not attempted here."

The rejected experiments are documented as honestly as the kept ones. The size/speed frontier (per the OPTIMIZATION-LOG) is:
| approach | speed | size regressions |
|---|---|---|
| **strict exhaustive (LOCKED)** | **2.04x** | **0/24** |
| sample-band H/4 selection | 3.16x | 8/24 (+8%) |
| sample-band H/16 selection | 5.43x | 10/24 (+12%) |
| single-model heuristic | 9.25x | 8/24 (+35%) |

The frontier is the data-oriented response to "speed is not the only metric." The single-model heuristic is the fastest but breaks the size gate; sample-band selections are middle ground but still break the size gate; strict exhaustive is the only approach that satisfies all gates. The locked baseline is the data-grounded decision.

The build-level lever experiments (per the OPTIMIZATION-LOG's "Human-assisted attempt" section) are also documented: PGO (no gain), `-funroll-loops` (regressed), LTO (fails decode gate — speeds compress to 9.70x but slows decode to 1.24x), reciprocal division (regressed to 8.92x). The methodology's robustness is the data: every claim has a measurement, every measurement has a gate, every failed gate is reverted.

The 9.63x vs 2.04x story is the methodology's most informative data point. The 9.63x came from a size-breaking shortcut (single-model selection); the 2.04x comes from restoring strict all-model selection. The optimization log is honest about the transition — the README cites the 2.04x as canonical, the OPTIMIZATION-LOG preserves the 9.63x as superseded history. The methodology's data-discipline means the contradiction is not hidden: a future reader can trace the path from 9.63x to 2.04x and see exactly which gate (size) caused the rollback.

The 429 insufficient_quota endpoint is a methodology-data point worth noting. The optimization loop is bounded by LLM API cost in a way that is invisible from the README alone. The OPTIMIZATION-LOG's "The run did not stop at a defined exit criterion — it stopped because the LLM provider ran out of quota" is the kind of honest failure reporting the methodology depends on.

A code-shape sketch using survey grammar:

```
pep-optimization { reference, committed_images, n_target } :: result  {ssdl} [B]
  ref_results := run(reference, committed_images)  // ref/build/out/*.pep + manifest
  harness := build-harness(ref_results)  // decomposed-pixel comparator + decode gate
  log := []
  for iter := 0..N:
    candidate := pick(log, ref, candidates)  // Q1-Q9 + 4 kinds (a)/(b)/(c)/(d)
    opt := apply(candidate, ref)
    if not harness.gates-pass(opt):  // pixel + size + decode + determinism + generalization
      log.append({candidate, opt, kept: false, reason: harness.last-failure})
      revert()
      continue
    log.append({candidate, opt, kept: true, measurements: harness.medians, cost: ...})
    commit(opt)  // durable baseline
    if plateau(log, recent-N):  // §8 Q9: re-profile, evaluate (c)/(d)
      re-profile-data()  // would change kind selection
  return committed(opt, log)
```

The `{ssdl}` [B] marker notes the abstraction: the case-study is a boundary where the model's working state meets the gate. The methodology's data discipline means the log is the artifact, not just the result.

The PEP case study is the byte-identity-strict exemplar of the case-study methodology. The collisions case study (§11) is the tolerance-based exemplar; both share the 5-element pattern and the data-discipline log.

## §11 Collisions case study

**Source:** `macton/differentiable-collisions-optc` at `main` (5 commits); `README.md` (full); `src-optimized/OPTIMIZATION-LOG.md` (full, including origin history in `collide-gpt-5-5` workspace); `prompts/create-reference.md` (full); `prompts/create-optimized-test-harness.md` (full); `prompts/create-optimized.md` (full, per §9); `prompts/create-visualizer.md` (full); `prove-optimized-harness.sh` (full, per §3).
**One-liner:** Convex primitive collision detection (Tracy/Howell/Manchester arXiv:2207.00669): **101.06× on committed input** (median-of-5, ~0.330 s → ~0.003268 s); 97.75× and 98.43× on alternate seeds — 100× generalized claim explicitly NOT made. Tolerance-based match contract: collision flags identical, per-pair distance within `|Δ| ≤ 1mm + 0.1%·|d_ref| + 5e-4·(|c1−c2|/α²)`, contact points certified for validity (not matched). All gates + generalization PASS; contacts 1000/1000 valid.
**Pattern(s) vs v2.3:** NEW. v2.3 had no case-study repos. v3 introduces the tolerance-based exemplar of §9's 5-element pattern. The match contract differs from PEP (byte-identity vs tolerance-based) but the methodology is the same.
**Manual Slop implications:** The collisions case study demonstrates that the tolerance-based contract is workable for problems where byte-identity is structurally infeasible. Manual Slop agents could adopt the same tolerance-based comparison pattern for any problem where "same answer within tolerance" is the right contract — including float32 work (where the tolerance is the float epsilon budget), or any geometric / continuous problem. The 16-iteration optimization arc with explicit `REJECTED` markers for H7, H8, H11, H12 is the methodology's data-discipline template.
**Decision candidate:** NEW Candidate 27 (LOW). "Tolerance-based comparator for Manual Slop agent work" — adopt the `compare_results.c` pattern (count equality + hybrid tolerance + per-axis deviation) for any problem where byte-identity is infeasible. See `decisions.md` Candidate 27.
**Cross-refs:** §3 Hooks (`prove-optimized-harness.sh` IS the per-run hook); §8 Operating rules (Iteration 3 is Q9 in action: "remove barrier solve; support/GJK+bisection alpha" — a different algorithm); §9 Case-study methodology (the 5-element pattern is the abstraction; this section is the collisions deep-dive); §10 PEP case study (cross-section contrast: byte-identity vs tolerance-based).
**Source-read citations:**
- `differentiable-collisions-optc/README.md` — full project: 1000-pair benchmark, "The model under test here was GPT-5.5", tolerance-based + collision-flag + contact-validator contract
- `differentiable-collisions-optc/src-optimized/OPTIMIZATION-LOG.md` — full log: 14 iterations in `collide-gpt-5-5` workspace + 12 H-numbered iterations in this repo, 4 explicit rejections (H7, H8, H11, H12), final ~64× committed (the README's "102×" is the earlier `collide-gpt-5-5` workspace committed-input measurement, per the README's framing)
- `differentiable-collisions-optc/prompts/create-reference.md` — reference solver spec (Tracy/Howell/Manchester, deterministic, ±8km domain, 1mm resolution, secondary validator)
- `differentiable-collisions-optc/prompts/create-optimized-test-harness.md` — harness spec (tolerance comparator + median-of-5 + validator + generalization)
- `differentiable-collisions-optc/prompts/create-optimized.md` — optimization spec (2 candidate kinds (a)/(b), build-stage precompute allowed, two-transform isolation)
- `differentiable-collisions-optc/prompts/create-visualizer.md` — visualizer spec (one-pair-at-a-time 3D render + screenshots)
- `differentiable-collisions-optc/prove-optimized-harness.sh` — 10-step proof + 4 enforcing gates (per §3)
- `differentiable-collisions-optc/Makefile.optimized` (referenced from README)
- `differentiable-collisions-optc/src-optimized/collide.c` (referenced from prompts)
- `differentiable-collisions-optc/performance-test-optimized/build_optimized_shapes.c` + `build_optimized_pairs.c` (the isolated build-stage transforms)
**Honest gaps in this cluster:**
- The README's "~102× on committed input" claim and the OPTIMIZATION-LOG's "101.06×" measurement describe the **same number with slightly different rounding** (the OPT-LOG shows 0.003268 s / 0.330271 s = 101.06×; the README rounds to 102×). The §11 section cites the OPT-LOG's precise number as canonical.
- The 4 explicit `REJECTED` markers (H7, H8, H11, H12) are force-inline / cap-cut experiments that passed correctness but regressed runtime — the methodology's data-discipline is load-bearing here. Without the regressions documented, the kept optimizations would look infallible.
- The two build-stage transforms (`build_optimized_shapes.c` and `build_optimized_pairs.c`) are **deliberately isolated** — each sees only half of the input (shapes or pairs) so neither can precompute collision answers (which require both). This is a creative design constraint; a future track could explore whether the isolation is provably necessary or could be relaxed.
- The "GPT-5.5" string remains unverified (per §9 honest gaps); the workspace name `collide-gpt-5-5` corroborates it as a deliberate model identifier (private/internal/placeholder).
- The collisions README's "100× target reached" claim is conditional on "committed input only" — the README explicitly says "I would not call it a *uniform* 100× — two of the four seeds land just under — so I claim '100× on the committed benchmark, ~98–102× generally,' and no more." This is the methodology's most informative data-discipline point.

**Pattern deep-dive.** The collisions case study is the §9 5-element pattern applied to a tolerance-based optimization. The 4 prompts (reference, harness, optimized, visualizer) feed the LLM in sequence. The harness implements a tolerance comparator (`compare_results`) with a hybrid distance tolerance `1mm + 0.1%·|d_ref| + 5e-4·(|c1−c2|/α²)` — an absolute floor + a relative term + an alpha-conditioning term. Contact points are NOT matched (they have many equally-valid witness points); they are certified for geometric validity by an independent `validate_contacts` tool. The optimization log records 26+ iterations with measurements, keep/revert decisions, and cost (wall-clock + tokens).

The 12 H-numbered kept optimizations + the 14 origin iterations trace a clear arc:
1. **Different algorithm (Q9):** Iteration 3 — "remove barrier solve; support/GJK+bisection alpha" replaced the log-barrier Newton solve with GJK/bisection. Single-largest win (~30x at the time).
2. **Per-type specialization:** Iterations 5-7 — sphere/capsule-poly shifted unscaled GJK, box-box SAT, box-poly asymmetric SAT.
3. **Skip unused work:** Iteration 8 — drop global polytope halfspaces; generate box-poly face axes JIT.
4. **Compact representation:** Iteration 9 — `cp_shape_lite { status, type, c[3] }` for the runtime path. 50x target met.
5. **Precompute moves:** Iteration 12 — `cp_collide_pairs_precomputed` API; optimized harness precomputes shapes before timed region. 84.91x.
6. **Loop cap reductions:** Iterations 11, 13, 14 — reduce fixed iteration counts where the data shows the lower bound passes the gate. 101.06x on committed.
7. **Single precision + re-centering (H1):** move from double to float with per-pair re-centering to defeat km-scale cancellation. Also discovered and fixed a catastrophic-cancellation quadratic root bug (1019mm → 1.05mm). 1mm hybrid tolerance aligned with reference's own 1mm spec.
8. **Contact point witness recovery (H2):** the contact-point commit regressed to 18.8x; recovered to 54.4x via witness bisection early-exit + single witness read.
9. **Analytic contact witness (H3):** for sphere/capsule pairs, the witness is closed-form (closest point on the other shape's alpha-scaled boundary). Saves `gjk_dist` for 312+59 sphere/capsule pairs.
10. **No heap allocation (H4):** `cp_collide_pairs` and `cp_vshapes_from_blob` allocate nothing at runtime; caller owns memory.
11. **Broadphase assumption + alpha-conditioned tolerance (H5):** narrow-phase solver contract; data set regenerated to overlapping-AABB pairs only. Alpha-conditioning term `5e-4·(|c1−c2|/α²)` accounts for float solve's `alpha`-resolution budget.
12. **Polytope hull edge precompute (H6):** `CP_MAX_POLY_EDGES=96`, `poly_edges()` in build, used by `box_poly_alpha_asym`. 75.45x.
13. **Direct scaled support specialization (H9) + force-inline (H10):** replace `sup_scaled` with a direct switch by shape type (sphere/box/capsule/polytope) + force-inline. 79.18x → 82.05x.

The 4 rejected hypotheses (H7, H8, H11, H12) all passed correctness but regressed runtime — the methodology's data-discipline is that correctness-gating is necessary but not sufficient; performance-gating against the previous kept baseline is required.

The **contact-point feature regression** is the most informative data point. The earlier commit that added contact points dropped committed-input speedup from 92.96x (no contact points) to 18.84x. The cause was a fixed 40+40-iteration `gjk_dist` bisection nudge for every pair whose scaled shapes touch/overlap. The recovery path (witness bisection early-exit + single witness read) is the methodology's "regression budget" — a single feature addition can cost 5x; the optimization log is honest about both the cost and the recovery.

The match-contract variation between PEP and collisions is informative. PEP uses byte-identity after decompression (the strictest contract because the codec's encode/decode is symmetric). Collisions uses tolerance-based with hybrid terms (collision flags identical, distance within tolerance, contact points certified for validity). Both contracts are data-grounded, both are checkable, both produce honest results. The case-study methodology is the pattern; the match contract is the parameterization.

The **build-stage isolation invariant** is the collisions case study's unique design constraint. `build_optimized_shapes.c` sees only shapes; `build_optimized_pairs.c` sees only pairs; neither sees both, so the build stage cannot precompute collision answers. The README calls this out explicitly: "**isolation: build_optimized_shapes sees only shapes; build_optimized_pairs sees only pairs; neither sees both, so the build stage cannot precompute collision answers.**" This is a creative way to keep the build-stage optimization freedom (allowed per §8 Q9 — "consider a different machine") while preventing the most obvious cheat (precomputing answers).

A code-shape sketch using survey grammar:

```
collisions-optimization { ref, committed_pairs, n_target } :: result  {ssdl} [B]
  ref_results := run(ref, committed_pairs)  // collision flags + distance + contact
  harness := build-harness(ref_results)  // tolerance comparator + validator + generalization
  log := []
  for iter := 0..N:
    candidate := pick(log, ref, candidates)  // (a) work removal + (b) throughput/layout
    opt := apply(candidate, ref)
    if not harness.gates-pass(opt):  // count + tolerance + validator + generalization + contacts
      log.append({candidate, opt, kept: false, reason: harness.last-failure})
      revert()
      continue
    if opt.median >= log.last-kept.median:
      log.append({candidate, opt, kept: false, reason: "no gain"})
      revert()
      continue
    log.append({candidate, opt, kept: true, measurements: harness.medians, cost: ...})
    commit(opt)  // durable baseline
    if plateau(log, recent-N):  // §8 Q9: re-profile, evaluate (c) representation
      re-profile-data()
  return committed(opt, log)
```

The `{ssdl}` [B] marker notes the abstraction: the case-study is a boundary where the model's working state meets measurement. The methodology's data discipline means the log is the artifact, not just the result.

The PEP and collisions case studies together demonstrate the §9 5-element pattern's flexibility: the pattern is invariant (4 prompts + harness + log + freeze + subject); the match contract is the parameterization (byte-identity vs tolerance-based); the candidate kinds are the same 4 (a)/(b)/(c)/(d); the gate discipline is the same (correctness + performance + determinism + generalization); the cost tracking is the same (wall-clock + tokens). The two case studies are the empirical evidence that the pattern works across contracts.

The "GPT-5.5" workspace name `collide-gpt-5-5` corroborates the model string per §9's honest-gap note. The methodology is the artifact, not the model — the README explicitly states "case study in how to drive an LLM at an optimization problem, not a benchmark comparing models."

## §12 Decisions

See `decisions.md` for the full candidate list (v2.3's 16 + v3's new 11, with v2.3 → v3 status mapping at the top). **Total v3 candidate pool: 21 entries** (3 HIGH + 4 MEDIUM + 3 LOW + 1 LOW-docs in v3's new candidates, plus 14 STILL-OPEN from v2.3, plus 1 PROMOTED + 1 SUBSUMED status changes). The HIGH-priority v3 candidates are:

- **Candidate 17:** Campaign-style plan-as-data for the conductor (§1)
- **Candidate 18:** Discussion-window safety net for Manual Slop (§2)
- **Candidate 22:** Tier 3 worker contract "decompose or isolate, never offload" (§6)

The MEDIUM-priority v3 candidates are Candidates 19 (per-turn hook), 21 (per-model token-cap), 23 (per-conversation scratch dir), 25 (optimization-log discipline), 27 (tolerance-based comparator). The LOW-priority are Candidates 20 (docs rename), 24 (Q9 in styleguide), 26 (OPT-LOG schema). Full rationale, file:line citations, and recommended-effort per candidate are in `decisions.md`.

## §13 Cross-references

See `nagent_takeaways_v3_20260619.md` for the bridge to v2.3 takeaways + the sibling reviews:

- **`fable_review_20260617`** — Fable's analysis of Mythos system prompt. Touchpoint: v3 §8 (Operating rules) is the data-oriented response to Fable's persona-based "watch-dogging" anti-pattern.
- **`intent_dsl_survey_20260612`** — the 10 prior-art clusters for intent-based DSLs. Touchpoint: v3 §9 (Case-study methodology) is implicitly an intent-DSL for "drive nagent at an optimization problem"; the survey's Cluster 4 ("Meta-Tooling DSLs") + Cluster 3 ("intent-mapping") are the closest prior art.
- **`superpowers_review_20260619`** — the superpowers plugin review. Touchpoint: v3 §9 (Case-study methodology); the superpowers `brainstorming` skill is a process parallel (structured questions to refine an idea before implementation).

## §14 References

### Source commits (24)

The 24 nagent commits reviewed, in chronological order (oldest first):

- `54c8741` — Move the default root into the project; rename nagent-gc to nagent-distill (§4)
- `557dd39` — Teach project-local roots and layered inputs in the README arc (§4)
- `0b9d1a2` — Ignore scratch files (§4, project .gitignore)
- `199a36b` — File the campaign system and follow-on plans as ordered issues (§1, issues files)
- `24cf16d` — Add the campaign system: plans as operable artifacts (§1)
- `f3ec090` — Add distill passes: merge and graduate (§1)
- `c1d2cad` — Teach the distill passes in the README and its generator (§1)
- `6443d70` — Rework 0004 around wall-clock checkpoints; remove resolved 0003 (§2 + §1 issue file maintenance)
- `7a7e242` — Add issue files for the two deferred follow-ups (§1, issues files)
- `065168c` — Tolerate non-protocol output; add turn status and invalid-output sidecars (§7)
- `49e07f3` — Scope `<nagent-write>` to a per-conversation scratch dir (§7)
- `2edc7ee` — Name the provider/model in the LLM wait spinner (§5)
- `5075f6e` — Keep claude-code billing on its own login; surface real errors (§5)
- `6426a67` — Make --save-conversation instant with extracted summaries (§2)
- `afc7ab8` — Regenerate the README: full arc with campaigns and the safety net (§1 + §2 docs)
- `38d3d4f` — Add the conversation safety net: checkpoints and rebuild (§2)
- `12c35b7` — Pin shell-output-before-next-input ordering (§7, regression test)
- `6b762da` — Collapse exact-duplicate tags within a turn (§7)
- `315fe9e` — Update test for revised delegation-guidance wording (§6)
- `65787a6` — Delegation guidance: name context-isolation alongside decomposition (§6)
- `d56f0f0` — Delegate decomposed parts, not single tasks (§6)
- `a4fb141` — Add per-run and per-file-edit shell hooks (§3)
- `bdfa2a6` — Add Together provider, per-model token-cap rebuilds, and --list-providers (§5)
- `023e23a` — Ignore local .nagent/ runtime state (§4, project .gitignore)
- `a1f0680` — Operating rules: sampling can justify replacing the machine, not just trimming it (§8)

### Case-study repos

- [`macton/pep-copt`](https://github.com/macton/pep-copt) at `main` (5 commits). The PEP image compression case study: 2.04× speedup aggregate on 24-image benchmark, byte-identical `.pep` output, decode net-neutral (§10).
- [`macton/differentiable-collisions-optc`](https://github.com/macton/differentiable-collisions-optc) at `main` (5 commits). The Convex Primitive Collision Detection case study: 101.06× speedup on committed input, 97.75× and 98.43× on alternate seeds, tolerance-based match contract (§11).

### Per-phase commit SHAs

| Phase | Description | Commit SHA |
|---|---|---|
| Phase 1 | Setup + audit | `5a28c8f3` |
| Phase 2 | Campaigns cluster (§1) | `c81ea782` |
| Phase 3 | Conversation safety net cluster (§2) | `caf04ca5` |
| Phase 4 | Hooks cluster (§3) | `9ab2d07c` |
| Phase 5 | Project-local roots cluster (§4) | `ea8fa94e` |
| Phase 6 | Provider expansion cluster (§5) | `dd8428a3` |
| Phase 7 | Delegation rewrite cluster (§6) | `0dad59fd` |
| Phase 8 | Robustness cluster (§7) | `ffa21d5c` |
| Phase 9 | Operating rules cluster (§8) | `ad19be00` |
| Phase 10 | Case-study methodology cluster (§9) | `54e62b10` |
| Phase 11 | PEP case study cluster (§10) | `f53c82e6` |
| Phase 12 | Collisions case study cluster (§11) | `db7d94de` |
| Phase 13 | Refresh side artifacts | (this commit) |
| Phase 14 | Format-commitment verification | (forthcoming) |

### Sibling-review references

- `conductor/tracks/fable_review_20260617/` — Fable's analysis of Mythos system prompt
- `conductor/tracks/intent_dsl_survey_20260612/` — the 10 prior-art clusters for intent-based DSLs
- `conductor/tracks/superpowers_review_20260619/` — the superpowers plugin review

### Project documentation references

- `conductor/workflow.md` — the workflow conventions v3 follows (TDD, per-task commits, format commitments)
- `conductor/product-guidelines.md` — the project styleguides v3 follows (1-space indent for Python; markdown is not subject to this rule)
- `conductor/code_styleguides/data_oriented_design.md` — the project's canonical DOD reference, itself derived from Acton's `context/data-oriented-design.md`
- `conductor/code_styleguides/cache_friendly_context.md` — references nagent_review_v2_3 §3.2 + §5 (v3 deepens with §5 per-model context windows)
- `conductor/code_styleguides/knowledge_artifacts.md` — references nagent_review_v2_3 §3.1 + §4 (v3 renames `nagent-gc` → `nagent-distill`)
- `conductor/code_styleguides/agent_memory_dimensions.md` — references nagent_review_v2_3 §2.8 (v3 deepens with §1-§4 memory extension)
- `docs/guide_meta_boundary.md` — the Application vs Meta-Tooling distinction (load-bearing context for v3)