Skip to content

TDD 0067: /build-tdds Retry and escalation, judged by the model that actually ran - #187

Merged
cahenesy merged 8 commits into
masterfrom
build/20261005-174458/0067-build-tdds-escalation
Oct 6, 2026
Merged

cahenesy merged 8 commits into
masterfrom
build/20261005-174458/0067-build-tdds-escalation

Conversation

@cahenesy

@cahenesy cahenesy commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Implements TDD 0067 revision 2 (PRD f6ef178, ADR 0015 + ADR 0016). This is the last of the model cost/performance stack; 0064–0066 are already on master.

What changed

  • Retry (step 3). A TDD that halted on a gate FAIL can be retried. The implementer fixes the failure on the existing build branch, using the failed gate's report, and then every gate re-runs.
    • Halt-time verdicts are archived into retry-<N>/, so an interrupted Retry resumes at test-first.
    • The step-3 prompt now also appears when every TDD in the run has failed.
  • Escalation (FR-88). --escalate / THROUGHLINE_ESCALATE=1, or automatic on a Retry when the TDD hasn't changed since the halt. --no-auto-escalate turns the automatic case off.
    • The decision is made before the queue confirmation and shown there, and recorded in the per-TDD sidecar.
  • Outcomes are judged by the model that actually ran (ADR 0016). After each escalated worker, tl_escalation_verify reads subagents/agent-<id>.jsonl:
    • a different family, or a model that can't be read, records fell-back with a reason;
    • credits_required counts as a fall-back, not a pause;
    • a substituted worker's output is kept, and the TDD's later workers inherit the parent model.
  • Live probe tests/live/escalation-probe.sh (run only in the verify gate): P1 uses the escalation binding, P2 a bogus model, P3 is a positive control on opus. It handles both sync and async Agent streams.
  • tests/escalation.test.sh: 303 checks, run against the real functions and the blocks extracted from the skill.
  • Version 3.48.0 → 3.49.0, plus a releases.json entry.

History

The first build halted BLOCKED at runtime-verify, which was correct:

Gates (retry)

Gate Result
test-first PASS (abf96dd test(failing) was red on the pre-retry code, then 835ac7d feat)
ci-checks PASS (10 evals; escalation 303/0)
runtime-verify PASS: 15/15 observations. Live probe exit 0: P1 fable refused → fell-back, P2 refused, P3 opus → escalated. P3's transcript was checked independently and reads claude-opus-5-5.
review PASS: no blockers or majors

Review follow-ups (not blocking)

  • Minor: dispatch-error text reaches a double-quoted shell argument in the fall-back prose (SKILL.md:346). Pass it via an env var instead.
  • Minor: the implementer's rate-limit rule doesn't point to the credits_required exception (SKILL.md:421), so a credits refusal could be classed as a pause and the refused model re-dispatched on every resume.
  • Minor: each probe run leaves ~/.claude/projects/-tmp-tmp-* session folders behind. The cleanup trap should delete them.
  • Minor: tl_run_retry_begin splits the .prev.txt copy and the report= line between the function and the skill block, and names the copy differently from the TDD (<slug>.<gate>.prev.txt). It works and is tested; the TDD wording should be aligned.
  • Nits:
    • TL_BASE_SHA isn't validated;
    • an exact-id override does match, so the TDD's "never matches" is too strong;
    • family matching accepts older versions of the same family.

After merging

claude plugin update throughline@throughline, then /reload-plugins, to get 3.49.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq

cahenesy and others added 8 commits October 5, 2026 14:56
tests/escalation.test.sh executes every TDD 0067 Verification-plan
observation (1-13, 11b, 11c) against a fixture repo (integration branch
master, runs built through tl_run_init / tl_run_set_tdd /
tl_verdict_write), extracts and runs the four new skill blocks plus
0066's confirm/record blocks with escalation, covers the approved
CLAUDE_CODE_SUBAGENT_MODEL mechanical-verify arm, and drives the live
probe's exit classification against a stub claude (no network).
Registered in tests/implement-gate.test.sh.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
… 0067)

