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.
55 lines
1.6 KiB
Python
55 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Entry router for deep-research.
|
|
|
|
Reads the caller's prompt from state. If it contains a usable research
|
|
topic, stores it as `topic` and falls through to the static `next`
|
|
(plan). If the prompt is empty, routes to `ask_topic` so the user can
|
|
supply one interactively.
|
|
|
|
Routing (`_next`):
|
|
- prompt present -> (no _next; static next: plan)
|
|
- prompt empty -> ask_topic
|
|
|
|
Fail-safe (R3): a crash here (e.g. malformed GRAPH_STATE) means the
|
|
caller's prompt cannot be read at all, so proceeding silently is
|
|
meaningless — the degraded route is `ask_topic`, which asks the user for
|
|
the topic directly, plus a PIPELINE-FAULT entry so the loss is visible in
|
|
the final report's Pipeline notes. Clobbering `pipeline_faults` is safe
|
|
here: this is the start node, so the list can only hold its initial [].
|
|
"""
|
|
import json
|
|
import os
|
|
|
|
|
|
def load_state():
|
|
path = os.environ.get("GRAPH_STATE_FILE")
|
|
if path:
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
|
|
|
|
|
def main():
|
|
state = load_state()
|
|
prompt = (state.get("initial_prompt") or "").strip()
|
|
if prompt:
|
|
print(json.dumps({"topic": prompt}))
|
|
else:
|
|
print(json.dumps({"_next": "ask_topic"}))
|
|
|
|
|
|
try:
|
|
main()
|
|
except Exception as e: # noqa: BLE001 — a crashed entry router must degrade, not die
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"_next": "ask_topic",
|
|
"pipeline_faults": [
|
|
f"PIPELINE-FAULT: request parsing crashed — {e}; asked the "
|
|
"user for the research topic instead"
|
|
],
|
|
}
|
|
)
|
|
)
|