feat(TASK-023): deep-research hardening — fail-safe scripts, fault fallbacks, pipeline notes
R3 guards: parse_request (crash → ask_topic), bootstrap_research,
combine_findings (crash → fault-text findings; also lifts branch-local
PIPELINE-FAULT findings into pipeline_faults, deduped), reflexion_gate
(crash fails forward to synthesize), incorporate_feedback (crash still
loops with a generic feedback note), verify_sources (top-level guard +
pipeline_notes fold).
Fallbacks per the plan's naming pins: plan → plan_fault → end_fault
(fail-closed; gains state_updates plan_failure per the TASK-016 rule);
research_one_question → branch-local question_fault (map collects the
fault finding); vet_sources/critique → degrade-visibly markers that
continue to the stage each node's own next pointed to; synthesize →
synth_fault → end_fault. end_fault renders
'DEEP_RESEARCH FAILED — PIPELINE-FAULT: <fault text>'.
Accepted-path visibility: verify_sources folds pipeline_faults into
pipeline_notes ('' when fault-free), appended to end_accepted's output
and the approval question — happy-path output stays byte-identical.
initial_state seeds questions: [] so a dying plan inside the static
fan-out leaves the sibling knowledge_lookup lane mapping over zero
items while end_fault renders.
R5: 19 python3-guarded regression tests + GraphParser wiring test;
bundled warning baseline unchanged.
This commit is contained in:
13 files changed
+1111
-10
No files matched your search
@@ -6,6 +6,19 @@ request, and writes a `source_check` summary into state so the human
|
||||
reviewer sees broken citations at the approval step.
|
||||
|
||||
Times out per request so a slow source cannot stall the graph.
|
||||
|
||||
Pipeline-notes fold: end-node templates render unconditionally, so the
|
||||
conditional "## Pipeline notes" section of the accepted report is built
|
||||
HERE — the last stop before the approval gate on every accepted path,
|
||||
including reflexion and feedback re-loops. `pipeline_notes` is the
|
||||
rendered section when `pipeline_faults` is non-empty and the empty
|
||||
string otherwise, keeping the happy-path `{{report}}{{pipeline_notes}}`
|
||||
output byte-identical to the pre-hardening `{{report}}`.
|
||||
|
||||
Fail-safe (R3): per-URL probes were already guarded; the top level now
|
||||
is too. On a crash the summary says sources were NOT checked, a
|
||||
PIPELINE-FAULT is recorded (best-effort preserving existing faults), and
|
||||
pipeline_notes still renders — the approval gate remains the backstop.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -25,6 +38,21 @@ def load_state():
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def safe_faults():
|
||||
try:
|
||||
state = load_state()
|
||||
return [f for f in (state.get("pipeline_faults") or []) if isinstance(f, str)]
|
||||
except Exception: # noqa: BLE001 — best-effort preservation only
|
||||
return []
|
||||
|
||||
|
||||
def render_notes(faults):
|
||||
entries = [f for f in (faults or []) if isinstance(f, str) and f.strip()]
|
||||
if not entries:
|
||||
return ""
|
||||
return "\n\n## Pipeline notes\n\n" + "\n".join(f"- {e}" for e in entries)
|
||||
|
||||
|
||||
def reachable(url, timeout=5.0):
|
||||
req = urllib.request.Request(url, method="HEAD")
|
||||
try:
|
||||
@@ -62,8 +90,27 @@ def main():
|
||||
+ "\n".join(results)
|
||||
)
|
||||
|
||||
print(json.dumps({"source_check": summary}))
|
||||
notes = render_notes(state.get("pipeline_faults"))
|
||||
print(json.dumps({"source_check": summary, "pipeline_notes": notes}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — a crashed source check must degrade, not die
|
||||
faults = safe_faults()
|
||||
faults.append(
|
||||
f"PIPELINE-FAULT: source verification crashed — {e}; cited sources "
|
||||
"were not checked for reachability"
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source_check": (
|
||||
"Source verification crashed (pipeline fault) — cited "
|
||||
"sources were NOT checked for reachability."
|
||||
),
|
||||
"pipeline_notes": render_notes(faults),
|
||||
"pipeline_faults": faults,
|
||||
}
|
||||
)
|
||||
)
|
||||
Reference in new issue
Block a user