A LangGraph multi-agent system that takes a business question through three stages: Problem Intake & Topic Planning → Research & Debate → Synthesis & Action Plan. Designed to be approachable for prompt-tweakers and extensible for those who want to reshape the agent graphs.
The agent code lives in 3_stage_agent/. All paths below
are relative to that folder unless noted.
The README is split into two tiers:
- Tier 1 — runs in Colab via the provided notebook. No local install. You only edit prompt text files.
- Tier 2 — for users running locally / from a terminal, or extending the graph itself. Local setup instructions are in this section.
Who this is for: anyone who wants to change what the agent says or asks without touching the graph wiring.
Setup: none locally. The Colab notebook clones the repo and installs
dependencies in its first cells; API keys go into Colab secrets / the
notebook's setup cell. You only need to edit prompt .txt files in the
Colab file browser.
All eight LLM prompts are plain text files in
3_stage_agent/prompts/. Edit a .txt to
change agent behavior — no Python edit needed.
A friendly map of which prompt drives which stage/node, with "common
tweaks" recipes and warnings, is in
3_stage_agent/PROMPTS.md. Read that first.
| File | Stage / node | What it shapes |
|---|---|---|
prompts/intake_system.txt |
Stage 1 — intake |
Persona / tone of the intake agent |
prompts/intake_parse.txt |
Stage 1 — intake |
Extraction of structured fields from question |
prompts/clarify_instruction.txt |
Stage 1 — clarify_problem |
Problem statement + clarifying questions |
prompts/plan_topics_instruction.txt |
Stage 1 — plan_research_topics |
How research topics are generated |
prompts/research_propose.txt |
Stage 2 — research_and_propose |
Researcher's job: web findings + proposal |
prompts/topic_critic.txt |
Stage 2 — topic_critic |
Critic's behavior + convergence rules |
prompts/synth.txt |
Stage 3 — synthesizer |
8-section recommendation report template |
prompts/action_plan.txt |
Stage 3 — action_plan_90d |
90-day action plan template |
Each stage runs one or more prompts in this order. Loops repeat until a human gate approves or a round limit is hit.
Stage 1 ─ Problem Intake & Topic Planning
intake_system.txt ─┐
├─► intake node (parse the question)
intake_parse.txt ──┘
clarify_instruction.txt ──► clarify_problem (loop, with human gate 1)
plan_topics_instruction.txt ──► plan_research_topics (with human gate 2)
Stage 2 ─ Research & Debate (runs once per research topic)
research_propose.txt ──► research_and_propose ◄┐
│ debate loop
topic_critic.txt ──► topic_critic ─────────┘
└─► human gate 3 (approve / revise)
Stage 3 ─ Synthesis & Action Plan
synth.txt ──► synthesizer (final report)
action_plan.txt ──► action_plan_90d (with human gate 4)
- Open the
.txtfile in any editor (Colab's built-in editor works — double-click the file in the left-side file browser). - Edit the text. Save.
- Re-run the agent. The next import of
common.pypicks up the change.
In Colab, expand the file browser on the left and navigate to
Final_Project_Sp26 / 3_stage_agent / prompts / — every prompt is a
plain .txt file you can double-click to edit:
You can drop these two lines at the end of your clone cell to print the path explicitly:
PROMPTS_DIR = f"{REPO_DIR}/3_stage_agent/prompts"
print(f"[Tier 1] Edit any .txt file in: {PROMPTS_DIR} to tweak prompts")Simple example: to make the intake agent more concise, edit the first
line of prompts/intake_system.txt from "a senior business strategy consultant" to "a brief, no-fluff strategy consultant who avoids long preambles".
- Do not remove placeholders. Some prompts have runtime substitutions:
plan_topics_instruction.txtuses{n}(filled fromstage1_intake.max_research_topicsinconfig.yaml). Removing it will leave the literal string{n}in the prompt.topic_critic.txtuses{{half_max}}(double braces) — this is substituted at runtime frommax_debate_rounds. Keep the double braces.
- Don't change what fields are requested. Stage 1 and Stage 2 use
Pydantic structured outputs (
IntakeOutput,TopicsOutput,TopicProposalOutput,CriticOutput— seecommon.py:71–111). If a prompt asks for a field that isn't in the Pydantic model — or stops asking for one that is — the parse step will fail or return empty values. To add a field, edit both the prompt and the Pydantic model. - Don't break the section structure of
synth.txt. The numbered sections feed directly into the DOCX/PDF report. Renaming sections is fine; reordering or removing them changes the report shape. - Avoid markdown tables in
synth.txt/action_plan.txt. The prompts explicitly forbid them because Word DOCX export renders tables poorly. Keep this constraint if you edit those prompts. - Don't delete a prompt file.
common.pywill fail at import with a clearFileNotFoundErrorpointing to the missing path. Restore it from git if this happens. - Test on one stage at a time. After editing, run only the affected
stage (
./run.sh --stage 1) before re-running the full pipeline. - Keep changes in version control. A bad prompt edit can be hard to
undo by memory —
git diff prompts/is your friend.
Who this is for: users who want to change graph structure, add nodes, swap models per-stage, change state shape, or add new tools — typically running locally rather than in Colab.
Run the setup script — it installs Python dependencies and creates
3_stage_agent/.env from the committed template:
python 3_stage_agent/setup.pyThen open 3_stage_agent/.env and replace the placeholders with your
real keys:
OPENROUTER_API_KEY— for LLM calls (https://openrouter.ai)TAVILY_API_KEY— for Stage 2 web search (https://tavily.com)
common.py auto-loads 3_stage_agent/.env at import time. The .env
file is git-ignored (only 3_stage_agent/env.example.txt is tracked),
so your real keys stay local. Re-running setup.py is safe — it never
overwrites an existing .env.
cd 3_stage_agent
# Full pipeline (interactive — you'll approve / edit at each gate)
./run.sh
# Override the input question without editing config.yaml
./run.sh --question "How should I launch a coffee subscription in Japan?"
# Run a single stage
./run.sh --stage 1
./run.sh --stage 2 --input results/stage1_intake/<dir>
./run.sh --stage 3 --input results/stage2_research/<dir>
# Suffix output folders for easier comparison across runs
./run.sh --name v1run.sh activates venv/ if present and forwards args to
run_cli.py, which chains the three stage
scripts via subprocess. Outputs land in
3_stage_agent/results/stage{1,2,3}_*/{timestamp}/.
If you don't want the bash wrapper, the stages also work standalone:
python stage1_intake.py
python stage2_research.py --input results/stage1_intake/<dir>
python stage3_synthesis.py --input results/stage2_research/<dir>Edit 3_stage_agent/config.yaml:
input_query— default business questionauto_approve— skip human gates (settruefor unattended runs)stage1_intake.model,stage2_research.model_researcher/model_critic,stage3_synthesis.model— pick a model per role. Any OpenRouter model id works (e.g.openai/gpt-5.2,anthropic/claude-opus-4.6,google/gemini-3-flash-preview).- Round limits:
max_clarify_rounds,max_research_topics,max_debate_rounds,max_human_revision_on_proposal,max_human_revision_on_plan.
Each stage is a LangGraph compiled in build_graph():
| Stage | File | build_graph line |
Nodes (line) |
|---|---|---|---|
| 1 | stage1_intake.py |
352 |
intake (75), clarify_problem (133), human_gate_1 (197), plan_research_topics (247), human_gate_2 (305) |
| 2 | stage2_research.py |
485 |
research_and_propose (89), topic_critic (228), human_gate_3 (370) |
| 3 | stage3_synthesis.py |
286 |
synthesizer (76), action_plan_90d (149), human_gate_4 (232) |
Common edits:
- Add a new node — write a
def my_node(state) -> dict:function in the stage file, then register it insidebuild_graph()withg.add_node(...)and connect it viag.add_edge(...)org.add_conditional_edges(...). - Change routing logic — each stage has
route_after_*functions that return the next node name based on state. Stage 1 routing is atstage1_intake.py:243and:342; Stage 2 at:332and:470; Stage 3 at:276. - Reshape state — state schemas extend
MessagesState:Stage1State—stage1_intake.py:50Stage2State—stage2_research.py:55Stage3State—stage3_synthesis.py:46Add fields here when a node needs to pass new data downstream.
- Add or remove a human gate — the gate nodes use
interrupt(prompt_text)fromlanggraph.types. Look athuman_gate_1(stage1_intake.py:197) as the cleanest reference implementation — it shows how to display content, capture human feedback, and feed it back into state.
| What | Where | Line |
|---|---|---|
| LLM factory (OpenRouter routing, retries) | common.py |
make_llm at :58 |
| Web search wrapper (Tavily) | common.py |
web_search at :123 |
| Pydantic output schemas | common.py |
:71–111 (8 models) |
| Config loading | common.py |
:39–50 (reads config.yaml at import time) |
| Per-stage logging / handoff I/O | common.py |
set_output_dir (:200), save_handoff, load_handoff (:340–356) |
| Markdown → DOCX export | report_export.py |
save_all (:85) |
Stages communicate via handoff.json:
- Stage 1 → 2:
results/stage1_intake/<dir>/handoff.jsoncontains the problem framing, constraints, and research topics list. - Stage 2 → 3:
results/stage2_research/<dir>/handoff.jsoncontains the approved per-topic proposals (with findings, citations, critic reviews).
If you change a stage's output shape, update the loader on the next stage
(load_handoff calls in each __main__ block) and the orchestrator key
list in run_stage.py:run_stage1 / run_stage2 if you also use the
notebook flow.
This repo supports both:
- Notebook flow (Colab-friendly, widget UI): driven by
run_stage.py+config_ui.py+stage1_runner.py. Used by theMulti_Agent_Stage1.ipynb/Multi_Agent_Stage2_3.ipynbnotebooks. - Terminal flow (this README's Tier 2): driven by
run_cli.py+run.sh, calling each stage's standalone__main__block.
Both share the same agent code (stage{1,2,3}_*.py, common.py,
config.yaml, prompts). Pick whichever fits your environment.
3_stage_agent/results/
├── stage1_intake/<timestamp>[_<name>]/
│ ├── handoff.json # input for Stage 2
│ ├── summary.md
│ ├── meta.txt
│ └── logs/
├── stage2_research/<timestamp>[_<name>]/
│ ├── handoff.json # input for Stage 3
│ ├── summary.md
│ └── logs/
└── stage3_synthesis/<timestamp>[_<name>]/
├── recommendation.md
├── recommendation.docx
├── action_plan.md
├── action_plan.docx
└── logs/
