fix(TASK-020): surface step-runner LLM-fault captures where the fallback lands
The three fallback-capture keys (orient_failure, sweep_failure,
handoff_failure) were write-only dead state: captured on LLM-node failure
but interpolated nowhere, so end_failure rendered an empty STEP_FAILED
shell and write_handoff carried no trace a dead sweep.
- new crash-proof script node note_llm_fault distills orient/handoff
'LLM node...' failure text into fault_note (prefix-anchored; empty when
neither key holds a fault); orient and write_handoff fallbacks now route
through it before end_failure
- end_failure renders 'Pipeline fault (if any): {{fault_note}}' (sentinel
and all existing fields preserved; fault_note defaulted in initial_state)
- write_handoff prompt interpolates {{sweep_failure}} with a
PIPELINE-FAULT VISIBILITY instruction mirroring the review_report
treatment (fault -> Deviations entry + step_summary; success/empty
ignored)
- tests: note_llm_fault fault/clean/crash/truncation paths via the
GRAPH_STATE harness; wiring test pins the retargeted fallbacks,
note_llm_fault routing, {{fault_note}} in end_failure, and the
sweep-fault prompt/instruction anchors; warning baseline unchanged
This commit is contained in:
3 files changed
+181
-6
No files matched your search
@@ -89,6 +89,7 @@ initial_state:
|
||||
handoff_attempts: 0
|
||||
handoff_fix: ''
|
||||
handoff_failure: ''
|
||||
fault_note: ''
|
||||
step_summary: ''
|
||||
|
||||
start: resolve_step
|
||||
@@ -196,7 +197,7 @@ nodes:
|
||||
]
|
||||
state_updates:
|
||||
orient_failure: '{{output}}'
|
||||
fallback: end_failure
|
||||
fallback: note_llm_fault
|
||||
next: route_staleness
|
||||
|
||||
route_staleness:
|
||||
@@ -474,6 +475,16 @@ nodes:
|
||||
did not run - <failure text>", repeat it under Notes for next step,
|
||||
and carry it into `step_summary` so the user sees it at the approval
|
||||
gate.
|
||||
|
||||
The same rule covers the "Edge-case sweep result" below: text starting
|
||||
with "LLM node" means the edge-case sweep DID NOT RUN - the graph
|
||||
routed here through the sweep's failure fallback. Never present it as
|
||||
sweep findings. Add a Deviations entry "PIPELINE-FAULT: edge-case
|
||||
sweep did not run - <failure text>" and carry it into `step_summary`
|
||||
so the user sees it at the approval gate. If it holds successful
|
||||
structured output or is empty, ignore it - the sweep's real results
|
||||
already flow through the "Edge cases discovered" / "Downstream plan
|
||||
updates" sections above.
|
||||
prompt: |
|
||||
## Step
|
||||
{{step_number}} ({{step_title}}) - plan at {{step_plan_path}}
|
||||
@@ -497,6 +508,9 @@ nodes:
|
||||
## Downstream plan updates
|
||||
{{downstream_updates}}
|
||||
|
||||
## Edge-case sweep result (fault detection)
|
||||
{{sweep_failure}}
|
||||
|
||||
## Independent review report (if any)
|
||||
{{review_report}}
|
||||
|
||||
@@ -527,9 +541,22 @@ nodes:
|
||||
required: [step_summary]
|
||||
state_updates:
|
||||
handoff_failure: '{{output}}'
|
||||
fallback: end_failure
|
||||
fallback: note_llm_fault
|
||||
next: check_handoff
|
||||
|
||||
note_llm_fault:
|
||||
id: note_llm_fault
|
||||
type: script
|
||||
description: |
|
||||
Make LLM-node fallback captures visible: when orient or write_handoff
|
||||
failed (their capture keys hold the engine's "LLM node..." failure
|
||||
text), distill it into fault_note so end_failure can render WHY the
|
||||
step died instead of an empty STEP_FAILED shell.
|
||||
script: scripts/note_llm_fault.sh
|
||||
timeout: 5
|
||||
fallback: end_failure
|
||||
next: end_failure
|
||||
|
||||
check_handoff:
|
||||
id: check_handoff
|
||||
type: script
|
||||
@@ -627,6 +654,7 @@ nodes:
|
||||
Step: {{step_number}} ({{step_title}})
|
||||
Fix attempts: {{fix_attempts}}/{{max_fix_attempts}}
|
||||
Blocking reason (if resolution failed): {{blocking_reason}}
|
||||
Pipeline fault (if any): {{fault_note}}
|
||||
|
||||
Coder result:
|
||||
{{coder_result}}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
orient_failure=$(echo "$state" | jq -r '.orient_failure // ""')
|
||||
handoff_failure=$(echo "$state" | jq -r '.handoff_failure // ""')
|
||||
|
||||
# Engine failure text is always prefix-anchored "LLM node" (llm.rs); on
|
||||
# success these keys hold the node's structured JSON output, which never
|
||||
# starts with that prefix.
|
||||
if [[ "$orient_failure" == "LLM node"* ]]; then
|
||||
fault_note="PIPELINE-FAULT: orient stage failed — ${orient_failure:0:300}"
|
||||
elif [[ "$handoff_failure" == "LLM node"* ]]; then
|
||||
fault_note="PIPELINE-FAULT: handoff stage failed — ${handoff_failure:0:300}"
|
||||
else
|
||||
fault_note=""
|
||||
fi
|
||||
|
||||
jq -nc --arg note "$fault_note" '{"fault_note": $note}'
|
||||
Reference in new issue
Block a user