T-NP7H-task-work-sub-agent-verdict-contract-escape-recurrence
Status: closed/superseded · Impact: high · Complexity: medium
LLM sub-agents are unreliable emitters of structured terminal output after
substantive work: dispatched /sdlc:task-work runs escape the one-line verdict
contract despite the strengthened “CRITICAL VERDICT CONTRACT” prompt preamble
shipped in [T-ICA5-task-work-sub-agent-verdict-contract-clarity](/planning/tasks/task-work-sub-agent-verdict-contract-clarity/). The
parent-side defenses that contain the damage have shipped (see Today); what
remains is structural: the parse cascade exists only as pseudocode inside
orchestrate’s spec, so every other dispatching parent must re-derive it, and
the verdict path still depends on prompt interpretation instead of a
deterministic command. This task extracts the parser into shared substrate and
reshapes dispatch so the verdict comes from code, not prompt compliance.
| Location | Role today |
|---|---|
apps/sdlc/skills/orchestrate/SKILL.md | Step 4 dispatch prompt with verdict-contract preamble, plus the full parse cascade as inline pseudocode: strict first-line match (TASK_WORK_VERDICT_RE) → lenient last-match scan (LENIENT-RECOVERY) → escape-marker recovery (ESCAPE_RECOVERY_MAP → ESCAPE-RECOVERY) → ANOMALY; per-slug re-dispatch back-off keeps a deterministically-escaping sub-agent from consuming a cap slot every tick |
apps/sdlc/skills/task-ensure-ready/SKILL.md | Emits slug-namespaced markers (ENSURE-READY-OK:, ENSURE-READY-NEEDS-DEFINITION:, …) so intermediate markers cannot match the final-verdict allowlist |
apps/sdlc/skills/task-define/SKILL.md | Same namespacing: TASK-DEFINE-DEFINED: / TASK-DEFINE-NO-CHANGES: / TASK-DEFINE-ALREADY-READY: |
apps/sdlc/skills/task-work/SKILL.md | Final verdicts are namespaced (TASK-WORK-DONE / TASK-WORK-BLOCKED / TASK-WORK-NEEDS-DEFINITION / ERROR); Steps 5b–10 are what an escaping sub-agent fails to continue through |
apps/sdlc/lib/services/orchestrator/ops/log-tick.ts | Digest writer renders the ANOMALY / LENIENT-RECOVERY / ESCAPE-RECOVERY event lines |
Two escape shapes are observed in the field; both are contained by the shipped parser, but only inside orchestrate:
| Shape | Sub-agent behavior | Shipped defense |
|---|---|---|
| Stop-at-intermediate-marker | Halts at a sub-skill marker and returns it as the final answer (3 escapes in 12 ticks in one 2026-05-24 orchestrate session, then-bare READY:) | Marker namespacing removed the collision; ESCAPE-RECOVERY maps each known marker back to a task-work outcome, no same-tick re-dispatch |
| Verdict-after-prose | Emits the correct namespaced verdict, but after a prose preamble (a 2026-05-30 case returned ~30 lines of dependency reasoning, verdict on the last line); a first-line-only guard reads the prose and files an ANOMALY, losing the diagnostic | Lenient last-match scan recovers the buried verdict and files LENIENT-RECOVERY, preserving prompt-tuning pressure without dropping signal |
Both shapes share one root cause: sub-agents don’t reliably comply with terse-output instructions after long tool chains. That makes this a class problem, not a task-work problem — every parent-style skill that dispatches sub-agents (orchestrate, pr-respond, task-close-out, future skills) hits the same risk, so the contract-and-parser pattern wants a project-level shape.
Proposed
Section titled “Proposed”Two deliverables, parser first:
- Shared verdict parser — extract the parse cascade (strict first-line →
lenient last-match → escape-marker recovery → anomaly) from orchestrate’s
spec pseudocode into a deterministic helper in
apps/sdlc/lib/util/, surfaced to skills as ansdlcCLI verb per the op-substrate rule ([D-H7FS-op-substrate-surface](/planning/decisions/op-substrate-surface/)). Every dispatching skill calls the verb; none re-derives the algorithm from prose. - Driver-shaped dispatch — the structural fix: the dispatched sub-agent’s terminal action becomes “run this one deterministic command and return its stdout”, so the verdict line is produced by code rather than by prompt-interpretation. Most invasive of the options considered, and the most reliable — it makes future dispatching skills correct-by-construction.
Approach
Section titled “Approach”- Implement the shared parser in
apps/sdlc/lib/util/subagent_verdict.ts(return text + strict allowlist + escape-marker map in; verdict or typed recovery/anomaly event out), with unit tests per parse class. - Expose it as an
sdlcCLI verb and repoint orchestrate SKILL.md Step 4 at the verb, deleting the inline pseudocode. The spec keeps the contract description; the verb owns the algorithm. - Adopt the verb wherever other dispatching parents (
pr-respond,task-close-out) parse sub-agent returns. - Design and ship the driver-shaped dispatch for task-work: decide how much of the flow tail (verdict assembly and emission) a deterministic CLI verb can own vs. what stays LLM-executed, then reshape the dispatch prompt to “run the verb, return its stdout”.
- Add an eval fixture under
apps/sdlc/skills/orchestrate/tests/: synthesized sub-agent returns for each parse class, asserting the right digest events land via the log-tick op.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/lib/util/subagent_verdict.ts | new | shared verdict parser: strict first-line → lenient last-match → escape-marker recovery → anomaly |
apps/sdlc/cli/ | modify | register the parse verb |
apps/sdlc/skills/orchestrate/SKILL.md | modify | replace the inline parse pseudocode with the verb invocation |
apps/sdlc/skills/pr-respond/SKILL.md | modify | parse sub-agent returns via the shared verb |
apps/sdlc/skills/task-close-out/SKILL.md | modify | parse sub-agent returns via the shared verb |
apps/sdlc/skills/task-work/SKILL.md | modify | driver-shaped verdict emission: terminal verdict produced by a deterministic verb’s stdout |
apps/sdlc/skills/orchestrate/tests/ | modify | eval fixture covering each parse class end-to-end |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Unit tests on the shared parser cover each parse class by name:
(a) clean first-line verdict, (b) verdict-after-prose recovered by
last-match with a
lenient-recoveryevent, (c) one case perESCAPE_RECOVERY_MAPentry asserting its mapped outcome, (d) a no-match return producinganomaly. - AC-2:
apps/sdlc/skills/orchestrate/SKILL.mdStep 4 invokes the CLI verb and no longer carries the inline parser pseudocode block. - AC-3: The task-work dispatch prompt instructs the sub-agent to run a deterministic command and return its stdout as the verdict line; the verdict is no longer assembled by the sub-agent from prose instructions.
- AC-4: All
quality_checksinsdlc.yamlpass.
Out of scope
Section titled “Out of scope”- Same-tick auto-resume of escaped dispatches — cross-tick re-pick with
per-slug back-off already recovers
stamped-but-stoppedtasks, and same-tick re-dispatch risks tight loops with a deterministically-escaping sub-agent. Add it as a layered defense only if escapes persist after the parser and driver ship. - Sub-agent prompt re-engineering — the verdict-contract preamble is already verbose and proven insufficient; more text is not the tool.
- Harness-level enforcement of the verdict contract (rejecting sub-agent returns at the platform layer) — outside the plugin’s scope.
Dependencies
Section titled “Dependencies”- none. Builds on
[T-ICA5-task-work-sub-agent-verdict-contract-clarity](/planning/tasks/task-work-sub-agent-verdict-contract-clarity/)(closed/done), which shipped the prompt preamble and strict-regex validation this task hardens structurally.
Discovery context
Section titled “Discovery context”- Surfaced during a 2026-05-24 rust-ontogen
/sdlc:orchestratesession: three verdict-contract escapes in 12 ticks, all stop-at-intermediate-marker, despite every dispatch carrying the strengthened preamble. A 2026-05-30 session added the verdict-after-prose shape. Companion to[T-NUML-task-work-preflight-permissions-probe-extension-for-skill-internal-scripts](/planning/tasks/task-work-preflight-permissions-probe-extension-for-skill-internal-scripts/), which surfaced in the same session.