Research Agents
5 agents in research-engine, each following the three-file pattern (FR-KIA-013):
prepare.py— normalize inputs · fail-fast validation (raisesPrepareError)experiment.py— the work (LLM call or pure logic)reshape.py— output conformance · error mapping
Researcher
- Input:
ResearchTask(priority tier: emergency / priority / batched) - Output: pending
Claim - LLM: GPT-4o primary → Gemini fallback
- Process:
MemoryClient.retrieve(research_question, patient_id)→ context- LLM sanitize → delimit (
<<<CLINICAL_DATA>>>) → de-ID → call - schema-validated response → emit pending claim w/
supports[]
- Fail-soft: retrieval error → PubMed-only fallback +
consumer.retrieval_fallbackaudit
Critic
- Input: pending claim + existing active claims for same entity
- Output:
CriticVerdict(promote · supersede · reject + score + reasoning) - LLM: GPT-4o
- Process:
- Build evidence comparison prompt
- Parse verdict + confidence
- If
supersede: atomically transition oldactive→supersededvia PATCH w/supersedes[]query param
- SLA: 30s
asyncio.wait_for. 3 consecutive timeouts → claim transitions toCRITIC_QUARANTINE.research.critic.quarantineaudit. Operator-drained only.
Correlator
- Input: entity cluster with ≥2 active claims
- Output:
Findingw/ 4-value enum (co_occurrence / temporal_trend / dose_response / shared_citation_cluster) ORNone - LLM: GPT-4o
- Process:
- Build cluster prompt
- LLM returns pattern or "no_pattern"
- Finding persisted to
correlator_findings(180d TTL)
- Audits:
research.correlator.findingorresearch.correlator.no_pattern
Replicator
- Input: active claim aged > 30 days
- Output: verdict (eroded / retracted_source / confirmed)
- LLM: via Researcher (delegates)
- Process:
- Re-run Researcher with same research question
- Compare new evidence vs old
supports[] eroded/retracted_source→ emit Finding + enqueue critic-role re-critique task via TaskQueueManager
- Audits:
research.replicator.finding·research.replicator.skipped
Librarian (LLM-free)
- Input:
memory_claimsbatch (100/tick) - Output:
Finding(broken_provenance / approaching_decay) - LLM: none (pure validator)
- Process:
- For each claim, resolve each
supports[]entry againstresearch_sources - Missing source →
broken_provenance(takes precedence) decay_atwithin 30-day warn window →approaching_decay- Re-check path: previously-flagged claim now healthy →
mark_resolved+research.librarian.resolvedaudit
- For each claim, resolve each
- Why LLM-free: validation is a pure function over structured data; no inference needed; cheap to run at scale
Agent scheduler base
All 4 periodic agents (Correlator + Replicator + Librarian) inherit AgentSchedulerBase (spec 050):
- Lifespan task + interval tick loop
- Cooperative pause / resume via admin routes
- Run-once with run-lock (409 if run in flight)
- 24h findings window accounting
- FindingsRepository — insert-only + guarded
mark_resolved+ 180d TTL
Admin routes under /api/research/v1/{correlator,replicator,librarian}/{status,run-once,cancel-current} — all @Roles('admin')-gated.
Evidence sources (12 active)
| Name | Connector | Auth | Spec |
|---|---|---|---|
| PubMed | pubmed.py | NCBI api_key optional (10 rps w/ key, 3 rps anon) | 048 |
| NEJM | nejm.py | Bearer key · 5 rps | 049 |
| JAMA · JBJS · Clinical Orthopaedics | lww.py (shared) | Bearer · 5 rps | 051 |
| JOR · JBMR-B | wiley.py (shared) | Bearer · 5 rps | 051 |
| Spine Journal · Acta Biomaterialia · J. Arthroplasty | elsevier.py (shared) | Bearer · 5 rps | 051 |
| NCCN | nccn.py | Bearer · guideline endpoint | 051 |
| Cochrane | cochrane.py | Bearer · review type counter | 051 |
| ClinicalTrials.gov | clinicaltrials_gov.py | Optional X-API-Key | 055 |
| ClinVar | clinvar.py | NCBI E-utilities | 055 |
| DrugBank (planned) | drugbank.py | Bearer (commercial license pending) | 055 |
Every connector: 24h cache, fail-soft on 429/5xx/transport (empty hits + WARNING), structured metadata via SourceHit prefix conventions, no PHI flows outbound (queries are scientific topics only).