@mastra/core
Version:
233 lines (159 loc) • 9.91 kB
Markdown
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
**Added in:** `@mastra/core@1.45.0`
> **Warning:** Durable agents are currently in **beta**. APIs may change in future releases.
A durable agent wraps a regular [`Agent`](https://mastra.ai/docs/agents/overview) so the agentic loop runs inside a workflow. Events flow through [PubSub](https://mastra.ai/docs/server/pubsub), which means a client can disconnect and reconnect without missing chunks. The run state is persisted, so it survives process restarts.
Use a durable agent when any of the following apply:
- The client may drop and reconnect mid-stream (mobile, spotty networks, long-running calls).
- The agentic loop may outlive a single HTTP request (background research, multi-step tool use).
- You need an observe/reconnect API where a second client picks up a stream that a first client started.
- You want [Inngest-powered execution](https://mastra.ai/guides/deployment/inngest) with step memoization, retries, and monitoring.
For short-lived, request-scoped calls where the client stays connected, a regular `Agent` with `stream()` or `generate()` is simpler.
## Quickstart
Wrap an existing agent with `createDurableAgent()` from `@mastra/core/agent/durable`:
```typescript
import { Agent } from '@mastra/core/agent'
import { createDurableAgent } from '@mastra/core/agent/durable'
const agent = new Agent({
id: 'researcher',
name: 'Researcher',
instructions: 'You research topics thoroughly.',
model: 'openai/gpt-5.5',
})
export const durableResearcher = createDurableAgent({ agent })
```
Register the durable agent with Mastra and call `stream()`:
```typescript
import { Mastra } from '@mastra/core'
import { durableResearcher } from './agents/researcher'
const mastra = new Mastra({
agents: { durableResearcher },
})
const { output, runId, cleanup } = await durableResearcher.stream(
'Research quantum computing advances in 2025',
)
for await (const chunk of output.fullStream) {
// Process each chunk as it arrives
}
// Release PubSub subscriptions and clear the run from the registry.
// If you skip this, an automatic cleanup timer fires after the stream ends.
cleanup()
```
The returned `runId` identifies the execution. Pass it to `observe()` to reconnect from a different client.
> **Note:** Visit the [DurableAgent reference](https://mastra.ai/reference/agents/durable-agent) for the full configuration and method API.
A durable agent adds three layers on top of a regular agent:
1. **Workflow execution**: `stream()` serializes the messages and options into a workflow input, then triggers the agentic loop inside a durable workflow. The workflow runs the same loop as `Agent.stream()` but each step can be memoized and replayed.
2. **PubSub streaming**: As the loop runs, chunks are published to a PubSub topic keyed by the run ID. The caller subscribes to this topic and pipes chunks into a `ReadableStream`. If the caller disconnects and reconnects, missed chunks are replayed from the cache.
3. **Cache layer**: An optional cache (in-memory by default, Redis or another backend in production) stores published events so that a late subscriber can catch up.
## Execution variants
Mastra provides three factory functions that produce durable agents. They differ in how the workflow is executed:
| Factory | Package | Best for |
| ---------------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
| `createDurableAgent()` | `@mastra/core` | Local development and single-process servers. You get a stream you can await directly. |
| `createEventedAgent()` | `@mastra/core` | Background execution. The workflow starts without blocking, and you consume chunks through PubSub. |
| `createInngestAgent()` | `@mastra/inngest` | Production deployments. Inngest adds step memoization, retries, and a monitoring dashboard. |
All three return an object you register with `Mastra` the same way as a regular agent. `createDurableAgent()` and `createEventedAgent()` return class instances that extend `Agent`. `createInngestAgent()` returns a Proxy-backed object that forwards `Agent` methods to the underlying agent.
### In-process with `createDurableAgent()`
Wrap your agent and call `stream()`. You get a `DurableAgentStreamResult` back in the same process. No external infrastructure is required, so this is the fastest way to get started:
```typescript
import { Agent } from '@mastra/core/agent'
import { createDurableAgent } from '@mastra/core/agent/durable'
const agent = new Agent({
id: 'helper',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-5.5',
})
export const durableHelper = createDurableAgent({ agent })
```
The workflow starts in the background without blocking the caller. You still receive chunks through PubSub, so `stream()` returns a result you can consume. The HTTP handler that triggered the run does not need to wait for the workflow to finish:
```typescript
import { Agent } from '@mastra/core/agent'
import { createEventedAgent } from '@mastra/core/agent/durable'
const agent = new Agent({
id: 'writer',
instructions: 'You write articles.',
model: 'openai/gpt-5.5',
})
export const eventedWriter = createEventedAgent({ agent })
```
Run the workflow on the [Inngest](https://www.inngest.com/docs) platform. Each tool call becomes a memoized step that Inngest can retry independently, and you get a dashboard for monitoring runs:
```typescript
import { Agent } from '@mastra/core/agent'
import { createInngestAgent } from '@mastra/inngest'
import { Inngest } from 'inngest'
const inngest = new Inngest({ id: 'my-app' })
const agent = new Agent({
id: 'analyst',
instructions: 'You analyze data.',
model: 'openai/gpt-5.5',
})
export const inngestAnalyst = createInngestAgent({ agent, inngest })
```
> **Note:** Visit the [`createInngestAgent()` reference](https://mastra.ai/reference/agents/inngest-agent) for the full API, including Inngest-specific options like PubSub and cache configuration.
Durable agents support resumable streams through PubSub and an event cache. When a client disconnects mid-stream, the cache continues storing events. The same client can reconnect by calling `observe()` with the `runId`:
```typescript
const { output, cleanup } = await durableResearcher.observe(runId)
for await (const chunk of output.fullStream) {
// Chunks from the run, including any missed while disconnected
}
cleanup()
```
`createDurableAgent()` and `createEventedAgent()` use an in-memory cache by default, which means resumable streams work within a single process. For production, provide a persistent cache backend (e.g., Redis) so cached events survive process restarts:
```typescript
import { createDurableAgent } from '@mastra/core/agent/durable'
import { RedisServerCache } from '@mastra/redis'
const cache = new RedisServerCache({ url: 'redis://localhost:6379' })
export const durableAgent = createDurableAgent({
agent,
cache,
})
```
`createInngestAgent()` does not enable caching by default. Pass a `cache` option or register the agent with a `Mastra` instance that has a `serverCache` configured to enable resumable streams.
Durable agents support the same [`untilIdle`](https://mastra.ai/reference/streaming/agents/stream) option as regular agents. When `untilIdle` is set, `stream()` keeps the connection open across background-task continuations until the agent is idle:
```typescript
const { output, cleanup } = await durableAgent.stream('Research and summarize the topic', {
untilIdle: true,
memory: { thread: 'thread-1', resource: 'user-1' },
})
for await (const chunk of output.fullStream) {
// Chunks from the initial turn AND any follow-up turns triggered by
// background task completions
}
cleanup()
```
Pass `{ maxIdleMs }` to customize the idle timeout (defaults to 5 minutes):
```typescript
await durableAgent.stream('Research topic', {
untilIdle: { maxIdleMs: 30_000 },
memory: { thread: 'thread-1', resource: 'user-1' },
})
```
> **Note:** Visit [Background tasks](https://mastra.ai/docs/long-running-agents/background-tasks) for the full background task guide, including configuration, subagents, and suspend/resume.
## Cleanup
Every `stream()` and `observe()` call returns a `cleanup` function. Calling it unsubscribes from PubSub and removes the run from the internal registry. If you forget to call it, an automatic timer fires after the stream ends, but calling `cleanup()` yourself frees resources immediately.
## Tool approval
Durable agents support tool approval (human-in-the-loop). When a tool call requires approval, the workflow suspends, emits an `onSuspended` callback, and waits for the caller to resume with `resume()`:
```typescript
const { output, runId, cleanup } = await durableAgent.stream('Delete the old records', {
requireToolApproval: true,
onSuspended: ({ toolCallId, toolName, args }) => {
// Notify the user and ask for approval
},
})
```
Resume the suspended run after approval:
```typescript
await durableAgent.resume(runId, { approved: true })
```
- [DurableAgent reference](https://mastra.ai/reference/agents/durable-agent)
- [`createInngestAgent()` reference](https://mastra.ai/reference/agents/inngest-agent)
- [Background tasks](https://mastra.ai/docs/long-running-agents/background-tasks)
- [Inngest deployment guide](https://mastra.ai/guides/deployment/inngest)
- [Agent overview](https://mastra.ai/docs/agents/overview)