Persistent Sub-Agents Example
A self-contained demo of persistent sub-agents (companion tools) on the JS runtime. It runs completely offline — no API key, no Docker, no wrangler — by driving the agents with a RoutingMockLLMAdapter, so npm test is a real, runnable correctness check of the feature.
Source:
examples/persistent-subagents
The shape
A coordinator parent declares a persistent child. Declaring persistentAgents is the only wiring needed — the framework auto-injects the companion__* tools onto the parent.
import { defineAgent } from '@helix-agents/core';
import { ResearcherAgent } from './researcher';
export const CoordinatorAgent = defineAgent({
name: 'coordinator',
// Declaring persistentAgents auto-injects companion__spawnAgent / sendMessage /
// listChildren / getChildStatus / terminateChild (+ waitForResult, because at
// least one child is `mode: 'blocking'`).
persistentAgents: [{ agent: ResearcherAgent, mode: 'blocking' }],
outputSchema: CoordinatorOutputSchema,
systemPrompt:
'Use companion__spawnAgent({ agent: "researcher", name, initialMessage }) to ' +
'delegate a topic. Because the researcher is BLOCKING, the spawn returns its ' +
'findings inline. When done, call __finish__ with { topic, summary }.',
llmConfig: { model: openai('gpt-4o-mini') }, // never called in the demo
maxSteps: 12,
});Driving it offline
The demo and tests inject a RoutingMockLLMAdapter (routes scripted responses by agentType, so the concurrent parent and child never race on a shared queue):
const llm = new RoutingMockLLMAdapter();
llm.setResponses('coordinator', [
spawnStep({ agent: 'researcher', name: 'ai-safety', initialMessage: 'Research AI safety' }),
finishStep({ topic: 'AI safety', summary: 'Researcher gathered findings.' }),
]);
llm.setResponses('researcher', [
callStep('gather_facts', { topic: 'AI safety' }),
finishStep({ findings: 'AI safety is an active area of research...' }),
]);
const executor = new JSAgentExecutor(stateStore, streamManager, llm);
const handle = await executor.execute(CoordinatorAgent, 'Coordinate research on AI safety', {
sessionId,
});
const result = await handle.result();What you can then assert (all deterministic — see the test file):
- A persistent
SubSessionRefexists with the deterministic id${sessionId}-agent-ai-safety,status: 'completed'. - The child session persisted its structured
output({ findings }). - A blocking spawn delivered the child's findings inline into the parent's conversation (the findings text appears in the parent's messages).
What the example covers
| Test | Demonstrates |
|---|---|
| Blocking spawn → inline result | companion__spawnAgent (blocking) returns the child's output inline. |
| Two named children + list | Multiple persistent children + companion__listChildren. |
| Terminate-truth | Terminating an already-completed child does not clobber its result. |
| Non-blocking spawn contrast | A non-blocking spawn returns { status: 'spawned' } immediately, with NO inline output (vs the blocking case). |
| Critic-loop re-consult | A maker re-consults the same critic (same name) across rounds. Re-consulting a completed child continues on its preserved session (same subSessionId, both rounds' consults retained, fresh round-2 verdict: 'pass') — proven for blocking spawnAgent and for sendMessage → waitForResult. |
| Structured-output continuation | A completed structured-output agent is re-execute()'d and continued (fresh typed output, round-1 turns retained) with no dangling-__finish__ failure — under a strict mock adapter, so the __finish__ heal has teeth. |
| Config sanity | Companion tools are auto-injected only when persistentAgents is declared. |
Run it
# From the repo root, build the workspace once:
npm run build
cd examples/persistent-subagents
npm test # offline mock-LLM tests (no API key)
npm run demo # prints the coordinator → researcher flowRunning on other runtimes
The same persistentAgents config works unchanged on Temporal, Cloudflare (Workflows / Durable Objects), and DBOS — only the executor and stores differ. See the runtime support matrix for per-runtime notes (e.g. workspaces on children are JS / Cloudflare-DO only; DBOS blocking-spawn has a known caveat).
Next steps
- Sub-Agents guide — the full feature reference
- Sub-Agent Execution internals — per-runtime mechanics