Skip to main content

Documentation Index

Fetch the complete documentation index at: https://langchain-5e9cc07a-preview-cbuipl-1779916257-33d1bcf.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

When a coordinator agent spawns specialist subagents (a researcher, an analyst, a writer), you need to render the orchestrator’s messages separately from each subagent’s streaming output. The v1 SDK keeps coordinator messages on the root stream and exposes subagents as discovery snapshots. Pass a snapshot to selector hooks or composables such as useMessages(stream, subagent) to render the specialist’s scoped stream. This is where the LangChain frontend SDKs go beyond a flat chat transcript: subagents are first-class stream entities with their own status, messages, tool-call metadata, and results. Your UI can show delegation, progress, errors, and final synthesis without asking users to read interleaved tokens from every worker.

Why selector-based subagent streams

The root stream stays focused on the coordinator conversation:
  • stream.messages contains only the coordinator’s messages
  • stream.subagents contains discovery snapshots with identity, namespace, and status
  • Each subagent’s messages, tool calls, and values are read with selector helpers
  • The UI stays clean: the coordinator’s reasoning is separate from the specialists’ work
This separation lets you render the orchestrator’s messages in one place and mount subagent cards only when the user needs to see specialist work. For large tasks, this also keeps the UI scalable. Users can skim the coordinator’s high-level plan, expand only the specialist work they care about, and still retain the full subagent trace for debugging, audit, or replay.

Setting up useStream

No extra stream options are required. Point the stream at your deep agent, render coordinator messages from stream.messages, and use stream.subagents to mount cards for active specialists. In chat layouts, index subagents by the tool-call ID that spawned them so each card appears under the coordinator turn Point the stream at your deep agent, render coordinator messages from stream.messages, and use stream.subagents to mount cards for active specialists. In chat layouts, index subagents by the tool-call ID that spawned them so each card appears under the coordinator turn that delegated the work.
The code examples use useStream<typeof myAgent> for type-safe stream state. See Type inference for Python or JavaScript backends.
import { useStream } from "@langchain/react";
import { AIMessage, HumanMessage } from "langchain";

const AGENT_URL = "http://localhost:2024";

export function DeepAgentChat() {
  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "deep_agent_subagent_cards",
  });
  const subagents = [...stream.subagents.values()];
  const subagentsByCallId = new Map(subagents.map((s) => [s.id, s]));

  return (
    <div>
      {stream.messages.map((msg) => {
        const turnSubagents = AIMessage.isInstance(msg)
          ? (msg.tool_calls ?? [])
              .map((tc) => subagentsByCallId.get(tc.id ?? ""))
              .filter((s): s is NonNullable<typeof s> => !!s)
          : [];

        return (
          <div key={msg.id}>
            {HumanMessage.isInstance(msg) && <HumanBubble>{msg.text}</HumanBubble>}
            {AIMessage.isInstance(msg) && msg.text.trim() && (
              <AIBubble>{msg.text}</AIBubble>
            )}
            {turnSubagents.map((subagent) => (
              <SubagentCard key={subagent.id} stream={stream} subagent={subagent} />
            ))}
          </div>
        );
      })}
    </div>
  );
}

Submitting messages

Submit messages through the root stream. Deep agent workflows often involve multiple layers of nested subgraphs, so set an appropriate recursion limit if your agent can delegate deeply:
stream.submit(
  { messages: [{ type: "human", content: text }] },
  { config: { recursion_limit: 100 } }
);
Deep Agents sets a default recursion limit of 10,000, which is sufficient for most multi-expert setups. You can override this via config.recursion_limit if needed.

The SubagentDiscoverySnapshot

Each SubagentDiscoverySnapshot is a lightweight discovery record for a subagent running inside the thread. It tells your UI that a subagent exists, where it sits in the subagent tree, and what lifecycle state it is in. The snapshot does not include the subagent’s streamed messages or tool calls. Instead, pass the snapshot to selector hooks such as useMessages(stream, subagent) or useToolCalls(stream, subagent). These hooks use the snapshot namespace to subscribe to the subagent’s stream primitives only when the corresponding card or panel is mounted.

Building the SubagentCard

