# Implementer Notes — read FIRST, before any phase

Audience: AI coding agents first — specifically the agent implementing `docs/plans/2026-07-07-design-gap-handoff.md`. This file carries the tacit knowledge the plan and specs cannot: identity model, cross-spec invariants, the transition seam, and the known failure modes of LLM implementers on fail-closed systems. Written 2026-07-07 by the planning agent (Claude Fable 5) as a handoff.

## Identity model — do NOT conflate these

- **slug** — plan identity. Keys: `docs/plans/*-<slug>.jsonl`, `runstate/<slug>.jsonl`, `runstate/<slug>.live.json`, run lock, branches `plan/<slug>` (integration) and `plan/<slug>--<taskId>` (task, FLAT double-dash).
- **runId** — deterministic binding id derived from (repo, worktree, slug) — see `deriveRunId` in `src/runner.js`. NOT per-attempt: a resume chain reuses the SAME runId; the same slug run from a different repo/worktree clone (e.g. `--isolate`) gets a different one. Keys: `~/.harness/runs/<runId>.json` registry pointers, supervisor socket path. Do NOT invent per-attempt run ids — attempt identity lives in journal records.
- **taskId** — plan-scoped, stable across resumes and amendments.
- Mapping lives in the registry pointer (`runId → journalPath` i.e. → slug). Journal is per-SLUG and shared across that slug's runIds — resume appends to the SAME journal. When a spec says "journal", it means the slug journal. NEVER create per-runId journals.

## Runner exit-code contract (single place it is written down)

- `0` — terminal, zero quarantines.
- `4` — terminal, ≥1 task quarantined (`run.done` present, summary printed). PARTIAL SUCCESS, not an error loop trigger.
- `2` — usage/invocation error (bad args, plan not found). Resolver: escalate, never retry.
- any other non-zero — failure. TARGET state (P2 work item): last stderr line is structured JSON `{failClass, scope, message, stack?}`. CURRENT state: runner prints plain formatted errors — the resolver's fallback path feeds raw stderr to `classifyFailure` (see `spec/RESOLVER.md` § Failure input contract). Do not assume the JSON line exists until the P2 change lands.
- No exit record + dead pid = crash. Distinguish from all the above.

## Cross-spec invariants (each spec states its own; these span them)

1. **Journal first, then act.** Every state-changing decision appends its record BEFORE its side effect executes (intent pattern for non-git effects). If you find code acting then journaling, it is a bug even if tests pass.
2. **seq is the only order.** Any code sorting journal records by `ts` is wrong — fix it on sight (`ts` is display-only).
3. **Journal event kinds/payloads are a frozen public API** consumed by resume, resolver, stats, TUI, web UI, OTel export. Extend payloads with OPTIONAL fields only; never rename a kind; never change a field's type. New behavior = new kind.
4. **Fail-closed means propagate, not swallow.** An error path either produces a CLASSIFIED failure (through `classifyFailure`) or re-throws. There is no third option.
5. **One journal reader.** stats, timeline, why, report, export all consume `src/state/journal.js` read API. A second parser WILL drift — if you need a new view, add a reducer, not a reader.
6. **Config through `resolveConfig` only** (X10). Direct `process.env` / file reads outside `src/config.js` are violations; grep for them before declaring a phase done.

## Transition seam the plan under-specifies (P2 before P3)

The resolver (P2) ships BEFORE the daemon (P3) exists. Build the resolver as a LIBRARY with a thin CLI: `runplan resolve <slug>` — reads journal + last exit artifacts, computes ONE action, executes it (or prints it with `--dry-run`), exits. Effects:
- P2 is fully testable and USABLE without the daemon — the babysit-runplan skill immediately delegates to `runplan resolve` instead of prose reasoning (partial autonomy win one phase early).
- P3's daemon calls the same library function in its resolving state — zero logic duplication.
Same pattern everywhere: every daemon behavior should be a library + CLI verb first; the daemon is a scheduler of library calls, not a home for logic.

## Known LLM-implementer failure modes on THIS kind of system — self-check per phase

1. **Fail-open catch blocks.** The classic: `try { ... } catch (e) { log(e); return null; }` to "make it robust". On this system that is the WORST bug class — it converts a classified halt into silent wrong progress. Every catch either classifies-and-reports or re-throws. Audit your own diff for `catch` before every commit.
2. **Breaker-less retry.** Any retry loop you write MUST have: max attempts, backoff, and a journal record per attempt. A bare `while`/recursive retry is a defect even against a "transient" error.
3. **Test-to-implementation circularity.** Write tests FROM the spec tables/scenarios BEFORE implementing (the chaos suite and spec test lists exist precisely for this). A test written after, from your own code's behavior, proves nothing. If a spec-derived test fails your implementation, the implementation is wrong — do not adjust the test; if you believe the SPEC is wrong, stop and escalate to the operator, never silently reconcile.
4. **Scope narrowing under difficulty.** When a spec item is hard (kill matrix, crash-kill fuzz, pid+start-time), the temptation is a simplified version that "covers the common case". This codebase's whole value is the uncommon case. Implement the spec or escalate — no silent downgrades. Every deliberate deviation gets journaled in the phase's plan doc.
5. **Helper duplication.** Before writing ANY utility (locking, JSON-line IO, git queries, worktree ops), grep `src/` and `lib/` — most exist. Duplicates drift; the journal had TWO writers when this plan started and retiring the second is a whole workstream (V6).
6. **"Improving" adjacent code.** Touch ONLY what the phase requires. Renames-for-clarity, comment additions, formatting of untouched code — all forbidden; they poison diffs and reviews.
7. **Timestamp reasoning.** You will be tempted to compare timestamps for ordering, staleness, and identity. Use seq (order), pid+start-time (identity), explicit budgets vs monotonic-derived ages (staleness). Wall clock lies on this machine and in tests.
8. **Forgetting the resume lens.** For every feature ask: "a crash happens in the middle of this — what does resume see?" If the answer requires in-memory state, the design is wrong (I8). This question catches more bugs in this codebase than any other.

## Working protocol

- One phase per branch; land per project rule (merge to main locally, push; NO PRs).
- Read the phase's spec END TO END before the first edit. Specs win over plan text; plan wins over design.txt.
- Phase done = its chaos scenarios green + ALL previously-green scenarios green + existing test suite green + zero unexplained warnings.
- Spec ambiguity found mid-implementation → write the question + your proposed resolution into the phase plan doc, pick the fail-closed interpretation, flag it in the commit message. NEVER pick the convenient interpretation silently.
- The chaos wrapper (`test/chaos/wrappers/chaos.sh`) doubles as a dev adapter: register it as preset `chaos` so any plan can be dry-exercised end-to-end without spending tokens. Cheapest debugging tool in the repo — build it FIRST inside P0 and use it constantly.
