UNPKG

@mastra/core

Version:
772 lines (488 loc) • 24 kB
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt # Session > **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` is the isolated runtime for one resource and optional scope. It owns its event bus, thread binding, state, mode and model selections, run control, approvals, suspensions, follow-ups, and display state. The [`AgentController`](https://mastra.ai/reference/agent-controller/agent-controller-class) supplies shared agents, configuration, storage, workspaces, and services. Create sessions through `controller.createSession()`. Direct construction and controller wiring methods aren't application APIs. For a conceptual introduction, see the [AgentController overview](https://mastra.ai/docs/harness/agent-controller). ## Usage example The following example uses the supported controller-to-session flow. ```typescript await controller.init() const session = await controller.createSession({ resourceId: 'project-42' }) const unsubscribe = session.subscribe(event => { if (event.type === 'display_state_changed') { render(event.displayState) } }) await session.sendMessage({ content: 'Review the current project.' }) unsubscribe() ``` ## 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. **om** (`SessionOM`): Observer and reflector model settings for observational memory. **permissions** (`SessionPermissions`): Tool and category permission policies represented in session state. **subagents** (`SessionSubagents`): Global and per-agent-type subagent model selection. **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** (`AgentControllerRequestState<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 ### Identity and events #### `getTags()` Return a copy of the tags supplied when the session was created. ```typescript const tags = session.getTags() ``` Returns: `Record<string, string>` #### `subscribe(listener)` Subscribe to this session's isolated event bus. The method returns an unsubscribe function. ```typescript const unsubscribe = session.subscribe(event => { console.log(event.type) }) unsubscribe() ``` Returns: `() => void` ### Messages and run control #### `sendMessage({ content, files?, requestContext? })` Send a user message. The session creates a thread first when no thread is active. ```typescript await session.sendMessage({ content: 'Summarize this file.', files: [{ data: fileContents, mediaType: 'text/plain', filename: 'notes.txt' }], }) ``` #### `steer({ content, requestContext? })` Queue steering content into an active run. ```typescript await session.steer({ content: 'Focus on the failing tests.' }) ``` #### `followUp({ content, requestContext? })` Queue a follow-up while a run is active, or send it immediately while idle. ```typescript await session.followUp({ content: 'Then propose a fix.' }) ``` #### `getCurrentRunId()` Return the active stream run identifier, the tracked run identifier, or `null` while idle. ```typescript const runId = session.getCurrentRunId() ``` Returns: `string | null` #### `abort()` Abort the active run and clear pending suspension display state. ```typescript session.abort() ``` ### Workspace #### `getWorkspace()` Return the workspace resolved for this session. This preserves session-level overrides and workspaces selected from the session scope. ```typescript const workspace = session.getWorkspace() const skill = await workspace.skills?.get('code-review') ``` Returns: `Workspace` ### Session grants 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 session.grantCategory('edit') ``` #### `grantTool(toolName)` Grant a specific tool for the current session. ```typescript session.grantTool('mastra_workspace_execute_command') ``` #### `getGrants()` Return the currently granted categories and tools. ```typescript const grants = session.getGrants() // { categories: string[], tools: string[] } ``` #### `hasCategoryGrant(category)` Return whether a category has an in-memory session grant. ```typescript const allowed = session.hasCategoryGrant('edit') ``` Returns: `boolean` #### `hasToolGrant(toolName)` Return whether a tool has an in-memory session grant. ```typescript const allowed = session.hasToolGrant('write_file') ``` Returns: `boolean` ### Tool approvals #### `resolveToolApproval(toolName)` Return the effective policy after applying explicit tool rules, session grants, and category rules. ```typescript const policy = session.resolveToolApproval('execute_command') ``` Returns: `PermissionPolicy` #### `respondToToolApproval({ decision, toolCallId?, requestContext?, declineContext? })` 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 session.respondToToolApproval({ decision: 'approve' }) session.respondToToolApproval({ decision: 'decline' }) session.respondToToolApproval({ decision: 'always_allow_category' }) ``` #### `respondToToolSuspension({ resumeData, toolCallId?, requestContext? })` Resume a suspended tool with application-provided data. Supply `toolCallId` when several tool calls are suspended. ```typescript await session.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData: ['src'], }) ``` For `submit_plan`, pass `{ action: 'approved' }` or `{ action: 'rejected', feedback }`. Approval can switch to the mode configured by `transitionsTo` before the tool resumes. ### Token usage #### `getTokenUsage()` Return a copy of the running token-usage tally for the active thread. ```typescript const usage = 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 don't 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 = session.identity.getId() ``` ### `session.identity.getOwnerId()` Return the stable owner identifier for the session. ```typescript const ownerId = session.identity.getOwnerId() ``` ### `session.identity.getResourceId()` Return the current resource ID. ```typescript const resourceId = session.identity.getResourceId() ``` ### `session.identity.getDefaultResourceId()` Return the resource ID the session was created with. ```typescript const defaultResourceId = session.identity.getDefaultResourceId() ``` To change the resource ID, use [`controller.setResourceId()`](https://mastra.ai/reference/agent-controller/agent-controller-class), which also clears the active thread. The session `id` and `ownerId` aren't affected by resource switches. ## Thread `session.thread` owns the active thread binding and resource-scoped thread operations. Stored threads and messages can survive controller recreation when storage is configured. The live session and its event bus don't. ### `session.thread.create({ title?, id? })` Create a thread, bind the session to it, and open its event stream. ```typescript const thread = await session.thread.create({ id: 'thread-7', title: 'Investigate login failure', }) ``` Returns: `Promise<AgentControllerThread>` ### `session.thread.rename({ title })` Rename the active stored thread. ```typescript await session.thread.rename({ title: 'Fix login failure' }) ``` ### `session.thread.clone({ sourceThreadId?, title?, resourceId? })` Clone an owned thread and its messages, then bind the session to the clone. ```typescript const clone = await session.thread.clone({ sourceThreadId: 'thread-7', title: 'Alternative approach', }) ``` Returns: `Promise<AgentControllerThread>` ### `session.thread.switch({ threadId, emitEvent? })` Switch to an owned stored thread and hydrate its mode, model, and observational memory settings. ```typescript await session.thread.switch({ threadId: 'thread-8' }) ``` ### `session.thread.delete({ threadId })` Delete an owned thread. Deleting the active thread also clears the current binding. ```typescript await session.thread.delete({ threadId: 'thread-8' }) ``` ### `session.thread.getId()` Return the active thread ID, or `null` when no thread is bound. ```typescript const threadId = 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 session.thread.list() const allThreads = await session.thread.list({ allResources: true }) const everything = await 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 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 session.thread.listActiveMessages({ limit: 50 }) ``` ### `session.thread.listMessages({ threadId, limit? })` Retrieve messages for a specific thread. ```typescript const messages = await session.thread.listMessages({ threadId: 'thread-abc123' }) ``` The message-reading methods `listActiveMessages`, `listMessages`, and `firstUserMessage` return `MastraDBMessage` objects, while `firstUserMessages` returns a `Map<string, MastraDBMessage>` keyed by thread ID. Each message has a `role`, an `id`, a `createdAt`, and a `content` object with `content.format` and a `content.parts` array. Read text, reasoning, tool calls, and attachments from `content.parts`. Signals such as system reminders and notifications are returned as separate messages with `role: 'signal'`. ### `session.thread.firstUserMessage({ threadId })` Retrieve the first user message for a thread, or `null` if none. ```typescript const firstMsg = await 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 session.thread.firstUserMessages({ threadIds: ['thread-a', 'thread-b'], }) ``` ### `session.thread.getSetting({ key })` Read a setting from the active thread metadata. ```typescript const value = await session.thread.getSetting({ key: 'omThreshold' }) ``` ### `session.thread.setSetting({ key, value })` Write a setting to the active thread metadata. ```typescript await session.thread.setSetting({ key: 'omThreshold', value: 0.8 }) ``` ### `session.thread.deleteSetting({ key })` Remove a setting from the active thread metadata. ```typescript await session.thread.deleteSetting({ key: 'omThreshold' }) ``` ## Mode `session.mode` owns the active mode selection. ### `session.mode.get()` Return the active mode ID. ```typescript const modeId = session.mode.get() ``` ### `session.mode.resolve()` Return the full `AgentControllerMode` object for the active mode, resolved against the controller's configured modes. ```typescript const mode = session.mode.resolve() ``` ### `session.mode.switch({ modeId })` Switch to another mode. The session saves the outgoing mode's model before persisting the new mode on the active thread. It then restores the incoming mode's selected or default model. The session emits `mode_changed` immediately and `model_changed` after model resolution. ```typescript await 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 = session.model.get() ``` ### `session.model.displayName()` Return the last segment of the active model ID as a short display name. Returns `'unknown'` when no model is selected. ```typescript const name = session.model.displayName() ``` ### `session.model.hasSelection()` Check whether a model is currently selected. ```typescript if (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 controller's `modelUseCountTracker` and emits a `model_changed` event. ```typescript // Set for the current session only await session.model.switch({ modelId: 'anthropic/claude-sonnet-4-6', scope: 'global', }) // Persist to the current thread (default) await 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 controller'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 = session.om.observer.modelId() const reflector = 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 = session.om.observer.threshold() const reflectionThreshold = 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 session.om.observer.switchModel({ modelId: 'anthropic/claude-haiku-4-5', }) await 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 configured model gateways, or `undefined` when no model ID is set or no resolver is configured. ```typescript const observerModel = session.om.observer.resolvedModel() const reflectorModel = session.om.reflector.resolvedModel() ``` ## Permissions `session.permissions` owns the tool-approval policy represented in `session.state`: the per-category and per-tool rules consulted during approval resolution. These are distinct from the in-memory grants documented under [Session grants](#session-grants). Grants reset with the live session. Permission rules aren't durable unless the host restores the corresponding session state. ### `session.permissions.getRules()` Return the current permission rules, or empty rules (`{ categories: {}, tools: {} }`) when none are set. ```typescript const rules = 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 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 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 = 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 session.subagents.model.set({ modelId: 'anthropic/claude-sonnet-4-6' }) // Set a per-type override await 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 = session.run.getRunId() const traceId = session.run.getTraceId() ``` ### `session.run.isRunning()` Return whether a run is currently in progress. ```typescript if (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 = session.stream.activeRunId() ``` ### `session.stream.isActive()` Return whether the stream currently has an active run. ```typescript if (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 (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 = session.suspensions.has({ toolCallId: event.toolCallId }) ``` Resume a suspended tool with [`session.respondToToolSuspension()`](#tool-approvals). ## 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 = session.followUps.count() ``` ### `session.followUps.isEmpty()` Return whether the follow-up queue is empty. ```typescript if (!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 (session.approval.isArmed()) { // Show the approval prompt } ``` Respond with [`session.respondToToolApproval()`](#tool-approvals). ## Display state `session.displayState` owns the canonical `AgentControllerDisplayState` snapshot a UI renders from, and the reducer that keeps it in sync with every session event. ### `session.displayState.get()` Return the current `AgentControllerDisplayState` snapshot for UI rendering. ```typescript const displayState = 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 doesn't emit an event, so re-render explicitly after calling it. ```typescript session.displayState.restoreTasks(replayedTasks) ``` After every event, the session emits `display_state_changed` with the latest snapshot. Subscribe with [`session.subscribe()`](#identity-and-events) or read the current value from `session.displayState.get()`. ## State `session.state` owns the schema-validated AgentController state for the conversation. It holds the current snapshot and validates updates against the `stateSchema` passed to the AgentController. Updates are serialized, and every change emits a `state_changed` event. ### `session.state.get()` Return a readonly copy of the current state snapshot. ```typescript const state = 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 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 session.state.update(current => ({ updates: { count: (current.count ?? 0) + 1 }, result: (current.count ?? 0) + 1, })) ``` ## Persistence boundaries A `Session` is a live runtime object. Its event bus, arbitrary `session.state`, permission rules, permission grants, pending approvals, suspensions, follow-ups, run state, and stream state don't automatically survive controller or process recreation. The host must restore any of this state when recreating a session. With configured storage, threads, messages, and token usage persist. Thread settings restore mode and model choices. They can also restore observational memory settings and subagent model selections, including per-agent-type overrides. A chat channel can map back to stored threads, but channel-to-session and auto-approval state held by `AgentControllerChannels` remains in memory. ## Related - [AgentController class](https://mastra.ai/reference/agent-controller/agent-controller-class) - [AgentController overview](https://mastra.ai/docs/harness/agent-controller) - [Threads and state](https://mastra.ai/docs/harness/agent-controller) - [Tool approvals](https://mastra.ai/docs/harness/agent-controller)