Mastra 1.46 Turns Harness Into a Session Factory, Which Is Exactly Where Agent Frameworks Are Headed

Mastra 1.46 Turns Harness Into a Session Factory, Which Is Exactly Where Agent Frameworks Are Headed

Mastra @mastra/[email protected] is the kind of release that makes a framework less convenient in the short term because it is becoming more real. The headline is a multi-session Harness architecture: Harness is no longer the singleton thing that owns one mutable session. It becomes a shared-resource factory, and isolated Session objects now own run control, model and mode switching, permissions, event subscriptions, observational memory, subagent settings, and thread lifecycle.

That sounds like API housekeeping until you try to host agents for actual users. A singleton runtime is fine for a demo, a notebook, or a single-user CLI. It becomes a liability as soon as two people, two browser tabs, two workers, or two background runs share the same process. In production, session identity is not a convenience wrapper. It is the boundary that keeps state, permissions, tool approvals, and events from bleeding across users.

The singleton had to die

The release’s breaking change is explicit: Harness no longer exposes harness.session. Callers must use await harness.createSession() and operate on the returned Session. That Session now owns the APIs developers actually use to run and control work: sendMessage, sendSignal, abort, respondToToolSuspension, event subscription, thread lifecycle, model and mode switching, observational-memory accessors, permissions, and subagent model accessors.

PR #18213 is the architecture move behind the changelog. “Session owns its own state” changed 117 files with 5,650 additions and 4,392 deletions. It moved model and mode state, the session event bus, stream lifecycle and finalization, thread APIs, signal settlement, observational-memory config, permissions, and subagent model config onto Session. That is not refactoring for taste. It is the framework drawing a line around mutable runtime state.

The practical reason is concurrency. If a Harness stores the current model, current mode, current permission policy, current stream, current thread, and current event bus globally, then every hosted scenario becomes a race disguised as a developer experience. One user switches modes while another run is active. A tool approval arrives for the wrong conversation. A stream finalizes against the wrong listener. A subagent setting meant for one workflow leaks into another. Those bugs are hard to reproduce because they are not logic errors in the agent graph; they are ownership errors in the runtime.

HTTP and SSE turn Harness into a control plane

Mastra also exposes Harness sessions over server routes and the JavaScript client. The release adds Harness-scoped routes for send, steer, abort, approve tool calls, manage threads, read state, and subscribe via SSE. @mastra/client-js gains a first-class harness resource through client.getHarness(id).session(resourceId). Registered Harnesses can now live on a parent Mastra instance via new Mastra({ harnesses }), inheriting storage, agents, gateways, and observability instead of spinning up an internal sidecar world.

That matters because a remote Harness is no longer just a library object. It is an agent control plane. Remote clients can steer work, abort it, approve tool calls, manage threads, and subscribe to events. Every one of those actions needs a stable identity and a scoped authority surface. Moving approvals and run control onto Session means an approval can be tied to the specific session, run, and thread that requested it, rather than floating around a shared object and hoping the caller guessed correctly.

This is the same lesson backend teams learned years ago. Shared services can own pools, registries, storage, and observability. Requests and sessions own mutable user state. Agent frameworks are relearning it because agent demos often begin as object graphs in one process. Mastra’s release is interesting because it marks the point where that mental model stops scaling.

Leases are not optional once agents wake across processes

The release’s most production-shaped feature may be cross-process signal coordination. PR #17723 adds a LeaseProvider abstraction and a unified accepted result. Signal and message APIs return an accepted promise resolving to routing decisions such as wake, deliver, persist, or discard, with an authoritative runId when applicable. The implementation includes in-memory leasing for EventEmitterPubSub and Redis-backed leasing for RedisStreamsPubSub.

This is the part that does not look impressive in a launch video and absolutely matters in a hosted system. A web request starts a run. A background worker continues it. A webhook signal arrives from Slack, GitHub, or a payment provider. A serverless function wakes up and tries to resume the thread. Without a lease, two processes can believe they own the same run. Without an accepted-result contract, the caller cannot tell whether the signal woke an idle run, delivered to an active run, persisted for later, or was discarded. Mastra’s vocabulary is valuable because it turns runtime behavior into something developers can log, alert on, and test.

The deterministic tool-mock work belongs in the same maturity bucket. PR #18036 adds item-level static toolMocks for agent experiments, with ordered mock consumption, strict or ignored argument matching, composed beforeToolCall hooks, abort-on-mismatch or exhaustion, and a per-item toolMockReport. Verification included 26 core tool-mock tests, 533 MySQL tests, and 796 libsql tests. Agent experiments are noisy enough with models in the loop. External tools should not add nondeterminism when the point is to evaluate orchestration logic.

The tenancy additions round out the story. Optional organization, project, candidate, and experiment identity fields are not glamorous, but eval datasets and experiment results routinely contain customer prompts, outputs, tool traces, and incident-specific artifacts. Multi-tenant products cannot treat that as a blob with a nice UI. Tenant filters need to exist in storage, indexes, APIs, and dashboards, not only in TypeScript types.

For practitioners, this upgrade should be handled as an architecture migration, not a package bump. Replace singleton Harness calls with explicit createSession() flows. Audit every approval, abort, thread, mode, model, permission, and event subscription call to ensure it is session-scoped. If you host across multiple workers, use a real lease provider and log accepted routing decisions. If tools dominate eval cost or flakiness, add deterministic mocks. If experiments serve multiple tenants, verify the tenancy model all the way down to storage queries.

Mastra is crossing from framework to control plane. That is a good thing, but it comes with control-plane obligations: sessions, leases, scoped approvals, deterministic replay, and tenant-aware data. The agent-runtime market does not need another demo where a model calls a weather tool. It needs fewer shared mutable objects pretending to be architecture.

Sources: Mastra @mastra/core 1.46.0 release, PR #18213, PR #17723, PR #18036, PR #18355