流式传输 (Streaming)
在执行期间流式传输来自 deep agent 及其子智能体的更新
Deep Agents 构建于 LangGraph 之上,继承了其完备的流式传输能力。 通过将 subgraphs: true 传递给 stream,你可以实时观察主协调器与被委派子智能体的每一步执行细节。
| 流式传输模式 (streamMode) | 输出内容 | 适用场景 |
|---|---|---|
values | 每个节点执行后的完整状态快照 | 状态监视看板、调试状态漂移 |
updates | 针对状态变更的精确增量更新 | 跟踪当前哪一步节点正在输出 |
messages | 模型生成的实时 Token 增量 | 终端或聊天窗口打字机流式渲染 |
custom | 工具通过 dispatchCustomEvent 写入的事件 | 自定义进度条、中间指标流 |
启用子图流式传输 (Enable subgraph streaming)
子智能体作为独立的嵌入式子图运行。默认情况下,stream 仅输出最顶层主智能体的事件。 若要接收来自子智能体的内部事件,请在调用流式方法时显式设置 subgraphs: true:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "google:gemini-3.6-flash",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}命名空间与来源追踪 (Namespaces)
当启用 subgraphs: true 时,每个流式 Chunk 都会附带一个命名空间元组(Tuple):
- 顶层主智能体:命名空间为空元组
[]。 - 子智能体:形如
["tools:task:<run_id>"]的命名空间,唯一标识该子任务的层级深度。
你可以利用命名空间将来自不同子智能体的输出精准分流到不同的前端卡片中。
监听子智能体进度 (Subagent progress)
使用 updates 模式跟踪子智能体的当前执行阶段:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}实时 LLM Token 流式渲染 (LLM tokens)
使用 messages 模式实现极致丝滑的打字机输出:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}工具调用流式传输 (Tool calls)
捕获工具何时被触发以及何时返回执行结果:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}自定义事件流 (Custom updates)
当工具需要长时间执行(如批量数据爬取、模型微调或下载大文件)时,可以通过 dispatchCustomEvent 向客户端推送结构化进度更新:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}多模式混合流式传输 (Stream multiple modes)
你可以同时传入多个流式模式数组,例如 streamMode: ["messages", "updates"],同时接收实时的打字机 Token 和图状态变更通知:
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}常见实战模式 (Common patterns)
追踪子智能体生命周期
监听子任务的派发、执行中与完成状态,计算并发执行数并在看板中展示。
处理人在回路中断 (Handle human-in-the-loop interrupts)
当工具命中 interruptOn 规则时,流式传输会优雅挂起并向客户端下发中断事件,等待客户端提交人工审批答案后恢复流式传输。
流式 Chunk 数据结构规范
根据所选模式,返回的 Chunk 具有明确的类型结构:
- 单模式 + 子图:返回形如
[namespace, chunkData]的元组。 - 多模式 + 子图:返回形如
[namespace, modeName, chunkData]的元组。
相关参考
- 事件流式传输 (Event streaming):面向前端 UI 的更高层抽象
stream.subagents。 - 前端流式集成模式:在 React/Vue 客户端中分离渲染子智能体进度。
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
systemPrompt: "You are a helpful research assistant",
subagents: [
{
name: "researcher",
description: "Researches a topic in depth",
systemPrompt: "You are a thorough researcher.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research quantum computing advances" },
],
},
{
streamMode: "updates",
subgraphs: true,
},
)) {
if (namespace.length > 0) {
// Subagent event - namespace identifies the source
console.log(`[subagent: ${namespace.join("|")}]`);
} else {
// Main agent event
console.log("[main agent]");
}
console.log(chunk);
}Open a public LangSmith run for this example.
Namespaces
When subgraphs is enabled, each streaming event includes a namespace that identifies which agent produced it. The namespace is a path of node names and task IDs that represents the agent hierarchy.
| Namespace | Source |
|---|---|
() (empty) | Main agent |
("tools:abc123",) | A subagent spawned by the main agent's task tool call abc123 |
("tools:abc123", "model_request:def456") | The model request node inside a subagent |
Use namespaces to route events to the correct UI component:
for await (const [namespace, chunk] of await agent.stream(
{ messages: [{ role: "user", content: "Plan my vacation" }] },
{ streamMode: "updates", subgraphs: true },
)) {
// Check if this event came from a subagent
const isSubagent = namespace.some((segment: string) =>
segment.startsWith("tools:"),
);
if (isSubagent) {
// Extract the tool call ID from the namespace
const toolCallId = namespace
.find((s: string) => s.startsWith("tools:"))
?.split(":")[1];
console.log(`Subagent ${toolCallId}:`, chunk);
} else {
console.log("Main agent:", chunk);
}
}Open a public LangSmith run for this example.
Subagent progress
Use stream_mode="updates" to track subagent progress as each step completes. This is useful for showing which subagents are active and what work they've completed.
import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "google:gemini-3.6-flash",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}import { createDeepAgent } from "deepagents";
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
systemPrompt:
"You are a project coordinator with no research knowledge. " +
"For every user request, you must call the task() tool with " +
"subagent_type set to researcher. Never answer research questions yourself. " +
"Keep your final response to one sentence.",
subagents: [
{
name: "researcher",
description: "Researches topics thoroughly",
systemPrompt:
"You are a thorough researcher. Research the given topic " +
"and provide a concise summary in 2-3 sentences.",
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Write a short summary about AI safety" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
// Main agent updates (empty namespace)
if (namespace.length === 0) {
for (const [nodeName, data] of Object.entries(chunk)) {
if (nodeName === "tools") {
// Subagent results returned to main agent
for (const msg of (data as any).messages ?? []) {
if (msg.type === "tool") {
console.log(`\nSubagent complete: ${msg.name}`);
console.log(` Result: ${String(msg.content).slice(0, 200)}...`);
}
}
} else {
console.log(`[main agent] step: ${nodeName}`);
}
}
}
// Subagent updates (non-empty namespace)
else {
for (const [nodeName] of Object.entries(chunk)) {
console.log(` [${namespace[0]}] step: ${nodeName}`);
}
}
}Open a public LangSmith run for this example.
Main agent step: model_request
[tools:call_abc123] step: model_request
[tools:call_abc123] step: tools
[tools:call_abc123] step: model_request
Subagent complete: task
Result: ## AI Safety Report...
Main agent step: model_request
[tools:call_def456] step: model_request
[tools:call_def456] step: model_request
Subagent complete: task
Result: # Comprehensive Report on AI Safety...
Main agent step: model_requestLLM tokens
Use stream_mode="messages" to stream individual tokens from both the main agent and subagents. Each message event includes metadata that identifies the source agent.
let currentSource = "";
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Research quantum computing advances",
},
],
},
{ streamMode: "messages", subgraphs: true },
)) {
const [message] = chunk;
// Check if this event came from a subagent (namespace contains "tools:")
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
// Token from a subagent
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
if (subagentNs !== currentSource) {
process.stdout.write(`\n\n--- [subagent: ${subagentNs}] ---\n`);
currentSource = subagentNs;
}
if (message.text) {
process.stdout.write(message.text);
}
} else {
// Token from the main agent
if ("main" !== currentSource) {
process.stdout.write(`\n\n--- [main agent] ---\n`);
currentSource = "main";
}
if (message.text) {
process.stdout.write(message.text);
}
}
}
process.stdout.write("\n");Open a public LangSmith run for this example.
Tool calls
When subagents use tools, you can stream tool call events to display what each subagent is doing. Tool call chunks appear in the messages stream mode.
import { AIMessageChunk, ToolMessage } from "langchain";
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Research recent quantum computing advances",
},
],
},
{ streamMode: "messages", subgraphs: true },
)) {
const [message] = chunk;
// Identify source: "main" or the subagent namespace segment
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
const source = isSubagent
? namespace.find((s: string) => s.startsWith("tools:"))!
: "main";
// Tool call chunks (streaming tool invocations)
if (AIMessageChunk.isInstance(message) && message.tool_call_chunks?.length) {
for (const tc of message.tool_call_chunks) {
if (tc.name) {
console.log(`\n[${source}] Tool call: ${tc.name}`);
}
// Args stream in chunks - write them incrementally
if (tc.args) {
process.stdout.write(tc.args);
}
}
}
// Tool results
if (ToolMessage.isInstance(message)) {
console.log(
`\n[${source}] Tool result [${message.name}]: ${message.text?.slice(0, 150)}`,
);
}
// Regular AI content (skip tool call messages)
if (
AIMessageChunk.isInstance(message) &&
message.text &&
!message.tool_call_chunks?.length
) {
process.stdout.write(message.text);
}
}
process.stdout.write("\n");Open a public LangSmith run for this example.
Custom updates
Use config.writer inside your subagent tools to emit custom progress events:
import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "google:gemini-3.6-flash",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "openai:gpt-5.5",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "anthropic:claude-sonnet-5",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}import { createDeepAgent } from "deepagents";
import { tool, type ToolRuntime } from "langchain";
import { z } from "zod";
/**
* A tool that emits custom progress events via config.writer.
* The writer sends data to the "custom" stream mode.
*/
const analyzeData = tool(
async ({ topic }: { topic: string }, config: ToolRuntime) => {
const writer = config.writer;
writer?.({ status: "starting", topic, progress: 0 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "analyzing", progress: 50 });
await new Promise((r) => setTimeout(r, 500));
writer?.({ status: "complete", progress: 100 });
return `Analysis of "${topic}": Customer sentiment is 85% positive, driven by product quality and support response times.`;
},
{
name: "analyze_data",
description:
"Run a data analysis on a given topic. " +
"This tool performs the actual analysis and emits progress updates. " +
"You MUST call this tool for any analysis request.",
schema: z.object({
topic: z.string().describe("The topic or subject to analyze"),
}),
},
);
const agent = createDeepAgent({
model: "ollama:north-mini-code-1.0",
systemPrompt:
"You are a coordinator. For any analysis request, you MUST delegate " +
"to the analyst subagent using the task tool. Never try to answer directly. " +
"After receiving the result, summarize it in one sentence.",
subagents: [
{
name: "analyst",
description: "Performs data analysis with real-time progress tracking",
systemPrompt:
"You are a data analyst. You MUST call the analyze_data tool " +
"for every analysis request. Do not use any other tools. " +
"After the analysis completes, report the result.",
tools: [analyzeData],
},
],
});
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze customer satisfaction trends",
},
],
},
{ streamMode: "custom", subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
if (isSubagent) {
const subagentNs = namespace.find((s: string) => s.startsWith("tools:"))!;
console.log(`[${subagentNs}]`, chunk);
} else {
console.log("[main]", chunk);
}
}Open a public LangSmith run for this example.
[tools:call_abc123] { status: 'fetching', progress: 0 }
[tools:call_abc123] { status: 'analyzing', progress: 50 }
[tools:call_abc123] { status: 'complete', progress: 100 }Stream multiple modes
Combine multiple stream modes to get a complete picture of agent execution:
// Skip internal middleware steps - only show meaningful node names
const INTERESTING_NODES = new Set(["model", "tools"]);
let lastSource = "";
let midLine = false; // true when we've written tokens without a trailing newline
for await (const [namespace, mode, data] of await agent.stream(
{
messages: [
{
role: "user",
content: "Analyze the impact of remote work on team productivity",
},
],
},
{ streamMode: ["updates", "messages", "custom"], subgraphs: true },
)) {
const isSubagent = namespace.some((s: string) => s.startsWith("tools:"));
const source = isSubagent ? "subagent" : "main";
if (mode === "updates") {
for (const nodeName of Object.keys(data)) {
if (!INTERESTING_NODES.has(nodeName)) continue;
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
console.log(`[${source}] step: ${nodeName}`);
}
} else if (mode === "messages") {
const [message] = data;
if (message.text) {
// Print a header when the source changes
if (source !== lastSource) {
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
process.stdout.write(`\n[${source}] `);
lastSource = source;
}
process.stdout.write(message.text);
midLine = true;
}
} else if (mode === "custom") {
if (midLine) {
process.stdout.write("\n");
midLine = false;
}
console.log(`[${source}] custom event:`, data);
}
}
process.stdout.write("\n");Open a public LangSmith run for this example.
Common patterns
Track subagent lifecycle
Monitor when subagents start, run, and complete:
function getToolCalls(message: unknown): Array<{
id?: string;
name?: string;
args?: Record<string, unknown>;
}> {
if (!message || typeof message !== "object") {
return [];
}
const record = message as Record<string, unknown>;
const toolCalls = record.tool_calls ?? record.toolCalls;
return Array.isArray(toolCalls)
? (toolCalls as Array<{
id?: string;
name?: string;
args?: Record<string, unknown>;
}>)
: [];
}
const activeSubagents = new Map<
string,
{ type?: string; description?: string; status: string }
>();
for await (const [namespace, chunk] of await agent.stream(
{
messages: [
{ role: "user", content: "Research the latest AI safety developments" },
],
},
{ streamMode: "updates", subgraphs: true },
)) {
for (const [nodeName, data] of Object.entries(chunk)) {
// ─── Phase 1: Detect subagent starting ────────────────────────
// When the main agent emits a task tool call, a subagent has been spawned.
if (namespace.length === 0) {
for (const msg of (data as { messages?: unknown[] }).messages ?? []) {
for (const tc of getToolCalls(msg)) {
if (tc.name === "task" && tc.id) {
activeSubagents.set(tc.id, {
type: tc.args?.subagent_type as string | undefined,
description: String(tc.args?.description ?? "").slice(0, 80),
status: "pending",
});
console.log(
`[lifecycle] PENDING → subagent "${tc.args?.subagent_type}" (${tc.id})`,
);
}
}
}
}
// ─── Phase 2: Detect subagent running ─────────────────────────
// When we receive events from a tools:UUID namespace, that
// subagent is actively executing.
if (namespace.length > 0 && namespace[0].startsWith("tools:")) {
const pregelId = namespace[0].split(":")[1];
// Check if any pending subagent needs to be marked running.
// Note: the pregel task ID differs from the tool_call_id,
// so we mark any pending subagent as running on first subagent event.
let markedRunning = false;
for (const [, sub] of activeSubagents) {
if (sub.status === "pending") {
sub.status = "running";
markedRunning = true;
console.log(
`[lifecycle] RUNNING → subagent "${sub.type}" (pregel: ${pregelId})`,
);
break;
}
}
if (!markedRunning && activeSubagents.size === 0) {
activeSubagents.set(pregelId, {
type: "researcher",
status: "running",
});
console.log(
`[lifecycle] RUNNING → subagent "researcher" (pregel: ${pregelId})`,
);
}
}
// ─── Phase 3: Detect subagent completing ──────────────────────
// When the main agent's tools node returns a tool message,
// the subagent has completed and returned its result.
if (namespace.length === 0 && nodeName === "tools") {
for (const msg of (data as { messages?: Array<Record<string, unknown>> })
.messages ?? []) {
if (msg.type === "tool") {
const toolCallId = String(msg.tool_call_id ?? msg.toolCallId ?? "");
const subagent = activeSubagents.get(toolCallId);
if (subagent) {
subagent.status = "complete";
console.log(
`[lifecycle] COMPLETE → subagent "${subagent.type}" (${toolCallId})`,
);
console.log(
` Result preview: ${String(msg.content).slice(0, 120)}...`,
);
}
}
}
}
}
}
// Print final state
console.log("\n--- Final subagent states ---");
for (const [id, sub] of activeSubagents) {
console.log(` ${sub.type}: ${sub.status}`);
}Open a public LangSmith run for this example.
Handle human-in-the-loop interrupts
When interrupt_on is configured, the updates stream may contain __interrupt__ entries that pause execution for human approval. Detect them by checking for the __interrupt__ key in update chunks. Resume with the same flat {"decisions": [...]} payload you use with .invoke():
import { v7 as uuid7 } from "uuid";
const config = { configurable: { thread_id: uuid7() } };
for await (const [mode, data] of await agent.stream(
{ messages: [{ role: "user", content: "Delete temp.txt" }] },
{ streamMode: ["messages", "updates"], ...config }
)) {
if (mode === "updates" && data?.__interrupt__) {
for (const interruptObj of data.__interrupt__) {
console.log("Approval needed:", interruptObj.value);
}
}
}See Human-in-the-loop: Handle interrupts with streaming for the complete stream-resume loop pattern.
Stream chunk shapes
With subgraphs=True, chunk shape depends on whether you pass one stream mode or several:
stream_mode | Chunk shape |
|---|---|
Single mode (for example "updates") | (namespace, data) |
Multiple modes (for example ["messages", "updates"]) | (namespace, mode, data) |
Without subgraphs, a single mode yields data directly, and multiple modes yield (mode, data).
# Single mode + subgraphs
for namespace, data in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing"}]},
stream_mode="updates",
subgraphs=True,
):
print(namespace) # () for main agent, ("tools:<id>",) for subagent
print(data)
# Multiple modes + subgraphs
for namespace, mode, data in agent.stream(
{"messages": [{"role": "user", "content": "Research quantum computing"}]},
stream_mode=["updates", "messages", "custom"],
subgraphs=True,
):
print(mode) # "updates", "messages", or "custom"
print(namespace) # () for main agent, ("tools:<id>",) for subagent
print(data)Related
- Human-in-the-loop—Configure approval workflows for sensitive tool operations
- Subagents—Configure and use subagents with Deep Agents
- Frontend streaming—Build React UIs with
useStreamfor Deep Agents - LangChain Event Streaming—General streaming concepts with LangChain agents
Connect these docs to your agent of choice via MCP for real-time answers.