models.sh: a third [escalation-model] resolver arg turns every `parent`
slot into `escalation` (pins keep their pin; mechanical verify keeps its
rule); tl_escalation_outcome (escalated | already-top | fell-back) and
tl_escalation_flags (--escalate / --no-auto-escalate and their env
twins); tl_models_confirm / tl_model_warnings take the escalation model
and outcome and add the `escalation:` confirmation line. Approved fix:
on Claude, mechanical verify resolves a non-empty
CLAUDE_CODE_SUBAGENT_MODEL as `pin:CLAUDE_CODE_SUBAGENT_MODEL` before
the parent-cap fallback.

run-record.sh: tl_run_set_models 6th arg; tl_run_latest_run,
tl_run_retry_candidates, tl_run_failed_report, tl_run_retry_begin
(archives only *.json verdicts into retry-<N>/, then `building`),
tl_run_set_halt_blob, tl_escalation_decide, tl_run_set_escalation (all
sidecar writes via _tl_models_write), tl_escalation_fellback_check.

skills/implement/SKILL.md: tl:escalation-flags, tl:retry-candidates,
tl:retry-begin and tl:escalation-decide blocks (block contract); step 3
offers Retry for failed/gate-fail TDDs; confirm/record wiring reads the
sidecar escalation; fall-back prose (transient first, except a
usage-credits refusal of the escalation model; an async worker's
failure arrives in its completion notification); halt blob after a
gate-fail; usage line; CLAUDE_CODE_SUBAGENT_MODEL applies to every
worker dispatched without a model parameter.

tl:retry-begin also copies the failed report to <report>.prev.txt and
prints implementer_report=<copy>, because step 7 recreates an empty
<slug>.build.txt (a test-first FAIL's report) before the implementer
reads it; the eval tracks that line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
tests/live/escalation-probe.sh (verification observation 14, run only by
the runtime-verify gate). Observed on Claude Code 2.1.289: the Agent
tool is async by default, so its tool_result is only an "Async agent
launched" ack. The probe now reads both shapes: a sync tool_result that
carries the reply, or the ack followed by the harness's
system/task_notification for that tool_use_id (status, summary) and the
subagent's own messages (text and its SubagentHandback input.message).
The ack's prompt, task_started and the parent's text echo the nonce and
are never read for it. P1 is also BLOCKED when a model seen for the
subagent (the ack's resolvedModel, the subagent's message.model, "model
sent to the API: <id>") is outside the requested family: a silent
substitution. Exit 1 names its cause, including a session that hit a
rate/usage limit before dispatching.

The eval's stub gains the real shapes: async pass, a usage-credits
failure, a family substitution, a completed agent without the nonce, an
ack with no notification, and a rate-limited session.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
TDD 0067 revision 2 / ADR 0016: renumber to the rev-2 Verification plan and
add obs 10 (tl_worker_actual_model), 11 (tl_escalation_verify, incl. the
opus alias case), the tl:escalation-verify block (obs 13 chain + every
post-worker arm), the step-7 rule order and text checks (obs 14), and the
reworked live-probe classification against a stub that writes worker
transcripts (P1 classified against the transcript, P2 refusal, P3 positive
control, session_id from system/init).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
…, ADR 0016)

- models.sh: tl_worker_actual_model <agent-id> reads the worker transcript
  (projects/*/<session>/subagents/agent-<id>.jsonl, quoted config dir,
  jq fromjson? -> python3 cascade, never a regex) and prints the distinct
  non-synthetic models; tl_escalation_verify <agent-id> <model> prints
  escalated | fell-back harness ran <id> | fell-back actual model
  unverified (<why>).
- SKILL.md: <!-- tl:escalation-verify --> block (fallback check, then
  verify; action=redispatch|keep|none; inherited workers never verified);
  step-7 rule order transient (credits_required is not transient) ->
  dispatch error -> tl_escalation_verify; wired into steps 9 and 10.
- live probe: P1 classified against the worker transcript, P2 refusal,
  P3 positive control on opus; CLAUDE_CODE_SESSION_ID from the child's
  system/init session_id; exit 0/3/1 per the revised design.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
@cahenesy
cahenesy merged commit 5b43876 into master Oct 6, 2026
1 check passed
@cahenesy
cahenesy deleted the build/20261005-174458/0067-build-tdds-escalation branch October 6, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant