# Threads and state Threads and state are how a Harness conversation survives beyond a single exchange. A **thread** is the persistent record of a conversation — its messages and metadata, saved to storage so a user can close the app and resume the same conversation later, or switch between several conversations. **State** is structured data attached to the conversation — values like model preferences, feature flags, or progress — that agents and your UI read and write as the conversation runs. The two work together: the thread is the message history, and state is the shared scratchpad alongside it. Both persist across mode switches, model changes, and restarts, so nothing is lost when a user switches from plan mode to build mode or reopens the app the next day. Thread _lifecycle_ transitions — create, switch, clone, delete — live on the Harness because they coordinate the shared thread lock and emit events. The active thread binding and thread/message _reads_ live on the [`Session`](https://mastra.ai/docs/harness/session) as `harness.session.thread`. ## Threads A thread holds one conversation's full message history. The Harness binds the Session to one active thread at a time; messages you send and the agent's replies are appended to that thread and saved to storage. Threads let users resume a past conversation, keep several conversations side by side, or branch one into alternatives. ### Creating and selecting threads On startup, call `selectOrCreateThread()` to resume the most recent thread or create a new one: ```typescript await harness.init() const thread = await harness.selectOrCreateThread() ``` Create a thread explicitly with a title: ```typescript const thread = await harness.createThread({ title: 'New conversation' }) ``` ### Switching threads Switch to an existing thread. The harness aborts any in-progress generation, acquires a lock on the new thread, and emits a `thread_changed` event: ```typescript await harness.switchThread({ threadId: 'thread-abc123' }) ``` ### Listing threads List threads for the current resource. Forked subagent threads are hidden by default: ```typescript const threads = await harness.session.thread.list() // Include all resources const allThreads = await harness.session.thread.list({ allResources: true }) ``` ### Cloning threads Clone a thread to create a branch of the conversation. The harness copies all messages and switches to the clone: ```typescript const cloned = await harness.cloneThread({ title: 'Alternative approach' }) ``` ### Thread locking Pass a `threadLock` to the Harness constructor to prevent concurrent access from multiple processes. The lock is acquired before any thread operation and released on switch or delete: ```typescript const harness = new Harness({ id: 'my-agent', threadLock: { acquire: async threadId => { /* acquire lock or throw */ }, release: async threadId => { /* release lock */ }, }, }) ``` ## State Where a thread stores the conversation's messages, state stores structured values that describe the conversation but aren't messages — model preferences, feature flags, UI settings, or progress markers. Agents can read and update state during a run, and your UI can react to changes, so state is how the agent and the interface stay in sync on shared facts. You define its shape with a schema, and every update is validated against that schema before it's applied. ### Defining a state schema Pass a `stateSchema` (Standard JSON Schema) to validate state and extract defaults: ```typescript import { Agent } from '@mastra/core/agent' import { Harness } from '@mastra/core/harness' import { z } from 'zod' const agent = new Agent({ name: 'assistant', instructions: 'Help the user manage a stateful session.', model: 'openai/gpt-5.5', }) const harness = new Harness({ id: 'stateful-agent', agent, modes: [{ id: 'default', name: 'Default', metadata: { default: true } }], stateSchema: z.object({ currentModelId: z.string().optional(), theme: z.enum(['light', 'dark']).default('dark'), }), }) ``` ### Reading and writing state State is owned by the [`Session`](https://mastra.ai/docs/harness/session) as `harness.session.state`: ```typescript // Read the current state snapshot const state = harness.session.state.get() // Update state — validates against schema and emits state_changed await harness.session.state.set({ theme: 'light' }) ``` State changes emit a `state_changed` event with the new state and the set of changed keys. ## Resource IDs Threads are scoped to a resource ID, which groups a conversation's threads by project, user, or workspace. Set it on the Harness constructor; it defaults to the harness `id` when omitted: ```typescript const harness = new Harness({ id: 'my-agent', resourceId: 'project-xyz', }) ``` The resource ID is part of a conversation's identity, so you read it from the Session: ```typescript const resourceId = harness.session.identity.getResourceId() ``` ## Related - [Harness overview](https://mastra.ai/docs/harness/overview) - [Session](https://mastra.ai/docs/harness/session) - [Modes](https://mastra.ai/docs/harness/modes) - [API reference](https://mastra.ai/reference/harness/harness-class)