UNPKG

@mastra/core

Version:
380 lines (238 loc) 17.7 kB
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt # DurableAgent `DurableAgent` wraps an existing [`Agent`](https://mastra.ai/reference/agents/agent) with durable execution and resumable streams. It runs the agentic loop so that a client can disconnect and reconnect without missing events, and it streams those events over [PubSub](https://mastra.ai/docs/server/pubsub). Use it when a run must outlive a single request or survive a dropped connection. Create one with the [`createDurableAgent`](#createdurableagentoptions) factory, or use [`createEventedAgent`](#createeventedagentoptions) for fire-and-forget execution on the built-in workflow engine. For Inngest-powered execution, use [`createInngestAgent`](https://mastra.ai/reference/agents/inngest-agent) from `@mastra/inngest`. ## Usage example ```typescript import { Mastra } from '@mastra/core' import { Agent } from '@mastra/core/agent' import { createDurableAgent } from '@mastra/core/agent/durable' const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: 'openai/gpt-5.6-sol', }) const durableAgent = createDurableAgent({ agent }) export const mastra = new Mastra({ agents: { myAgent: durableAgent }, }) ``` Stream a response and read the result. The `cleanup` function unsubscribes from PubSub when you are done with the run: ```typescript const { output, runId, cleanup } = await durableAgent.stream('Hello!') const text = await output.text cleanup() ``` ### Using the `durable` config flag Set `durable: true` on `AgentConfig` and the agent is automatically wrapped with `createDurableAgent` when it's attached to a `Mastra` instance. Use an object to forward advanced options such as `cache`, `pubsub`, `maxSteps`, or `cleanupTimeoutMs`. ```typescript import { Mastra } from '@mastra/core' import { Agent } from '@mastra/core/agent' const myAgent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: 'openai/gpt-5.6-sol', durable: true, // or: { maxSteps: 10, cleanupTimeoutMs: 60_000 } }) export const mastra = new Mastra({ agents: { myAgent }, }) ``` `mastra.getAgent('myAgent')` returns the wrapped `DurableAgent`. Standalone agents (constructed but never registered on a `Mastra` instance) don't become durable. The wrapping is applied at registration. ## `createDurableAgent(options)` Wraps an `Agent` with durable execution and resumable streams. This is the recommended way to create a `DurableAgent`. ```typescript import { createDurableAgent } from '@mastra/core/agent/durable' const durableAgent = createDurableAgent({ agent }) ``` Returns: `DurableAgent` ### Parameters **agent** (`Agent`): The Agent to wrap with durable execution capabilities. Agent methods delegate to this agent. **id** (`string`): ID override. (Default: `agent.id`) **name** (`string`): Name override. (Default: `agent.name`) **cache** (`MastraServerCache | false`): Cache for stored stream events, which enables resumable streams. If omitted, the agent inherits the cache from the Mastra instance, or uses an InMemoryServerCache. Set to false to disable caching, which makes streams non-resumable. **pubsub** (`PubSub`): PubSub instance for streaming events. (Default: `EventEmitterPubSub`) **maxSteps** (`number`): Maximum number of steps for the agentic loop. ## `createEventedAgent(options)` Wraps an `Agent` with fire-and-forget durable execution on the built-in workflow engine. Like `createDurableAgent`, it returns a result you stream from, but the underlying workflow runs non-blocking (via `startAsync`) instead of running to completion before the stream is wired up. Use it when you want the run to progress independently of the caller. It doesn't accept `id` or `name` overrides. ```typescript import { createEventedAgent } from '@mastra/core/agent/durable' const eventedAgent = createEventedAgent({ agent }) ``` Returns: `EventedAgent` (a subclass of `DurableAgent`) ### Parameters **agent** (`Agent`): The Agent to wrap with evented durable execution capabilities. **cache** (`MastraServerCache | false`): Cache for stored stream events, which enables resumable streams. If omitted, the agent inherits the cache from the Mastra instance, or uses an InMemoryServerCache. Set to false to disable caching. **pubsub** (`PubSub`): PubSub instance for streaming events. (Default: `EventEmitterPubSub`) **maxSteps** (`number`): Maximum number of steps for the agentic loop. ## Constructor parameters The `DurableAgent` class accepts the same options as `createDurableAgent`, plus `cleanupTimeoutMs`. Prefer the factory unless you need to subclass. **agent** (`Agent`): The Agent to wrap with durable execution capabilities. **id** (`string`): ID override. (Default: `agent.id`) **name** (`string`): Name override. (Default: `agent.name`) **cache** (`MastraServerCache | false`): Cache for stored stream events. If omitted, inherits from the Mastra instance or uses an InMemoryServerCache. Set to false to disable caching. **pubsub** (`PubSub`): PubSub instance for streaming events. (Default: `EventEmitterPubSub`) **maxSteps** (`number`): Maximum number of steps for the agentic loop. **cleanupTimeoutMs** (`number`): Grace period in milliseconds before registry entries are cleaned up automatically after a stream finishes or errors. Set to 0 to disable auto-cleanup and require a manual cleanup() call. Auto-cleanup does not fire on suspended events. (Default: `30000`) ## Methods ### Execution #### `stream(messages, options?)` Streams a response using durable execution. Returns immediately with a result whose `output` produces events as the run progresses. ```typescript const { output, runId, cleanup } = await durableAgent.stream('Hello!', { onChunk: chunk => console.log(chunk), onFinish: result => console.log('done', result), }) const text = await output.text cleanup() ``` Returns: [`Promise<DurableAgentStreamResult>`](#durableagentstreamresult) #### `resume(runId, resumeData, options?)` Resumes a suspended run, for example after a tool approval. Pass the `runId` from the original stream and the data the run was waiting on. Throws if no registry entry exists for the run. ```typescript const { output, cleanup } = await durableAgent.resume(runId, { approved: true, }) await output.text cleanup() ``` Returns: [`Promise<DurableAgentStreamResult>`](#durableagentstreamresult) #### `observe(runId, options?)` Reconnects to an existing run, replaying cached events before delivering live ones. Use this after a network disconnection. Pass `offset` to start replay from a known position. ```typescript const { output, cleanup } = await durableAgent.observe(runId, { offset: 0, onChunk: chunk => console.log(chunk), }) await output.text ``` By default `observe()` waits indefinitely for events. If the process running the run stops unexpectedly, the run stops producing events but never emits a completion event, so the observed stream would wait forever. Pass `idleTimeoutMs` to bound that wait: after that many milliseconds of silence the stream ends. An optional `isAlive` check is consulted first. Return `true` while the run is still being worked on (for example a long-running tool call, or a run paused waiting for human input) to keep waiting. Returning `false`, or omitting `isAlive`, ends the stream with an error. A transient throw from `isAlive` is treated as "still alive", so a momentary check failure never ends a live stream. ```typescript const { output } = await durableAgent.observe(runId, { idleTimeoutMs: 30_000, isAlive: () => runHeartbeat.isFresh(runId), }) ``` Ending a run on idle timeout runs the same cleanup as a run that errors (see the warning below), so its cached state is released rather than retained. Both options are opt-in. Omit them for the previous wait-indefinitely behavior. Returns: `Promise<DurableAgentStreamResult>` > **Warning:** The `cleanup()` returned by `observe()` destroys the run's registry entries and cached events. Only call it when you are done with the run. If the run is suspended and you intend to resume later, don't call `cleanup()`. Let the auto-cleanup timer handle it after the run finishes or errors. Auto-cleanup doesn't fire on suspended events. #### `prepare(messages, options?)` Prepares a run for durable execution without starting it. Registers the run in the internal registry and returns the serialized workflow input. Use this when you need to control when and how the workflow is triggered. ```typescript const { runId, messageId, workflowInput, threadId, resourceId } = await durableAgent.prepare( 'Summarize the document', { memory: { threadId: 'thread-1', resourceId: 'user-1' }, }, ) ``` Returns: ```typescript interface PrepareResult { runId: string messageId: string workflowInput: any registryEntry: object threadId?: string resourceId?: string } ``` ### Recovery #### `recoverActiveRuns(options?)` Discovers runs stuck in `running` status for this agent and re-drives them from the last persisted snapshot. Recovers up to `options.limit` runs (default: 100). Returns a summary of what was recovered. ```typescript const result = await durableAgent.recoverActiveRuns() // { recovered: [{ runId, status }], succeeded: 2, failed: 0 } ``` Pass a `runId` to recover a single known run: ```typescript await durableAgent.recoverActiveRuns({ runId: 'run-abc-123' }) ``` Returns: ```typescript interface DurableAgentRecoverActiveRunsResult { recovered: Array<{ runId: string; status: 'success' | 'failed'; error?: Error }> succeeded: number failed: number } ``` **options.runId** (`string`): Recover a specific run by ID. When set, discovery filters are ignored. **options.limit** (`number`): Maximum number of active runs to discover. Defaults to 100. **options.createdBefore** (`Date`): Only recover runs created before this date. #### `recover(runId, options?)` Recovers a single run by ID. Returns a streamable result with the same shape as `stream()`. Use this when you need to observe the recovery stream in real time. ```typescript const { output, cleanup } = await durableAgent.recover('run-abc-123', { onChunk: chunk => console.log(chunk), onError: ({ error }) => console.error(error), }) await output.text cleanup() ``` Returns: [`Promise<DurableAgentStreamResult>`](#durableagentstreamresult) ## Stream options `stream()` accepts a `DurableAgentStreamOptions` object. It supports the agent execution options below, plus lifecycle callbacks. **runId** (`string`): Unique identifier for this run. Use it later with resume() or observe(). **instructions** (`AgentExecutionOptions['instructions']`): Overrides the agent's default instructions for this run. Accepts a static string or the same dynamic instructions value the agent supports. **context** (`ModelMessage[]`): Additional context messages to provide to the agent. **memory** (`object`): Memory configuration for conversation persistence and retrieval. **requestContext** (`RequestContext`): Request context carrying dynamic configuration and state for this run. **maxSteps** (`number`): Maximum number of steps to run for this stream. **toolsets** (`object`): Additional tool sets available for this run. **clientTools** (`object`): Client-side tools available during execution. **toolChoice** (`'auto' | 'none' | 'required' | { type: 'tool'; toolName: string }`): Tool selection strategy. **activeTools** (`string[]`): Restricts execution to the named subset of the agent's tools. **modelSettings** (`object`): Model-specific settings such as temperature. Credential-bearing headers (Authorization, X-Api-Key, and similar) are stripped from the serialized snapshot before it crosses process boundaries. **stopWhen** (`AgentExecutionOptions['stopWhen']`): Predicate or composition that ends the agentic loop early. The closure rides on the in-process run registry; cross-process resumes degrade to maxSteps only. **system** (`string | string[]`): Additional system message appended after the agent instructions and before user messages. **requireToolApproval** (`boolean | ((args: { toolName: string; args: unknown; requestContext: RequestContext; workspace?: string }) => boolean | Promise<boolean>)`): Require approval for tool calls. Pass true or false to gate all or none, or a function for per-call policy. Function-form policies live on the in-process run registry; cross-process resumes fall back to a true shadow. **autoResumeSuspendedTools** (`boolean`): Automatically resume tools that suspended, instead of waiting for an external resume() call. **toolCallConcurrency** (`number`): Maximum number of tool calls to execute concurrently. **includeRawChunks** (`boolean`): Include raw provider chunks in the stream output. **maxProcessorRetries** (`number`): Maximum number of processor retries per generation. **structuredOutput** (`object`): Structured output configuration. **untilIdle** (`boolean | { maxIdleMs?: number }`): When set, keeps the stream open across background-task continuations until the agent is idle. Pass true for the default 5-minute idle timeout, or { maxIdleMs } to customise. Equivalent to the deprecated streamUntilIdle() method. Also supported on resume(). **disableBackgroundTasks** (`boolean`): Disable background-task dispatch for this run. Background-eligible tools execute inline instead. **tracingOptions** (`AgentExecutionOptions['tracingOptions']`): Tracing metadata, tags, trace ID, parent span ID, and requestContextKeys forwarded to the agent and model spans. Fully JSON-serializable. **actor** (`AgentExecutionOptions['actor']`): Per-call actor signal forwarded to FGA checks and tool execution. **transform** (`AgentExecutionOptions['transform']`): Per-invocation tool payload transform policy. The transformToolPayload closure lives on the in-process run registry; only the JSON-safe targets shadow is serialized. **prepareStep** (`AgentExecutionOptions['prepareStep']`): Per-step preparation hook invoked as a PrepareStepProcessor at the start of every iteration. Closure-only — stored on the in-process run registry. Cross-process resumes lose the hook. **isTaskComplete** (`AgentExecutionOptions['isTaskComplete']`): Per-call completion policy. Scorer instances and onComplete live on the in-process run registry; the JSON-safe primitives (strategy, timeout, parallel, suppressFeedback, scorerNames) are serialized for cross-process observability. **delegation** (`AgentExecutionOptions['delegation']`): Sub-agent delegation hooks (onDelegationStart, onDelegationComplete, messageFilter). Callbacks are baked into the sub-agent tool wrappers at prepare time. Cross-process resumes lose the callbacks. **versions** (`object`): Version overrides for sub-agent delegation. **abortSignal** (`AbortSignal`): External abort signal. Forwarded to the durable run's internal AbortController, so either source can cancel the run. Cross-process resumes cannot recover the signal — pass a fresh one to resume() if you need post-resume abortability. **onChunk** (`(chunk: ChunkType) => void | Promise<void>`): Called for each streamed chunk. **onStepFinish** (`(result: AgentStepFinishEventData) => void | Promise<void>`): Called when a step in the agentic loop finishes. **onFinish** (`(result: AgentFinishEventData) => void | Promise<void>`): Called when the run finishes. **onError** (`(error: Error) => void | Promise<void>`): Called when the run errors. **onSuspended** (`(data: AgentSuspendedEventData) => void | Promise<void>`): Called when the run suspends, for example for tool approval. **onAbort** (`AgentExecutionOptions['onAbort']`): Called when the run is aborted via abortSignal or result.abort(). **onIterationComplete** (`AgentExecutionOptions['onIterationComplete']`): Called after every agentic-loop iteration with the latest messageList, finishReason, and isFinal flag. Observation-only on durable agents: returning continue: false or feedback does not influence the loop. `resume()` and `observe()` accept the same lifecycle callbacks (`onChunk`, `onStepFinish`, `onFinish`, `onError`, `onSuspended`). `observe()` also accepts an `offset` to control where replay starts. ## DurableAgentStreamResult The object returned by `stream()`, `resume()`, `observe()`, and `recover()`. ```typescript interface DurableAgentStreamResult<OUTPUT = undefined> { output: MastraModelOutput<OUTPUT> readonly fullStream: ReadableStream<any> runId: string threadId?: string resourceId?: string cleanup: () => void abort: () => void } ``` **output** (`MastraModelOutput`): The streaming output. Await output.text for the full text, or consume output.fullStream. **fullStream** (`ReadableStream`): The full event stream, delegating to output.fullStream. **runId** (`string`): The unique run ID. Pass it to resume() or observe() to reconnect. **threadId** (`string`): Thread ID when using memory. **resourceId** (`string`): Resource ID when using memory. **cleanup** (`() => void`): Unsubscribes from PubSub and clears registry entries for the run. Call it when done with the run. **abort** (`() => void`): Aborts the run by flipping the internal AbortController. Surfaces as an AbortError inside the durable LLM-execution step and fires the onAbort callback. Safe to call after the run has finished — a no-op in that case. ## Related - [`createInngestAgent()`](https://mastra.ai/reference/agents/inngest-agent) - [Agent class](https://mastra.ai/reference/agents/agent) - [PubSub](https://mastra.ai/docs/server/pubsub) - [`.getMemory()`](https://mastra.ai/reference/agents/getMemory)