Thomas Piacentini
← Atelier

Fiche

Research Agent Pipeline from Scratch

Texte original · anglais

Generate hypotheses, retrieve papers, run sandboxed experiments, judge results, and publish a cited report.

The research loop

queue headnot settledExperimentResultverdictbest branchdraftHypothesisGeneratorlesson 50, ranked queuemodelRetrievalClientlesson 51, BM25 +citation graphstoreExperimentRunnerlesson 52, subprocesslimitsprocessEvaluatorlesson 53, paired t testprocessSeed prompttemperature rampclientTree searchnovelty, quality, budgetprocessLaTeX writervision critiquemodelReviewer ensemble5 judgesmodel
Nodes and connections
NodeRoleKind
HypothesisGeneratorlesson 50, ranked queuemodel
RetrievalClientlesson 51, BM25 + citation graphstore
ExperimentRunnerlesson 52, subprocess limitsprocess
Evaluatorlesson 53, paired t testprocess
Seed prompttemperature rampclient
Tree searchnovelty, quality, budgetprocess
LaTeX writervision critiquemodel
Reviewer ensemble5 judgesmodel
FromToConnection
Seed promptHypothesisGenerator
HypothesisGeneratorRetrievalClientqueue head
RetrievalClientExperimentRunnernot settled
ExperimentRunnerEvaluatorExperimentResult
EvaluatorHypothesisGeneratorverdict
Tree searchLaTeX writerbest branch
LaTeX writerReviewer ensembledraft
Sources

Run it

CommandWhat it does
python3 code/main.pyLessons 50 to 53 demo. Prints JSON
python main.pyLesson 05 tree search, budget $30
docker run --network=none --memory=8g --cpus=2 --pids-limit=256 --read-onlySandbox for one experiment
run_report.py "<question>" --out out/ --code <dir>Report, trace and evidence JSON
run_report.py --eval heldout/questions.jsonScore the public fixtures
run_report.py "<question>" --model replay --cassette <file>Plan from recorded replies
run_report.py "<question>" --corpus <dir> --compare <old>/report.jsonWrite changes.json
project_test.py research-report-agent --stage <n> --path <dir>Grade a stage and all earlier ones
Sources

Loop calls in code

TaskCode
Configure the rampGeneratorConfig(n_passes=4, t_min=0.2, t_max=1.1)
Draft and rankqueue, logs = generator.run(seed_prompt)
Build a retrieverbuild_client(RetrievalConfig(top_k_lexical=5, max_hops=2))
Search papersresult = client.search(query)
Run one specExperimentRunner().run(spec)
Sweep one knobAblationRunner(runner).sweep(base, "k", [4, 8, 16, 32])
Pick the metricMetricSpec(name="perplexity", direction=LOWER, scale=LOG)
Judge the runsevaluator.evaluate(1, metric_spec, candidates, baselines)
Sources

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, crash
base_result.to_dict()metrics, peak_rss_mb, stderr_tail
ablate(base, "k", [4, 8])spec_id demo_base_k_4
Sources

Report agent run

  1. index BM25Index(documents) ranks the markdown corpus in Rust.
  2. plan plan_research splits the question into up to 4 facets.
  3. gather gather_snippets keeps 3 snippets per facet with offsets.
  4. write write_report composes sentences with [S1] markers.
  5. verify review scores each sentence. apply_verdicts drops the rest.
  6. state decide_state gives completed, needs_review or failed.
  7. publish write_outputs writes report.html, report.json and trace.json.
Sources

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).
Sources

Loop defaults

SettingDefaultNote
n_passes6t_min 0.2 to t_max 1.2
novelty_threshold0.25Lower distance is a duplicate
BM25 k1, b1.5b is 0.75
top_k_lexical10Seeds for the graph walk
max_hops2Values above 2 are clamped
wall_timeout_s30.0memory_cap_mb is 512
improvement_threshold0.02Smaller change is noise
significance_threshold0.05Larger p value is noise
Sources

Report agent defaults

SettingDefaultNote
--modelrulesAlso replay or live
Budget max_steps50BudgetExceeded gives failed
Budget max_tokens20000estimate_tokens is len / 4
review threshold0.6Overlap needed to keep a sentence
citation_precision threshold0.8Stricter than the critic
max_facets4Overview, how, risks, when
max_sentences3Per section
Reference score87.5Six public questions, out of 100
Sources

Failures and fixes

SymptomCauseFix
Log shows parse:no hypothesis block foundReply has no tagged blockKeep temperature at or below 1.2
Every pass logs duplicateDrafts share most nounsWiden the seed prompt and run again
terminal is crash with metricsNon-zero exit codeFix the script. Partial runs count as failed
PairingErrorSeeds differ between the listsRerun both arms on the same seeds
Verdict noise with a big gainFewer than 2 pairs, no p valueRun more seeds
Node fails loss_divergedLoss above 4.0Drop that branch. Expand others
Sentence dropped: names or numbers not in sourceSwapped name or changed numberKeep writing extractive
Plan source is rules-fallbackBad model JSON or cassette missRead the plan note. Rerecord the cassette
Sources

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.
Sources
Original source
---
id: research-agent-pipeline
title: Research Agent Pipeline from Scratch
code: RP
subtitle: Generate hypotheses, retrieve papers, run sandboxed experiments, judge results, and publish a cited report.
hue: 165
sources: phases/19-capstone-projects/05-autonomous-research-agent, phases/19-capstone-projects/50-hypothesis-generator, phases/19-capstone-projects/51-literature-retrieval, phases/19-capstone-projects/52-experiment-runner, phases/19-capstone-projects/53-result-evaluator, projects/research-report-agent
source_hash: e3bed71e0634229b
---

## The research loop
type: architecture
from: phases/19-capstone-projects/50-hypothesis-generator/docs/en.md, phases/19-capstone-projects/51-literature-retrieval/docs/en.md, phases/19-capstone-projects/52-experiment-runner/docs/en.md, phases/19-capstone-projects/53-result-evaluator/docs/en.md, phases/19-capstone-projects/05-autonomous-research-agent/docs/en.md

```arch
node | gen | 1,1 | HypothesisGenerator | lesson 50, ranked queue | model
node | ret | 2,1 | RetrievalClient | lesson 51, BM25 + citation graph | store
node | run | 3,1 | ExperimentRunner | lesson 52, subprocess limits | process
node | eval | 4,1 | Evaluator | lesson 53, paired t test | process
node | seed | 1,2 | Seed prompt | temperature ramp | client
node | tree | 2,2 | Tree search | novelty, quality, budget | process
node | write | 3,2 | LaTeX writer | vision critique | model
node | rev | 4,2 | Reviewer ensemble | 5 judges | model
edge | seed | gen |
edge | gen | ret | queue head
edge | ret | run | not settled
edge | run | eval | ExperimentResult
edge | eval | gen | verdict
edge | tree | write | best branch
edge | write | rev | draft
```

## Run it
type: command-table
span: 2
from: phases/19-capstone-projects/50-hypothesis-generator/code/main.py, phases/19-capstone-projects/51-literature-retrieval/code/main.py, phases/19-capstone-projects/52-experiment-runner/code/main.py, phases/19-capstone-projects/53-result-evaluator/code/main.py, phases/19-capstone-projects/05-autonomous-research-agent/code/main.py, phases/19-capstone-projects/05-autonomous-research-agent/docs/en.md, projects/research-report-agent/README.md, projects/research-report-agent/solution/run_report.py

| Command | What it does |
|---|---|
| `python3 code/main.py` | Lessons 50 to 53 demo. Prints JSON |
| `python main.py` | Lesson 05 tree search, budget $30 |
| `docker run --network=none --memory=8g --cpus=2 --pids-limit=256 --read-only` | Sandbox for one experiment |
| `run_report.py "<question>" --out out/ --code <dir>` | Report, trace and evidence JSON |
| `run_report.py --eval heldout/questions.json` | Score the public fixtures |
| `run_report.py "<question>" --model replay --cassette <file>` | Plan from recorded replies |
| `run_report.py "<question>" --corpus <dir> --compare <old>/report.json` | Write `changes.json` |
| `project_test.py research-report-agent --stage <n> --path <dir>` | Grade a stage and all earlier ones |

## Loop calls in code
type: task-table
span: 2
from: phases/19-capstone-projects/50-hypothesis-generator/code/main.py, phases/19-capstone-projects/51-literature-retrieval/code/main.py, phases/19-capstone-projects/52-experiment-runner/code/main.py, phases/19-capstone-projects/53-result-evaluator/code/main.py

| Task | Code |
|---|---|
| Configure the ramp | `GeneratorConfig(n_passes=4, t_min=0.2, t_max=1.1)` |
| Draft and rank | `queue, logs = generator.run(seed_prompt)` |
| Build a retriever | `build_client(RetrievalConfig(top_k_lexical=5, max_hops=2))` |
| Search papers | `result = client.search(query)` |
| Run one spec | `ExperimentRunner().run(spec)` |
| Sweep one knob | `AblationRunner(runner).sweep(base, "k", [4, 8, 16, 32])` |
| Pick the metric | `MetricSpec(name="perplexity", direction=LOWER, scale=LOG)` |
| Judge the runs | `evaluator.evaluate(1, metric_spec, candidates, baselines)` |

## Spec a sandboxed experiment
type: annotated-code
lang: python
from: phases/19-capstone-projects/52-experiment-runner/code/main.py, phases/19-capstone-projects/52-experiment-runner/docs/en.md

```python
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, crash
base_result.to_dict()                         # metrics, peak_rss_mb, stderr_tail
ablate(base, "k", [4, 8])                     # spec_id demo_base_k_4
```

## Report agent run
type: flow
from: projects/research-report-agent/solution/report_agent/pipeline.py, projects/research-report-agent/solution/report_agent/critic.py, projects/research-report-agent/solution/report_agent/writer.py, projects/research-report-agent/solution/report_agent/planner.py, projects/research-report-agent/README.md

1. **index**: `BM25Index(documents)` ranks the markdown corpus in Rust.
2. **plan**: `plan_research` splits the question into up to 4 facets.
3. **gather**: `gather_snippets` keeps 3 snippets per facet with offsets.
4. **write**: `write_report` composes sentences with `[S1]` markers.
5. **verify**: `review` scores each sentence. `apply_verdicts` drops the rest.
6. **state**: `decide_state` gives completed, needs_review or failed.
7. **publish**: `write_outputs` writes report.html, report.json and trace.json.

## Scores and formulas
type: concepts
from: phases/19-capstone-projects/50-hypothesis-generator/docs/en.md, phases/19-capstone-projects/51-literature-retrieval/docs/en.md, phases/19-capstone-projects/53-result-evaluator/docs/en.md, phases/19-capstone-projects/05-autonomous-research-agent/code/main.py, projects/research-report-agent/solution/report_agent/evaluate.py

- **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).

## Loop defaults
type: defaults
from: phases/19-capstone-projects/50-hypothesis-generator/code/main.py, phases/19-capstone-projects/51-literature-retrieval/code/main.py, phases/19-capstone-projects/52-experiment-runner/code/main.py, phases/19-capstone-projects/53-result-evaluator/code/main.py

| 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 defaults
type: defaults
from: projects/research-report-agent/solution/report_agent/critic.py, projects/research-report-agent/solution/report_agent/evaluate.py, projects/research-report-agent/solution/report_agent/writer.py, projects/research-report-agent/solution/report_agent/planner.py, projects/research-report-agent/solution/run_report.py, projects/research-report-agent/README.md

| 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
type: troubleshooting
from: phases/19-capstone-projects/50-hypothesis-generator/code/main.py, phases/19-capstone-projects/50-hypothesis-generator/docs/en.md, phases/19-capstone-projects/52-experiment-runner/docs/en.md, phases/19-capstone-projects/53-result-evaluator/docs/en.md, phases/19-capstone-projects/53-result-evaluator/code/main.py, phases/19-capstone-projects/05-autonomous-research-agent/code/main.py, projects/research-report-agent/solution/report_agent/critic.py, projects/research-report-agent/solution/report_agent/planner.py, projects/research-report-agent/README.md

| 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
type: qa
from: phases/19-capstone-projects/50-hypothesis-generator/docs/en.md, phases/19-capstone-projects/51-literature-retrieval/docs/en.md, phases/19-capstone-projects/52-experiment-runner/docs/en.md, phases/19-capstone-projects/53-result-evaluator/docs/en.md, projects/research-report-agent/stages/05-verify-every-claim/docs/en.md

- **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.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