Complete documentation for developers and AI agents
research-agent [query] [--flags]
| Flag | Type | Default | Description |
|---|---|---|---|
query | string | — | Research query (positional, optional) |
--eval | flag | false | Run evaluation suite |
--eval-runs | int | 2 | Runs per eval task |
--gepa | flag | false | Run GEPA self-improvement loop |
--gepa-generations | int | 3 | Number of GEPA generations |
--gepa-pool | int | 4 | Candidates per generation |
--dogfood | flag | false | Full pipeline: eval + GEPA + self-patch |
--searx-instance | string | — | SearXNG instance URL (e.g. http://localhost:8888) |
--self-update | flag | false | Update research-agent.py from GitHub latest release |
--state | string | research_state.json | Path to state file |
--force | flag | false | Re-run even if completed |
--clear | flag | false | Clear saved state and exit |
--report | flag | false | Print last report and exit |
# Basic research
research-agent "Latest developments in RAG"
# Run eval suite (3 tasks × 4 checks each)
research-agent --eval --eval-runs 1
# Self-improvement loop (5 generations, 6 candidates each)
research-agent --gepa --gepa-generations 5 --gepa-pool 6
# Full dogfood pipeline
research-agent --dogfood
# Use custom SearXNG instance
research-agent --searx-instance http://searxng.local:8888 "latest AI papers"
# Update to latest version
research-agent --self-update
# View last report
research-agent --report
Import and use research-agent as a Python module:
from research_agent import ResearchAgent
import asyncio
agent = ResearchAgent("Impact of AI on healthcare?")
state = asyncio.run(agent.run())
print(state.report[:500])
print(f"Status: {state.status}")
from research_agent import EvaluationRunner
runner = EvaluationRunner(n_runs=2)
results = asyncio.run(runner.run_all())
for task in results["tasks"]:
errors = sum(r["errors"] for r in task["runs"])
print(f"{task['title']}: {'PASS' if errors == 0 else 'FAIL'}")
from research_agent import GEPALoop, EvaluationRunner
gepa = GEPALoop(pool_size=4, max_generations=3)
eval_runner = EvaluationRunner(n_runs=1)
results = asyncio.run(gepa.run_self_improvement(eval_runner))
print(gepa.format_report(results))
The evaluation suite tests research quality across 3 tasks with 4 deterministic checks each:
| ID | Task | Checks |
|---|---|---|
| eval-001 | Ambiguous Health Claim — coffee & heart health | Executive summary, findings with sources, bibliography, contradictions |
| eval-002 | Emerging Technology Impact — quantum computing & cryptography | Executive summary, findings with sources, bibliography, contradictions |
| eval-003 | Historical Revisionism — fall of the Roman Empire | Executive summary, findings with sources, bibliography, contradictions |
Each check is a keyword-based heuristic scan of the generated report. All checks are deterministic and complete in ~ms. Adding new eval tasks is straightforward:
EVAL_TASKS.append({
"title": "Your Task",
"query": "Your research question",
"checks": [
lambda r: ("keyword" in r.lower()),
lambda r: (r.count("###") >= 3),
],
})
| Grader | Method | Dependencies |
|---|---|---|
| HeuristicGrader | Keyword-based report quality analysis | None (built-in) |
| OllamaGrader | Local LLM inference via Ollama API | Ollama installed + model pulled |
The heuristic grader checks 4 dimensions: synthesis quality (conclusion language), thoroughness (analysis terms), balance (contrastive language), and specificity (citations, numbers). Each dimension is scored 0-0.25 for a total score of 0.0-1.0.
After a GEPA run, the best candidate's prompts are written to _self_patch.py:
research-agent --gepa --gepa-generations 3
python _self_patch.py
# Patched to v2.X.X with N prompt updates
The patch script modifies research_agent.py in-place using re.sub for surgical replacements. It updates SCRIPT_VERSION and modifies the BASE_PROMPTS dictionary.
@dataclass
class PromptCandidate:
id: str # e.g. "gen0-3"
parent_id: str | None # e.g. "gen0-0"
prompt_template: dict[str, str] # one prompt per module
accuracy: float = 0.0
speed_ms: float = 0.0
token_efficiency: float = 0.0
generation: int = 0
pareto_front: bool = False
Reflections produce structured lessons that drive targeted mutations:
@dataclass
class Lesson:
module: str # Which module needs improvement
failure: str # Which check failed (with task ID)
suggestion: str # What to change in the prompt
| Version | Changes |
|---|---|
| 2.0.0 | Full GEPA loop, Ollama grading, self-patching, Rich CLI |
| 2.0.1 | SearXNG support, --self-update, bug fixes, CI/CD, Docs site |