fix(implement-loop): guard plan-extract against silent truncation on big plans

The plan-extract step pulled every requested task's verbatim block through a
single schema-bound agent() response. On a large plan that response self-limits
and returns a well-formed PREFIX (commonly just task 1); the only guard rejected
an empty list, so a truncated non-empty subset ran to a clean DONE with the
missing tasks never attempted, yet the end-report read as a completion.

Replace the single-shot dump with an enumerate pass + per-task extraction,
guarded by cardinality checks (issue #22 suggested either direction; this does
both, plus a backstop the issue did not scope):

- plan-index enumerates ids + one-line titles only (bounded output, self-limits
  far later than a verbatim dump) and reports total_tasks, the whole-plan count
  computed from the fully-readable plan text independently of the emitted list.
- plan-extract:<id> carries one task's verbatim block per agent() call, removing
  the across-all-tasks output ceiling. Calls fan out via parallel() (which
  honours the concurrency cap on a big plan); the requested id is bound inside
  each thunk and results are re-associated by it, never by array position, so
  the mapping holds regardless of the order parallel() resolves in.
- Cardinality guards: (a) the whole-plan enumerate must list exactly total_tasks
  (the only backstop the no-range path can have without a caller range); (b) a
  task_range must be well-formed and covered exactly by the plan; (c) every
  expected id must extract non-empty text. Any breach is a hard BLOCKED with a
  precise "got N of M" reason, never a silent short run.

Known residual (documented in-code): a single task whose own verbatim body
overflows its dedicated response still returns an accepted prefix — bounded only
by the planner's bite-sized-task invariant, far narrower than the closed vector.

Verified by driving the real script through 11 extraction scenarios in a stubbed
harness plus an order- and self-id-independence association test, and an
adversarial three-lens review of the diff.

closes #22
This commit is contained in:
2026-07-06 14:14:36 +02:00
parent 21a745d090
commit a75a821a29
2 changed files with 174 additions and 16 deletions
+1 -1
View File
@@ -159,7 +159,7 @@ pin exists to remove. Effort follows the model split:
Workflow scripts pin a third tier inline: schema-bound
extraction/verification stages that author no code (preflight,
plan-extract, mini-verify, tree-check, finalize, build/suite verify)
plan-index, plan-extract, mini-verify, tree-check, finalize, build/suite verify)
run `medium` via the `agent()` call's `effort:` option. As with
`model:`, every `agent()` call passes `effort:` explicitly — whether
frontmatter effort propagates through an `agentType` dispatch is