Session class
The Harness feature is in alpha stage and subject to breaking changes in minor versions until it graduates from its alpha status.
A Session owns all the state tied to a single conversation. The Harness 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 harness.session.
For a conceptual introduction, see the Harness overview.
Usage exampleDirect link to Usage example
// Read per-conversation state through harness.session
const modeId = harness.session.mode.get()
const modelId = harness.session.model.get()
const threadId = harness.session.thread.getId()
const grants = harness.session.getGrants()
// Render from the coalesced display-state snapshot
harness.subscribe(event => {
if (event.type === 'display_state_changed') {
render(harness.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:
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.
harness.session.grantCategory('edit')
grantTool(toolName)Direct link to granttooltoolname
Grant a specific tool for the current session.
harness.session.grantTool('mastra_workspace_execute_command')
getGrants()Direct link to getgrants
Return the currently granted categories and tools.
const grants = harness.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.
harness.session.respondToToolApproval({ decision: 'approve' })
harness.session.respondToToolApproval({ decision: 'decline' })
harness.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 = harness.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.
harness.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 = harness.session.getTokenUsage()
// { promptTokens, completionTokens, totalTokens, ... }
IdentityDirect link to Identity
session.identity owns the resource ID for the conversation.
session.identity.getResourceId()Direct link to sessionidentitygetresourceid
Return the current resource ID.
const resourceId = harness.session.identity.getResourceId()
session.identity.getDefaultResourceId()Direct link to sessionidentitygetdefaultresourceid
Return the resource ID the session was created with.
const defaultResourceId = harness.session.identity.getDefaultResourceId()
To change the resource ID, use harness.setResourceId(), which also tears down the active thread.
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 Harness 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 = harness.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 harness.session.thread.list()
const allThreads = await harness.session.thread.list({ allResources: true })
const everything = await harness.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 harness.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 harness.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 harness.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 harness.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 harness.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 harness.session.thread.setSetting({ key: 'omThreshold', value: 0.8 })
const value = await harness.session.thread.getSetting({ key: 'omThreshold' })
await harness.session.thread.deleteSetting({ key: 'omThreshold' })
ModeDirect link to Mode
session.mode owns the active mode selection. Switching modes is performed on the Harness because it emits events and rebinds the stream.
session.mode.get()Direct link to sessionmodeget
Return the active mode ID.
const modeId = harness.session.mode.get()
session.mode.resolve()Direct link to sessionmoderesolve
Return the full HarnessMode object for the active mode, resolved against the harness's configured modes.
const mode = harness.session.mode.resolve()
ModelDirect link to Model
session.model owns the active model selection, including per-mode model memory. Switching models is performed on the Harness.
session.model.get()Direct link to sessionmodelget
Return the active model ID.
const modelId = harness.session.model.get()
session.model.hasSelection()Direct link to sessionmodelhasselection
Check whether a model is currently selected.
if (harness.session.model.hasSelection()) {
// Ready to send messages
}
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 = harness.session.run.getRunId()
const traceId = harness.session.run.getTraceId()
session.run.isRunning()Direct link to sessionrunisrunning
Return whether a run is currently in progress.
if (harness.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 = harness.session.stream.activeRunId()
session.stream.isActive()Direct link to sessionstreamisactive
Return whether the stream currently has an active run.
if (harness.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 (harness.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 = harness.session.suspensions.has({ toolCallId: event.toolCallId })
Resume a suspended tool with harness.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 = harness.session.followUps.count()
session.followUps.isEmpty()Direct link to sessionfollowupsisempty
Return whether the follow-up queue is empty.
if (!harness.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 (harness.session.approval.isArmed()) {
// Show the approval prompt
}
Respond with session.respondToToolApproval().
Display stateDirect link to Display state
session.displayState owns the canonical HarnessDisplayState snapshot a UI renders from, and the reducer that keeps it in sync with every harness event.
session.displayState.get()Direct link to sessiondisplaystateget
Return the current HarnessDisplayState snapshot for UI rendering.
const displayState = harness.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.
harness.session.displayState.restoreTasks(replayedTasks)
After every event the harness 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 harness.subscribe() and read the latest snapshot from session.displayState.get().
StateDirect link to State
session.state owns the schema-validated Harness state for the conversation. It holds the current snapshot, validates updates against the stateSchema passed to the Harness, 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 = harness.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 harness.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 harness.session.state.update(current => ({
updates: { count: (current.count ?? 0) + 1 },
result: (current.count ?? 0) + 1,
}))