diff --git a/src/content/docs/agents/api-reference/durable-execution.mdx b/src/content/docs/agents/api-reference/durable-execution.mdx index ded710684a0dd0f..e79704784064ab6 100644 --- a/src/content/docs/agents/api-reference/durable-execution.mdx +++ b/src/content/docs/agents/api-reference/durable-execution.mdx @@ -190,6 +190,8 @@ Sub-agents do not have independent alarm slots, so the top-level parent owns the This keeps recovery local to the child while preserving the single physical alarm slot owned by the parent. A recovered continuation can use `schedule()` from inside the facet; the parent owns the physical alarm and routes the callback back to the child. +Workflows started from sub-agents follow the same locality model for tracking and callbacks. The workflow row lives in the sub-agent's SQLite database, and `AgentWorkflow.agent` routes RPC and workflow callbacks back to the originating facet. + #### Error during execution ```txt diff --git a/src/content/docs/agents/api-reference/run-workflows.mdx b/src/content/docs/agents/api-reference/run-workflows.mdx index 6fce219237104d3..70435faaba9314d 100644 --- a/src/content/docs/agents/api-reference/run-workflows.mdx +++ b/src/content/docs/agents/api-reference/run-workflows.mdx @@ -51,7 +51,7 @@ export class ProcessingWorkflow extends AgentWorkflow { }); // Broadcast to connected WebSocket clients - this.broadcastToClients({ type: "update", taskId: params.taskId }); + await this.broadcastToClients({ type: "update", taskId: params.taskId }); await step.do("save-results", async () => { // Call Agent methods via RPC @@ -184,7 +184,7 @@ Broadcast a message to all WebSocket clients connected to the Agent. ```ts -this.broadcastToClients({ type: "update", data: result }); +await this.broadcastToClients({ type: "update", data: result }); ``` @@ -234,17 +234,17 @@ Methods available on the `Agent` class for Workflow management. ### runWorkflow(workflowName, params, options?) -Start a workflow instance and track it in the Agent database. +Start a workflow instance and track it in the originating Agent's database. **Parameters:** -| Parameter | Type | Description | -| ---------------------- | ------ | ----------------------------------------------------- | -| `workflowName` | string | Workflow binding name from `env` | -| `params` | object | Parameters to pass to the workflow | -| `options.id` | string | Custom workflow ID (auto-generated if not provided) | -| `options.metadata` | object | Metadata stored for querying (not passed to workflow) | -| `options.agentBinding` | string | Agent binding name (auto-detected if not provided) | +| Parameter | Type | Description | +| ---------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | +| `workflowName` | string | Workflow binding name from `env` | +| `params` | object | Parameters to pass to the workflow | +| `options.id` | string | Custom workflow ID (auto-generated if not provided) | +| `options.metadata` | object | Metadata stored for querying (not passed to workflow) | +| `options.agentBinding` | string | Agent binding name (auto-detected if not provided). When called from a sub-agent, this is the root Agent binding name. | **Returns:** `Promise` - Workflow instance ID @@ -262,6 +262,46 @@ const instanceId = await this.runWorkflow( +#### Sub-agents + +Sub-agents can call `this.runWorkflow()` directly. The workflow is tracked in the originating sub-agent's SQLite database, and `this.agent` inside `AgentWorkflow` routes back to that same sub-agent for RPC calls, callbacks, state updates, and broadcasts. + +Parent agents do not automatically list or control workflows that a sub-agent starts. `SubAgentStub` only exposes user-defined methods, not inherited `Agent` methods such as `approveWorkflow()` or `getWorkflow()`. To control a child-started workflow from the parent, define small wrapper methods on the child and call those wrappers through the sub-agent stub. + + + +```ts +export class ParentAgent extends Agent { + async startChildWorkflow(childName: string, task: string) { + const child = await this.subAgent(ChildAgent, childName); + return child.startWorkflow(task); + } + + async approveChildWorkflow(childName: string, workflowId: string) { + const child = await this.subAgent(ChildAgent, childName); + return child.approveChildWorkflow(workflowId); + } +} + +export class ChildAgent extends Agent { + async startWorkflow(task: string) { + return this.runWorkflow("CHILD_WORKFLOW", { task }); + } + + async approveChildWorkflow(workflowId: string) { + return this.approveWorkflow(workflowId); + } + + async getChildWorkflow(workflowId: string) { + return this.getWorkflow(workflowId); + } +} +``` + + + +For sub-agent origins, `AgentWorkflow.agent` is an RPC-only stub. Use it to call Agent methods, but use `routeSubAgentRequest()` or the `/agents/{parent}/{name}/sub/{child}/{name}` URL shape for external HTTP or WebSocket routing instead of `this.agent.fetch()`. + ### sendWorkflowEvent(workflowName, instanceId, event) Send an event to a running workflow. @@ -542,7 +582,7 @@ class MyAgent extends Agent { ## Workflow tracking -Workflows started with `runWorkflow()` are automatically tracked in the Agent's internal database. You can query, filter, and manage workflows using the methods described above (`getWorkflow()`, `getWorkflows()`, `deleteWorkflow()`, etc.). +Workflows started with `runWorkflow()` are automatically tracked in the originating Agent's internal database. If a sub-agent starts the workflow, the tracking record belongs to that sub-agent, not its parent. Query, filter, and manage those workflows from the originating Agent instance. From a parent, define child wrapper methods that call the workflow methods described above (`getWorkflow()`, `getWorkflows()`, `deleteWorkflow()`, etc.) inside the child. ### Status values @@ -781,7 +821,7 @@ const data = await this.agent.getData(taskId); // Non-durable callbacks (may repeat on retry, use for frequent updates) await this.reportProgress({ step: "process", percent: 0.5 }); -this.broadcastToClients({ type: "update", data }); +await this.broadcastToClients({ type: "update", data }); // Durable callbacks via step (idempotent, won't repeat on retry) await step.reportComplete(result); @@ -795,6 +835,8 @@ await step.mergeAgentState({ progress: 0.5 }); +If a sub-agent started the workflow, `this.agent` routes RPC back to that originating facet. It does not support `.fetch()`; use `routeSubAgentRequest()` or the nested `/agents/.../sub/...` URL shape for external HTTP or WebSocket traffic. + ### Agent to Workflow diff --git a/src/content/docs/agents/api-reference/store-and-sync-state.mdx b/src/content/docs/agents/api-reference/store-and-sync-state.mdx index d0b98cd996add6a..5e7765ace0f8a20 100644 --- a/src/content/docs/agents/api-reference/store-and-sync-state.mdx +++ b/src/content/docs/agents/api-reference/store-and-sync-state.mdx @@ -450,7 +450,7 @@ class MyWorkflow extends Workflow { -These are durable operations - they persist even if the workflow retries. +These are durable operations - they persist even if the workflow retries. They update the workflow's originating Agent. If a sub-agent started the workflow, the state update applies to that sub-agent facet and broadcasts to that facet's clients. ## SQL API diff --git a/src/content/docs/agents/api-reference/sub-agents.mdx b/src/content/docs/agents/api-reference/sub-agents.mdx index 1a64bdde846395e..43727796e2341e0 100644 --- a/src/content/docs/agents/api-reference/sub-agents.mdx +++ b/src/content/docs/agents/api-reference/sub-agents.mdx @@ -88,6 +88,10 @@ class Agent { Returns a `SubAgentStub` — a typed RPC stub where every user-defined method on `T` is available as a Promise-returning remote call. +Sub-agents can start [Workflows](/agents/api-reference/run-workflows/) with `this.runWorkflow()`. Workflow tracking is local to the sub-agent's SQLite database, and `AgentWorkflow.agent` routes RPC, callbacks, state updates, and broadcasts back to the originating sub-agent. Parent agents do not automatically list or control child-started workflows. Because `SubAgentStub` only exposes user-defined child methods, add child wrapper methods for controls such as `getWorkflow()`, `approveWorkflow()`, or `terminateWorkflow()`, then call those wrappers through `await this.subAgent(Child, name)`. + +For sub-agent workflow origins, `AgentWorkflow.agent` is RPC-only. Use it to call Agent methods, but use `routeSubAgentRequest()` or the nested `/agents/{parent}/{name}/sub/{child}/{name}` URL shape for external HTTP or WebSocket routing instead of `this.agent.fetch()`. If you pass `runWorkflow(..., { agentBinding })` from a sub-agent, use the root Agent binding name, not a child binding name. + ### SubAgentStub The stub exposes all public instance methods you define on the child class. Methods inherited from `Agent` (lifecycle hooks, `setState`, `broadcast`, `sql`, and so on) are excluded — only your custom methods appear on the stub. diff --git a/src/content/docs/agents/concepts/human-in-the-loop.mdx b/src/content/docs/agents/concepts/human-in-the-loop.mdx index 37465d2bc8fcf15..de3c9a948ede4b0 100644 --- a/src/content/docs/agents/concepts/human-in-the-loop.mdx +++ b/src/content/docs/agents/concepts/human-in-the-loop.mdx @@ -33,13 +33,13 @@ Human-in-the-Loop (HITL) workflows integrate human judgment and oversight into a The Agents SDK provides five patterns for human-in-the-loop. Choose based on your architecture: -| Use Case | Pattern | Best For | -| --- | --- | --- | +| Use Case | Pattern | Best For | +| ---------------------- | ----------------- | ------------------------------------------------------------------------- | | Long-running workflows | Workflow Approval | Multi-step processes, durable approval gates that can wait hours or weeks | -| AIChatAgent tools | `needsApproval` | Chat-based tool calls with server-side approval before execution | -| Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction before execution | -| MCP servers | Elicitation | MCP tools requesting structured user input during execution | -| Simple confirmations | State + WebSocket | Lightweight approval flows without AI chat or workflows | +| AIChatAgent tools | `needsApproval` | Chat-based tool calls with server-side approval before execution | +| Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction before execution | +| MCP servers | Elicitation | MCP tools requesting structured user input during execution | +| Simple confirmations | State + WebSocket | Lightweight approval flows without AI chat or workflows | ### Decision tree @@ -65,6 +65,8 @@ For durable, multi-step processes with approval gates that can wait hours, days, - `approveWorkflow(workflowId, { reason?, metadata? })` — Approve a waiting workflow - `rejectWorkflow(workflowId, { reason? })` — Reject a waiting workflow +If a sub-agent starts the workflow, approval and rejection are scoped to that sub-agent. Parent agents should resolve the child with `subAgent()` and call child-defined wrapper methods that run the workflow control methods inside the child. + **Best for:** Expense approvals, content publishing pipelines, data export requests ## Pattern 2: `needsApproval` (AI chat tools) diff --git a/src/content/docs/agents/concepts/long-running-agents.mdx b/src/content/docs/agents/concepts/long-running-agents.mdx index 482c32aed0eb66f..6d9a23569cb0a86 100644 --- a/src/content/docs/agents/concepts/long-running-agents.mdx +++ b/src/content/docs/agents/concepts/long-running-agents.mdx @@ -222,6 +222,8 @@ try { | Minutes to hours | [Workflows](/agents/concepts/workflows/) | | Hours to days | Async pattern: start job, hibernate, wake on completion | +If a sub-agent starts a workflow, tracking and workflow controls are scoped to that sub-agent. A parent can still coordinate the workflow by resolving the child with `subAgent()` and calling child-defined wrapper methods that run `getWorkflow()`, `approveWorkflow()`, `terminateWorkflow()`, or cleanup helpers inside the child. + ## Surviving crashes: fibers and recovery An agent can be evicted at any time — a deploy, a platform restart, or hitting resource limits. If the agent was mid-task, that work is lost unless it was checkpointed. @@ -603,6 +605,8 @@ export class ProjectManager extends Agent { ), }); + // Clean up old workflow tracking records started by this same agent. + // Child-started workflows must be cleaned up on the child facet. this.deleteWorkflows({ status: ["complete", "errored"], createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), diff --git a/src/content/docs/agents/concepts/workflows.mdx b/src/content/docs/agents/concepts/workflows.mdx index 07b5fcdee4925a1..8d1fe8fd0098dc7 100644 --- a/src/content/docs/agents/concepts/workflows.mdx +++ b/src/content/docs/agents/concepts/workflows.mdx @@ -121,7 +121,7 @@ This durability model means workflows are well-suited for tasks where partial co ## Workflow tracking -When an Agent starts a workflow using `runWorkflow()`, the workflow is automatically tracked in the Agent's internal database. This enables: +When an Agent or sub-agent starts a workflow using `runWorkflow()`, the workflow is automatically tracked in that originating Agent's internal database. If a sub-agent starts the workflow, parent agents do not automatically list or control that workflow. To manage it from the parent, define child methods that call `getWorkflow()`, `approveWorkflow()`, `terminateWorkflow()`, or other workflow methods inside the child, then call those methods through the sub-agent stub. - Querying workflow status by ID, name, or metadata with cursor-based pagination - Monitoring progress through lifecycle callbacks (`onWorkflowProgress`, `onWorkflowComplete`, `onWorkflowError`) @@ -182,6 +182,8 @@ const result = await step.do( A Workflow updates Agent state at key milestones using `step.updateAgentState()` or `step.mergeAgentState()`. These state changes broadcast to all connected clients, keeping UIs synchronized without polling. +If a sub-agent started the workflow, `AgentWorkflow.agent` routes RPC and state updates back to that originating facet. It is RPC-only for sub-agent origins; use sub-agent HTTP/WebSocket routing for external `fetch()` traffic. + ## Related resources { +If a sub-agent starts the workflow, approval and rejection methods are scoped to that sub-agent. Parent agents should resolve the child with `subAgent()` and call child-defined wrapper methods that run `approveWorkflow()` or `rejectWorkflow()` inside the child; `onWorkflowProgress()`, `onWorkflowComplete()`, and related callbacks also run on the originating sub-agent. + ### Timeout handling Set timeouts to prevent workflows from waiting indefinitely: