Subagent isolation
Orchestrator-worker with isolated contexts but shared durable memory — per-subagent runs, linked back for synthesis, nothing lost to summarization.
In this pattern an orchestrator fans work out to subagents that each get their own run_id, so no subagent's evidence pollutes another's context. Each subagent writes its findings to memory as it works and returns only a distilled summary in-band; the orchestrator links the runs and queries across them to synthesize. Use it when subtasks are independent enough to parallelize and each would generate more evidence than you want in any single context window.
How it works
orchestrator run: nimbus:ari:ticket-1130
│ link_run ×3
├────────────► sub run A (logs digger) writes observations
├────────────► sub run B (reproducer) writes observations
└────────────► sub run C (incident history) writes facts
▲ each returns a short summary in-band
recall(include_linked_runs=True) reads across all four runsThe division of labor: isolation by run, durability by memory, synthesis by linking.
- Each subagent works in its own run — a hard boundary, per Runs, sessions, and scopes — and writes findings as
observationandfactentries scoped to that run as it goes. - Only a distilled summary travels back in-band. But unlike plain summarization, nothing is lost: the full evidence sits in the subagent's run, and the orchestrator — or a human debugging the job next week — can recall any subagent's complete findings on demand.
- The orchestrator links each subagent run to its own and queries with
include_linked_runs=True. The response'sconsulted_runsfield lists exactly which runs contributed evidence, so you can verify all three subagents were actually heard from.
Walkthrough
At Nimbus, Ari hits a hard ticket — Acme's exports fail only on Mondays — and spawns three research subagents: one digs logs, one attempts reproduction, one checks incident history.
orchestrator_run = "nimbus:ari:ticket-1130"
sub_runs = {
"logs": "nimbus:ari:ticket-1130:sub-logs",
"repro": "nimbus:ari:ticket-1130:sub-repro",
"history": "nimbus:ari:ticket-1130:sub-history",
}
# Each subagent works in its own run and writes findings as it goes.
# (Inside the logs subagent:)
client.remember(
session_id=sub_runs["logs"],
agent_id="ari-sub-logs",
content="Export worker OOMs correlate with the Monday 06:00 UTC "
"analytics batch job — memory headroom drops to ~5%.",
intent="observation",
)
# ... dozens more observations; only a 3-line summary returns in-band.
# Orchestrator: link each subagent run back.
# link_run is a raw op — call it via the advanced passthrough
# (client.advanced.link_run in Python, client.advanced.linkRun in JS,
# client.advanced().link_run(json!({...})) in Rust); there is no typed helper.
for sub_run in sub_runs.values():
client.advanced.link_run(
run_id=orchestrator_run,
linked_run_id=sub_run,
)
# Synthesis: one query across the orchestrator run + all linked sub runs.
synthesis = client.recall(
session_id=orchestrator_run,
query="Why do Acme's exports fail on Mondays? Combine log evidence, "
"reproduction results, and incident history.",
include_linked_runs=True,
limit=10,
)
# Which runs actually contributed evidence?
print(synthesis["consulted_runs"])
# → ["nimbus:ari:ticket-1130", "...:sub-logs", "...:sub-repro", "...:sub-history"]A subagent whose run never appears in consulted_runs either found nothing relevant or never wrote its findings down — both worth knowing before you trust the synthesis. reflect(session_id=orchestrator_run, include_linked_runs=True) works the same way, extracting lessons from the combined history of all four runs.
The payoff shows up later: two weeks on, when someone asks "what exactly did the reproduction attempt rule out?", the answer isn't gone with the summary — it's a recall() against nimbus:ari:ticket-1130:sub-repro.
The same flow works in TypeScript (client.advanced.linkRun({...}), recall({ include_linked_runs: true })) and Rust (client.advanced().link_run(json!({...})), RecallOptions.include_linked_runs = true) — see the helper catalog.
handoff() and this pattern compose: if a subagent's conclusion needs review before the orchestrator acts on it, have the subagent write a memory-backed handoff instead of just a summary.
When not to split
Isolation has a price: subagents can't see each other's reasoning mid-flight. Decisions that are tightly coupled — where subtask B's approach should change based on what subtask A just found — lose coherence when split across contexts; each subagent optimizes locally and the orchestrator inherits the contradictions. For that shape of work, keep one run and give each concern a lane instead: everyone shares the run's memory, lanes keep the channels tidy, and no evidence has to cross a run boundary to be seen.
| Signal | Split into sub-runs | One run + lanes |
|---|---|---|
| Subtasks independent, parallelizable | Yes | — |
| Each subtask generates heavy evidence | Yes | — |
| Steps must react to each other's findings | — | Yes |
| One coherent decision at the end from interleaved work | — | Yes |
Related
- Runs, sessions, and scopes — run isolation and linking semantics
- Cross-run recall — the same linking machinery for continuity over time
- Lane-scoped memory — the single-run alternative for coupled work
- Retrieval semantics — how evidence from multiple runs is ranked
- Run management —
link_run/unlink_runreference