Skip to main content

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 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.

Usage example
Direct link to Usage example

// 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
Direct link to 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
Direct link to Methods

Permissions
Direct link to Permissions

Session-scoped grants auto-approve tools without prompting. Grants are ephemeral — they reset when the session restarts and are never persisted.

grantCategory(category)
Direct link to grantcategorycategory

Grant a tool category for the current session. Tools in this category are auto-approved.

agentController.session.grantCategory('edit')

grantTool(toolName)
Direct link to granttooltoolname

Grant a specific tool for the current session.

agentController.session.grantTool('mastra_workspace_execute_command')

getGrants()
Direct link to getgrants

Return the currently granted categories and tools.

const grants = agentController.session.getGrants()
// { categories: string[], tools: string[] }

Tool approvals
Direct link to Tool approvals

respondToToolApproval({ decision, requestContext? })
Direct link to 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.

agentController.session.respondToToolApproval({ decision: 'approve' })
agentController.session.respondToToolApproval({ decision: 'decline' })
agentController.session.respondToToolApproval({ decision: 'always_allow_category' })

Run control
Direct link to Run control

getCurrentRunId()
Direct link to 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.

const runId = agentController.session.getCurrentRunId()

abortRun()
Direct link to abortrun

Abort the in-flight run: aborts the live stream, requests abort on the run, and clears parked tool suspensions.

agentController.session.abortRun()

Token usage
Direct link to Token usage

getTokenUsage()
Direct link to gettokenusage

Return a copy of the running token-usage tally for the active thread.

const usage = agentController.session.getTokenUsage()
// { promptTokens, completionTokens, totalTokens, ... }

Identity
Direct link to 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()
Direct link to sessionidentitygetid

Return the stable session identifier.

const sessionId = agentController.session.identity.getId()

session.identity.getOwnerId()
Direct link to sessionidentitygetownerid

Return the stable owner identifier for the session.

const ownerId = agentController.session.identity.getOwnerId()

session.identity.getResourceId()
Direct link to sessionidentitygetresourceid

Return the current resource ID.

const resourceId = agentController.session.identity.getResourceId()

session.identity.getDefaultResourceId()
Direct link to sessionidentitygetdefaultresourceid

Return the resource ID the session was created with.

const defaultResourceId = agentController.session.identity.getDefaultResourceId()

To change the resource ID, use agentController.setResourceId(), which also tears down the active thread. The session id and ownerId are not affected by resource switches.

Thread
Direct link to Thread

session.thread owns the active thread binding plus thread and message reads. Thread lifecycle (create, switch, clone, delete, rename) lives on the AgentController because it coordinates the shared thread lock and event bus.

session.thread.getId()
Direct link to sessionthreadgetid

Return the active thread ID, or null when no thread is bound.

const threadId = agentController.session.thread.getId()

session.thread.list(options?)
Direct link to sessionthreadlistoptions

List threads from storage. By default only threads for the current resource are returned, and transient forked subagent threads are hidden.

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 })
Direct link to sessionthreadgetbyid-threadid-

Return a single thread by ID, or null if it doesn't exist.

const thread = await agentController.session.thread.getById({ threadId: 'thread-abc123' })

session.thread.listActiveMessages(options?)
Direct link to sessionthreadlistactivemessagesoptions

Retrieve messages for the active thread. Returns an empty array when no thread is bound.

const messages = await agentController.session.thread.listActiveMessages({ limit: 50 })

session.thread.listMessages({ threadId, limit? })
Direct link to sessionthreadlistmessages-threadid-limit-

Retrieve messages for a specific thread.

const messages = await agentController.session.thread.listMessages({ threadId: 'thread-abc123' })

session.thread.firstUserMessage({ threadId })
Direct link to sessionthreadfirstusermessage-threadid-

Retrieve the first user message for a thread, or null if none.

const firstMsg = await agentController.session.thread.firstUserMessage({
threadId: 'thread-abc123',
})

session.thread.firstUserMessages({ threadIds })
Direct link to sessionthreadfirstusermessages-threadids-

Retrieve the first user message for many threads at once, returned as a map.

const firstByThread = await agentController.session.thread.firstUserMessages({
threadIds: ['thread-a', 'thread-b'],
})

session.thread.getSetting({ key }) / setSetting({ key, value }) / deleteSetting({ key })
Direct link to sessionthreadgetsetting-key---setsetting-key-value---deletesetting-key-

Read, write, and remove per-thread settings stored on the active thread's metadata.

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
Direct link to Mode

session.mode owns the active mode selection.

session.mode.get()
Direct link to sessionmodeget

Return the active mode ID.

const modeId = agentController.session.mode.get()

session.mode.resolve()
Direct link to sessionmoderesolve

Return the full AgentControllerMode object for the active mode, resolved against the agentController's configured modes.

const mode = agentController.session.mode.resolve()

session.mode.switch({ modeId })
Direct link to sessionmodeswitch-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.

await agentController.session.mode.switch({ modeId: 'build' })

Model
Direct link to Model

session.model owns the active model selection, including per-mode model memory.

session.model.get()
Direct link to sessionmodelget

Return the active model ID.

const modelId = agentController.session.model.get()

session.model.displayName()
Direct link to sessionmodeldisplayname

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.

const name = agentController.session.model.displayName()

session.model.hasSelection()
Direct link to sessionmodelhasselection

Check whether a model is currently selected.

if (agentController.session.model.hasSelection()) {
// Ready to send messages
}

session.model.switch({ modelId, scope?, modeId? })
Direct link to sessionmodelswitch-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.

// 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
Direct link to 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()
Direct link to sessionomobservermodelid--sessionomreflectormodelid

Return the role's model ID, or undefined when neither session state nor omConfig provides one.

const observer = agentController.session.om.observer.modelId()
const reflector = agentController.session.om.reflector.modelId()

session.om.observer.threshold() / session.om.reflector.threshold()
Direct link to sessionomobserverthreshold--sessionomreflectorthreshold

Return the role's threshold in tokens (observation threshold for the observer, reflection threshold for the reflector), or undefined when unset.

const observationThreshold = agentController.session.om.observer.threshold()
const reflectionThreshold = agentController.session.om.reflector.threshold()

session.om.observer.switchModel({ modelId }) / session.om.reflector.switchModel({ modelId })
Direct link to sessionomobserverswitchmodel-modelid---sessionomreflectorswitchmodel-modelid-

Switch the role's model. Persists the setting to thread metadata and emits an om_model_changed event.

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()
Direct link to sessionomobserverresolvedmodel--sessionomreflectorresolvedmodel

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.

const observerModel = agentController.session.om.observer.resolvedModel()

Permissions
Direct link to 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; grants reset each session, whereas these rules are persisted in session state.

session.permissions.getRules()
Direct link to sessionpermissionsgetrules

Return the current permission rules, or empty rules ({ categories: {}, tools: {} }) when none are set.

const rules = agentController.session.permissions.getRules()
// { categories: { execute: 'ask' }, tools: { dangerous_tool: 'deny' } }

session.permissions.setForCategory({ category, policy })
Direct link to sessionpermissionssetforcategory-category-policy-

Set the approval policy ('allow' | 'ask' | 'deny') for a tool category. Resolves once the change is persisted to session state.

await agentController.session.permissions.setForCategory({ category: 'execute', policy: 'ask' })

session.permissions.setForTool({ toolName, policy })
Direct link to sessionpermissionssetfortool-toolname-policy-

Set the approval policy for a specific tool. Per-tool policies take precedence over category policies. Resolves once persisted.

await agentController.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' })

Subagents
Direct link to Subagents

session.subagents owns subagent configuration. It currently exposes the subagent model selection under session.subagents.model.

session.subagents.model.get({ agentType? })
Direct link to sessionsubagentsmodelget-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.

const modelId = agentController.session.subagents.model.get({ agentType: 'explore' })

session.subagents.model.set({ modelId, agentType? })
Direct link to sessionsubagentsmodelset-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.

// 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
Direct link to Run

session.run owns run and trace identity plus abort state for the in-flight run.

session.run.getRunId() / getTraceId()
Direct link to sessionrungetrunid--gettraceid

Return the stored run ID and trace ID for the current run, or null when idle.

const runId = agentController.session.run.getRunId()
const traceId = agentController.session.run.getTraceId()

session.run.isRunning()
Direct link to sessionrunisrunning

Return whether a run is currently in progress.

if (agentController.session.run.isRunning()) {
// A run is active
}

Stream
Direct link to Stream

session.stream owns the live subscription to the agent thread stream and its dedup key.

session.stream.activeRunId()
Direct link to sessionstreamactiverunid

Return the run ID active on the live stream, or null when no stream is open.

const runId = agentController.session.stream.activeRunId()

session.stream.isActive()
Direct link to sessionstreamisactive

Return whether the stream currently has an active run.

if (agentController.session.stream.isActive()) {
// The current thread's stream is producing output
}

Suspensions
Direct link to Suspensions

session.suspensions owns parked interactive tool calls (such as ask_user and request_access) awaiting a resume.

session.suspensions.hasPending()
Direct link to sessionsuspensionshaspending

Return whether any tool is currently suspended.

if (agentController.session.suspensions.hasPending()) {
// At least one interactive tool is waiting for a response
}

session.suspensions.has({ toolCallId })
Direct link to sessionsuspensionshas-toolcallid-

Return whether a specific tool call is suspended.

const waiting = agentController.session.suspensions.has({ toolCallId: event.toolCallId })

Resume a suspended tool with agentController.respondToToolSuspension().

Follow-ups
Direct link to Follow-ups

session.followUps owns the FIFO queue of messages submitted while a run is in progress.

session.followUps.count()
Direct link to sessionfollowupscount

Return the number of queued follow-ups.

const queued = agentController.session.followUps.count()

session.followUps.isEmpty()
Direct link to sessionfollowupsisempty

Return whether the follow-up queue is empty.

if (!agentController.session.followUps.isEmpty()) {
// Messages are waiting to be processed
}

Approval
Direct link to Approval

session.approval owns the pending tool-approval gate.

session.approval.isArmed()
Direct link to sessionapprovalisarmed

Return whether a tool is currently awaiting an approval decision.

if (agentController.session.approval.isArmed()) {
// Show the approval prompt
}

Respond with session.respondToToolApproval().

Display state
Direct link to 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()
Direct link to sessiondisplaystateget

Return the current AgentControllerDisplayState snapshot for UI rendering.

const displayState = agentController.session.displayState.get()

session.displayState.restoreTasks(tasks)
Direct link to sessiondisplaystaterestoretaskstasks

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.

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() and read the latest snapshot from session.displayState.get().

State
Direct link to 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()
Direct link to sessionstateget

Return a readonly copy of the current state snapshot.

const state = agentController.session.state.get()

session.state.set(updates)
Direct link to sessionstatesetupdates

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.

await agentController.session.state.set({ yolo: true })

session.state.update(updater)
Direct link to sessionstateupdateupdater

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.

const added = await agentController.session.state.update(current => ({
updates: { count: (current.count ?? 0) + 1 },
result: (current.count ?? 0) + 1,
}))

Scoping tags
Direct link to 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()
Direct link to gettags

Return a copy of the session's scoping tags. Empty object when the session is unscoped.

const tags = agentController.session.getTags()
// { projectPath: '/my/project' }
On this page