Session class
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 exampleDirect 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())
}
})
PropertiesDirect link to Properties
The session is organized into sub-objects, each owning one domain of per-conversation state.
identity:
thread:
mode:
model:
run:
stream:
suspensions:
followUps:
approval:
displayState:
state:
browser:
MethodsDirect link to Methods
PermissionsDirect 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 approvalsDirect 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 controlDirect 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 usageDirect 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, ... }
IdentityDirect 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.
ThreadDirect 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' })
ModeDirect 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' })
ModelDirect 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 MemoryDirect 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()
PermissionsDirect 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' })
SubagentsDirect 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',
})
RunDirect 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
}
StreamDirect 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
}
SuspensionsDirect 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-upsDirect 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
}
ApprovalDirect 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 stateDirect 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().
StateDirect 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 tagsDirect 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' }