Each subagent card shows the specialist’s name, status, streaming content, and tool calls. Use selector hooks to subscribe to the subagent namespace:
import { useState } from "react";
import { AIMessage } from "langchain";
import {
  useMessages,
  useToolCalls,
  type AnyStream,
  type SubagentDiscoverySnapshot,
} from "@langchain/react";

function SubagentCard({
  stream,
  subagent,
}: {
  stream: AnyStream;
  subagent: SubagentDiscoverySnapshot;
}) {
  const [expanded, setExpanded] = useState(true);
  const messages = useMessages(stream, subagent);
  const toolCalls = useToolCalls(stream, subagent);

  const lastAIMessage = messages
    .filter(AIMessage.isInstance)
    .at(-1);

  const displayContent =
    lastAIMessage?.text ?? subagent.output ?? "";

  return (
    <div className="rounded-lg border bg-white shadow-sm">
      <button
        onClick={() => setExpanded(!expanded)}
        className="flex w-full items-center justify-between p-4"
      >
        <div className="flex items-center gap-3">
          <StatusIcon status={subagent.status} />
          <div>
            <h4 className="font-semibold capitalize">{subagent.name}</h4>
            <p className="text-xs text-gray-500">
              {toolCalls.length} tool call{toolCalls.length === 1 ? "" : "s"}
            </p>
          </div>
        </div>
        <div className="flex items-center gap-2">
          <StatusBadge status={subagent.status} />
        </div>
      </button>

      {expanded && displayContent && (
        <div className="border-t px-4 py-3">
          <div className="prose prose-sm max-w-none line-clamp-6">
            {displayContent}
            {subagent.status === "running" && (
              <span className="inline-block h-4 w-1 animate-pulse bg-blue-500" />
            )}
          </div>
        </div>
      )}
    </div>
  );
}

Progress tracking

Show a progress bar and counter so users know how many subagents have finished:
function SubagentProgress({
  subagents,
}: {
  subagents: SubagentDiscoverySnapshot[];
}) {
  const completed = subagents.filter((s) => s.status === "complete").length;
  const total = subagents.length;
  const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;

  return (
    <div className="space-y-1">
      <div className="flex items-center justify-between text-xs text-gray-500">
        <span>Subagent progress</span>
        <span>
          {completed}/{total} complete
        </span>
      </div>
      <div className="h-2 overflow-hidden rounded-full bg-gray-200">
        <div
          className="h-full rounded-full bg-blue-500 transition-all duration-300"
          style={{ width: `${percentage}%` }}
        />
      </div>
    </div>
  );
}

Rendering messages with subagent cards

The key layout pattern is to render coordinator messages from the root stream and attach subagent cards to the AI message whose tool call spawned them:
function DeepAgentLayout({ stream }: { stream: AnyStream }) {
  const subagents = [...stream.subagents.values()];
  const subagentsByCallId = new Map(subagents.map((s) => [s.id, s]));

  return (
    <div className="space-y-3">
      {stream.messages.map((message) => {
        const turnSubagents = AIMessage.isInstance(message)
          ? (message.tool_calls ?? [])
              .map((tc) => subagentsByCallId.get(tc.id ?? ""))
              .filter((s): s is SubagentDiscoverySnapshot => !!s)
          : [];

        return (
          <div key={message.id}>
            <Message message={message} />
            {turnSubagents.length > 0 && (
              <div className="ml-4 space-y-3 border-l-2 border-blue-200 pl-4">
                <SubagentProgress subagents={subagents} />
                {turnSubagents.map((subagent) => (
                  <SubagentCard key={subagent.id} stream={stream} subagent={subagent} />
                ))}
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}
You can combine inline cards with a global subagent view: index subagents by the coordinator tool call that spawned them for transcript cards, and use stream.subagents for a persistent sidebar that summarizes all active workers. That gives users both local context and a bird’s-eye view of the whole run.

Best practices

  • Mount selectors only where needed. Scoped messages and tool calls stream when a card calls useMessages(stream, subagent) or useToolCalls(stream, subagent).
  • Show specialist names. subagent.name tells users which worker is active.
  • Use collapsible cards. In workflows with 5+ subagents, auto-collapse completed cards so users can focus on active work.
  • Override recursion only when needed. Deep Agents sets a high default recursion limit; pass config.recursion_limit only for unusually deep custom workflows.
  • Handle errors per subagent. One subagent failing shouldn’t crash the entire UI. Show the error in that subagent’s card while others continue running.