Per-instance subagent identity
When a deep-agent workflow dispatches the same configured subagent multiple times in one turn — for example, a planner that fans out to four parallel subagent-specialist calls — the Alien stream tags each dispatch with a distinct composite identity. Consumers can group output events by dispatch instead of collapsing them onto the subagent's type name.
This page is the consumer-side guide. The underlying wire format is defined in the Responses API and Chat Completions API specs.
The wire format
Item IDs in output_item.added / output_item.done events (Responses API) and x_alien.agent_id strings (Chat Completions) follow this grammar:
agent:<type>::<kind>_<ordinal> # legacy / single-dispatch fallback
agent:<type>#<dispatch_id>::<kind>_<ordinal> # per-instance (new)
| Segment | Meaning |
|---|---|
<type> | Human-readable subagent name (MAIN, subagent-specialist, subagent-planner, …) |
<dispatch_id> | Opaque per-dispatch handle. Present when the worker resolves one, absent otherwise. |
<kind> | msg (message), fc (function call), or rs (reasoning summary) |
<ordinal> | Response-scoped monotonic counter |
The # character is reserved inside <type>: no configured subagent name contains it. Composite IDs are a strict superset of the legacy format — clients that treat the whole segment between agent: and :: opaquely keep working unchanged, they simply lose the per-instance disambiguation.
The same composite appears in the agent registry:
- Responses API —
metadata.x_alien_agent_registry[].id - Chat Completions —
XAlienAgentRegister.id
The name field of each registry entry retains the plain type name for UI display, regardless of composite identity.
Registry entry fields
Each registry entry carries the following fields:
| Field | Meaning |
|---|---|
id | The composite (or legacy) agent identity — the grouping key. Matches the agent:<…> prefix on this agent's item IDs. |
kind | main, subagent, or tool. |
name | Plain type name for UI display (e.g. subagent-specialist), retained regardless of composite identity. |
parent_id | The id of the dispatching agent, or null for the root. Walk this chain to group dispatches by round (see Granularity). |
dispatched_by_tool_call_id | The tool_calls[].id of the parent agent's task() call that dispatched this subagent instance. Optional — present only when the worker resolves it. Unlike <dispatch_id> (the opaque per-dispatch handle embedded in id, which is a LangChain run_id), this is the LLM-assigned tool-call id, so it can be correlated with the parent message's tool_calls[].id — see Rule 2 for why <dispatch_id> cannot. |
Parsing
type ParsedAgentItemId = {
agentType: string
dispatchId: string | undefined
instanceKey: string
kind: "msg" | "fc" | "rs"
ordinal: number
}
function parseAgentItemId(itemId: string): ParsedAgentItemId | null {
const match = itemId.match(/^agent:([^#:]+)(?:#([^:]+))?::([^_]+)_(\d+)$/)
if (match === null) return null
const [, agentType, dispatchId, kind, ordinal] = match
return {
agentType,
dispatchId,
instanceKey: dispatchId ? `${agentType}#${dispatchId}` : agentType,
kind: kind as "msg" | "fc" | "rs",
ordinal: Number(ordinal),
}
}
Use instanceKey as your grouping key and agentType as the rendered label.
Granularity
The composite identity is per task() call, not per concurrent batch.
An iterative deep-agent workflow that runs
planner → 4 specialists in parallel → critic → planner → 4 more specialists → critic
emits
- 8 distinct
subagent-specialist#…composites - 2 distinct
subagent-planner#…composites - 2 distinct
subagent-critic#…composites
even though the workflow has only one configured planner and one configured critic. Each invocation gets its own composite because each task() call carries a distinct dispatch handle. This is intentional: stream consumers can render iteration progress (round 1 vs round 2) without inferring rounds from temporal heuristics.
Consumers that want to group dispatches by round can walk the registry's parent_id chain back to the dispatching planner instance.
Rules
- Composite is a strict superset of the legacy format. Old parsers that ignore the
#<dispatch_id>middle segment still get the type name and behave as before. You only need to opt in if you want per-instance grouping. dispatchIdis opaque. Do not correlate it with the parent message'stool_calls[].id. The worker substitutes a per-dispatch LangChainrun_idbecause deepagents ≥ 0.6.x does not expose the LLM-assignedtoolu_*id in the task tool's input. If you need that correlation, match via the agent'stool.call.endevents.- Use
namefor UI labels,idfor grouping. Registry entries keepname = "subagent-specialist"regardless of composite identity, so existing renderers display the configured subagent name unchanged. - Registry can truncate past 512 bytes (OpenAI's metadata-value cap). Watch for
metadata.x_alien_registry_truncated === "true". When truncated, the per-itemitem.idprefix is the source of truth — you can reconstruct full per-instance identity from item IDs alone without the registry.
Worked example
In the prod-shaped reference fixture (packages/backend/lib/streaming/specs/fixtures/responses-v1/parallel-same-name-subagent-dispatch.sse), a single turn dispatches two subagent-specialist calls in parallel with distinct dispatch IDs. After parsing, you observe exactly three instanceKey values:
MAIN
subagent-specialist#toolu_01_CALL_A
subagent-specialist#toolu_01_CALL_B
Every output_item.added event in that fixture maps to exactly one of those three buckets via parseAgentItemId(event.item.id).instanceKey. Descendant message items inherit the dispatching marker's composite prefix automatically.
Reference fixtures
| Fixture | Use for |
|---|---|
lib/streaming/specs/fixtures/responses-v1/parallel-same-name-subagent-dispatch.sse | Responses API stream with two parallel subagent-specialist#… dispatches |
lib/streaming/specs/fixtures/chat-completions-v1/parallel-same-name-subagent-dispatch.sse | Chat Completions counterpart with composite x_alien.agent_id strings |
lib/streaming/specs/fixtures/agent-event-v1/parallel-same-name-subagent-dispatch.ndjson | Internal AgentEvent NDJSON source used to generate both wire formats |
All paths are relative to web-app/packages/backend/ in the platform repo.
Full specs
| Spec | Section | What it covers |
|---|---|---|
lib/streaming/specs/responses_v1.md | §4 | Responses API wire format — item-id grammar, registry serialization, composite parsing rules |
lib/streaming/specs/chat_completions_v1.md | §4.1 | Chat Completions wire format — x_alien.agent_id, agent_register semantics, composite parsing |
lib/streaming/specs/agent_event_v1.md | §3.10 | Internal AgentEvent format — worker semantics, ancestor-walk obligation, run-id fallback rationale, per-dispatch granularity discussion |