Agent Controller API
The Agent Controller API reaches an AgentController registered on a Mastra instance over its HTTP routes. Use it from a browser or any other process that doesn't own the controller. A process that owns the controller uses the in-process Session instead.
Usage exampleDirect link to Usage example
import { MastraClient } from '@mastra/client-js'
const client = new MastraClient({ baseUrl: 'http://localhost:4111' })
const session = client.getAgentController('coding-controller').session('user-123')
await session.create()
const subscription = await session.subscribe({
onEvent: event => handleEvent(event),
onError: error => showDisconnected(error),
onReconnect: () => {
void session.state().then(resync).catch(showDisconnected)
},
reconnect: true,
})
await session.sendMessage('Summarize the open pull requests')
// Call when the UI disconnects.
subscription.unsubscribe()
Listing agent controllersDirect link to Listing agent controllers
Retrieve the agent controllers hosted on the connected Mastra instance:
const controllers = await client.listAgentControllers()
Working with a specific agent controllerDirect link to Working with a specific agent controller
Get an instance of an agent controller by the ID it's registered under:
const controller = client.getAgentController('coding-controller')
listModes()Direct link to listmodes
Lists the modes configured on the controller, such as build and plan.
Returns: Promise<AgentControllerModeInfo[]>
listModels()Direct link to listmodels
Lists the models available on the controller, with their auth status and use counts.
Returns: Promise<AgentControllerAvailableModel[]>
listActiveRuns()Direct link to listactiveruns
Lists the runs in flight on the controller across all resources.
Returns: Promise<AgentControllerActiveRun[]>
workspaceStatus()Direct link to workspacestatus
Returns the controller's workspace status.
Returns: Promise<AgentControllerWorkspaceStatus>
session(resourceId, scope?)Direct link to sessionresourceid-scope
Returns an AgentControllerSession bound to one resource. Sessions are get-or-create on the server, so calling create() on the same resourceId and scope resumes the existing conversation instead of forking it.
Pass scope to address an independent session over the same resourceId. Sessions that share a resourceId but use different scopes each get their own run loop, thread binding, mode, model, and state. A common pattern is one session per git worktree with the worktree path as the scope. The scope travels on every request as a sessionScope query parameter.
Session methodsDirect link to Session methods
create(options?)Direct link to createoptions
Creates or resumes the session.
threadId?:
Returns: Promise<CreateAgentControllerSessionResponse>
subscribe(options)Direct link to subscribeoptions
Subscribes to the session's event stream over SSE. The promise resolves once the stream is established and rejects when it can't connect, so a rejected call leaves nothing running in the background. reconnect only governs re-establishing a stream that drops after it was established. To retry the initial connection, loop around subscribe().
onEvent:
onError?:
onReconnect?:
session.state() and, for the message gap, session.listMessages().reconnect?:
delayMs (default 1000) up to maxDelayMs (default 30000). maxRetries (default Infinity) bounds the attempts per outage and resets once a connection is re-established. When retries are exhausted, onError fires.Returns: Promise<AgentControllerSubscription>, an object with an unsubscribe() method that stops reading and releases the stream.
sendMessage(message, options?)Direct link to sendmessagemessage-options
Sends a user message to the session. Pass a string, or { content, files } to attach base64-encoded files, where each file is { data, mediaType, filename? }. The reply arrives as message_* events on the subscription, not as the return value of the call.
Pass options.requestContext to merge custom context into the run's request context. Server-controlled keys win.
steer(message, options?)Direct link to steermessage-options
Injects a message into the in-flight run without starting a new turn.
followUp(message, options?)Direct link to followupmessage-options
Queues a follow-up message. If the session is idle it sends immediately. If a run is active it queues for after the run completes.
abort()Direct link to abort
Aborts the in-flight run.
approveTool(toolCallId, approved, options?)Direct link to approvetooltoolcallid-approved-options
Approves or declines a pending tool call raised by a tool_approval_required event.
respondToToolSuspension(toolCallId, resumeData, options?)Direct link to respondtotoolsuspensiontoolcallid-resumedata-options
Resumes a suspended interactive tool raised by a tool_suspended event. The resumeData shape depends on the tool: a string or string[] for ask_user, "Yes" or "No" for request_access, and a PlanResume for submit_plan.
interface PlanResume {
action: 'approved' | 'rejected'
feedback?: string
path?: string
title?: string
plan?: string
}
state(options?)Direct link to stateoptions
Returns the session's current mode, model, and thread for initial UI hydration and re-syncing after a reconnect. Pass { threadId } to read the state for a specific thread.
Returns: Promise<AgentControllerSessionState>
setState(updates)Direct link to setstateupdates
Merges key-value pairs into the session state. Existing keys not in the payload are preserved.
switchMode(modeId)Direct link to switchmodemodeid
Switches the active mode.
switchModel(modelId, options?)Direct link to switchmodelmodelid-options
Switches the model. options.scope is 'thread' (default) or 'global'. options.modeId targets a specific mode.
listThreads(options?)Direct link to listthreadsoptions
Lists the session's threads, newest first. Pass { limit } to cap the count and { tags } to scope to threads matching every tag. A bare number is shorthand for { limit }.
Returns: Promise<AgentControllerThreadInfo[]>
switchThread(threadId)Direct link to switchthreadthreadid
Switches the session to an existing thread and rebinds the stream and state.
createThread(title?)Direct link to createthreadtitle
Creates a new thread and binds the session to it.
Returns: Promise<CreateAgentControllerThreadResponse>
cloneThread(options?)Direct link to clonethreadoptions
Clones a thread and its messages, then binds the session to the clone. Accepts { sourceThreadId?, title? }.
Returns: Promise<CreateAgentControllerThreadResponse>
renameThread(threadId, title)Direct link to renamethreadthreadid-title
Renames a thread.
deleteThread(threadId)Direct link to deletethreadthreadid
Deletes a thread. If it's the active thread, the session unbinds.
listMessages(threadId, limit?)Direct link to listmessagesthreadid-limit
Lists the messages of a thread with createdAt hydrated to Date.
Returns: Promise<MastraDBMessage[]>
getGoal(), setGoal(objective, options?), updateGoal(options), clearGoal()Direct link to getgoal-setgoalobjective-options-updategoaloptions-cleargoal
Read, set, update, and clear the goal for the session's thread. setGoal accepts { judgeModelId?, maxRuns? }. updateGoal also accepts status: 'active' | 'paused' | 'done'. The agent's in-loop judge evaluates progress after each turn and reports it as goal_evaluation events.
getPermissions(), setPermissionForCategory(category, policy), setPermissionForTool(toolName, policy)Direct link to getpermissions-setpermissionforcategorycategory-policy-setpermissionfortooltoolname-policy
Read and set the per-category and per-tool approval policies.
getResourceIds(), setResourceId(newResourceId)Direct link to getresourceids-setresourceidnewresourceid
Read the known resource IDs for the session and change the session's resource identity.
getOMRecord()Direct link to getomrecord
Returns the observational memory record for the session's thread.
sendNotification(input)Direct link to sendnotificationinput
Sends a notification signal to the session. The agent's delivery policy decides whether the notification wakes an idle thread immediately or is held and summarised for later.
Returns: Promise<SendNotificationResult>
EventsDirect link to Events
onEvent receives every event the session emits, discriminated by event.type:
| Group | Events |
|---|---|
| Run | agent_start, agent_end, usage_update, goal_evaluation, follow_up_queued |
| Messages | message_start, message_update, message_end |
| Tools | tool_input_start, tool_input_delta, tool_input_end, tool_start, tool_update, tool_end, shell_output, command_exit, tool_approval_required, tool_suspended, tool_suspension_cancelled, task_updated |
| Session | state_changed, display_state_changed, mode_changed, model_changed, thread_changed, thread_created, thread_deleted, thread_title_updated |
| Subagents | subagent_start, subagent_text_delta, subagent_tool_start, subagent_tool_end, subagent_end, subagent_model_changed |
| Memory | om_observation_start, om_observation_end, om_observation_failed, om_reflection_start, om_reflection_end, om_reflection_failed, om_buffering_start, om_buffering_end, om_buffering_failed, om_model_changed, om_activation, om_status, om_thread_title_updated |
| Workspace | workspace_ready, workspace_error, workspace_status_changed |
| Notification | notification, notification_summary, info, error |
message_* events carry a MastraDBMessage and thread_created carries a thread, with timestamps hydrated to Date.
A controller can also emit events the SDK doesn't type. AgentControllerEvent is the union of KnownAgentControllerEvent and OtherAgentControllerEvent. Because OtherAgentControllerEvent.type is string, comparing event.type to a literal doesn't narrow the union. Narrow with isKnownAgentControllerEvent(event) first:
import { isKnownAgentControllerEvent } from '@mastra/client-js'
function handleEvent(event: AgentControllerEvent) {
if (!isKnownAgentControllerEvent(event)) return
switch (event.type) {
case 'message_update':
render(event.message)
break
case 'tool_approval_required':
showApproval(event.toolCallId)
break
}
}
Use agentControllerMessageText(message) to pull the plain text out of a message's nested content parts.