Skip to content

Harness primitive rationale#13956

Closed
abhiaiyer91 wants to merge 6 commits intomainfrom
cursor/harness-primitive-rationale-aa60
Closed

Harness primitive rationale#13956
abhiaiyer91 wants to merge 6 commits intomainfrom
cursor/harness-primitive-rationale-aa60

Conversation

@abhiaiyer91
Copy link
Contributor

Description

This PR initiates the refactoring effort to integrate the capabilities of the Harness primitive directly into the Agent class. The primary goal is to reduce cognitive overhead and simplify the architecture by making Agent a more comprehensive and flexible primitive for building interactive agent applications, thereby eliminating the need for a separate, application-level orchestration layer.

This first phase introduces three foundational, optional features to the Agent class:

  1. Event System: Adds subscribe(), on(), and emitEvent() methods for internal and external event handling.
  2. Modes: Enables defining and switching between named agent configurations (instructions, model, tools) within a single Agent instance.
  3. Shared State: Provides Zod-validated, mutable state that can persist across mode switches, accessible via getState() and setState().

These changes lay the groundwork for future phases to migrate more Harness functionality into Agent and eventually deprecate Harness.

Related Issue(s)

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test update

Checklist

  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works
  • I have addressed all Coderabbit comments on this PR

Open in Web Open in Cursor 

Add foundational orchestration capabilities to Agent as optional features:

- Event system: subscribe()/on() for agent lifecycle events
- Modes: named presets of instructions/model/tools with switchMode()
- State: Zod-validated shared state with getState()/setState()

These are the first step toward folding Harness capabilities into Agent,
reducing the number of concepts users need to learn. All features are
fully opt-in and have zero impact on existing Agent usage.

Includes tests (20 passing) and reference documentation updates.
@cursor
Copy link

cursor bot commented Mar 7, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Mar 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mastra-docs Ready Ready Preview, Comment Mar 8, 2026 8:13am
mastra-docs-1.x Building Building Preview, Comment Mar 8, 2026 8:13am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 7, 2026

🦋 Changeset detected

Latest commit: 3988ee5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@mastra/server Minor
@mastra/core Minor
@mastra/deployer Minor
@mastra/express Patch
@mastra/fastify Patch
@mastra/hono Patch
@mastra/koa Patch
mastracode Patch
@mastra/mcp-docs-server Patch
@internal/playground Patch
@mastra/client-js Patch
@mastra/opencode Patch
@mastra/longmemeval Patch
mastra Patch
@mastra/deployer-cloud Minor
@mastra/deployer-cloudflare Patch
@mastra/deployer-netlify Patch
@mastra/deployer-vercel Patch
@mastra/playground-ui Patch
@mastra/react Patch
create-mastra Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added the Documentation Improvements or additions to documentation label Mar 7, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d94376b8-6d4b-494a-81f7-4189694d39b1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/harness-primitive-rationale-aa60

Comment @coderabbitai help to get the list of available commands and usage tips.

Add the orchestration loop to Agent as .send() — a higher-level method
that wraps stream() with full event emission for streaming lifecycle,
messages, tools, usage, and errors.

- send() emits send_start/send_end, message_start/update/end,
  tool_start/end, tool_approval_required, usage_update, and error events
- abort() cancels in-progress sends
- respondToToolApproval() handles interactive tool approval flows
- Expanded AgentEvent union with 12 new event types
- 5 new tests (25 total passing)
- Reference docs updated with send/abort/respondToToolApproval
Add five new server routes exposing Agent orchestration features:

- GET  /api/agents/:agentId/modes        — list modes + current mode
- POST /api/agents/:agentId/modes/switch — switch mode
- GET  /api/agents/:agentId/state        — get shared state
- POST /api/agents/:agentId/state        — update shared state
- POST /api/agents/:agentId/send         — send message, stream events via SSE

The /send endpoint subscribes to Agent events, calls agent.send(), and
pipes all lifecycle events (message deltas, tool calls, usage, errors)
to the client as Server-Sent Events.

Includes Zod schemas, route registration, and documentation updates.
Each agent.send() call now returns a SendOperation with its own isolated:
- events: AsyncIterable<AgentEvent> (per-operation event stream)
- result: Promise<{ message }> (final message)
- abort(): cancel this specific operation
- respondToToolApproval(): handle approvals for this operation

This fixes the concurrency issue where multiple callers sharing the same
Agent singleton would stomp on each other's abort controllers, event
listeners, and pending approval state.

Events still bubble up to global agent.subscribe() listeners, but each
SendOperation's event stream only contains events from that operation.

The server /send route now pipes op.events directly instead of relying
on the global subscribe, making concurrent requests safe.

New test: concurrent sends have isolated event streams (27 total passing).
agent.send() now accepts an optional modeId parameter that selects the
mode for that specific operation. In multi-user server contexts, each
request passes its own modeId — switching modes never affects other
concurrent requests.

For single-user TUI contexts (like MastraCode), switchMode() still
works as a convenience that sets the instance-level default.

Server changes:
- POST /send now accepts modeId in the request body
- Removed POST /modes/switch and POST /state routes (singleton-unsafe)
- Kept GET /modes and GET /state (read-only, safe for shared access)
The Agent is a singleton. Session-scoped state (modes, state) cannot
live on it without causing cross-user interference. This refactor:

- Adds `harness: { modes, stateSchema }` config key to Agent
- Removes singleton-mutating methods: switchMode(), getCurrentMode(),
  getCurrentModeId(), getState(), setState()
- Keeps read-only config accessors: listModes(), hasModes(),
  getDefaultMode(), getStateSchema()
- .send() accepts per-operation modeId and state, validated against
  harness config
- Server: removes /modes/switch and /state routes entirely
- Server: /modes GET returns available modes from harness config
- Server: /send accepts modeId and state in request body

The harness config defines what's AVAILABLE. The caller owns session
state and passes it per-operation. This is safe for both single-user
TUI and multi-user server contexts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants