@mastra/core
Version:
594 lines (378 loc) • 20.5 kB
Markdown
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
# Session class
> **Beta:** The `AgentController` feature is in beta stage and subject to breaking changes in minor versions until it graduates from its beta status.
A `Session` owns all the state tied to a single conversation. The [`AgentController`](https://mastra.ai/reference/agent-controller/agent-controller-class) is the shared host — agents, storage, config, the thread lock, and the event bus — while the `Session` holds everything that is per-conversation: identity, the active thread binding and reads, mode and model selection, run and abort state, the live agent stream, tool suspensions, follow-ups, approvals, permission grants, token usage, and the display-state snapshot.
Access the session through `agentController.session`.
For a conceptual introduction, see the [AgentController overview](https://mastra.ai/docs/agent-controller/overview).
## Usage example
```typescript
// Read per-conversation state through agentController.session
const modeId = agentController.session.mode.get()
const modelId = agentController.session.model.get()
const threadId = agentController.session.thread.getId()
const grants = agentController.session.getGrants()
// Render from the coalesced display-state snapshot
agentController.subscribe(event => {
if (event.type === 'display_state_changed') {
render(agentController.session.displayState.get())
}
})
```
## Properties
The session is organized into sub-objects, each owning one domain of per-conversation state.
**identity** (`SessionIdentity`): Stable session, owner, and resource identity for the conversation. See identity methods below.
**thread** (`SessionThread`): Active thread binding and thread/message reads. See thread methods below.
**mode** (`SessionMode`): Active mode selection. See mode methods below.
**model** (`SessionModel`): Active model selection, including per-mode persistence. See model methods below.
**run** (`SessionRun`): Run and trace identity plus abort state for the in-flight run. See run methods below.
**stream** (`SessionStream`): The live subscription to the agent thread stream. See stream methods below.
**suspensions** (`SessionSuspensions`): Parked interactive tool calls awaiting a resume. See suspensions methods below.
**followUps** (`SessionFollowUps`): Queue of messages submitted while a run is in progress. See follow-up methods below.
**approval** (`SessionApproval`): The pending tool-approval gate. See approval methods below.
**displayState** (`SessionDisplayState`): The canonical AgentControllerDisplayState snapshot a UI renders from. See display-state methods below.
**state** (`SessionState<TState>`): The schema-validated, session-owned AgentController state. See state methods below.
**browser** (`MastraBrowser | undefined`): The browser automation instance for this session. Set at creation via createSession, or from the AgentController config default. Undefined when no browser is configured.
## Methods
### Permissions
Session-scoped grants auto-approve tools without prompting. Grants are ephemeral — they reset when the session restarts and are never persisted.
#### `grantCategory(category)`
Grant a tool category for the current session. Tools in this category are auto-approved.
```typescript
agentController.session.grantCategory('edit')
```
#### `grantTool(toolName)`
Grant a specific tool for the current session.
```typescript
agentController.session.grantTool('mastra_workspace_execute_command')
```
#### `getGrants()`
Return the currently granted categories and tools.
```typescript
const grants = agentController.session.getGrants()
// { categories: string[], tools: string[] }
```
### Tool approvals
#### `respondToToolApproval({ decision, requestContext? })`
Respond to a pending tool approval request, raised by a `tool_approval_required` event. Pass `always_allow_category` to also grant the tool's whole category for the rest of the session.
```typescript
agentController.session.respondToToolApproval({ decision: 'approve' })
agentController.session.respondToToolApproval({ decision: 'decline' })
agentController.session.respondToToolApproval({ decision: 'always_allow_category' })
```
### Run control
#### `getCurrentRunId()`
Return the run ID of the in-flight run, or `null` when idle. Prefers the live stream's active run, falling back to the last stored run ID.
```typescript
const runId = agentController.session.getCurrentRunId()
```
#### `abortRun()`
Abort the in-flight run: aborts the live stream, requests abort on the run, and clears parked tool suspensions.
```typescript
agentController.session.abortRun()
```
### Token usage
#### `getTokenUsage()`
Return a copy of the running token-usage tally for the active thread.
```typescript
const usage = agentController.session.getTokenUsage()
// { promptTokens, completionTokens, totalTokens, ... }
```
## Identity
`session.identity` owns the stable identifiers for the conversation: the resource ID, a session `id`, and an `ownerId`. The `id` and `ownerId` are stable for the life of the session and do not change when the resource ID is switched. They mirror the `id` and `ownerId` fields on `SessionRecord` in storage.
### `session.identity.getId()`
Return the stable session identifier.
```typescript
const sessionId = agentController.session.identity.getId()
```
### `session.identity.getOwnerId()`
Return the stable owner identifier for the session.
```typescript
const ownerId = agentController.session.identity.getOwnerId()
```
### `session.identity.getResourceId()`
Return the current resource ID.
```typescript
const resourceId = agentController.session.identity.getResourceId()
```
### `session.identity.getDefaultResourceId()`
Return the resource ID the session was created with.
```typescript
const defaultResourceId = agentController.session.identity.getDefaultResourceId()
```
To change the resource ID, use [`agentController.setResourceId()`](https://mastra.ai/reference/agent-controller/agent-controller-class), which also tears down the active thread. The session `id` and `ownerId` are not affected by resource switches.
## Thread
`session.thread` owns the active thread binding plus thread and message reads. Thread lifecycle (create, switch, clone, delete, rename) lives on the [`AgentController`](https://mastra.ai/reference/agent-controller/agent-controller-class) because it coordinates the shared thread lock and event bus.
### `session.thread.getId()`
Return the active thread ID, or `null` when no thread is bound.
```typescript
const threadId = agentController.session.thread.getId()
```
### `session.thread.list(options?)`
List threads from storage. By default only threads for the current resource are returned, and transient forked subagent threads are hidden.
```typescript
const threads = await agentController.session.thread.list()
const allThreads = await agentController.session.thread.list({ allResources: true })
const everything = await agentController.session.thread.list({ includeForkedSubagents: true })
```
### `session.thread.getById({ threadId })`
Return a single thread by ID, or `null` if it doesn't exist.
```typescript
const thread = await agentController.session.thread.getById({ threadId: 'thread-abc123' })
```
### `session.thread.listActiveMessages(options?)`
Retrieve messages for the active thread. Returns an empty array when no thread is bound.
```typescript
const messages = await agentController.session.thread.listActiveMessages({ limit: 50 })
```
### `session.thread.listMessages({ threadId, limit? })`
Retrieve messages for a specific thread.
```typescript
const messages = await agentController.session.thread.listMessages({ threadId: 'thread-abc123' })
```
### `session.thread.firstUserMessage({ threadId })`
Retrieve the first user message for a thread, or `null` if none.
```typescript
const firstMsg = await agentController.session.thread.firstUserMessage({
threadId: 'thread-abc123',
})
```
### `session.thread.firstUserMessages({ threadIds })`
Retrieve the first user message for many threads at once, returned as a map.
```typescript
const firstByThread = await agentController.session.thread.firstUserMessages({
threadIds: ['thread-a', 'thread-b'],
})
```
### `session.thread.getSetting({ key })` / `setSetting({ key, value })` / `deleteSetting({ key })`
Read, write, and remove per-thread settings stored on the active thread's metadata.
```typescript
await agentController.session.thread.setSetting({ key: 'omThreshold', value: 0.8 })
const value = await agentController.session.thread.getSetting({ key: 'omThreshold' })
await agentController.session.thread.deleteSetting({ key: 'omThreshold' })
```
## Mode
`session.mode` owns the active mode selection.
### `session.mode.get()`
Return the active mode ID.
```typescript
const modeId = agentController.session.mode.get()
```
### `session.mode.resolve()`
Return the full `AgentControllerMode` object for the active mode, resolved against the agentController's configured modes.
```typescript
const mode = agentController.session.mode.resolve()
```
### `session.mode.switch({ modeId })`
Switch to a different mode. 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.
```typescript
await agentController.session.mode.switch({ modeId: 'build' })
```
## Model
`session.model` owns the active model selection, including per-mode model memory.
### `session.model.get()`
Return the active model ID.
```typescript
const modelId = agentController.session.model.get()
```
### `session.model.displayName()`
Return a short display name for the active model: the last segment of the model ID (for example, `claude-sonnet-4` from `anthropic/claude-sonnet-4`). Returns `'unknown'` when no model is selected.
```typescript
const name = agentController.session.model.displayName()
```
### `session.model.hasSelection()`
Check whether a model is currently selected.
```typescript
if (agentController.session.model.hasSelection()) {
// Ready to send messages
}
```
### `session.model.switch({ modelId, scope?, modeId? })`
Switch the active model. When `scope` is `'thread'` (the default), the model ID is persisted as the per-mode model so it's restored when switching back. Reports the selection to the agentController's `modelUseCountTracker` and emits a `model_changed` event.
```typescript
// Set for the current session only
await agentController.session.model.switch({
modelId: 'anthropic/claude-sonnet-4-6',
scope: 'global',
})
// Persist to the current thread (default)
await agentController.session.model.switch({ modelId: 'anthropic/claude-sonnet-4-6' })
```
## Observational Memory
The observational-memory model selection, grouped by role under `session.om.observer` and `session.om.reflector`. Both roles expose the same methods. Reads return the value from session state when set, falling back to the agentController's `omConfig` defaults.
### `session.om.observer.modelId()` / `session.om.reflector.modelId()`
Return the role's model ID, or `undefined` when neither session state nor `omConfig` provides one.
```typescript
const observer = agentController.session.om.observer.modelId()
const reflector = agentController.session.om.reflector.modelId()
```
### `session.om.observer.threshold()` / `session.om.reflector.threshold()`
Return the role's threshold in tokens (observation threshold for the observer, reflection threshold for the reflector), or `undefined` when unset.
```typescript
const observationThreshold = agentController.session.om.observer.threshold()
const reflectionThreshold = agentController.session.om.reflector.threshold()
```
### `session.om.observer.switchModel({ modelId })` / `session.om.reflector.switchModel({ modelId })`
Switch the role's model. Persists the setting to thread metadata and emits an `om_model_changed` event.
```typescript
await agentController.session.om.observer.switchModel({
modelId: 'anthropic/claude-haiku-4-5',
})
await agentController.session.om.reflector.switchModel({
modelId: 'anthropic/claude-haiku-4-5',
})
```
### `session.om.observer.resolvedModel()` / `session.om.reflector.resolvedModel()`
Resolve the role's model ID to a model instance via the agentController's `resolveModel`, or `undefined` when no model ID is set or no resolver is configured.
```typescript
const observerModel = agentController.session.om.observer.resolvedModel()
```
## Permissions
`session.permissions` owns the persisted tool-approval _policy_ — the per-category and per-tool rules consulted during approval resolution. These are distinct from the in-memory session _grants_ documented under [Methods → Permissions](#permissions); grants reset each session, whereas these rules are persisted in session state.
### `session.permissions.getRules()`
Return the current permission rules, or empty rules (`{ categories: {}, tools: {} }`) when none are set.
```typescript
const rules = agentController.session.permissions.getRules()
// { categories: { execute: 'ask' }, tools: { dangerous_tool: 'deny' } }
```
### `session.permissions.setForCategory({ category, policy })`
Set the approval policy (`'allow' | 'ask' | 'deny'`) for a tool category. Resolves once the change is persisted to session state.
```typescript
await agentController.session.permissions.setForCategory({ category: 'execute', policy: 'ask' })
```
### `session.permissions.setForTool({ toolName, policy })`
Set the approval policy for a specific tool. Per-tool policies take precedence over category policies. Resolves once persisted.
```typescript
await agentController.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' })
```
## Subagents
`session.subagents` owns subagent configuration. It currently exposes the subagent model selection under `session.subagents.model`.
### `session.subagents.model.get({ agentType? })`
Return the subagent model ID, preferring the per-`agentType` value when one is given, then the global subagent model, or `null` when neither is set.
```typescript
const modelId = agentController.session.subagents.model.get({ agentType: 'explore' })
```
### `session.subagents.model.set({ modelId, agentType? })`
Set the subagent model ID. Pass an `agentType` to set a per-type override, or omit it to set the global default. Persists to thread settings and emits a `subagent_model_changed` event.
```typescript
// Set the global subagent model
await agentController.session.subagents.model.set({ modelId: 'anthropic/claude-sonnet-4-6' })
// Set a per-type override
await agentController.session.subagents.model.set({
modelId: 'anthropic/claude-haiku-4-5',
agentType: 'explore',
})
```
## Run
`session.run` owns run and trace identity plus abort state for the in-flight run.
### `session.run.getRunId()` / `getTraceId()`
Return the stored run ID and trace ID for the current run, or `null` when idle.
```typescript
const runId = agentController.session.run.getRunId()
const traceId = agentController.session.run.getTraceId()
```
### `session.run.isRunning()`
Return whether a run is currently in progress.
```typescript
if (agentController.session.run.isRunning()) {
// A run is active
}
```
## Stream
`session.stream` owns the live subscription to the agent thread stream and its dedup key.
### `session.stream.activeRunId()`
Return the run ID active on the live stream, or `null` when no stream is open.
```typescript
const runId = agentController.session.stream.activeRunId()
```
### `session.stream.isActive()`
Return whether the stream currently has an active run.
```typescript
if (agentController.session.stream.isActive()) {
// The current thread's stream is producing output
}
```
## Suspensions
`session.suspensions` owns parked interactive tool calls (such as `ask_user` and `request_access`) awaiting a resume.
### `session.suspensions.hasPending()`
Return whether any tool is currently suspended.
```typescript
if (agentController.session.suspensions.hasPending()) {
// At least one interactive tool is waiting for a response
}
```
### `session.suspensions.has({ toolCallId })`
Return whether a specific tool call is suspended.
```typescript
const waiting = agentController.session.suspensions.has({ toolCallId: event.toolCallId })
```
Resume a suspended tool with [`agentController.respondToToolSuspension()`](https://mastra.ai/reference/agent-controller/agent-controller-class).
## Follow-ups
`session.followUps` owns the FIFO queue of messages submitted while a run is in progress.
### `session.followUps.count()`
Return the number of queued follow-ups.
```typescript
const queued = agentController.session.followUps.count()
```
### `session.followUps.isEmpty()`
Return whether the follow-up queue is empty.
```typescript
if (!agentController.session.followUps.isEmpty()) {
// Messages are waiting to be processed
}
```
## Approval
`session.approval` owns the pending tool-approval gate.
### `session.approval.isArmed()`
Return whether a tool is currently awaiting an approval decision.
```typescript
if (agentController.session.approval.isArmed()) {
// Show the approval prompt
}
```
Respond with [`session.respondToToolApproval()`](#respondtotoolapproval-decision-requestcontext-).
## Display state
`session.displayState` owns the canonical `AgentControllerDisplayState` snapshot a UI renders from, and the reducer that keeps it in sync with every agentController event.
### `session.displayState.get()`
Return the current `AgentControllerDisplayState` snapshot for UI rendering.
```typescript
const displayState = agentController.session.displayState.get()
```
### `session.displayState.restoreTasks(tasks)`
Restore the task portion of the snapshot after a UI replays persisted task tool history. This is a pure update of the snapshot and does not emit an event, so re-render explicitly after calling it.
```typescript
agentController.session.displayState.restoreTasks(replayedTasks)
```
After every event the agentController emits `display_state_changed`, so high-frequency events such as `message_update`, `tool_update`, and `tool_input_delta` are coalesced into the next snapshot. Subscribe with [`agentController.subscribe()`](https://mastra.ai/reference/agent-controller/agent-controller-class) and read the latest snapshot from `session.displayState.get()`.
## State
`session.state` owns the schema-validated AgentController state for the conversation. It holds the current snapshot, validates updates against the `stateSchema` passed to the AgentController, serializes concurrent writes, and emits a `state_changed` event on every change.
### `session.state.get()`
Return a readonly copy of the current state snapshot.
```typescript
const state = agentController.session.state.get()
```
### `session.state.set(updates)`
Merge a partial update into the state. Updates are queued so concurrent calls apply in order, validated against the schema, and emit `state_changed` with the changed keys.
```typescript
await agentController.session.state.set({ yolo: true })
```
### `session.state.update(updater)`
Run an updater against the current snapshot and apply its result atomically within the write queue. Use this for read-modify-write changes that must see the latest state. The updater returns `updates` to merge, optional `events` to emit, and a `result` value that `update()` resolves to.
```typescript
const added = await agentController.session.state.update(current => ({
updates: { count: (current.count ?? 0) + 1 },
result: (current.count ?? 0) + 1,
}))
```
## Scoping tags
Sessions carry scoping tags (e.g. `{ projectPath }`) seeded at creation and stamped onto every thread the session creates. Thread listings can be filtered back to the session's scope using these tags.
### `getTags()`
Return a copy of the session's scoping tags. Empty object when the session is unscoped.
```typescript
const tags = agentController.session.getTags()
// { projectPath: '/my/project' }
```
## Related
- [AgentController class](https://mastra.ai/reference/agent-controller/agent-controller-class)
- [AgentController overview](https://mastra.ai/docs/agent-controller/overview)
- [Threads and state](https://mastra.ai/docs/agent-controller/threads-and-state)
- [Tool approvals](https://mastra.ai/docs/agent-controller/tool-approvals)