360 lines
12 KiB
YAML
360 lines
12 KiB
YAML
name: deep-research
|
|
description: |
|
|
Deep web research workflow. Plans an investigation, decomposes it
|
|
into sub-questions researched in parallel, grounds the work in a
|
|
local knowledge corpus, vets the credibility of cited sources, runs
|
|
a reflexion self-critique loop to revise weak or incomplete findings,
|
|
delegates the final write-up to a focused sub-agent, checks that the
|
|
cited sources are reachable, and gates the result behind human
|
|
approval. A reviewer's free-form feedback at the approval step feeds
|
|
back into another research pass.
|
|
|
|
This is the canonical Coyote graph-agent reference: it exercises every
|
|
node type (script, llm, rag, map, agent, input, approval, end) and
|
|
both static fan-out and dynamic map fan-out.
|
|
|
|
version: '1.0'
|
|
|
|
global_tools:
|
|
- web_search_coyote.sh
|
|
- fetch_url_via_curl.sh
|
|
- search_arxiv.sh
|
|
|
|
mcp_servers:
|
|
- ddg-search
|
|
|
|
conversation_starters:
|
|
- 'How does HTTP/3 differ from HTTP/2?'
|
|
- 'Summarize recent advances in solid-state battery chemistry'
|
|
|
|
settings:
|
|
max_loop_iterations: 40
|
|
log_state_snapshots: false
|
|
validate_before_run: true
|
|
max_concurrency: 4
|
|
|
|
initial_state:
|
|
research_feedback: ''
|
|
research_attempts: 0
|
|
local_context: ''
|
|
local_sources: ''
|
|
# Fault-path defaults. `questions: []` also keeps the
|
|
# knowledge_lookup lane alive for one super-step when `plan` dies inside the
|
|
# static fan-out: research_each_question maps over zero items while the
|
|
# plan_fault → end_fault route renders the failure sentinel.
|
|
pipeline_faults: []
|
|
pipeline_notes: ''
|
|
fault_text: ''
|
|
questions: []
|
|
|
|
start: parse_request
|
|
|
|
nodes:
|
|
parse_request:
|
|
id: parse_request
|
|
type: script
|
|
script: scripts/parse_request.py
|
|
next: bootstrap_research
|
|
|
|
ask_topic:
|
|
id: ask_topic
|
|
type: input
|
|
question: 'What would you like me to research?'
|
|
validation: 'len(input) > 0'
|
|
state_updates:
|
|
topic: '{{input}}'
|
|
next: bootstrap_research
|
|
|
|
bootstrap_research:
|
|
id: bootstrap_research
|
|
type: script
|
|
script: scripts/bootstrap_research.py
|
|
next: [plan, knowledge_lookup]
|
|
|
|
plan:
|
|
id: plan
|
|
type: llm
|
|
instructions: |
|
|
You are a research planner. Given a topic, produce a focused
|
|
research plan and decompose it into 3-5 specific sub-questions
|
|
that can each be researched independently in parallel.
|
|
|
|
The plan is a short narrative naming the key questions and the
|
|
kinds of sources that would be authoritative. The sub-questions
|
|
are precise, self-contained queries (each one is sent on its own
|
|
to a separate research worker, so they must be answerable
|
|
without each other's context).
|
|
prompt: 'Research topic: {{topic}}'
|
|
tools: []
|
|
output_schema:
|
|
type: object
|
|
properties:
|
|
research_plan:
|
|
type: string
|
|
description: A short plan narrative.
|
|
questions:
|
|
type: array
|
|
items: { type: string }
|
|
minItems: 1
|
|
maxItems: 6
|
|
description: 3-5 specific, self-contained sub-questions.
|
|
required: [research_plan, questions]
|
|
# On success plan_failure holds the node's structured JSON output; on
|
|
# failure the engine writes "LLM node ...failed: <chain>" into it BEFORE
|
|
# routing to the fallback (schema properties are only merged on success).
|
|
state_updates:
|
|
plan_failure: '{{output}}'
|
|
fallback: plan_fault
|
|
next: research_each_question
|
|
|
|
plan_fault:
|
|
id: plan_fault
|
|
type: script
|
|
description: Fail-closed marker for a dead planning stage (reached only via plan's fallback) — normalizes the failure text captured in plan_failure into a PIPELINE-FAULT and continues to end_fault; no plan means nothing to research, so the DEEP_RESEARCH FAILED sentinel always renders. The sibling knowledge_lookup lane survives one more super-step by mapping over the empty initial_state `questions` list.
|
|
script: scripts/plan_fault.py
|
|
fallback: end_fault
|
|
next: end_fault
|
|
|
|
knowledge_lookup:
|
|
id: knowledge_lookup
|
|
type: rag
|
|
documents:
|
|
- ./knowledge/
|
|
query: '{{topic}}'
|
|
top_k: 6
|
|
chunk_size: 1000
|
|
chunk_overlap: 100
|
|
state_updates:
|
|
local_context: '{{output.context}}'
|
|
local_sources: '{{output.sources}}'
|
|
next: research_each_question
|
|
|
|
research_each_question:
|
|
id: research_each_question
|
|
type: map
|
|
over: '{{questions}}'
|
|
as: question
|
|
branch: research_one_question
|
|
collect_into: question_findings
|
|
output_key: finding
|
|
max_concurrency: 3
|
|
next: combine_findings
|
|
|
|
research_one_question:
|
|
id: research_one_question
|
|
type: llm
|
|
instructions: |
|
|
You are a web research assistant. Investigate the SINGLE question
|
|
given to you using your tools: search the web, fetch and read
|
|
pages, and search arXiv for academic sources.
|
|
|
|
Rules:
|
|
- Every factual claim must be backed by a real source you
|
|
actually retrieved. Never fabricate URLs, page titles,
|
|
authors, or DOIs.
|
|
- Prefer primary and authoritative sources over aggregators.
|
|
- Where sources disagree, report the disagreement rather than
|
|
papering over it.
|
|
- Put the URL (or DOI) inline next to each claim it supports.
|
|
|
|
Return organized findings in plain text. Do not include
|
|
meta-commentary about the process.
|
|
prompt: |
|
|
Research question: {{question}}
|
|
|
|
Local context that may help:
|
|
{{local_context}}
|
|
|
|
{{research_feedback}}
|
|
tools:
|
|
- web_search_coyote
|
|
- fetch_url_via_curl
|
|
- search_arxiv
|
|
- mcp:ddg-search
|
|
max_iterations: 10
|
|
max_attempts: 2
|
|
state_updates:
|
|
finding: '{{output}}'
|
|
# On failure the engine writes "LLM node ...failed: <chain>" into `finding`
|
|
# via the state_updates above BEFORE routing to the fallback. The marker
|
|
# is branch-local: a map branch's fallback must stay inside the branch.
|
|
fallback: question_fault
|
|
|
|
question_fault:
|
|
id: question_fault
|
|
type: script
|
|
description: Fail-closed marker for a dead research lane (reached only via research_one_question's fallback route inside the map branch). Normalizes the engine's failure text in `finding` into a PIPELINE-FAULT finding the map collects — one dead lane never sinks the whole map. combine_findings later lifts PIPELINE-FAULT findings into pipeline_faults for the report's Pipeline notes.
|
|
script: scripts/question_fault.py
|
|
|
|
combine_findings:
|
|
id: combine_findings
|
|
type: script
|
|
script: scripts/combine_findings.py
|
|
next: vet_sources
|
|
|
|
vet_sources:
|
|
id: vet_sources
|
|
type: llm
|
|
instructions: |
|
|
You assess the credibility of the sources cited in a set of
|
|
research findings. For every distinct source URL in the findings,
|
|
call the `classify_source` tool to get its credibility tier. Then
|
|
summarize: which claims rest on HIGH-credibility sources, and
|
|
which rest on PREPRINT or UNVERIFIED sources and so need
|
|
corroboration. Do NOT do any new research -- assess only what is
|
|
already cited.
|
|
prompt: |
|
|
Findings to assess:
|
|
{{findings}}
|
|
tools:
|
|
- classify_source
|
|
max_iterations: 15
|
|
state_updates:
|
|
source_assessment: '{{output}}'
|
|
fallback: vet_fault
|
|
next: critique
|
|
|
|
vet_fault:
|
|
id: vet_fault
|
|
type: script
|
|
description: Degrade-visibly marker for a dead vetting stage (reached only via vet_sources' fallback) — vetting is a quality lane, not the product. Records a PIPELINE-FAULT, rewrites source_assessment to a neutral "unvetted" note, and continues to critique, the stage vet_sources' own next pointed to.
|
|
script: scripts/vet_fault.py
|
|
fallback: critique
|
|
next: critique
|
|
|
|
critique:
|
|
id: critique
|
|
type: llm
|
|
instructions: |
|
|
You are a meticulous research reviewer. Judge whether the
|
|
findings below are good enough to synthesize a complete,
|
|
well-supported report that answers the research plan.
|
|
|
|
Mark the findings REVISE if ANY of these hold:
|
|
- A research-plan question is unanswered or only weakly
|
|
addressed.
|
|
- A factual claim has no source, or cites a source that looks
|
|
fabricated.
|
|
- The findings lean on a single source where corroboration is
|
|
needed.
|
|
- A key claim rests only on a PREPRINT or UNVERIFIED source,
|
|
per the source credibility assessment below.
|
|
- An obvious counter-perspective or recent development is
|
|
missing.
|
|
Otherwise mark them PASS.
|
|
|
|
Respond in EXACTLY this format, nothing else:
|
|
|
|
VERDICT: <PASS or REVISE>
|
|
FEEDBACK: <if REVISE, be specific and actionable -- name the gaps
|
|
and what kind of source would close them; if PASS, write "none">
|
|
prompt: |
|
|
Research plan:
|
|
{{research_plan}}
|
|
|
|
Findings under review:
|
|
{{findings}}
|
|
|
|
Source credibility assessment:
|
|
{{source_assessment}}
|
|
tools: []
|
|
state_updates:
|
|
critique: '{{output}}'
|
|
fallback: critique_fault
|
|
next: reflexion_gate
|
|
|
|
critique_fault:
|
|
id: critique_fault
|
|
type: script
|
|
description: Degrade-visibly marker for a dead critique stage (reached only via critique's fallback) — critique is a quality lane, not the product. Records a PIPELINE-FAULT and rewrites `critique` to a neutral note with deliberately NO "VERDICT:" line, so reflexion_gate's malformed-critique PASS default proceeds to synthesis; then continues to reflexion_gate, the stage critique's own next pointed to.
|
|
script: scripts/critique_fault.py
|
|
fallback: reflexion_gate
|
|
next: reflexion_gate
|
|
|
|
reflexion_gate:
|
|
id: reflexion_gate
|
|
type: script
|
|
script: scripts/reflexion_gate.py
|
|
next: synthesize
|
|
|
|
synthesize:
|
|
id: synthesize
|
|
type: agent
|
|
agent: report-writer
|
|
prompt: |
|
|
Research topic: {{topic}}
|
|
|
|
Findings (organized by sub-question, with inline citations):
|
|
{{findings}}
|
|
|
|
Source credibility assessment:
|
|
{{source_assessment}}
|
|
|
|
Produce the final report following your instructions.
|
|
timeout: 300
|
|
max_attempts: 2
|
|
state_updates:
|
|
report: '{{output}}'
|
|
# On failure the engine writes "Agent node failed: <chain>" into `report`
|
|
# via the state_updates above BEFORE routing to the fallback.
|
|
fallback: synth_fault
|
|
next: verify_sources
|
|
|
|
synth_fault:
|
|
id: synth_fault
|
|
type: script
|
|
description: Fail-closed marker for a dead synthesis stage (reached only via synthesize's fallback) — no report means no product. Normalizes the "Agent node failed:" text captured in `report` into a PIPELINE-FAULT and continues to end_fault, which renders the DEEP_RESEARCH FAILED sentinel.
|
|
script: scripts/synth_fault.py
|
|
fallback: end_fault
|
|
next: end_fault
|
|
|
|
verify_sources:
|
|
id: verify_sources
|
|
type: script
|
|
script: scripts/verify_sources.py
|
|
next: approve
|
|
|
|
approve:
|
|
id: approve
|
|
type: approval
|
|
question: |
|
|
Research report on: {{topic}}
|
|
|
|
{{report}}
|
|
|
|
----
|
|
{{source_check}}{{pipeline_notes}}
|
|
----
|
|
|
|
Accept this report? Pick "accept" or "reject", or type specific
|
|
feedback to send the research back for another pass.
|
|
options:
|
|
- 'accept'
|
|
- 'reject'
|
|
routes:
|
|
'accept': end_accepted
|
|
'reject': end_rejected
|
|
on_other: incorporate_feedback
|
|
state_updates:
|
|
decision: '{{choice}}'
|
|
|
|
incorporate_feedback:
|
|
id: incorporate_feedback
|
|
type: script
|
|
script: scripts/incorporate_feedback.py
|
|
|
|
end_accepted:
|
|
id: end_accepted
|
|
type: end
|
|
output: '{{report}}{{pipeline_notes}}'
|
|
|
|
end_rejected:
|
|
id: end_rejected
|
|
type: end
|
|
output: "Research on '{{topic}}' was rejected and discarded."
|
|
|
|
end_fault:
|
|
id: end_fault
|
|
type: end
|
|
output: 'DEEP_RESEARCH FAILED — PIPELINE-FAULT: {{fault_text}}'
|