sessions_spawn.
How it works
The subagent context lifecycle is a pipeline that begins when the parent agent callssessions_spawn. The call returns a run ID immediately; the lifecycle ends
when the terminal result is durably recorded and its immutable-origin delivery
reaches an accepted or explicitly recorded failure state. A parent can also
await owned children with subagents action wait without polling.
Each stage has its own configuration options and graceful fallback behavior.
If execution or post-processing fails, the run retains a typed terminal reason
and a bounded summary. Delivery uses the same durable operation identity for
retries and uncertainty handling, so an acknowledged result is not followed by
a contradictory timeout or failure notice.
Worked example: parent spawns a research subagent
Say a primary agent named Atlas is asked to compile a brief on quantum computing. Atlas decides it needs background research before writing, so it spawns a sub-agent. The parent invokes thesessions_spawn tool:
subagents wait result, carries a condensed result like
this:
Spawn packets
A spawn packet is the structured context bundle passed to a sub-agent. Whensessions_spawn is called,
Comis assembles a packet containing everything the sub-agent needs to understand
its task, constraints, and environment.
The spawn packet is injected into the sub-agent’s context as structured
sections — domain knowledge, artifact references, and the objective each get
their own clearly labeled section so the sub-agent can distinguish between its
task, its reference material, and its constraints.
When a sub-agent is spawned as part of an execution graph, the spawn packet
also includes the path to a shared pipeline folder. This directory is created
per-graph and gives all nodes read-write access for exchanging files and
artifacts. See Pipelines: Shared Data Folder
for the full lifecycle.
Spawn limits
Two limits prevent runaway sub-agent recursion and resource exhaustion:maxSpawnDepth (default: 3) — Controls how deep the spawn chain can go.
A depth of 3 means parent, child, and grandchild. If a sub-agent at the maximum
depth tries to spawn another sub-agent, it receives a structured error
explaining the limit — no crash, no silent failure.
maxChildrenPerAgent (default: 5) — Controls how many active children a
single parent can have at once. If a parent already has 5 active sub-agents and
tries to spawn a sixth, the spawn is rejected with a structured error.
Execution graph (pipeline) nodes bypass the per-agent children limit but
still respect depth limits. This allows complex pipeline orchestrations while
preventing unbounded recursion.
Result condensation
When a sub-agent finishes, its result goes through a three-level condensation pipeline. The goal is to give the parent agent a useful summary without overwhelming its context window.
Level 1 is the most common path — most sub-agent results are concise enough to
pass through unchanged. Level 2 produces the highest quality condensation by
using an LLM to extract the most important information into a structured format.
Level 3 is a last-resort fallback that ensures the parent always receives
something, even when LLM condensation is unavailable.
Regardless of condensation level, the full result is written to disk at
~/.comis/subagent-results/{tenantId}/{runId}.json. Results are retained
for 24 hours by default (configurable via resultRetentionMs), after which
they are automatically swept.Narrative casting
After condensation, the result is formatted with a tagged prefix and metadata footer so the parent agent can clearly distinguish sub-agent output from its own conversation. This prevents role confusion when the parent has multiple active sub-agents. Here is an example of a narrative-cast result:[Subagent Result: {label}] tag at the top makes it easy for the parent
agent to reference specific sub-agent outputs when coordinating multiple
concurrent tasks. The metadata footer provides observability into cost, runtime,
and condensation effectiveness.
Failure announcements retain both the failed run status and its terminal
classification. For example, an error-classified failed run is rendered as
Failed — Halted (error) rather than being flattened to an unqualified
Failed, so delivery does not erase the reason recorded by status and
observability surfaces.
Objective reinforcement
When a sub-agent’s conversation grows long enough to trigger the context engine’s compaction step (see Compaction), there is a risk that the sub-agent loses track of its original objective. Objective reinforcement prevents this. After compaction produces a summary of the older messages, a[Objective Reinforcement] message is injected immediately after the
compaction summary. This message contains the sub-agent’s original objective
from its spawn packet, ensuring the sub-agent re-reads what it was asked to do
before continuing.
This is enabled by default (objectiveReinforcement: true) and uses dual
detection — both a flag and a text pattern match — to identify compaction
events regardless of which layer triggered them.
Lifecycle hooks
The subagent context lifecycle provides two hooks for managing resources and emitting observability events:prepareSpawn — Awaited before the sub-agent begins execution. The built-in
hook emits the prepared lifecycle event but deliberately does not pre-create a
result directory: the success and failure writers create their own directories
when they persist output. A hook implementation may return a rollback handle;
the runner invokes it if admission or execution preparation fails.
onEnded — Invoked as a best-effort, fire-and-forget hook after the run has
claimed its terminal state and handed any announcement to its governed delivery
path. It emits a
session:sub_agent_lifecycle_ended event with lifecycle metadata (end reason,
runtime, token counts, and condensation level). Its promise is not part
of the active execution set, so a stalled hook cannot retain sub-agent
concurrency capacity, keep a provider execution promise alive, or delay prompt
shutdown. A rejection emits a WARN after the terminal outcome is already owned.
prepareSpawn is awaited because admission resources need a rollback handle;
onEnded cannot change or contradict the terminal result.
Configuration
All subagent context settings live undersecurity.agentToAgent.subagentContext
in your config file. The defaults work well for most setups — you only need to
configure values you want to change.
~/.comis/config.yaml
security.agentToAgent options.
Steering a running sub-agent
Thesubagents tool’s steer action redirects a sub-agent that is already running, and it is distinct from kill:
killterminates the child and tears it down cleanly. The run ends with thekilledend reason and its in-flight work is discarded.steersends a high-priority steering message to the child. How that message is delivered is gated behindsecurity.agentToAgent.steerInject(defaultfalse):- Flag off (default) —
steerfalls back to kill + respawn: the running child is killed and a fresh run is spawned with the steering message as its new task. The prior transcript and progress are discarded, and the run gets a newrunId. This is today’s behavior, unchanged. - Flag on —
steerinjects the message into the running child’s live session, preserving its transcript and progress (no kill, no respawn; the samerunIdcontinues).
- Flag off (default) —
subagent:steered event records which via its mode field — counts/ids/mode only, never the message body.)
A steer is a message, not a privilege grant. A steered child’s tool set is fixed at spawn time, so it cannot be steered into using a tool on the sub-agent tool denylist — a steered request for a denied tool is still refused with denied to ALL sub-agents -- the parent must perform this step, and the child’s sandbox posture is unchanged. The steer text cannot widen what the child is allowed to do; it can only redirect what the child works on within its existing governance. See subagent.steer for the RPC surface and its discriminated-union response.
steer targets a running child. If the target run has already completed/failed or is still queued, steer fails fast with Run <id> is not running (status: <status>) -- cannot steer; use kill+respawn instead. (mirroring kill’s precondition error) rather than attempting an inject against a child that has no live session.
steer is intentionally ungated, unlike kill. kill requires an explicit confirmation (it discards the child’s in-flight work, a destructive teardown). steer has no confirmation gate: in inject mode it is non-destructive (the transcript and progress are preserved), and even in the flag-off kill+respawn fallback it is a course-correction, not a bare teardown. A steer is a steering message, so the asymmetry is deliberate.
End reasons
When a sub-agent run reaches a terminal state, one of five completion reasons is returned bysession.run_status and subagent.wait:
swept is reserved for result-file retention cleanup after resultRetentionMs;
it is not a run-completion reason returned by those status surfaces. Terminal
reasons are also included in lifecycle observability and narrative metadata.
Related
Sessions Tool Reference
Parameters and usage for sessions_spawn, subagents kill, and other session tools.
Compaction
How the context engine manages conversation length, including the compaction
that triggers objective reinforcement.
Config YAML Reference
Full configuration reference for all subagentContext options and other
security settings.
Event Bus
Developer guide for subscribing to lifecycle events like
session:sub_agent_lifecycle_ended.
Resilience
Timeout guards, provider health monitoring, and dead-letter queue.
