Skip to main content

Context-Isolated & Dynamic Subagents

Delegation is how an agent stays focused on a long task: it hands a self-contained sub-task to a subagent that works in its own fresh conversation and returns only the result. The subagent's intermediate reasoning and tool calls never enter the parent's context window — the parent pays only for the final answer. This is the third piece of the Chronos agent harness, after planning and the virtual filesystem.

Quick start

import (
"github.com/spawn08/chronos/sdk/agent"
"github.com/spawn08/chronos/sdk/harness"
)

parent, _ := agent.New("lead", "Lead").WithModel(provider).Build()

// Derive a subagent service from the built parent (subagents inherit its model
// and draw tools from its registry), register specialists, attach the tool.
svc, _ := harness.NewSubAgentService(parent)
svc.Register(harness.SubAgentSpec{
Name: "researcher",
Description: "Researches a topic and returns a concise finding.", // shown to the model
SystemPrompt: "You are a focused researcher.",
ToolNames: []string{"web_search"}, // a subset of the parent's tools
})
harness.Attach(svc, harness.NewInProcessRunner(svc))

Attach registers a single spawn_subagent tool on the parent (the service already holds the parent's registry). NewSubAgentService is called after Build() because the service is derived from the built agent; a builder method is intentionally not offered (the agent package must not depend on harness). Each registered subagent's Description is surfaced in the tool description so the model knows what each specialist does. The service is a concurrency-safe registry: Register may be called while spawns are in flight.

Context isolation

When the model calls spawn_subagent, Chronos builds a fresh agent with only the subagent's own system prompt and the delegated task — none of the parent's conversation. It runs its own tool-calling loop, and the tool returns just:

{ "agent": "researcher", "result": "…the final finding…" }

So the parent's context grows by one short result, not by the subagent's entire transcript. This is what lets a coordinator drive many sub-tasks without overflowing its window.

Dynamic subagents

A subagent doesn't have to be registered ahead of time. The model can invent one per task by passing a system_prompt (and an optional tools subset) directly in the spawn_subagent call:

{
"task": "Summarize these findings into three bullets.",
"system_prompt": "You are a precise summarizer.",
"tools": ["fs_read"]
}

Granted tool names must exist in the parent's registry; unknown names are rejected. A spawn_subagent call that names a registered agent that doesn't exist fails closed (so a typo surfaces as an error rather than silently running an ad-hoc agent). Nesting is bounded by WithMaxDepth (default 3) so a subagent can't recurse without limit — the depth is carried across the durable queue too, so the bound holds even when delegation runs on another worker.

Durable delegation

InProcessRunner runs the subagent in the calling process. For long or critical sub-tasks, use QueuedRunner instead: it enqueues the subagent as a durable graph run on the work queue, so if the worker executing it dies, the reaper re-leases the run and another worker completes it — the subagent is resumable and can run on any node.

runner := harness.NewQueuedRunner(svc, queue, store, harness.WithTimeout(30*time.Second))
harness.Attach(svc, runner)

WithTimeout bounds how long a spawn waits for a worker; without it the call waits until its context is done, so pass a timeout (or a cancelable context) if no worker may be draining the queue.

Durable delegation requires the subagent to be registered (a remote worker rebuilds it by name), and every worker must run the shared subagent graph:

g, _ := harness.NewSubAgentGraph(svc)
exec := graph.NewQueuedExecutor(store, graph.SingleGraphResolver(g)).Executor()
worker, _ := queue.NewWorker(queue, exec, queue.WorkerConfig{ID: "w1", Lease: 30 * time.Second})
go worker.Run(ctx)

Dynamic (inline) subagents can only use InProcessRunner, since another worker cannot reconstruct a subagent that was described at runtime.

Example

A complete, runnable example (no API keys) is in examples/subagents. It shows a lead agent delegating to a researcher and receiving only the finding.