Story-2646 Walkthrough

Change: Blake's Job 1 carve engine now runs in the real agent runtime. One uploaded physical bundle (a compound filing — one Main Document plus a stack of Annexures/Exhibits stitched into one PDF) is carved into a navigable family: a head, N members, and a materialized Index — via the locked code → LLM → code sandwich. The already-merged canonical prompt (prompts/carve.py) is reused verbatim; this Story is the plumbing around it.

Mental model. Code reads the furniture, not the content. It strips each page to one line, detects the filing's own repeating marker scheme, and proposes candidate member boundaries. Blake makes one cheap call to confirm/label each candidate and name the head — reading only the high-signal pages, never all 1,100. Then deterministic code owns the exact page ranges, the exhaustive partition, and the "no page dropped" guarantee. The model can propose a cut; it can never emit a range or certify the partition.

The sandwich (three real modules, not a script)

StepWhereWhat it does
0 · Header stripcarve.build_header_stripOne line per page (page# + top line). The full map at ~19–30 tokens/page — the proof measured 307 pages = 5,786 tokens, 34× smaller than the 197k-token full text.
0.5 · Scheme-adaptive detectioncarve.detect_schemesDetects the filing's OWN dominant repeating marker family (Annexure P-1 / Exhibit A / CW1/91 / NFRA/16 / Schedule II / Appendix) and proposes standalone-marker candidates. NOT a fixed regex — the real-bundle finding.
1 · Candidates + packetcarve.build_carve_prompt_for_pagesStrip (all pages) + Index page(s) + main-document opening window + first page of each candidate member + the candidate skeleton, wrapped in a random per-run nonce fence.
2 · One carve callcarve._run_carve_modelThe merged canonical prompt, one seat from llm.yaml (primary + 2 fallbacks) with a failover loop: timeout / transport error / empty family / bad-JSON → next model.
3 · Monotonic assemblyfamily_models.assemble_carve_familyModel START pages → increasing, non-overlapping ranges → the family (head + N members + Index). Code owns every end; a stray/duplicate/out-of-range start is retained for counsel, never overlapped.
4 · Effectevents.document_family_eventThe typed document.family effect on the frozen agent_event_stream.v1, mirroring the document.card/document.sections builders.
5 · Runtimeruntime._handle_carve_requestA new mode="carve" path, fail-isolated like the card (D13): run_carve never raises, so a failed carve is a status="error" effect, never a stuck bundle.

Why scheme-adaptive is mandatory (the real-bundle proof)

The proof-of-concept ran on a real 307-page NFRA complaint bundle whose members key on NFRA/N. A fixed ANNEXURE|EXHIBIT regex under-detected — 6 hits, all intra-document noise ("Annexure A to the Auditor's Report", a section inside an annual report). A raw model with a generic prompt over-split into 164 "members". Deterministic detection on the filing's own NFRA/N scheme + the scheme-teaching prompt yields 16. The engine keeps the scheme detection generous (code proposes) and lets the prompt reject the intra-document false-positive class (the model rules).

Truth boundary (held)

Immutable sourceMembers are LOGICAL page ranges over the original file — the PDF is never rewritten.
No model-emitted coordinatesThe model emits START pages only; code derives every end and certifies the partition (_assert_family_partition: contiguous, no gap, no overlap, no dropped page).
declared_sideCounsel truth; the carve pass is a side-check only and originates nothing.
Writes nothingThe pass emits a document.family effect the broker persists (ADR-110 / ADR-128); the model call (OpenRouter, ZDR-pinned) is the only egress.

The seat (llm.yaml — Spike-2643 crowns it)

The carve seat is config data, not Python (ADR-151), on the same llm.yaml surface the sibling agents (Mira, Vega) use. Primary google/gemini-2.5-flash-lite + two different-vendor fallbacks; REGISTRAR_CARVE_MODEL overrides the whole list per deploy. The eval_candidates.carve pool is what Spike-2643 runs the bakeoff over — this Story wires the live seat + failover so that bakeoff has a real path to measure.

Gold-bundle #1 reproduces the verified family (acceptance)

Offline, no live model, no PDF: the deterministic halves reproduce the hand-verified NFRA_COMPLAINT_BUNDLE manifest — 1 complaint (pp 1–34) + NFRA/2…NFRA/16 = 16 logical documents, an exhaustive non-overlapping partition of all 307 pages with none dropped. Scheme detection yields the 16 member starts and rejects a mid-sentence "Annexure A" mention. The whole run_carve orchestration + the effect are covered with a stubbed model.

Verification

uv run ruff check agents/registrar
uv run --package registrar pytest agents/registrar/tests/test_registrar_carve.py -q
# live bakeoff harness (needs the gold PDF + OPENROUTER_API_KEY, not a per-PR gate):
cd agents/registrar && uv run python -m evals.carve_eval
# then the full repository Verify gate from .github/workflows/ci.yml

Focused result: 20 carve tests passed; ruff clean; the error-contract gate green. Gold bundle #1 reproduces the 16-member family and certifies the partition.

Scope boundary

In scope: the agents-lane carve engine + the carve runtime mode + the seat. Out of scope (sibling children): Job 2 (the Card), Indigo, the registrar→blake rename (Chore-2649), the functions-lane processor.py routing / family fanout, and the Terraform deploy. The counsel-facing Bundle UX is a separate design track — internal family vocabulary never surfaces to counsel (Bundle / Main Document / Annexure / Index).