@mastra/mcp-docs-server 1.1.49-alpha.0 → 1.1.49-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docs/docs/harness/modes.md +116 -0
- package/.docs/docs/harness/overview.md +129 -0
- package/.docs/docs/harness/session.md +136 -0
- package/.docs/docs/harness/subagents.md +109 -0
- package/.docs/docs/harness/threads-and-state.md +134 -0
- package/.docs/docs/harness/tool-approvals.md +145 -0
- package/.docs/docs/mastra-platform/server.md +3 -3
- package/.docs/reference/harness/harness-class.md +124 -186
- package/.docs/reference/harness/session.md +429 -0
- package/.docs/reference/index.md +1 -0
- package/CHANGELOG.md +14 -0
- package/package.json +4 -4
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Modes
|
|
2
|
+
|
|
3
|
+
Modes define the different personalities or capability sets available within a Harness. Each mode layers its own instructions and tool overrides on top of a shared backing agent. The Harness ensures only one mode is active at a time and handles transitions between them.
|
|
4
|
+
|
|
5
|
+
## When to use modes
|
|
6
|
+
|
|
7
|
+
Use modes when a single conversational session needs to switch between distinct behaviors. For example, a planning mode that reasons about tasks and a build mode that executes them. Modes share the same thread and state, so the agent retains context across switches.
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
Import the `Harness` class and create a new instance with your agent and modes:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Harness } from '@mastra/core/harness'
|
|
15
|
+
import { myAgent } from './agents'
|
|
16
|
+
|
|
17
|
+
const harness = new Harness({
|
|
18
|
+
id: 'multi-mode',
|
|
19
|
+
agent: myAgent,
|
|
20
|
+
modes: [
|
|
21
|
+
{
|
|
22
|
+
id: 'plan',
|
|
23
|
+
name: 'Plan',
|
|
24
|
+
metadata: { default: true },
|
|
25
|
+
instructions: 'Reason about the task before making changes.',
|
|
26
|
+
},
|
|
27
|
+
{ id: 'build', name: 'Build', instructions: 'Implement the approved plan.' },
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
await harness.init()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Defining modes
|
|
35
|
+
|
|
36
|
+
Each mode requires an `id`. When a top-level `agent` is provided, modes layer instructions and tool overrides on the shared agent. Each mode can also specify a `defaultModelId` to bootstrap model selection:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { Harness } from '@mastra/core/harness'
|
|
40
|
+
|
|
41
|
+
const harness = new Harness({
|
|
42
|
+
id: 'multi-mode',
|
|
43
|
+
agent: myAgent,
|
|
44
|
+
modes: [
|
|
45
|
+
{
|
|
46
|
+
id: 'plan',
|
|
47
|
+
name: 'Plan',
|
|
48
|
+
metadata: { default: true },
|
|
49
|
+
defaultModelId: 'anthropic/claude-sonnet-4-6',
|
|
50
|
+
instructions: 'Reason about the task before making changes.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'build',
|
|
54
|
+
name: 'Build',
|
|
55
|
+
defaultModelId: 'anthropic/claude-sonnet-4-6',
|
|
56
|
+
instructions: 'Implement the approved plan.',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Mode tool overrides
|
|
63
|
+
|
|
64
|
+
Modes support two strategies for tool configuration. Use `tools` to replace the backing agent's tools entirely, or `additionalTools` to layer extra tools on top:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Replace — agent sees only these tools in plan mode
|
|
68
|
+
const planMode = { id: 'plan', tools: { planTool } }
|
|
69
|
+
|
|
70
|
+
// Augment — agent keeps its own tools plus these
|
|
71
|
+
const buildMode = { id: 'build', additionalTools: { deployTool } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
You can't set both `tools` and `additionalTools` on the same mode.
|
|
75
|
+
|
|
76
|
+
### Mode transitions
|
|
77
|
+
|
|
78
|
+
A mode can declare a `transitionsTo` target. When the `submit_plan` built-in tool runs in that mode, the Harness transitions to the target mode on approval:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const planMode = {
|
|
82
|
+
id: 'plan',
|
|
83
|
+
name: 'Plan',
|
|
84
|
+
transitionsTo: 'build',
|
|
85
|
+
instructions: 'Reason about the task and submit a plan.',
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
On plan approval, the Harness automatically switches to `build` mode. On rejection, the agent remains in `plan` mode to revise.
|
|
90
|
+
|
|
91
|
+
## Switching modes
|
|
92
|
+
|
|
93
|
+
Call `switchMode()` to change the active mode. The Harness aborts any in-progress generation, saves the current model to the outgoing mode, loads the incoming mode's model, and emits `mode_changed` and `model_changed` events:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
await harness.switchMode({ modeId: 'build' })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Querying modes
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// List all configured modes
|
|
103
|
+
const modes = harness.listModes()
|
|
104
|
+
|
|
105
|
+
// Get the current mode ID
|
|
106
|
+
const modeId = harness.session.mode.get()
|
|
107
|
+
|
|
108
|
+
// Get the full mode object
|
|
109
|
+
const mode = harness.session.mode.resolve()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- [Harness overview](https://mastra.ai/docs/harness/overview)
|
|
115
|
+
- [Threads and state](https://mastra.ai/docs/harness/threads-and-state)
|
|
116
|
+
- [API reference](https://mastra.ai/reference/harness/harness-class)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Harness overview
|
|
2
|
+
|
|
3
|
+
> **Alpha:** The `Harness` feature is in alpha stage and subject to breaking changes in minor versions until it graduates from its alpha status.
|
|
4
|
+
|
|
5
|
+
The Harness is a session controller for building interactive agent applications. It handles the runtime concerns that sit between your UI and the agent loop: managing conversation threads, switching between agent modes, persisting state, gating tool execution with approvals, and coordinating subagents. You can focus on what your agent does rather than how to wire it together.
|
|
6
|
+
|
|
7
|
+
A Harness exposes a [`Session`](https://mastra.ai/docs/harness/session) — the per-conversation runtime state that tracks the active mode, model, thread binding, permission grants, follow-up queue, and token usage. The Harness is the shared host; the Session is the conversation running inside it. In a multi-user host, the same Harness can back many Sessions at once.
|
|
8
|
+
|
|
9
|
+
[Mastra Code](https://code.mastra.ai/) is the flagship Harness implementation: A terminal-based coding agent with multi-model support, persistent conversations, and plan-then-execute workflows.
|
|
10
|
+
|
|
11
|
+
## What you can build
|
|
12
|
+
|
|
13
|
+
The Harness is designed for applications where a user has an ongoing, interactive session with one or more AI agents:
|
|
14
|
+
|
|
15
|
+
| Application | How the Harness helps |
|
|
16
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| **Coding agents** (TUI or IDE) | Plan mode reasons about changes, build mode executes them. Thread persistence resumes conversations across sessions. Tool approvals gate destructive operations like file writes. |
|
|
18
|
+
| **Multi-step assistants** | A research mode gathers information, a drafting mode produces output. Shared state tracks progress. Subagents handle focused subtasks (e.g., web search, code review). |
|
|
19
|
+
| **Copilot UIs** | Model switching lets users pick the right model for the job. Permission policies control which tools run automatically vs. requiring confirmation. Event subscriptions drive real-time UI updates. |
|
|
20
|
+
| **Autonomous task runners** | Background agents with structured task lists, heartbeat handlers for health checks, and observational memory for learning across threads. |
|
|
21
|
+
|
|
22
|
+
## When to use the Harness
|
|
23
|
+
|
|
24
|
+
Use the Harness when your application needs:
|
|
25
|
+
|
|
26
|
+
- Multiple agent modes that share one conversation thread (e.g., plan → build → review)
|
|
27
|
+
- A control layer between your UI and the agent loop (model switching, state persistence, thread management)
|
|
28
|
+
- Tool approval flows and permission policies for human-in-the-loop gating
|
|
29
|
+
- Subagent orchestration to delegate focused subtasks with constrained tools
|
|
30
|
+
- Session continuity with persistent threads, state, and observational memory across restarts
|
|
31
|
+
|
|
32
|
+
For single-shot agent calls or basic request-response patterns, use the [Agent class](https://mastra.ai/docs/agents/overview) directly. The Harness adds value when you need session state, mode switching, or interactive approval flows.
|
|
33
|
+
|
|
34
|
+
## Key capabilities
|
|
35
|
+
|
|
36
|
+
- **Session**: Per-conversation state — active thread, mode, model, grants, follow-ups, token usage, and the display snapshot — accessed through `harness.session`. See [Session](https://mastra.ai/docs/harness/session).
|
|
37
|
+
- **Modes**: Define distinct agent personalities (instructions, tools, model) and switch between them without losing conversation context. See [Modes](https://mastra.ai/docs/harness/modes).
|
|
38
|
+
- **Threads and state**: Persist conversations and structured state across sessions, users, and mode switches. See [Threads and state](https://mastra.ai/docs/harness/threads-and-state).
|
|
39
|
+
- **Subagents**: Spawn focused child agents with constrained tools for subtasks, optionally forking the parent conversation. See [Subagents](https://mastra.ai/docs/harness/subagents).
|
|
40
|
+
- **Tool approvals and permissions**: Configure which tools require user confirmation, grant session-wide exceptions, and handle interactive tool suspension. See [Tool approvals](https://mastra.ai/docs/harness/tool-approvals).
|
|
41
|
+
- **Model management**: Switch models per-mode at runtime, track usage, and resolve gateway-backed models.
|
|
42
|
+
- **Follow-ups and steering**: Queue messages while the agent is running, or inject mid-stream instructions to redirect the agent.
|
|
43
|
+
- **Event system**: Subscribe to typed events (message updates, mode changes, tool approvals) or coalesced `HarnessDisplayState` snapshots to drive your UI.
|
|
44
|
+
- **Observational memory**: Automatic summarization and reflection across threads for long-running agent sessions.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
Import the `Harness` class and create a new instance with an agent, storage backend, and modes:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { Agent } from '@mastra/core/agent'
|
|
52
|
+
import { Harness } from '@mastra/core/harness'
|
|
53
|
+
import { LibSQLStore } from '@mastra/libsql'
|
|
54
|
+
|
|
55
|
+
const agent = new Agent({
|
|
56
|
+
name: 'assistant',
|
|
57
|
+
instructions: 'Help the user plan and complete tasks.',
|
|
58
|
+
model: 'openai/gpt-5.5',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const harness = new Harness({
|
|
62
|
+
id: 'my-agent',
|
|
63
|
+
agent,
|
|
64
|
+
storage: new LibSQLStore({ url: 'file:./data.db' }),
|
|
65
|
+
modes: [
|
|
66
|
+
{
|
|
67
|
+
id: 'plan',
|
|
68
|
+
name: 'Plan',
|
|
69
|
+
metadata: { default: true },
|
|
70
|
+
instructions: 'Reason about changes before making them.',
|
|
71
|
+
},
|
|
72
|
+
{ id: 'build', name: 'Build', instructions: 'Implement the approved plan.' },
|
|
73
|
+
],
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
harness.subscribe(event => {
|
|
77
|
+
if (event.type === 'message_update') {
|
|
78
|
+
console.log(event.message)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await harness.init()
|
|
83
|
+
await harness.selectOrCreateThread()
|
|
84
|
+
await harness.sendMessage({ content: 'Hello!' })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **Note:** Visit the [Harness reference](https://mastra.ai/reference/harness/harness-class) for the full constructor parameters and method signatures.
|
|
88
|
+
|
|
89
|
+
## Architecture
|
|
90
|
+
|
|
91
|
+
The Harness sits between your application layer and the underlying agent loop:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
┌──────────────────────────┐
|
|
95
|
+
│ Your App (TUI/Web/API) │
|
|
96
|
+
└────────────┬─────────────┘
|
|
97
|
+
│ commands (sendMessage, switchMode, approve)
|
|
98
|
+
│ events (message_update, tool_approval_required)
|
|
99
|
+
┌────────────▼─────────────┐
|
|
100
|
+
│ Harness (shared host) │
|
|
101
|
+
│ Config · Storage │
|
|
102
|
+
│ Thread lifecycle │
|
|
103
|
+
│ Permission policy │
|
|
104
|
+
│ Subagent spawning │
|
|
105
|
+
│ Event bus │
|
|
106
|
+
│ ┌─────────────────────┐ │
|
|
107
|
+
│ │ harness.session │ │
|
|
108
|
+
│ │ identity · thread │ │
|
|
109
|
+
│ │ mode · model · run │ │
|
|
110
|
+
│ │ grants · follow-ups │ │
|
|
111
|
+
│ │ display state │ │
|
|
112
|
+
│ └─────────────────────┘ │
|
|
113
|
+
└────────────┬─────────────┘
|
|
114
|
+
│
|
|
115
|
+
┌────────────▼──────────────┐
|
|
116
|
+
│ Agent + Memory + Tools │
|
|
117
|
+
└───────────────────────────┘
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Your app sends commands (send a message, switch mode, approve a tool call) and receives typed events. The Harness manages the lifecycle internally: persisting threads, routing to the correct mode agent, enforcing permissions, and emitting events as state changes.
|
|
121
|
+
|
|
122
|
+
## Next steps
|
|
123
|
+
|
|
124
|
+
- [Session](https://mastra.ai/docs/harness/session): The per-conversation state on a Harness
|
|
125
|
+
- [Modes](https://mastra.ai/docs/harness/modes): Define and switch between agent personalities
|
|
126
|
+
- [Threads and state](https://mastra.ai/docs/harness/threads-and-state): Manage persistent conversations
|
|
127
|
+
- [Subagents](https://mastra.ai/docs/harness/subagents): Delegate focused subtasks
|
|
128
|
+
- [Tool approvals and permissions](https://mastra.ai/docs/harness/tool-approvals): Human-in-the-loop gating
|
|
129
|
+
- [API reference](https://mastra.ai/reference/harness/harness-class): Full constructor and method docs
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Session
|
|
2
|
+
|
|
3
|
+
A `Session` holds the per-conversation state of a Harness: who the conversation is for, which thread is active, the selected mode and model, permission grants, queued follow-ups, token usage, application state, and the display snapshot. You reach it through `harness.session`.
|
|
4
|
+
|
|
5
|
+
## Harness vs. Session
|
|
6
|
+
|
|
7
|
+
The Harness is the shared host. It owns the machinery that doesn't change from one conversation to the next: configuration, storage, the agent registry, the thread lock, the event bus, and permission _policy_. The Session is the per-conversation state that sits on top of that machinery.
|
|
8
|
+
|
|
9
|
+
A useful way to remember the split: the **Harness** is the application, and the **Session** is the current conversation running inside it. Operations that mutate shared infrastructure or coordinate the event bus stay on the Harness; everything that describes "this conversation right now" lives on the Session.
|
|
10
|
+
|
|
11
|
+
| Lives on the Harness (shared host) | Lives on the Session (per-conversation) |
|
|
12
|
+
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| Configuration, storage, and memory | Resource ID and active thread binding ([`session.identity`](#identity), [`session.thread`](#thread)) |
|
|
14
|
+
| Thread lifecycle: `createThread`, `switchThread`, `cloneThread`, `renameThread`, `deleteThread` | Thread and message reads ([`session.thread.list()`](#thread)) |
|
|
15
|
+
| The event bus (`subscribe`, event emission) | Selected mode and model ([`session.mode`](#mode-and-model), [`session.model`](#mode-and-model)) |
|
|
16
|
+
| Permission _policy_ (`setPermissionForCategory`, `getToolCategory`) | Permission _grants_ ([`session.grantCategory`](#permission-grants)) |
|
|
17
|
+
| Mode and model _definitions_ (`config.modes`) | Run state, abort, follow-ups, suspensions ([`session.run`](#run-state), [`session.followUps`](#follow-ups)) |
|
|
18
|
+
| The `stateSchema` definition | Application state and its updates ([`session.state`](#state)) |
|
|
19
|
+
| The shared agent loop and subagent spawning | Token usage and the display snapshot ([`session.displayState`](#display-state)) |
|
|
20
|
+
|
|
21
|
+
This is why thread _creation_ is `harness.createThread()` (it acquires the shared lock and emits an event) while thread _reads_ are `harness.session.thread.list()` (they describe the current conversation's view).
|
|
22
|
+
|
|
23
|
+
In single-player apps there is one Harness and one Session, so the split is mostly organizational. In a multi-user host, the same Harness can serve many conversations — each backed by its own Session — without one conversation's mode, grants, or active thread leaking into another.
|
|
24
|
+
|
|
25
|
+
## Identity
|
|
26
|
+
|
|
27
|
+
`session.identity` holds the resource ID the conversation belongs to and the currently bound thread ID. Threads are scoped to a resource ID, so this is what groups a conversation's threads together:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const resourceId = harness.session.identity.getResourceId()
|
|
31
|
+
const threadId = harness.session.thread.getId()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To change the resource a conversation operates under, use `harness.setResourceId()` — it lives on the Harness because it also clears the active thread and releases the shared lock.
|
|
35
|
+
|
|
36
|
+
## Thread
|
|
37
|
+
|
|
38
|
+
`session.thread` owns the active thread binding and read access to threads and messages. The Harness performs lifecycle transitions; the Session reads the result:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// Lifecycle transitions live on the Harness
|
|
42
|
+
await harness.switchThread({ threadId: 'thread-abc123' })
|
|
43
|
+
|
|
44
|
+
// Reads live on the Session
|
|
45
|
+
const threads = await harness.session.thread.list()
|
|
46
|
+
const messages = await harness.session.thread.listActiveMessages()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
See [Threads and state](https://mastra.ai/docs/harness/threads-and-state) for the full lifecycle.
|
|
50
|
+
|
|
51
|
+
## Mode and model
|
|
52
|
+
|
|
53
|
+
The Harness defines the available modes in `config.modes`. The Session tracks which mode and model are _currently_ selected:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Switch mode (Harness orchestration — aborts the run, emits events)
|
|
57
|
+
await harness.switchMode({ modeId: 'build' })
|
|
58
|
+
|
|
59
|
+
// Read the current selection (Session state)
|
|
60
|
+
const modeId = harness.session.mode.get()
|
|
61
|
+
const mode = harness.session.mode.resolve()
|
|
62
|
+
const modelId = harness.session.model.get()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
See [Modes](https://mastra.ai/docs/harness/modes) for how modes carry their own model.
|
|
66
|
+
|
|
67
|
+
## Permission grants
|
|
68
|
+
|
|
69
|
+
The Harness owns permission _policy_ — which categories or tools require approval. The Session owns the _grants_ a user makes during the conversation. A grant lets a tool run for the rest of the session without prompting again:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Grant a category or tool for the rest of the session
|
|
73
|
+
harness.session.grantCategory('edit')
|
|
74
|
+
harness.session.grantTool('mastra_workspace_execute_command')
|
|
75
|
+
|
|
76
|
+
// Inspect current grants
|
|
77
|
+
const grants = harness.session.getGrants()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Grants are intentionally in-memory and reset when the session restarts. See [Tool approvals](https://mastra.ai/docs/harness/tool-approvals) for the approval flow.
|
|
81
|
+
|
|
82
|
+
## Run state
|
|
83
|
+
|
|
84
|
+
`session.run` tracks the in-flight generation: whether a run is active, its run and trace IDs, and the abort signal. Use it to reflect run status in your UI:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
if (harness.session.run.isRunning()) {
|
|
88
|
+
// show a stop button
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Abort the active run (Harness orchestration clears related state)
|
|
92
|
+
harness.abort()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Follow-ups
|
|
96
|
+
|
|
97
|
+
While a run is in progress, queued follow-up messages live on `session.followUps`. They are sent when the current run finishes:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const queued = harness.session.followUps.count()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## State
|
|
104
|
+
|
|
105
|
+
`session.state` holds the conversation's application state — structured values that agents and the UI share, such as model preferences, feature flags, or progress. The Harness defines the shape with a `stateSchema`; the Session owns the live snapshot, validates updates against the schema, and emits `state_changed` on every write:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const state = harness.session.state.get()
|
|
109
|
+
await harness.session.state.set({ theme: 'light' })
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See [Threads and state](https://mastra.ai/docs/harness/threads-and-state) for defining a schema.
|
|
113
|
+
|
|
114
|
+
## Display state
|
|
115
|
+
|
|
116
|
+
`session.displayState` is the coalesced `HarnessDisplayState` snapshot — the single object a UI renders from. It folds run status, streaming messages, active tools, tasks, token usage, and pending approvals into one place:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const snapshot = harness.session.displayState.get()
|
|
120
|
+
|
|
121
|
+
// React to changes
|
|
122
|
+
harness.subscribe(event => {
|
|
123
|
+
if (event.type === 'display_state_changed') {
|
|
124
|
+
render(harness.session.displayState.get())
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Note:** Visit the [Session reference](https://mastra.ai/reference/harness/session) for the full list of sub-objects and method signatures.
|
|
130
|
+
|
|
131
|
+
## Related
|
|
132
|
+
|
|
133
|
+
- [Harness overview](https://mastra.ai/docs/harness/overview)
|
|
134
|
+
- [Threads and state](https://mastra.ai/docs/harness/threads-and-state)
|
|
135
|
+
- [Tool approvals](https://mastra.ai/docs/harness/tool-approvals)
|
|
136
|
+
- [Session reference](https://mastra.ai/reference/harness/session)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Subagents
|
|
2
|
+
|
|
3
|
+
Subagents let a parent agent delegate focused tasks to child agents with constrained tools and instructions. The Harness auto-generates a `subagent` built-in tool that the parent model can call to spawn any configured subagent type.
|
|
4
|
+
|
|
5
|
+
## When to use subagents
|
|
6
|
+
|
|
7
|
+
Use subagents when:
|
|
8
|
+
|
|
9
|
+
- A task needs a different toolset or instruction set than the parent agent
|
|
10
|
+
- You want to isolate a subtask's messages from the parent thread
|
|
11
|
+
- The parent agent needs to delegate to a cheaper or faster model for specific work
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
Define subagent types in the Harness constructor:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Agent } from '@mastra/core/agent'
|
|
19
|
+
import { Harness } from '@mastra/core/harness'
|
|
20
|
+
|
|
21
|
+
const agent = new Agent({
|
|
22
|
+
name: 'assistant',
|
|
23
|
+
instructions: 'Use subagents when a focused task needs a narrower toolset.',
|
|
24
|
+
model: 'openai/gpt-5.5',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const harness = new Harness({
|
|
28
|
+
id: 'with-subagents',
|
|
29
|
+
agent,
|
|
30
|
+
modes: [{ id: 'default', name: 'Default', metadata: { default: true } }],
|
|
31
|
+
subagents: [
|
|
32
|
+
{
|
|
33
|
+
id: 'explore',
|
|
34
|
+
name: 'Explore',
|
|
35
|
+
description: 'Reads files and gathers context without making changes.',
|
|
36
|
+
instructions: 'You are a read-only exploration agent.',
|
|
37
|
+
allowedWorkspaceTools: ['read_file', 'list_directory', 'grep_search'],
|
|
38
|
+
defaultModelId: 'anthropic/claude-haiku-4-5',
|
|
39
|
+
maxSteps: 30,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'execute',
|
|
43
|
+
name: 'Execute',
|
|
44
|
+
description: 'Makes changes to files and runs commands.',
|
|
45
|
+
instructions: 'You are an execution agent.',
|
|
46
|
+
allowedWorkspaceTools: ['read_file', 'write_file', 'execute_command'],
|
|
47
|
+
defaultModelId: 'anthropic/claude-sonnet-4-6',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The parent agent can then call the auto-generated `subagent` tool with a `task` description and optional `agentType`.
|
|
54
|
+
|
|
55
|
+
For the full list of subagent configuration options, see [`Harness` reference](https://mastra.ai/reference/harness/harness-class).
|
|
56
|
+
|
|
57
|
+
## Forked subagents
|
|
58
|
+
|
|
59
|
+
By default, a subagent runs with a fresh context and doesn't see the parent conversation. **Forked subagents** run on a clone of the parent thread and reuse the parent agent's configuration. This preserves prompt-cache prefixes and gives the subagent full conversation context.
|
|
60
|
+
|
|
61
|
+
Enable forked mode per-type:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const collaborator = {
|
|
65
|
+
id: 'collaborator',
|
|
66
|
+
name: 'Collaborator',
|
|
67
|
+
description: 'Continues the conversation in a fork.',
|
|
68
|
+
instructions: '...', // Ignored in forked mode
|
|
69
|
+
forked: true,
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or per-invocation via the `subagent` tool's `forked` input parameter.
|
|
74
|
+
|
|
75
|
+
### Forked mode semantics
|
|
76
|
+
|
|
77
|
+
- Memory must be configured on the Harness (forking clones the thread)
|
|
78
|
+
- The parent agent's instructions, tools, model, and `maxSteps` apply
|
|
79
|
+
- The subagent definition's own `instructions`, `tools`, and `defaultModelId` are ignored
|
|
80
|
+
- Fork threads are tagged with `metadata.forkedSubagent === true` and hidden from `session.thread.list()` by default
|
|
81
|
+
- Recursive forks are blocked at runtime to prevent infinite nesting
|
|
82
|
+
|
|
83
|
+
### When to prefer non-forked mode
|
|
84
|
+
|
|
85
|
+
Use non-forked subagents when the child needs a strictly smaller toolset, a different system prompt, or a cheaper model. Pass any required context explicitly in the `task` description.
|
|
86
|
+
|
|
87
|
+
## Subagent model management
|
|
88
|
+
|
|
89
|
+
Set a global or per-type subagent model at runtime:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Global subagent model
|
|
93
|
+
await harness.setSubagentModelId({ modelId: 'anthropic/claude-sonnet-4-6' })
|
|
94
|
+
|
|
95
|
+
// Per-type override
|
|
96
|
+
await harness.setSubagentModelId({
|
|
97
|
+
modelId: 'anthropic/claude-haiku-4-5',
|
|
98
|
+
agentType: 'explore',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Read current model
|
|
102
|
+
const modelId = harness.getSubagentModelId({ agentType: 'explore' })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Related
|
|
106
|
+
|
|
107
|
+
- [Harness overview](https://mastra.ai/docs/harness/overview)
|
|
108
|
+
- [Tool approvals and permissions](https://mastra.ai/docs/harness/tool-approvals)
|
|
109
|
+
- [API reference](https://mastra.ai/reference/harness/harness-class)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Threads and state
|
|
2
|
+
|
|
3
|
+
The Harness manages conversation threads and shared state that persist across mode switches, model changes, and sessions. Threads hold messages while state holds structured data that agents and the UI can read and write.
|
|
4
|
+
|
|
5
|
+
Thread _lifecycle_ transitions (create, switch, clone, delete) live on the Harness because they coordinate the shared thread lock and emit events. The active thread binding and thread/message _reads_ live on the [`Session`](https://mastra.ai/docs/harness/session) as `harness.session.thread`.
|
|
6
|
+
|
|
7
|
+
## When to use threads and state
|
|
8
|
+
|
|
9
|
+
Use threads when your application needs persistent conversation history, multi-session continuity, or when users switch between threads. Use state for structured values (model preferences, feature flags, UI settings) that agents and the UI layer share.
|
|
10
|
+
|
|
11
|
+
## Threads
|
|
12
|
+
|
|
13
|
+
### Creating and selecting threads
|
|
14
|
+
|
|
15
|
+
On startup, call `selectOrCreateThread()` to resume the most recent thread or create a new one:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
await harness.init()
|
|
19
|
+
const thread = await harness.selectOrCreateThread()
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Create a thread explicitly with a title:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const thread = await harness.createThread({ title: 'New conversation' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Switching threads
|
|
29
|
+
|
|
30
|
+
Switch to an existing thread. The harness aborts any in-progress generation, acquires a lock on the new thread, and emits a `thread_changed` event:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
await harness.switchThread({ threadId: 'thread-abc123' })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Listing threads
|
|
37
|
+
|
|
38
|
+
List threads for the current resource. Forked subagent threads are hidden by default:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const threads = await harness.session.thread.list()
|
|
42
|
+
|
|
43
|
+
// Include all resources
|
|
44
|
+
const allThreads = await harness.session.thread.list({ allResources: true })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Cloning threads
|
|
48
|
+
|
|
49
|
+
Clone a thread to create a branch of the conversation. The harness copies all messages and switches to the clone:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const cloned = await harness.cloneThread({ title: 'Alternative approach' })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Thread locking
|
|
56
|
+
|
|
57
|
+
Pass a `threadLock` to the Harness constructor to prevent concurrent access from multiple processes. The lock is acquired before any thread operation and released on switch or delete:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const harness = new Harness({
|
|
61
|
+
id: 'my-agent',
|
|
62
|
+
threadLock: {
|
|
63
|
+
acquire: async threadId => {
|
|
64
|
+
/* acquire lock or throw */
|
|
65
|
+
},
|
|
66
|
+
release: async threadId => {
|
|
67
|
+
/* release lock */
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## State
|
|
74
|
+
|
|
75
|
+
### Defining a state schema
|
|
76
|
+
|
|
77
|
+
Pass a `stateSchema` (Standard JSON Schema) to validate state and extract defaults:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Agent } from '@mastra/core/agent'
|
|
81
|
+
import { Harness } from '@mastra/core/harness'
|
|
82
|
+
import { z } from 'zod'
|
|
83
|
+
|
|
84
|
+
const agent = new Agent({
|
|
85
|
+
name: 'assistant',
|
|
86
|
+
instructions: 'Help the user manage a stateful session.',
|
|
87
|
+
model: 'openai/gpt-5.5',
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const harness = new Harness({
|
|
91
|
+
id: 'stateful-agent',
|
|
92
|
+
agent,
|
|
93
|
+
modes: [{ id: 'default', name: 'Default', metadata: { default: true } }],
|
|
94
|
+
stateSchema: z.object({
|
|
95
|
+
currentModelId: z.string().optional(),
|
|
96
|
+
theme: z.enum(['light', 'dark']).default('dark'),
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Reading and writing state
|
|
102
|
+
|
|
103
|
+
State is owned by the [`Session`](https://mastra.ai/docs/harness/session) as `harness.session.state`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Read the current state snapshot
|
|
107
|
+
const state = harness.session.state.get()
|
|
108
|
+
|
|
109
|
+
// Update state — validates against schema and emits state_changed
|
|
110
|
+
await harness.session.state.set({ theme: 'light' })
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
State changes emit a `state_changed` event with the new state and the set of changed keys.
|
|
114
|
+
|
|
115
|
+
## Resource IDs
|
|
116
|
+
|
|
117
|
+
Threads are scoped to a resource ID. The resource ID defaults to the harness `id` but can be set explicitly to group threads by project, user, or workspace:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const harness = new Harness({
|
|
121
|
+
id: 'my-agent',
|
|
122
|
+
resourceId: 'project-xyz',
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Change resource ID at runtime (clears the current thread)
|
|
126
|
+
harness.setResourceId({ resourceId: 'project-abc' })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Related
|
|
130
|
+
|
|
131
|
+
- [Harness overview](https://mastra.ai/docs/harness/overview)
|
|
132
|
+
- [Session](https://mastra.ai/docs/harness/session)
|
|
133
|
+
- [Modes](https://mastra.ai/docs/harness/modes)
|
|
134
|
+
- [API reference](https://mastra.ai/reference/harness/harness-class)
|