Session
A Session holds the live state of a Harness: who the session is for, which thread is active, the selected mode and model, permission grants, queued follow-ups, token usage, application state, and the display snapshot. You reach it through harness.session.
What the Session tracksDirect link to What the Session tracks
The Session is the live state of a single user's interaction with the Harness. It answers "what is true right now": who the session belongs to, which thread is currently bound, which mode and model are selected, what the user has approved, what's queued or running, the application state, and the snapshot to render. A session has one active thread at a time but can list, switch between, and clone many threads over its lifetime. Anything that describes the current state lives here; the Harness owns the shared infrastructure every session runs on.
Because each session has its own identity and state, one Harness can serve many users at once — each backed by its own Session — without one session's mode, grants, or active thread leaking into another.
The rest of this page walks through the Session's state. Each part is a focused sub-object on harness.session:
- Who and where: Identity sets the resource the session belongs to, and Thread tracks the currently bound thread.
- How the agent behaves: Mode and model controls which agent profile and model are active, and Permission grants records what the user has approved to run without prompting.
- What's happening right now: Run state reflects the in-flight generation, and Follow-ups holds messages queued to send when it finishes.
- What you store and render: State holds your application's structured data, and Display state is the single snapshot your UI renders from.
The Harness performs actions that change this state — switching threads, modes, or models — because those operations coordinate shared infrastructure and emit events. The Session is where you read the result. The sections below note this split where it matters.
IdentityDirect link to Identity
session.identity holds the resource ID the conversation belongs to and the currently bound thread ID. Threads are scoped to a resource ID, so this is what groups a conversation's threads together:
const resourceId = harness.session.identity.getResourceId()
const threadId = harness.session.thread.getId()
The resource ID is set on the Harness constructor and defaults to the harness id. See Resource IDs for how it scopes threads.
ThreadDirect link to Thread
session.thread owns the active thread binding and read access to threads and messages. The Harness performs lifecycle transitions; the Session reads the result:
// Lifecycle transitions live on the Harness
await harness.switchThread({ threadId: 'thread-abc123' })
// Reads live on the Session
const threads = await harness.session.thread.list()
const messages = await harness.session.thread.listActiveMessages()
See Threads and state for the full lifecycle.
Mode and modelDirect link to Mode and model
The Harness defines the available modes in config.modes. The Session tracks which mode and model are currently selected:
// Switch mode (Harness orchestration — aborts the run, emits events)
await harness.switchMode({ modeId: 'build' })
// Read the current selection (Session state)
const modeId = harness.session.mode.get()
const mode = harness.session.mode.resolve()
const modelId = harness.session.model.get()
See Modes for how modes carry their own model.
Permission grantsDirect link to Permission grants
The Harness owns permission policy — which categories or tools require approval. The Session owns the grants a user makes during the conversation. A grant lets a tool run for the rest of the session without prompting again:
// Grant a category or tool for the rest of the session
harness.session.grantCategory('edit')
harness.session.grantTool('mastra_workspace_execute_command')
// Inspect current grants
const grants = harness.session.getGrants()
Grants are intentionally in-memory and reset when the session restarts. See Tool approvals for the approval flow.
Run stateDirect link to Run state
session.run tracks the in-flight generation: whether a run is active, its run and trace IDs, and the abort signal. Use it to reflect run status in your UI:
if (harness.session.run.isRunning()) {
// show a stop button
}
// Abort the active run (Harness orchestration clears related state)
harness.abort()
Follow-upsDirect link to Follow-ups
A user may want to add to the conversation while the agent is still working. Instead of dropping or interrupting those messages, the Session queues them on session.followUps and sends them when the current run finishes:
const queued = harness.session.followUps.count()
Queue a follow-up with harness.followUp({ content }). To redirect the agent mid-run instead of waiting, use harness.steer({ content }). Both build on signals.
StateDirect link to State
session.state holds the conversation's application state — structured values that agents and the UI share, such as model preferences, feature flags, or progress. The Harness defines the shape with a stateSchema; the Session owns the live snapshot, validates updates against the schema, and emits state_changed on every write:
const state = harness.session.state.get()
await harness.session.state.set({ theme: 'light' })
See Threads and state for defining a schema.
Display stateDirect link to Display state
A conversation emits many fine-grained events: tokens streaming in, tools starting and finishing, approvals pending, tasks updating, token usage climbing. Subscribing to each event type and reassembling the current picture yourself is tedious and error-prone. session.displayState does that work for you.
It's a reducer-maintained snapshot. The Session keeps one HarnessDisplayState object and folds every harness event into it as it happens, so the snapshot always reflects the latest state. It captures everything a UI needs to render: whether the agent is running and what it's streaming, which tools and subagents are active, anything waiting on the user such as approvals, the current task list, and running totals like token usage and queued follow-ups.
Because it's a single object, you can drive an entire UI from one place: read the current snapshot with get(), and re-render whenever the Harness emits display_state_changed (fired after every other event):
const snapshot = harness.session.displayState.get()
// Re-render from the snapshot on every change
harness.subscribe(event => {
if (event.type === 'display_state_changed') {
render(harness.session.displayState.get())
}
})
This is the recommended pattern for most UIs. Subscribe to individual typed events only when you need to react to a specific transition rather than re-render the whole view.
Visit the Session reference for the full list of sub-objects and method signatures.