How to Stream Session Events and Track Sub-Agent Lifecycle in the Copilot SDK
Register an event handler with session.on() to capture real-time SessionEvent streams and filter by event type to monitor sub-agent start, progress, and termination states.
The github/copilot-sdk exposes a Session object that serves as the central entry point for all interactions with the Copilot CLI runtime. To build responsive applications that react to incremental assistant responses or monitor complex tool-driven workflows, you must tap into the session's event streaming API and understand how sub-agent lifecycles are reported through that same channel.
Setting Up Session Event Streaming
The primary mechanism for streaming session events is the session.on() method, defined in nodejs/src/session.ts at lines 668-671. This method registers a handler function that receives a continuous stream of SessionEvent objects while the session processes a request.
Unlike the session.send() method—which merely returns a message ID and triggers processing—the event stream captures incremental content such as partial assistant responses, tool execution results, and runtime status changes.
import { createSession } from "@github/copilot-sdk";
const session = await createSession();
// Subscribe to all session events
const unsubscribe = session.on((event) => {
console.log(`[${event.type}]`, event.data);
if (event.type === "assistant.message_delta") {
processPartialMessage(event.data);
}
});
// send() returns an ID but does not block for the full response
const msgId = await session.send({ prompt: "Analyze the codebase" });
console.log("Message sent, id =", msgId);
// Event handler continues receiving deltas until unsubscribed
Tracking Sub-Agent Lifecycle Events
When a tool call spawns a sub-agent—for example, a custom tool that runs its own model inference—the runtime emits dedicated lifecycle events through the same event stream. These events allow you to correlate sub-agent activity with their parent tool invocations.
Key Event Types
According to nodejs/src/generated/session-events.ts, you can track sub-agents by filtering on these event type discriminators:
subagent.start– Emitted when a sub-agent begins executionsubagent.stop– Emitted upon completion, includes duration and token consumption metricsassistant.message_delta– Streaming fragments originating from sub-agent models
Parent-Tool Linkage
Sub-agent events carry linkage fields that map them to their originating tool call. In nodejs/src/generated/session-events.ts at lines 3659-3661, events include a parentToolCallId field that references the specific tool invocation that spawned the sub-agent:
session.on((event) => {
if (event.type === "subagent.start") {
console.log(`Sub-agent ${event.data.subAgentId} started from tool ${event.data.parentToolCallId}`);
}
if (event.type === "subagent.stop") {
// Lines 5454-5533 in session-events.ts define these fields:
console.log(`Sub-agent completed in ${event.data.durationMs}ms`);
console.log(`Total tokens consumed: ${event.data.tokenCount}`);
}
});
Configuring Sub-Agent Event Visibility
By default, the SDK forwards all sub-agent streaming events to your handler. If you need to reduce event noise or only care about top-level session activity, disable this behavior via the SessionConfig interface.
In nodejs/src/types.ts at lines 2418-2423, the includeSubAgentStreaming boolean controls whether delta events from sub-agents are forwarded:
const session = await createSession({
includeSubAgentStreaming: false // Suppress sub-agent delta events
});
// This handler will receive only top-level session events
session.on(e => console.log(e.type));
Complete Implementation Example
Combine streaming registration with lifecycle tracking to build a robust monitoring layer:
import { createSession, SessionEvent } from "@github/copilot-sdk";
async function monitorSessionWithSubAgents() {
const session = await createSession({
includeSubAgentStreaming: true // Ensure we see all sub-agent activity
});
const activeSubAgents = new Set<string>();
const unsubscribe = session.on((event: SessionEvent) => {
// Handle real-time content streaming
if (event.type === "assistant.message_delta") {
process.stdout.write(event.data.content);
}
// Track sub-agent lifecycle
if (event.type === "subagent.start") {
activeSubAgents.add(event.data.subAgentId);
console.log(`\n[Sub-agent ${event.data.subAgentId} started]`);
}
if (event.type === "subagent.stop") {
activeSubAgents.delete(event.data.subAgentId);
console.log(`\n[Sub-agent ${event.data.subAgentId} finished: ${event.data.summary}]`);
}
// Error handling
if (event.type === "error") {
console.error("Runtime error:", event.data.message);
}
});
// Trigger a request that spawns sub-agents via tool calls
await session.send({
prompt: "Read ./config.json and validate it against the schema",
tools: [{ name: "view", description: "Read file contents" }]
});
// Cleanup when done
// unsubscribe();
}
Summary
- Register handlers using
session.on()innodejs/src/session.tsto capture real-time events rather than pollingsession.send()results. - Filter by event type to distinguish between assistant messages, tool calls, and sub-agent lifecycle events ("subagent.start", "subagent.stop").
- Correlate sub-agents to parent tools via the
parentToolCallIdfield defined innodejs/src/generated/session-events.ts. - Control verbosity with the
includeSubAgentStreamingoption inSessionConfig(lines 2418-2423 ofnodejs/src/types.ts) when you only need top-level session data.
Frequently Asked Questions
What is the difference between the return value of session.send() and session events?
The session.send() method returns a message ID string immediately after dispatching the request to the runtime, as documented in nodejs/src/session.ts at lines 664-672. However, the actual response content—including incremental assistant messages and tool results—arrives asynchronously through the SessionEvent stream subscribed via session.on(). The message ID tracks the request scope, while the event stream delivers the actual execution data.
How do I distinguish between main agent and sub-agent events in the stream?
Sub-agent events include a parentToolCallId field (defined in nodejs/src/generated/session-events.ts, lines 3659-3661) that references the specific tool invocation that spawned them. Main agent events lack this field. Additionally, sub-agent events emit specific lifecycle types such as "subagent.start" and "subagent.stop", whereas main agent events typically use "assistant.message_delta" without parent linkage.
Can I disable sub-agent event streaming to reduce noise?
Yes. Set includeSubAgentStreaming: false in your SessionConfig when calling createSession(). According to nodejs/src/types.ts (lines 2418-2423), this option prevents forwarding of streaming delta events from sub-agents to your session handler, though lifecycle events like "subagent.start" may still be emitted depending on runtime configuration.
What metrics are available when a sub-agent terminates?
The "subagent.stop" event includes detailed lifecycle metadata defined in nodejs/src/generated/session-events.ts (lines 5454-5533). This payload typically contains the wall-clock duration, total tokens consumed, subAgentId, and a summary description of what the sub-agent accomplished during its execution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →