@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
537 lines • 19.5 kB
TypeScript
import type { AgentSignalContents, AgentSignalInput } from '../agent/signals.js';
import type { MastraBrowser } from '../browser/browser.js';
import { Mastra } from '../mastra/index.js';
import type { MastraMemory } from '../memory/memory.js';
import type { TracingContext, TracingOptions } from '../observability/index.js';
import { RequestContext } from '../request-context/index.js';
import type { ObservationalMemoryRecord } from '../storage/types.js';
import { Workspace } from '../workspace/workspace.js';
import type { TaskItemSnapshot } from './tools.js';
import type { AvailableModel, HeartbeatHandler, HarnessConfig, HarnessDisplayState, HarnessDisplayStateListener, HarnessDisplayStateSubscriptionOptions, HarnessEventListener, HarnessMessage, HarnessMode, HarnessQuestionAnswer, HarnessSession, HarnessThread, ModelAuthStatus, PermissionPolicy, PermissionRules, TokenUsage, ToolCategory } from './types.js';
/**
* The Harness orchestrates multiple agent modes, shared state, memory, and storage.
* It's the core abstraction that a TUI (or other UI) controls.
*
* @example
* ```ts
* const harness = new Harness({
* id: "my-coding-agent",
* storage: new LibSQLStore({ url: "file:./data.db" }),
* stateSchema: z.object({
* currentModelId: z.string().optional(),
* }),
* modes: [
* { id: "plan", name: "Plan", default: true, agent: planAgent },
* { id: "build", name: "Build", agent: buildAgent },
* ],
* })
*
* harness.subscribe((event) => {
* if (event.type === "message_update") renderMessage(event.message)
* })
*
* await harness.init()
* await harness.sendMessage({ content: "Hello!" })
* ```
*/
export declare class Harness<TState = {}> {
#private;
readonly id: string;
private config;
private stateSchema;
private state;
private currentModeId;
private currentThreadId;
private resourceId;
private defaultResourceId;
private listeners;
private displayStateSchedulers;
private abortController;
private abortRequested;
private currentRunId;
private currentTraceId;
private currentOperationId;
private agentThreadSubscription;
private agentThreadSubscriptionKey;
private followUpQueue;
private pendingApprovalResolve;
private pendingApprovalToolName;
private pendingSuspensionRunId;
private pendingSuspensionToolCallId;
private pendingQuestions;
private pendingPlanApprovals;
private workspace;
private workspaceFn;
private workspaceInitialized;
private browser;
private browserFn;
private heartbeatTimers;
private tokenUsage;
private sessionGrantedCategories;
private sessionGrantedTools;
private displayState;
private stateUpdateQueue;
constructor(config: HarnessConfig<TState>);
/**
* Access the internal Mastra instance.
* Available after `init()` when storage is configured.
* Useful for scorer registration, observability access, and eval tooling.
*/
getMastra(): Mastra | undefined;
/**
* Sets or updates the harness-level browser and propagates it to static mode agents.
*/
setBrowser(browser: MastraBrowser | undefined): void;
/**
* Initialize the harness — loads storage and workspace.
* Must be called before using the harness.
*/
init(): Promise<void>;
/**
* Select the most recent thread, or create one if none exist.
*/
selectOrCreateThread(): Promise<HarnessThread>;
private getMemoryStorage;
/**
* Get current harness state (read-only snapshot).
*/
getState(): Readonly<TState>;
private applyStateUpdates;
/**
* Update harness state. Validates against schema if provided.
* Emits state_changed event.
*/
setState(updates: Partial<TState>): Promise<void>;
private updateState;
private getSchemaDefaults;
listModes(): HarnessMode<TState>[];
getCurrentModeId(): string;
getCurrentMode(): HarnessMode<TState>;
/**
* Switch to a different mode.
* Aborts any in-progress generation and switches to the mode's default model.
*/
switchMode({ modeId }: {
modeId: string;
}): Promise<void>;
/**
* Load the stored model ID for a specific mode.
* Falls back to: thread metadata -> mode's defaultModelId -> current model.
*/
private loadModeModelId;
private propagateRuntimeServicesToAgent;
/**
* Get the agent for the current mode.
*/
private getCurrentAgent;
/**
* Get a short display name from the current model ID.
*/
getModelName(): string;
/**
* Get the full model ID (e.g., "anthropic/claude-sonnet-4").
*/
getFullModelId(): string;
/**
* Switch to a different model at runtime.
*/
switchModel({ modelId, scope, modeId, }: {
modelId: string;
scope?: 'global' | 'thread';
modeId?: string;
}): Promise<void>;
getCurrentModelId(): string;
hasModelSelected(): boolean;
/**
* Check if the current model's provider has authentication configured.
* Uses the provider registry's `apiKeyEnvVar` and the optional `modelAuthChecker` hook.
*/
getCurrentModelAuthStatus(): Promise<ModelAuthStatus>;
/**
* Get all available models from the provider registry with auth status.
* Uses the optional `modelAuthChecker`, `modelUseCountProvider`, and
* `customModelCatalogProvider` hooks.
*/
listAvailableModels(): Promise<AvailableModel[]>;
private getProviderApiKeyEnvVar;
getCurrentThreadId(): string | null;
getResourceId(): string;
getResolvedMemory(): Promise<MastraMemory | null>;
setResourceId({ resourceId }: {
resourceId: string;
}): void;
getDefaultResourceId(): string;
getKnownResourceIds(): Promise<string[]>;
createThread({ title }?: {
title?: string;
}): Promise<HarnessThread>;
/**
* Returns a memory accessor with thread and message management methods.
*/
get memory(): {
createThread: ({ title }?: {
title?: string;
}) => Promise<HarnessThread>;
switchThread: ({ threadId }: {
threadId: string;
}) => Promise<void>;
listThreads: (options?: {
allResources?: boolean;
/**
* Include forked subagent fork threads. Defaults to false: forks are
* transient clones used by the runtime and should not show up in user-facing
* thread lists / pickers / startup flows. Set to true for admin / debug
* tooling that needs to see every thread.
*/
includeForkedSubagents?: boolean;
}) => Promise<HarnessThread[]>;
renameThread: ({ title }: {
title: string;
}) => Promise<void>;
deleteThread: ({ threadId }: {
threadId: string;
}) => Promise<void>;
};
private deleteThread;
renameThread({ title }: {
title: string;
}): Promise<void>;
cloneThread({ sourceThreadId, title, resourceId, }?: {
sourceThreadId?: string;
title?: string;
resourceId?: string;
}): Promise<HarnessThread>;
switchThread({ threadId }: {
threadId: string;
}): Promise<void>;
listThreads(options?: {
allResources?: boolean;
/**
* Include forked subagent fork threads. Defaults to false: forks are
* transient clones used by the runtime and should not show up in user-facing
* thread lists / pickers / startup flows. Set to true for admin / debug
* tooling that needs to see every thread.
*/
includeForkedSubagents?: boolean;
}): Promise<HarnessThread[]>;
setThreadSetting({ key, value }: {
key: string;
value: unknown;
}): Promise<void>;
private deleteThreadSetting;
private loadThreadMetadata;
/**
* Load observational memory progress for the current thread.
* Reads the OM record and recent messages to reconstruct status,
* then emits an `om_status` event for the UI.
*/
loadOMProgress(): Promise<void>;
getObservationalMemoryRecord(): Promise<ObservationalMemoryRecord | null>;
/**
* Returns the observer model ID from state, falling back to omConfig defaults.
*/
getObserverModelId(): string | undefined;
/**
* Returns the reflector model ID from state, falling back to omConfig defaults.
*/
getReflectorModelId(): string | undefined;
/**
* Returns the observation threshold from state, falling back to omConfig defaults.
*/
getObservationThreshold(): number | undefined;
/**
* Returns the reflection threshold from state, falling back to omConfig defaults.
*/
getReflectionThreshold(): number | undefined;
/**
* Resolves the observer model ID to a language model instance via `resolveModel`.
*/
getResolvedObserverModel(): import("../memory").MastraLanguageModel | undefined;
/**
* Resolves the reflector model ID to a language model instance via `resolveModel`.
*/
getResolvedReflectorModel(): import("../memory").MastraLanguageModel | undefined;
/**
* Switch the Observer model.
*/
switchObserverModel({ modelId }: {
modelId: string;
}): Promise<void>;
/**
* Switch the Reflector model.
*/
switchReflectorModel({ modelId }: {
modelId: string;
}): Promise<void>;
getSubagentModelId({ agentType }?: {
agentType?: string;
}): string | null;
setSubagentModelId({ modelId, agentType }: {
modelId: string;
agentType?: string;
}): Promise<void>;
grantSessionCategory({ category }: {
category: ToolCategory;
}): void;
grantSessionTool({ toolName }: {
toolName: string;
}): void;
getSessionGrants(): {
categories: ToolCategory[];
tools: string[];
};
getToolCategory({ toolName }: {
toolName: string;
}): ToolCategory | null;
setPermissionForCategory({ category, policy }: {
category: ToolCategory;
policy: PermissionPolicy;
}): void;
setPermissionForTool({ toolName, policy }: {
toolName: string;
policy: PermissionPolicy;
}): void;
getPermissionRules(): PermissionRules;
/**
* Resolve whether a tool call should be auto-approved, denied, or asked.
* Resolution chain: per-tool deny → yolo → per-tool policy → session tool grant →
* session category grant → category policy → "ask"
*/
private resolveToolApproval;
private cleanupAgentThreadSubscription;
private getAgentThreadSubscriptionKey;
private ensureAgentThreadSubscription;
private ensureCurrentAgentThreadSubscription;
private drainFollowUpQueue;
private isActiveAgentThreadSubscription;
private finishSubscribedStreamRun;
private handleSubscribedStreamError;
private processSubscribedThreadStream;
/**
* Send a signal to the current agent/thread.
*/
sendSignal(input: AgentSignalInput | {
content: AgentSignalContents;
tracingContext?: TracingContext;
tracingOptions?: TracingOptions;
requestContext?: RequestContext;
}): {
id: string;
type: AgentSignalInput['type'];
accepted: Promise<{
accepted: true;
runId: string;
}>;
};
/**
* Send a message to the current agent.
* Streams the response and emits events.
*/
sendMessage({ content, files, tracingContext, tracingOptions, requestContext: requestContextInput, }: {
content: string;
files?: Array<{
data: string;
mediaType: string;
filename?: string;
}>;
tracingContext?: TracingContext;
tracingOptions?: TracingOptions;
requestContext?: RequestContext;
}): Promise<void>;
listMessages(options?: {
limit?: number;
}): Promise<HarnessMessage[]>;
saveSystemReminderMessage({ message, reminderType, role, metadata, }: {
message: string;
reminderType: string;
role?: 'user' | 'assistant' | 'system';
metadata?: Record<string, unknown>;
}): Promise<HarnessMessage | null>;
listMessagesForThread({ threadId, limit }: {
threadId: string;
limit?: number;
}): Promise<HarnessMessage[]>;
getFirstUserMessageForThread({ threadId }: {
threadId: string;
}): Promise<HarnessMessage | null>;
getFirstUserMessagesForThreads({ threadIds }: {
threadIds: string[];
}): Promise<Map<string, HarnessMessage>>;
private convertToHarnessMessage;
/**
* Process a stream response (shared between sendMessage and tool approval).
*/
private createStreamState;
private abortForOmFailure;
private processStream;
private processStreamChunk;
private finishStreamState;
/**
* Abort the current operation.
*/
abort(): void;
/**
* Steer the agent mid-stream: aborts current run and sends a new message.
*/
steer({ content, requestContext }: {
content: string;
requestContext?: RequestContext;
}): Promise<void>;
/**
* Queue a follow-up message to be processed after the current operation completes.
*/
followUp({ content, requestContext }: {
content: string;
requestContext?: RequestContext;
}): Promise<void>;
getFollowUpCount(): number;
isRunning(): boolean;
getCurrentRunId(): string | null;
isCurrentThreadStreamActive(): boolean;
/**
* Resolve once the current thread's stream is fully idle.
*
* After `abort()` is called the run's status can still be `'running'` for a
* few microtasks while the underlying model stream finalizes. Callers that
* need to send a fresh signal after an abort (e.g. plan approval → mode
* switch → trigger reminder) should await this before calling `sendSignal`
* to avoid the new signal being queued onto the dying run, which would then
* be drained with the previous run's already-aborted abortSignal.
*/
private waitForCurrentThreadStreamIdle;
getCurrentTraceId(): string | null;
private getSubagentDisplayName;
/**
* Returns a read-only snapshot of the canonical display state.
* UIs should use this to render instead of building up state from raw events.
*/
getDisplayState(): Readonly<HarnessDisplayState>;
/**
* Restore task display state after a UI replays persisted task tool history.
* This updates the Harness-owned display snapshot without emitting a live
* `task_updated` event, since no task tool just ran.
*/
restoreDisplayTasks(tasks: TaskItemSnapshot[]): void;
/**
* Reset display state fields that are scoped to a thread.
* Called on thread switch/creation.
*/
private resetThreadDisplayState;
/**
* Respond to a pending tool approval from the UI.
* "always_allow_category" grants the tool's category for the rest of the session, then approves.
*/
respondToToolApproval({ decision, requestContext, }: {
decision: 'approve' | 'decline' | 'always_allow_category';
requestContext?: RequestContext;
}): void;
/**
* Respond to a pending tool suspension from the UI.
* Provides resume data so the suspended tool can continue execution.
*/
respondToToolSuspension({ resumeData, requestContext, }: {
resumeData: any;
requestContext?: RequestContext;
}): Promise<void>;
/**
* Register a pending question resolver.
* Called by agent tools (e.g., ask_user) to pause execution until the UI responds.
*/
registerQuestion({ questionId, resolve, }: {
questionId: string;
resolve: (answer: HarnessQuestionAnswer) => void;
}): void;
/**
* Resolve a pending question with the user's answer.
* Called by the UI when the user responds to a question dialog.
*/
respondToQuestion({ questionId, answer }: {
questionId: string;
answer: HarnessQuestionAnswer;
}): void;
/**
* Register a pending plan approval resolver.
* Called by agent tools (e.g., submit_plan) to pause execution until approval.
*/
registerPlanApproval({ planId, resolve, }: {
planId: string;
resolve: (result: {
action: 'approved' | 'rejected';
feedback?: string;
}) => void;
}): void;
/**
* Respond to a pending plan approval.
* On approval: resolves the suspended plan tool, then switches to the default mode.
* On rejection: resolves with feedback (stays in current mode).
*/
respondToPlanApproval({ planId, response, }: {
planId: string;
response: {
action: 'approved' | 'rejected';
feedback?: string;
};
}): Promise<void>;
private handleToolApprove;
private handleToolDecline;
private handleToolResume;
/**
* Subscribe to harness events. Returns an unsubscribe function.
*/
subscribe(listener: HarnessEventListener): () => void;
/**
* Subscribe to coalesced display state snapshots.
*
* Use this for UI rendering paths that only need the latest display state.
* Raw event consumers should continue to use `subscribe()`.
*/
subscribeDisplayState(listener: HarnessDisplayStateListener, options?: HarnessDisplayStateSubscriptionOptions): () => void;
private emit;
private dispatchDisplayStateChanged;
private dispatchToListeners;
/**
* Apply a display state update based on an incoming event.
* This is the centralized state machine that keeps HarnessDisplayState in sync
* with every event the Harness emits.
*/
private applyDisplayStateUpdate;
/**
* Build the toolsets object that includes built-in harness tools (ask_user, submit_plan,
* and optionally subagent) plus any user-configured tools.
* Used by sendMessage, handleToolApprove, and handleToolDecline.
*/
private buildToolsets;
/**
* Build request context for agent execution.
* Tools can access harness state via requestContext.get('harness').
*/
private buildRequestContext;
/**
* Resolve memory from config — handles both static instances and dynamic factory functions.
*/
private resolveMemory;
getTokenUsage(): TokenUsage;
private persistTokenUsage;
getWorkspace(): Workspace | undefined;
/**
* Eagerly resolve the workspace. For dynamic workspaces (factory function),
* this triggers resolution and caches the result so getWorkspace() returns it.
* Useful for code paths outside the request flow (e.g. slash commands).
*/
resolveWorkspace({ requestContext, }?: {
requestContext?: RequestContext;
}): Promise<Workspace | undefined>;
hasWorkspace(): boolean;
isWorkspaceReady(): boolean;
destroyWorkspace(): Promise<void>;
private startHeartbeats;
registerHeartbeat(handler: HeartbeatHandler): void;
removeHeartbeat({ id }: {
id: string;
}): Promise<void>;
stopHeartbeats(): Promise<void>;
destroy(): Promise<void>;
getSession(): Promise<HarnessSession>;
private generateId;
}
//# sourceMappingURL=harness.d.ts.map