Agent Task Run Runtime
The agent/taskrun package defines a control-plane API for persistent
background task runs that execute agents through runner.Run. It is useful
when an application wants to return from the current user turn while delegated
work continues in another session.
The package separates the stable API from the single-process implementation:
agent/taskrundefinesRun,Status,SpawnRequest,ListFilter, andController.agent/taskrun/inprocessprovides a goroutine-based implementation withMemoryStoreandFileStore.tool/taskrunexposes optional control tools for agent-driven task runs.
inprocess.Service is intended for tests, local runtimes, and product
adapters that run in one process. Distributed deployments should provide their
own taskrun.Controller backed by external storage, queueing, leases, and a
cross-node cancellation mechanism. SpawnRequest is a Go API, not a wire
protocol: fields such as RuntimeState map[string]any are only safe inside
implementations that call runner.Run directly. Distributed controllers
should normalize those values at the product adapter boundary instead of
serializing arbitrary Go objects.
agenttool.NewTool, transfer_to_agent, and taskrun
tRPC-Agent-Go has three common ways to delegate work to another agent. They all let another agent participate, but they do not mean the same thing.
| Mechanism | Use it when | Waits for a result | Has a manageable run id |
|---|---|---|---|
agenttool.NewTool / tool/agent.Tool |
A parent agent needs to call a specialist agent like a normal synchronous tool | Yes | No |
transfer_to_agent |
The current invocation should hand control to a sub-agent that continues the turn | Continues in the same invocation | No |
tool/taskrun |
Work should run in a separate child session and remain queryable, waitable, or cancelable | Sync or async | Yes |
Use the distinction as three separate questions:
- "Do I need a specialist to return a value now?" Use
agenttool.NewTool. - "Should another agent take over this turn?" Use
transfer_to_agent. - "Should this become a tracked task that can be queried, waited for, or
canceled?" Use
taskrun.
Synchronous specialist calls: agenttool.NewTool
agenttool.NewTool wraps an already constructed agent as a normal tool. When
the parent calls that tool, it waits for the child agent to finish. By default,
the tool result aggregates non-empty assistant messages from the child agent.
When configured with agenttool.WithResponseMode(agenttool.ResponseModeFinalOnly),
as shown below, the tool returns only the last complete assistant message. This
is a good fit for small synchronous subtasks such as reviewing a paragraph,
checking a design, or producing a short analysis.
There is no background run and no run id. The model sees a normal tool named
reviewer; after the tool call finishes, the parent continues reasoning in
the current invocation.
Handing off the current turn: transfer_to_agent
When the parent decides that another agent should continue the current turn,
configure a sub-agent and use transfer instead of a task run. When an
LLMAgent has sub-agents, the framework exposes transfer_to_agent.
The model can call:
This is not a background task. It means control for the current invocation
moves to support, and the emitted events still belong to that run.
Manageable background work: taskrun
Use taskrun when the delegated work may take longer, when the parent should
continue without waiting, or when the application needs status, waiting, or
cancellation. Every start_task_run creates a child session and returns a run
id. The parent can later call get_task_run, wait_task_run, or
cancel_task_run.
If the parent must wait for the answer, use sync:
sync only means the tool waits for the child run to reach a terminal status.
It still creates a run id and follows the same taskrun lifecycle. If
wait_timeout_seconds expires, the child run is not canceled; the parent can
still use the returned run id to check it later.
Code-Side Usage
Create one normal runner.Runner, then wrap it with
inprocess.Service. The same runner decides which agent executes the child
run. Pass AgentName when you want to select a named runner agent.
Tool Usage
Use tool/taskrun when the parent agent should decide when to delegate work
to background task runs. The tools use the current invocation session as the
parent session.
If the parent agent needs the taskrun tools, and inprocess.Service also
needs the same runner, create the tools first without a controller and call
SetController after the service is created. This avoids an initialization
cycle where the parent needs the service, the service needs the runner, and
the runner needs the parent.
If your application already has a taskrun.Controller, pass it directly:
The tool names are:
start_task_runlist_task_runsget_task_runcancel_task_runwait_task_run
When taskruntool.WithSessionService is configured, tool/taskrun also
adds:
read_task_run_transcript
start_task_run accepts a mode field:
asyncis the default. The tool starts the child run and returns the run id immediately.syncstarts the child run and waits until it reaches a terminal status before returning.
timeout_seconds limits the child run itself. wait_timeout_seconds only
limits how long the tool waits in sync mode; it does not cancel the child
run when the wait times out.
Nested task runs are rejected by default. Enable them explicitly with
taskruntool.WithNestedSpawns(true) only when the application has its own
fan-out limits.
Guidance for the Parent Agent
After exposing tool/taskrun, describe when the parent agent should use it.
Do not only tell the model that it can start tasks; explain the boundary.
If the parent only needs a specialist to synchronously return analysis, wrap
that specialist with agenttool.NewTool. If the current invocation should be
routed to another agent, configure sub-agents and use transfer_to_agent. Use
taskrun only when you need a run id, status checks, waiting, or cancellation.
Choosing Named Agents
The agent_name field in start_task_run maps to a named agent registered on
the runner. The simplest form is runner.WithAgent, which registers an
already constructed worker.
If the worker must be built per request, for example with tenant-specific
prompts, models, or sandboxes, use runner.WithAgentFactory. taskrun passes
agent_name to the runner, and the runner resolves the target agent when the
child run starts.
Prefer named agents such as fast_worker, deep_reviewer, or
readonly_researcher to represent execution policies. The model chooses a
business role, while the application owns the actual model, tools, and
permission boundary.
Progress and Transcript
Run.Progress is a small status snapshot collected from the child run event
stream. It records event count, tool call count, tool result count, token
counts, and the latest observed event time. The full task transcript is not
copied into Run; it remains in the child session identified by
Run.ChildSessionID.
The in-process controller keeps active progress available through polling APIs. Observers still receive persisted lifecycle updates; the final progress snapshot is persisted with the terminal run state.
This keeps the task control plane small:
list_task_runs,get_task_run, andwait_task_runreturn lightweight status and progress.- The child session stores the detailed transcript.
- Applications remain free to build their own UI, notifications, logs, or artifact storage on top of task run observers and session events.
To let an agent inspect the child transcript, pass the same session service used by the runner:
The read_task_run_transcript tool reads recent events from the child
session. Its limit field is optional, defaults to 40 recent events, and is
capped at 200 events. Access is limited to runs owned by the current user and
created from the current parent session. When the run records an app name, for
example from agent.WithAppName, transcript reads use that app name to locate
the child session.
Runtime State
Every child run receives these runtime-state keys:
taskrun.RuntimeStateKeyRuntaskrun.RuntimeStateKeyRunIDtaskrun.RuntimeStateKeyParentSessionID
Application adapters can merge additional runtime state through
SpawnRequest.RuntimeState, or override the injected key names through
SpawnRequest.RuntimeStateKeys when they expose a product-specific runtime
surface. Set SpawnRequest.AppName when the child run should use a specific
session app namespace. Local adapters that call runner.Run directly can
also pass per-run agent.RunOption values through SpawnRequest.RunOptions;
this is for in-process runner configuration and should not be serialized as a
cross-node contract. SpawnRequest.RunContext can add local context values to
the child runner context for the same in-process use case.