Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp
| Tool call progress | `toolProgress()` | `toolProgress` |
| Tool calls with results | `toolCalls()` | `toolCalls` |
| Branch / history | `branch()` / `history()` | `branch` / `history` |
| Subagent streaming | Planned next | `subagents` / `activeSubagents` |
| Subagent streaming | `subagents()` / `activeSubagents()` | `subagents` / `activeSubagents` |
| Reactive thread switching | `Signal<string \| null>` input | prop |
| Submit | `submit(values, opts?)` | `submit(values, opts?)` |
| Stop | `stop()` | `stop()` |
Expand Down
26 changes: 25 additions & 1 deletion apps/website/content/docs/agent/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -739,9 +739,27 @@
"kind": "interface",
"description": "An event emitted by a LangGraph stream.",
"properties": [
{
"name": "messageMetadata",
"type": "Record<string, unknown>",
"description": "",
"optional": true
},
{
"name": "messages",
"type": "unknown[]",
"description": "",
"optional": true
},
{
"name": "namespace",
"type": "string[]",
"description": "",
"optional": true
},
{
"name": "type",
"type": "\"error\" | \"values\" | \"messages\" | `messages/${string}` | \"updates\" | \"tools\" | \"custom\" | \"metadata\" | \"checkpoints\" | \"tasks\" | \"debug\" | \"events\" | \"interrupt\" | \"interrupts\"",
"type": "\"error\" | \"values\" | `values|${string}` | \"messages\" | `messages|${string}` | `messages/${string}` | `messages/${string}|${string}` | \"updates\" | `updates|${string}` | \"tools\" | `tools|${string}` | \"custom\" | `custom|${string}` | `error|${string}` | \"metadata\" | \"checkpoints\" | `checkpoints|${string}` | \"tasks\" | `tasks|${string}` | \"debug\" | `debug|${string}` | \"events\" | `events|${string}` | \"interrupt\" | \"interrupts\"",
"description": "Event type identifier (e.g., 'values', 'messages', 'error', 'interrupt').",
"optional": false
}
Expand All @@ -759,6 +777,12 @@
"description": "Messages from the subagent conversation.",
"optional": false
},
{
"name": "name",
"type": "string",
"description": "Optional human-readable subagent type/name.",
"optional": true
},
{
"name": "status",
"type": "Signal<\"running\" | \"error\" | \"pending\" | \"complete\">",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,8 @@ export class MultiAgentComponent {
</Tab>
</Tabs>

<Callout type="warning" title="Subagent signal status">
Tool calls, tool progress, and tool results stream today. Dedicated `subagents()` and `activeSubagents()` tracking is planned for the next implementation phase; use `toolCalls()` and `toolProgress()` for current delegated-work visibility.
</Callout>

<Callout type="tip" title="subagentToolNames is the key">
The `subagentToolNames` option will tell agent() which graph nodes are subagents. Until dedicated tracking lands, subagent execution appears through the regular tool-call and tool-progress signals.
The `subagentToolNames` option tells agent() which tool calls spawn subagents. The default Deep Agents tool name is `task`; set this option when your graph uses custom delegation tool names.
</Callout>

## Error Handling and Recovery
Expand Down Expand Up @@ -662,7 +658,7 @@ builder.add_node("analyst", analyst_subgraph)
builder.add_conditional_edges("supervisor", route_to_agent)
```

**Angular signals used today:** `messages()`, `toolCalls()`, `toolProgress()`, `status()`; dedicated `subagents()` / `activeSubagents()` tracking is planned next.
**Angular signals used:** `messages()`, `subagents()`, `activeSubagents()`, `toolCalls()`, `toolProgress()`, `status()`

### Decision Matrix

Expand Down
11 changes: 7 additions & 4 deletions apps/website/content/docs/agent/concepts/langgraph-basics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,16 @@ builder.add_node("analyst", analyst_subgraph)
builder.add_conditional_edges("supervisor", lambda s: s["next_agent"])
```

**Angular connection:** Dedicated subagent tracking is planned for the next implementation phase. Today, track delegated work through tool progress and tool results:
**Angular connection:** Track delegated work through dedicated subagent signals:
```typescript
const orchestrator = agent<OrchestratorState>({
assistantId: 'orchestrator',
subagentToolNames: ['task'],
filterSubagentMessages: true,
});

const activeTools = computed(() => orchestrator.toolProgress());
const completedTools = computed(() => orchestrator.toolCalls());
const workers = computed(() => orchestrator.activeSubagents());
const workerCount = computed(() => workers().length);
```

### Pattern 4: Persistent Conversations
Expand Down Expand Up @@ -322,7 +324,8 @@ agent.branch() // Signal<string> — time-travel branch

agent.toolCalls() // Signal<ToolCallWithResult[]> — tool results
agent.toolProgress() // Signal<ToolProgress[]> — active tool execution
// Dedicated subagent signals are planned next.
agent.subagents() // Signal<Map<string, Subagent>> — delegated agents
agent.activeSubagents() // Signal<SubagentStreamRef[]> — running workers
```

</Tab>
Expand Down
4 changes: 0 additions & 4 deletions apps/website/content/docs/agent/guides/subgraphs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ Subgraphs let you compose complex agents from smaller, focused units. agent() tr
LangGraph calls them subgraphs (modular graph composition). Deep Agents calls them subagents (task delegation). agent() supports both patterns through the same API.
</Callout>

<Callout type="warning" title="Implementation status">
Tool calls, tool progress, and tool results stream today. The `subagents()` / `activeSubagents()` examples below describe the planned Phase 2 API; until that lands, use `toolCalls()` and `toolProgress()` for visibility into delegated work.
</Callout>

## How subgraph composition works

Subgraph composition starts on the agent side. Each subgraph is a fully compiled `StateGraph` that can be added as a node in a parent graph.
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/components/docs/mdx/FeatureChips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const CHIPS: ChipData[] = [
{ icon: '💾', title: 'Persistence', signal: 'threadId', href: '/docs/guides/persistence', gradient: 'linear-gradient(135deg, rgba(16,185,129,0.06), rgba(52,199,89,0.08))', border: 'rgba(16,185,129,0.1)' },
{ icon: '✋', title: 'Interrupts', signal: 'chat.interrupt()', href: '/docs/guides/interrupts', gradient: 'linear-gradient(135deg, rgba(232,147,12,0.06), rgba(245,180,60,0.08))', border: 'rgba(232,147,12,0.1)' },
{ icon: '⏪', title: 'Time Travel', signal: 'chat.history()', href: '/docs/guides/time-travel', gradient: 'linear-gradient(135deg, rgba(221,0,49,0.05), rgba(255,100,130,0.07))', border: 'rgba(221,0,49,0.08)' },
{ icon: '🔀', title: 'Subagents', signal: 'Phase 2', href: '/docs/guides/subgraphs', gradient: 'linear-gradient(135deg, rgba(0,64,144,0.05), rgba(0,100,180,0.07))', border: 'rgba(0,64,144,0.08)' },
{ icon: '🔀', title: 'Subagents', signal: 'chat.subagents()', href: '/docs/guides/subgraphs', gradient: 'linear-gradient(135deg, rgba(0,64,144,0.05), rgba(0,100,180,0.07))', border: 'rgba(0,64,144,0.08)' },
{ icon: '🔧', title: 'Tool Calls', signal: 'chat.toolCalls()', href: '/docs/guides/streaming', gradient: 'linear-gradient(135deg, rgba(100,80,200,0.05), rgba(120,100,210,0.07))', border: 'rgba(100,80,200,0.08)' },
{ icon: '🧪', title: 'Testing', signal: 'MockTransport', href: '/docs/guides/testing', gradient: 'linear-gradient(135deg, rgba(16,185,129,0.05), rgba(40,200,140,0.07))', border: 'rgba(16,185,129,0.08)' },
];
Expand Down
20 changes: 10 additions & 10 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,17 @@ automatically on `submit()` calls.

---

### Limitation: subagent tracking is deferred
### Limitation: subagent helper methods are not exposed

**Feature:** `subagents()` / `activeSubagents()` / `filterSubagentMessages` /
`subagentToolNames`
**Feature:** `getSubagent()` / `getSubagentsByType()` /
`getSubagentsByMessage()`

**React behavior:** `useStream()` can track Deep Agent subagent execution by
combining subgraph stream events with tool-call registration.
**React behavior:** `useStream()` exposes helper methods for looking up
subagent streams by tool call ID, subagent type, or triggering message.

**Angular behavior:** Tool calls, tool progress, message metadata, and
per-message tool results are implemented. Subagent-specific stream routing is
deferred to the next implementation phase.
**Angular behavior:** `subagents()` and `activeSubagents()` are implemented.
Use the `subagents()` map directly for lookups. Helper methods can be added
later if Angular consumers need parity beyond the signal surface.

**Workaround:** Use `toolCalls()` and `toolProgress()` for tool-level visibility
until dedicated subagent tracking lands.
**Workaround:** Read from `subagents().get(toolCallId)` or filter
`[...subagents().values()]` in a computed signal.
56 changes: 56 additions & 0 deletions libs/langgraph/src/lib/agent.fn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,62 @@ describe('agent', () => {
});
});

it('subagents() and activeSubagents() expose delegated work as signals', async () => {
const transport = new MockAgentTransport();
const ref = withInjectionContext(() =>
agent({
apiUrl: '',
assistantId: 'a',
transport,
throttle: false,
subagentToolNames: ['task'],
filterSubagentMessages: true,
})
);

ref.submit({ message: 'hello' });
transport.emit([{
type: 'messages',
messages: [{
id: 'ai-1',
type: 'ai',
content: '',
tool_calls: [{
id: 'call-1',
name: 'task',
args: { subagent_type: 'researcher', description: 'Research Angular signals' },
}],
}],
} satisfies StreamEvent]);
transport.emit([{
type: 'messages|tools:call-1' as StreamEvent['type'],
namespace: ['tools:call-1'],
messages: [{ id: 'sub-ai-1', type: 'ai', content: 'Subagent note' }],
messageMetadata: { checkpoint_ns: 'tools:call-1|model:abc' },
} satisfies StreamEvent]);

await new Promise(r => setTimeout(r, 20));

expect(ref.activeSubagents()).toHaveLength(1);
expect(ref.activeSubagents()[0].status()).toBe('running');
expect(ref.subagents().get('call-1')?.name).toBe('researcher');
expect(ref.subagents().get('call-1')?.messages()).toEqual([
expect.objectContaining({ id: 'sub-ai-1', role: 'assistant', content: 'Subagent note' }),
]);
expect(ref.messages()).toHaveLength(1);
expect(ref.messages()[0].id).toBe('ai-1');

transport.emit([{
type: 'messages',
messages: [{ id: 'tool-1', type: 'tool', tool_call_id: 'call-1', content: 'done', status: 'success' }],
} satisfies StreamEvent]);
transport.close();
await new Promise(r => setTimeout(r, 20));

expect(ref.activeSubagents()).toHaveLength(0);
expect(ref.subagents().get('call-1')?.status()).toBe('complete');
});

it('events$ is an Observable-like with .subscribe', () => {
const transport = new MockAgentTransport();
const ref = withInjectionContext(() =>
Expand Down
1 change: 1 addition & 0 deletions libs/langgraph/src/lib/agent.fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ function toInterrupt(ix: Interrupt<unknown>): AgentInterrupt {
function toSubagent(sa: SubagentStreamRef): Subagent {
return {
toolCallId: sa.toolCallId,
name: sa.name,
status: sa.status,
messages: computed(() => sa.messages().map(toMessage)) as Signal<Message[]>,
state: sa.values as Signal<Record<string, unknown>>,
Expand Down
14 changes: 14 additions & 0 deletions libs/langgraph/src/lib/agent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,31 @@ export interface StreamEvent {
/** Event type identifier (e.g., 'values', 'messages', 'error', 'interrupt'). */
type:
| 'values'
| `values|${string}`
| 'messages'
| `messages|${string}`
| `messages/${string}`
| `messages/${string}|${string}`
| 'updates'
| `updates|${string}`
| 'tools'
| `tools|${string}`
| 'custom'
| `custom|${string}`
| 'error'
| `error|${string}`
| 'metadata'
| 'checkpoints'
| `checkpoints|${string}`
| 'tasks'
| `tasks|${string}`
| 'debug'
| `debug|${string}`
| 'events'
| `events|${string}`
| 'interrupt'
| 'interrupts';
namespace?: string[];
messages?: unknown[];
messageMetadata?: Record<string, unknown>;
[key: string]: unknown;
Expand Down Expand Up @@ -122,6 +134,8 @@ export interface AgentOptions<T, ResolvedBag extends BagTemplate> {
export interface SubagentStreamRef {
/** The tool call ID that spawned this subagent. */
toolCallId: string;
/** Optional human-readable subagent type/name. */
name?: string;
/** Current execution status of the subagent. */
status: Signal<'pending' | 'running' | 'complete' | 'error'>;
/** Current state values from the subagent. */
Expand Down
Loading
Loading