Research Agent Pipeline from Scratch
Generate hypotheses, retrieve papers, run sandboxed experiments, judge results, and publish a cited report.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Defaults and numbers
- Troubleshooting
- Questions and tips
The research loop#
Run it#
| Command | What it does |
|---|---|
python3 code/ | Lessons 50 to 53 demo. Prints JSON |
python main.py | Lesson 05 tree search, budget $30 |
docker run --network= | Sandbox for one experiment |
run_report.py "<question>" --out out/ | Report, trace and evidence JSON |
run_report.py --eval heldout/ | Score the public fixtures |
run_report.py "<question>" --model replay --cassette <file> | Plan from recorded replies |
run_report.py "<question>" --corpus <dir> --compare <old>/ | Write changes.json |
project_test.py research-report-agent --stage <n> --path <dir> | Grade a stage and all earlier ones |
Loop calls in code#
| Task | Code |
|---|---|
| Configure the ramp | GeneratorConfig(n_passes= |
| Draft and rank | queue, |
| Build a retriever | build_client(RetrievalConfig(top_k_lexical= |
| Search papers | result = |
| Run one spec | ExperimentRunner().run(spec) |
| Sweep one knob | AblationRunner(runner).sweep(base, |
| Pick the metric | MetricSpec(name= |
| Judge the runs | evaluator.evaluate(1, |
Spec a sandboxed experiment#
base = ExperimentSpec( spec_id="demo_base", hypothesis_id=1, script_path=script,prints one json line config={"k": 16, "steps": 4, "sleep_s": 0.0}, seed=7,sent as config["__seed"] wall_timeout_s=15.0,hard, killed on exceed memory_cap_mb=256,soft, polled RSS metric_keys=["perplexity", "final_loss"],)runner = ExperimentRunner()base_result = runner.run(base)terminal: ok, timeout, oom, crashbase_result.to_dict()metrics, peak_rss_mb, stderr_tailablate(base, "k", [4, 8])spec_id demo_base_k_4Loop defaults#
| Setting | Default | Note |
|---|---|---|
n_passes | 6 | t_min 0.2 to t_max 1.2 |
novelty_threshold | 0.25 | Lower distance is a duplicate |
BM25 k1, b | 1.5 | b is 0.75 |
top_k_lexical | 10 | Seeds for the graph walk |
max_hops | 2 | Values above 2 are clamped |
wall_timeout_s | 30.0 | memory_cap_mb is 512 |
improvement_threshold | 0.02 | Smaller change is noise |
significance_threshold | 0.05 | Larger p value is noise |
Report agent run#
Scores and formulas#
- Cosine distance
1 - dot(a, b)on unit vectors.- rank_score
- 0.4 novelty + 0.3 specificity + 0.3 testability.
- final_score
- 0.5 normalized BM25 + 0.3 graph + 0.2 recency.
- graph_score
- 1 for a lexical hit, 0.6 at one hop, 0.3 at two hops.
- Node score
- novelty 0.4 + quality 0.5 + budget weight 0.1.
- Improvement
- (baseline - candidate) / abs(baseline) when lower is better.
- Paired t
t_stat = mean / sqrt(variance / n), df is n - 1.- Report score
- 100 x (0.4 precision + 0.3 recall + 0.3 facts).
Report agent defaults#
| Setting | Default | Note |
|---|---|---|
--model | rules | Also replay or live |
Budget max_steps | 50 | BudgetExceeded gives failed |
Budget max_tokens | 20000 | estimate_tokens is len / 4 |
review threshold | 0.6 | Overlap needed to keep a sentence |
citation_precision threshold | 0.8 | Stricter than the critic |
max_facets | 4 | Overview, how, risks, when |
max_sentences | 3 | Per section |
| Reference score | 87.5 | Six public questions, out of 100 |
Failures and fixes#
| Symptom | Cause | Fix |
|---|---|---|
Log shows parse:no hypothesis block found | Reply has no tagged block | Keep temperature at or below 1.2 |
Every pass logs duplicate | Drafts share most nouns | Widen the seed prompt and run again |
terminal is crash with metrics | Non-zero exit code | Fix the script. Partial runs count as failed |
PairingError | Seeds differ between the lists | Rerun both arms on the same seeds |
Verdict noise with a big gain | Fewer than 2 pairs, no p value | Run more seeds |
Node fails loss_diverged | Loss above 4.0 | Drop that branch. Expand others |
| Sentence dropped: names or numbers not in source | Swapped name or changed number | Keep writing extractive |
Plan source is rules-fallback | Bad model JSON or cassette miss | Read the plan note. Rerecord the cassette |
Questions#
- Why ramp the temperature?
- Later drafts wander. Below 0.2 the model parrots the seed.
- Why walk the citation graph?
- It finds papers that use other words than the query.
- Why only two hops?
- Three hops blows up the result and drifts off topic.
- Why a subprocess?
- Untrusted code must not crash the orchestrator.
- Why one knob at a time?
- Full factorial sweeps grow exponentially.
- Why a paired test?
- Each seed gives a difference. Noise floor is their standard error.
- Is needs_review a failure?
- No. It tells a human exactly where to look.