Skip to content

Example: Research Assistant

A lab asks for a research assistant that surveys the literature on a question about protein folding and proposes experiments. Its outputs are documents for the lab’s scientists. It has no lab access, runs no experiments and sends nothing outside the system except search queries.

Unlike the coding agent, this system needs a model to coordinate: deciding which sub-questions matter is a judgment call. The design question is how to let a capable model plan without giving the plan leverage nobody checks.

flowchart TB
    H[Scientist] -->|question| PL[Planner<br/>tier 5]
    PL -->|structured plan| PV[Plan check<br/>code + trusted monitor]
    PV -->|approved by scientist| O[Orchestrator<br/>code, tier 2]
    O --> S[Search<br/>code]
    O --> SU[Summarizer<br/>model, no tools]
    SU --> G[Grounding check<br/>code]
    G --> HY[Hypothesis generator<br/>tier 5]
    HY --> CR[Critic<br/>other provider]
    CR --> R[Report to scientist]
ComponentImplementationCan reachPattern
PlannerUntrusted frontier model (tier 5)The question and a list of components; nothing elseTask Decomposition Pipeline
Plan checkSchema and budget validation in code; a trusted monitor (tier 4) screens for anomaliesThe planTrusted Monitor
OrchestratorTested code: routing, budgets, rate limits, circuit breakerComponent invocation onlyExposure-Envelope Gateway
SearchCode: API calls, ranking, deduplicationThe search API
SummarizerA model with no tools, no network and no access to the lab’s notesOne paper at a timeNeed-to-Know Compartments
Grounding checkCode: every quoted span must appear in the sourceSummary and sourceConsistency Triangulation
Hypothesis generatorUntrusted frontier modelChecked summaries and the lab’s notes; never raw papers
CriticA model from a different provider, prompted to attack each hypothesisHypotheses and summariesAdversarial Collaboration

Every call, input and output is logged by the orchestrator (Flight Recorder). No component keeps memory between projects; the only carried state is a bibliography the scientists curate (Selective Amnesia).

Making a weaker model the planner because it is “less risky” inverts how AI Control uses weak models (Greenblatt et al. 2023). A trusted model monitors the untrusted model’s work; it does not make the most consequential decisions in its place. A weak planner would make more mistakes, and the coordinator’s leverage would spread them.

Here the planner is capable, and its leverage is removed instead:

  • Its output is a structured plan (sub-questions, queries, a budget per step), not free text and not tool calls.
  • Code checks the plan against a schema and the project budget. The trusted monitor flags anything unusual, such as a sub-question unrelated to the topic or a step that asks for the lab’s notes.
  • A scientist approves the research direction before anything runs. Changing direction mid-project requires approval again (Escalation Ladder).
  • The planner never sees intermediate results, so it cannot steer later steps by what earlier ones found.

None of this makes scheming impossible. Frontier models have schemed within a single episode when given a goal and an opportunity (Meinke et al. 2024). What the decomposition does is limit what any one context knows and can reach, and keep each step small enough to check. See Coordinator Constraints.

Harm modeChannelMain mitigation
Summary misstates a paper’s findingsAccidentGrounding check on quotes; critic; scientist reads the sources for anything the report relies on
Report cites a paper or quote that does not existAccident, or defection (reward hacking)Search results are the only admissible sources; the grounding check rejects quotes not found verbatim
Planner or generator steers the report toward a conclusionDefectionCritic from another provider; scientist approval of direction; monitor on the plan
A paper contains instructions that make the summarizer leak the lab’s notes or slant its summaryHijackThe summarizer cannot see the notes and cannot send anything out; the generator, which sees the notes, never reads raw papers
A hypothesis has dual-use implicationsAccidentA narrow classifier (tier 3) with a measured false-negative rate flags candidates for human review

The hijack row drives the layout. The only outbound channel is search queries, and they are fixed in the approved plan before any paper is read. Summaries still carry text from the papers, quoted spans included, so the generator is not insulated from injected instructions. What bounds the row is that no component that has read untrusted text can send data out. The layout combines two published defenses against the hijack channel: plan-then-execute (actions fixed before untrusted text is read) and a dual-LLM split (the model that reads papers has no tools). Their authors note that both leave injected text free to shape the content that flows through (Beurer-Kellner et al. 2025), which here means slanted summaries and hypotheses. The critic and the scientist’s reading of sources are the only checks on that.

The grounding check is tier 2: deterministic, and testable on a fixed set.

def quotes_grounded(summary: dict, source_text: str) -> bool:
"""Every claim must carry a quote that appears verbatim in the source."""
def norm(s: str) -> str:
return " ".join(s.split()).lower()
source = norm(source_text)
return all(
claim.get("quote") and norm(claim["quote"]) in source
for claim in summary["claims"]
)

It proves that quotes exist, not that a claim follows from its quote. That second judgment is left to the critic and the scientist.

The circuit breaker trips after repeated failures and stays open until a person resets it.

class CircuitBreaker:
def __init__(self, threshold: int = 5):
self.threshold = threshold
self.consecutive_failures = 0
self.open = False
def allow(self) -> bool:
return not self.open
def record(self, ok: bool, reason: str = "") -> None:
if ok:
self.consecutive_failures = 0
return
self.consecutive_failures += 1
if self.consecutive_failures >= self.threshold and not self.open:
self.open = True
alert_humans(f"circuit breaker tripped: {reason}")
def reset(self, operator_id: str) -> None:
log_event("breaker_reset", operator_id)
self.open = False
self.consecutive_failures = 0

When it is open, the orchestrator stops invoking components and returns the work done so far, marked incomplete (Graceful Degradation).

Composing Patterns sets this design’s Least-X choices beside the coding agent’s.