@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
1,487 lines (1,486 loc) • 101 kB
JavaScript
import { OrchestrationWorker, BackgroundTaskWorker, SchedulerWorker } from './chunk-YX7UTM3D.js';
import { computeNextFireAt, WorkflowEventProcessor } from './chunk-NNKKOMEN.js';
import { isToolLoopAgentLike, toolLoopAgentToMastraAgent } from './chunk-L4BINS4U.js';
import { initContextStorage } from './chunk-7F7LYRRK.js';
import { DatasetsManager } from './chunk-PAAKN4O7.js';
import { createOnScorerHook } from './chunk-RTJNKJR7.js';
import { agentThreadStreamRuntime, augmentWithInit, AgentChannels, isDurableAgentLike } from './chunk-AM3IOVFX.js';
import { DualLogger, noopLogger } from './chunk-CFNCK3E2.js';
import { EventEmitterPubSub } from './chunk-SQDHPWBX.js';
import { registerHook } from './chunk-L54GIUCB.js';
import { defaultGateways } from './chunk-7SS36SRG.js';
import { InMemoryServerCache } from './chunk-JZ7Q75IW.js';
import { NoOpObservability, noOpLoggerContext, noOpMetricsContext } from './chunk-4ZCIE3Q5.js';
import { BackgroundTaskManager } from './chunk-QOKJTCIS.js';
import { ConsoleLogger, LogLevel } from './chunk-DBBWTK24.js';
import { normalizeToolPayloadTransformPolicy } from './chunk-GNP47JBD.js';
import { MastraError } from './chunk-FJEVLHJT.js';
import { randomUUID } from 'crypto';
function createUndefinedPrimitiveError(type, value, key) {
const typeLabel = type === "mcp-server" ? "MCP server" : type;
const errorId = `MASTRA_ADD_${type.toUpperCase().replace("-", "_")}_UNDEFINED`;
return new MastraError({
id: errorId,
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Cannot add ${typeLabel}: ${typeLabel} is ${value === null ? "null" : "undefined"}. This may occur if config was spread ({ ...config }) and the original object had getters or non-enumerable properties.`,
details: { status: 400, ...key && { key } }
});
}
function targetsEqual(a, b) {
if (a === b) return true;
if (!a) return false;
return JSON.stringify(a) === JSON.stringify(b);
}
function collectWorkflowScheduleConfigs(workflow) {
const w = workflow;
if (typeof w.getScheduleConfigs === "function") {
return w.getScheduleConfigs() ?? [];
}
if (typeof w.getScheduleConfig === "function") {
const cfg = w.getScheduleConfig();
if (!cfg) return [];
return Array.isArray(cfg) ? cfg : [cfg];
}
return [];
}
function declarativeScheduleRowId(workflowId, scheduleId) {
const encodedWorkflow = encodeURIComponent(workflowId);
if (scheduleId === void 0) return `wf_${encodedWorkflow}`;
return `wf_${encodedWorkflow}__${encodeURIComponent(scheduleId)}`;
}
function ownerWorkflowIdForRow(rowId, byWorkflow) {
for (const workflowId of byWorkflow.keys()) {
const prefix = `wf_${encodeURIComponent(workflowId)}`;
if (rowId === prefix || rowId.startsWith(`${prefix}__`)) {
return workflowId;
}
}
return void 0;
}
function ownerWorkflowIdFromRowId(rowId) {
if (!rowId.startsWith("wf_")) return void 0;
const rest = rowId.slice("wf_".length);
const sep = rest.indexOf("__");
const encoded = sep === -1 ? rest : rest.slice(0, sep);
if (!encoded) return void 0;
try {
return decodeURIComponent(encoded);
} catch {
return void 0;
}
}
function metadataEqual(a, b) {
const aNorm = a ?? void 0;
const bNorm = b ?? void 0;
if (aNorm === bNorm) return true;
if (!aNorm || !bNorm) return false;
return JSON.stringify(aNorm) === JSON.stringify(bNorm);
}
var Mastra = class {
#vectors;
#agents;
#logger;
#workflows;
#observability;
#tts;
#deployer;
#serverMiddleware = [];
#storage;
#scorers;
#tools;
#processors;
#processorConfigurations = /* @__PURE__ */ new Map();
#memory;
#workspace;
#workspaces = {};
#server;
#serverAdapter;
#mcpServers;
#bundler;
#idGenerator;
#pubsub;
#backgroundTaskConfig;
#backgroundTaskManager;
#schedulerConfig;
/**
* Tracks whether any registered workflow has declared a `schedule` config.
* Used as a fast short-circuit so users without scheduled workflows pay
* zero cost beyond a boolean check.
*/
#hasScheduledWorkflow = false;
#gateways;
#channels;
#environment;
#toolPayloadTransform;
#workers = [];
#workerFilter;
// Lazily-constructed processor used by handleWorkflowEvent(). Shared between
// pull-mode workers (OrchestrationWorker) and push-mode entry points
// (in-process EventEmitter listener, the /api/workers/events HTTP route).
#workflowEventProcessor;
// Callback registered against the pubsub when running in push mode so we can
// unsubscribe it cleanly during stopWorkers().
#pushSubscription;
// Tracks (topic, listener) pairs registered against the pubsub on behalf of
// user-defined event listeners during startWorkers(). Used to make
// startWorkers()/stopWorkers() idempotent — a second startWorkers() call
// must not double-subscribe the same listener.
#userEventSubscriptions = [];
#events = {};
#internalMastraWorkflows = {};
// Server cache for temporary persistence and durable agent resumable streams
#serverCache;
// Cache for stored agents to allow in-memory modifications (like model changes) to persist across requests
#storedAgentsCache = /* @__PURE__ */ new Map();
// Cache for stored scorers to allow in-memory modifications to persist across requests
#storedScorersCache = /* @__PURE__ */ new Map();
// Registry for prompt blocks (stored or code-defined)
#promptBlocks = {};
// Editor instance for handling agent instantiation and configuration
#editor;
#datasets;
// Global version overrides for primitives (agents, etc.)
#versions;
get pubsub() {
return this.#pubsub;
}
get agentThreadStreamRuntime() {
return agentThreadStreamRuntime;
}
get workers() {
return this.#workers;
}
getWorker(name) {
return this.#workers.find((w) => w.name === name);
}
get backgroundTaskManager() {
return this.#backgroundTaskManager;
}
/**
* Returns the workflow scheduler owned by the SchedulerWorker,
* or undefined if the scheduler is not enabled / not yet started.
*
* The scheduler is created when `startWorkers()` initializes the
* SchedulerWorker (guarded by `#shouldEnableScheduler()`). Use it
* to create, pause, resume, or delete schedules imperatively.
*/
get scheduler() {
return this.#findSchedulerWorker()?.scheduler;
}
get datasets() {
if (!this.#datasets) {
this.#datasets = new DatasetsManager(this);
}
return this.#datasets;
}
/**
* Gets the currently configured ID generator function.
*
* @example
* ```typescript
* const mastra = new Mastra({
* idGenerator: context =>
* context?.idType === 'message' && context.threadId
* ? `msg-${context.threadId}-${Date.now()}`
* : `custom-${Date.now()}`
* });
* const generator = mastra.getIdGenerator();
* console.log(generator?.({ idType: 'message', threadId: 'thread-123' })); // \"msg-thread-123-1234567890\"
* ```
*/
getIdGenerator() {
return this.#idGenerator;
}
/**
* Gets the currently configured editor instance.
* The editor is responsible for handling agent instantiation and configuration.
*
* @example
* ```typescript
* const mastra = new Mastra({
* editor: new MastraEditor({ logger })
* });
* const editor = mastra.getEditor();
* ```
*/
getEditor() {
return this.#editor;
}
/**
* Gets a registered channel provider by its key.
*
* @example
* ```typescript
* import { SlackProvider } from '@mastra/slack';
* const slack = mastra.getChannelProvider<SlackProvider>('slack');
* ```
*/
getChannelProvider(key) {
return this.#channels?.[key];
}
/**
* Gets all registered channel providers.
*/
getChannelProviders() {
return this.#channels;
}
/**
* Shorthand getter for platform channels.
* Usage: `mastra.channels.slack.connect(agentId)`
*/
get channels() {
return this.#channels ?? {};
}
/**
* Returns the global version overrides configured on this Mastra instance.
* These are used as defaults when resolving sub-agent versions during delegation.
*/
getVersionOverrides() {
return this.#versions;
}
/**
* Returns the deployment environment name configured on this Mastra instance,
* falling back to `process.env.NODE_ENV` when unset, or `undefined` if neither
* is provided.
*
* Observability automatically reads this and attaches it to all signals so
* consumers can filter by environment without passing
* `tracingOptions.metadata.environment` on each call.
*/
getEnvironment() {
return this.#environment;
}
getToolPayloadTransform() {
return this.#toolPayloadTransform;
}
/**
* Gets the stored agents cache
* @internal
*/
getStoredAgentCache() {
return this.#storedAgentsCache;
}
/**
* Gets the stored scorers cache
* @internal
*/
getStoredScorerCache() {
return this.#storedScorersCache;
}
/**
* Generates a unique identifier using the configured generator or defaults to `crypto.randomUUID()`.
*
* This method is used internally by Mastra for creating unique IDs for various entities
* like workflow runs, agent conversations, and other resources that need unique identification.
*
* @param context - Optional context information about what type of ID is being generated
* and where it's being requested from. This allows custom ID generators
* to create deterministic IDs based on context.
*
* @throws {MastraError} When the custom ID generator returns an empty string
*
* @example
* ```typescript
* const mastra = new Mastra();
* const id = mastra.generateId();
* console.log(id); // "550e8400-e29b-41d4-a716-446655440000"
*
* // With context for deterministic IDs
* const messageId = mastra.generateId({
* idType: 'message',
* source: 'agent',
* threadId: 'thread-123'
* });
* ```
*/
generateId(context) {
if (this.#idGenerator) {
const id = this.#idGenerator(context);
if (!id) {
const error = new MastraError({
id: "MASTRA_ID_GENERATOR_RETURNED_EMPTY_STRING",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: "ID generator returned an empty string, which is not allowed"
});
this.#logger?.trackException(error);
throw error;
}
return id;
}
return randomUUID();
}
/**
* Sets a custom ID generator function for creating unique identifiers.
*
* The ID generator function will be used by `generateId()` instead of the default
* `crypto.randomUUID()`. This is useful for creating application-specific ID formats
* or integrating with existing ID generation systems. The function receives
* optional context about what is requesting the ID.
*
* @example
* ```typescript
* const mastra = new Mastra();
* mastra.setIdGenerator(context =>
* context?.idType === 'run' && context.entityId
* ? `run-${context.entityId}-${Date.now()}`
* : `custom-${Date.now()}`
* );
* const id = mastra.generateId({ idType: 'run', entityId: 'agent-123' });
* console.log(id); // "run-agent-123-1234567890"
* ```
*/
setIdGenerator(idGenerator) {
this.#idGenerator = idGenerator;
}
/**
* Sets the server configuration for this Mastra instance.
*
* @param server - The server configuration object
*
* @example
* ```typescript
* mastra.setServer({ ...mastra.getServer(), auth: new MastraAuthWorkos() });
* ```
*/
setServer(server) {
this.#server = server;
}
/**
* Registers an exporter on the default observability instance.
*
* If the current observability is a no-op (user didn't configure any), it is
* first replaced with the provided entrypoint and the instance is registered
* as default. If a real observability entrypoint already exists, the exporter
* is added directly to the existing default instance.
*
* @param exporter - The exporter to register (e.g. a MastraPlatformExporter)
* @param instance - An ObservabilityInstance pre-configured with the exporter, used as default when bootstrapping
* @param entrypoint - A real ObservabilityEntrypoint to bootstrap if the current one is a no-op
*/
registerExporter(exporter, instance, entrypoint) {
if (this.#observability instanceof NoOpObservability) {
this.#observability = entrypoint;
this.#observability.setLogger({ logger: this.#logger });
this.#observability.setMastraContext({ mastra: this });
this.#observability.registerInstance("default", instance, true);
}
const defaultInstance = this.#observability.getDefaultInstance();
if (defaultInstance?.registerExporter) {
defaultInstance.registerExporter(exporter);
}
}
/**
* Creates a new Mastra instance with the provided configuration.
*
* The constructor initializes all the components specified in the config, sets up
* internal systems like logging and observability, and registers components with each other.
*
* @example
* ```typescript
* const mastra = new Mastra({
* agents: {
* assistant: new Agent({
* id: 'assistant',
* name: 'Assistant',
* instructions: 'You are a helpful assistant',
* model: 'openai/gpt-5'
* })
* },
* storage: new PostgresStore({
* connectionString: process.env.DATABASE_URL
* }),
* logger: new PinoLogger({ name: 'MyApp' }),
* observability: new Observability({
* configs: { default: { serviceName: 'mastra', exporters: [new MastraStorageExporter()] } },
* }),
* });
* ```
*/
constructor(config) {
initContextStorage();
this.#serverCache = config?.cache ?? new InMemoryServerCache();
this.#editor = config?.editor;
if (this.#editor && typeof this.#editor.registerWithMastra === "function") {
this.#editor.registerWithMastra(this);
}
this.#versions = config?.versions;
this.#environment = config?.environment ?? process.env.NODE_ENV;
this.#toolPayloadTransform = normalizeToolPayloadTransformPolicy(
config?.transform ?? config?.toolPayloadProjection
);
if (config?.pubsub) {
this.#pubsub = config.pubsub;
} else {
this.#pubsub = new EventEmitterPubSub();
}
this.#events = {};
for (const topic in config?.events ?? {}) {
if (!Array.isArray(config?.events?.[topic])) {
this.#events[topic] = [config?.events?.[topic]];
} else {
this.#events[topic] = config?.events?.[topic] ?? [];
}
}
const rawWorkersEnv = process.env.MASTRA_WORKERS;
let workersOption;
if (rawWorkersEnv === "false") {
workersOption = false;
} else {
workersOption = config?.workers;
if (rawWorkersEnv && rawWorkersEnv !== "false") {
const names = rawWorkersEnv.split(",").map((s) => s.trim()).filter(Boolean);
if (names.length > 0) {
this.#workerFilter = new Set(names);
}
}
}
if (workersOption === false) ; else if (Array.isArray(workersOption)) {
this.#workers = workersOption;
for (const w of this.#workers) {
w.__registerMastra(this);
}
} else {
const pubsubModes = this.#pubsub.supportedModes ?? ["pull"];
const defaultWorkers = [];
if (pubsubModes.includes("pull")) {
defaultWorkers.push(new OrchestrationWorker());
}
if (config?.backgroundTasks?.enabled) {
defaultWorkers.push(new BackgroundTaskWorker(config.backgroundTasks));
}
this.#workers = defaultWorkers;
for (const w of this.#workers) {
w.__registerMastra(this);
}
}
let logger;
if (config?.logger === false) {
logger = noopLogger;
} else {
if (config?.logger) {
logger = config.logger;
} else {
const levelOnEnv = process.env.NODE_ENV === "production" && process.env.MASTRA_DEV !== "true" ? LogLevel.WARN : LogLevel.INFO;
logger = new ConsoleLogger({ name: "Mastra", level: levelOnEnv });
}
}
this.#logger = logger;
this.#idGenerator = config?.idGenerator;
let storage = config?.storage;
if (storage) {
storage = augmentWithInit(storage);
}
if (config?.observability) {
if (typeof config.observability.getDefaultInstance === "function") {
this.#observability = config.observability;
this.#observability.setLogger({ logger: this.#logger });
} else {
this.#logger?.warn(
'Observability configuration error: Expected an Observability instance, but received a config object. Import and instantiate: import { Observability, MastraStorageExporter } from "@mastra/observability"; then pass: observability: new Observability({ configs: { default: { serviceName: "mastra", exporters: [new MastraStorageExporter()] } } }). Observability has been disabled.'
);
this.#observability = new NoOpObservability();
}
} else {
this.#observability = new NoOpObservability();
}
const dualLogger = new DualLogger(this.#logger, () => this.loggerVNext);
this.#logger = dualLogger;
this.#storage = storage;
this.#backgroundTaskConfig = config?.backgroundTasks;
if (workersOption !== false) {
this.#ensureBackgroundTaskManager();
}
this.#schedulerConfig = config?.scheduler;
this.#vectors = {};
this.#mcpServers = {};
this.#tts = {};
this.#agents = {};
this.#scorers = {};
this.#tools = {};
this.#processors = {};
this.#memory = {};
this.#workflows = {};
this.#gateways = {};
if (config?.tools) {
Object.entries(config.tools).forEach(([key, tool]) => {
if (tool != null) {
this.addTool(tool, key);
}
});
}
if (config?.processors) {
Object.entries(config.processors).forEach(([key, processor]) => {
if (processor != null) {
this.addProcessor(processor, key);
}
});
}
if (config?.memory) {
Object.entries(config.memory).forEach(([key, memory]) => {
if (memory != null) {
this.addMemory(memory, key);
}
});
}
if (config?.vectors) {
Object.entries(config.vectors).forEach(([key, vector]) => {
if (vector != null) {
this.addVector(vector, key);
}
});
}
if (config?.workspace) {
this.#workspace = config.workspace;
this.addWorkspace(config.workspace, void 0, { source: "mastra" });
}
if (config?.scorers) {
Object.entries(config.scorers).forEach(([key, scorer]) => {
if (scorer != null) {
this.addScorer(scorer, key, { source: "code" });
}
});
}
if (config?.workflows) {
Object.entries(config.workflows).forEach(([key, workflow]) => {
if (workflow != null) {
this.addWorkflow(workflow, key);
}
});
}
if (config?.gateways) {
Object.entries(config.gateways).forEach(([key, gateway]) => {
if (gateway != null) {
this.addGateway(gateway, key);
}
});
}
for (const gateway of defaultGateways) {
const key = gateway.getId();
if (!this.#gateways[key]) {
this.#gateways[key] = gateway;
}
}
if (config?.mcpServers) {
Object.entries(config.mcpServers).forEach(([key, server]) => {
if (server != null) {
this.addMCPServer(server, key);
}
});
}
if (config?.tts) {
Object.entries(config.tts).forEach(([key, tts]) => {
if (tts != null) {
this.#tts[key] = tts;
}
});
}
if (config?.server) {
this.#server = config.server;
}
if (config?.channels) {
this.#channels = config.channels;
const channelRoutes = [];
for (const [, channel] of Object.entries(config.channels)) {
if (channel == null) continue;
if (channel.__attach) {
channel.__attach(this);
}
const routes = channel.getRoutes();
channelRoutes.push(...routes);
}
if (channelRoutes.length > 0) {
const existingRoutes = this.#server?.apiRoutes ?? [];
this.#server = {
...this.#server,
apiRoutes: [...existingRoutes, ...channelRoutes]
};
}
}
if (config?.agents) {
Object.entries(config.agents).forEach(([key, agent]) => {
if (agent != null) {
this.addAgent(agent, key);
}
});
}
registerHook("onScorerRun" /* ON_SCORER_RUN */, createOnScorerHook(this));
this.#observability.setMastraContext({ mastra: this });
this.setLogger({ logger });
if (this.#channels) {
void Promise.resolve().then(async () => {
for (const [key, channel] of Object.entries(this.#channels ?? {})) {
if (channel.initialize) {
try {
await channel.initialize();
} catch (err) {
console.error(`[Mastra] Failed to initialize channel "${key}":`, err);
}
}
}
});
}
}
#ensureBackgroundTaskManager() {
if (!this.#backgroundTaskConfig?.enabled || !this.#storage || this.#backgroundTaskManager) {
return;
}
const bgManager = new BackgroundTaskManager(this.#backgroundTaskConfig);
bgManager.__registerMastra(this);
this.#backgroundTaskManager = bgManager;
const tools = this.#tools;
if (tools) {
for (const [name, tool] of Object.entries(tools)) {
this.#registerToolWithBackgroundManager(name, tool);
}
}
void bgManager.init(this.#pubsub).catch((error) => {
this.#logger?.error("Failed to initialize background task manager", error);
});
}
/**
* Build a `ToolExecutor` adapter for a Mastra-registered tool and stash it
* on the background task manager's static registry. Skipped if the tool has
* no `execute` (declarative-only tools, e.g. MCP descriptors).
*/
#registerToolWithBackgroundManager(name, tool) {
if (!this.#backgroundTaskManager) return;
if (typeof tool.execute !== "function") return;
const execute = tool.execute.bind(tool);
this.#backgroundTaskManager.registerStaticExecutor(name, {
execute: async (args, options) => {
return execute(
args,
{
toolCallId: "",
messages: [],
abortSignal: options?.abortSignal
}
);
}
});
}
/**
* Returns the flat list of declarative schedules sourced from currently
* registered workflows. Single-schedule workflows yield one entry keyed by
* `wf_<encoded(workflowId)>`. Array-form workflows yield one entry per array
* entry keyed by `wf_<encoded(workflowId)>__<encoded(scheduleId)>` so the
* prefix uniquely identifies "all rows owned by this workflow's declarative
* config" even when ids contain `__` or other delimiter-like characters.
*/
#collectDeclarativeSchedules() {
const out = [];
const workflows = this.#workflows;
for (const workflow of Object.values(workflows ?? {})) {
const configs = collectWorkflowScheduleConfigs(workflow);
if (configs.length === 0) continue;
const isArrayForm = configs.length > 1 || configs.length === 1 && configs[0].id !== void 0;
for (const cfg of configs) {
const scheduleId = isArrayForm ? declarativeScheduleRowId(workflow.id, cfg.id) : declarativeScheduleRowId(workflow.id);
out.push({ scheduleId, workflowId: workflow.id, cfg });
}
}
return out;
}
#shouldEnableScheduler() {
if (this.#schedulerConfig?.enabled === false) return false;
if (this.#schedulerConfig?.enabled === true) return true;
return this.#hasScheduledWorkflow;
}
/**
* Find the SchedulerWorker from the workers list (if present).
*/
#findSchedulerWorker() {
return this.#workers.find((w) => w.name === "scheduler");
}
/**
* Sync code-declared schedule configs to the database. Called by
* SchedulerWorker during init and by addWorkflow() for late registrations.
*
* @internal — public so SchedulerWorker can call it, not part of the user API.
*/
async registerDeclarativeSchedules(schedulesStore) {
const declared = this.#collectDeclarativeSchedules();
const declaredIds = new Set(declared.map((d) => d.scheduleId));
const declaredIdsByWorkflow = /* @__PURE__ */ new Map();
const workflows = this.#workflows;
for (const workflow of Object.values(workflows ?? {})) {
declaredIdsByWorkflow.set(workflow.id, /* @__PURE__ */ new Set());
}
for (const { workflowId, scheduleId } of declared) {
if (!declaredIdsByWorkflow.has(workflowId)) declaredIdsByWorkflow.set(workflowId, /* @__PURE__ */ new Set());
declaredIdsByWorkflow.get(workflowId).add(scheduleId);
}
for (const { scheduleId, workflowId, cfg } of declared) {
try {
const existing = await schedulesStore.getSchedule(scheduleId);
const now = Date.now();
const target = {
type: "workflow",
workflowId,
inputData: cfg.inputData,
initialState: cfg.initialState,
requestContext: cfg.requestContext
};
if (!existing) {
await schedulesStore.createSchedule({
id: scheduleId,
target,
cron: cfg.cron,
timezone: cfg.timezone,
status: "active",
nextFireAt: computeNextFireAt(cfg.cron, { timezone: cfg.timezone, after: now }),
createdAt: now,
updatedAt: now,
metadata: cfg.metadata
});
continue;
}
const patch = {};
const cronChanged = existing.cron !== cfg.cron;
const timezoneChanged = (existing.timezone ?? void 0) !== (cfg.timezone ?? void 0);
if (cronChanged) patch.cron = cfg.cron;
if (timezoneChanged) patch.timezone = cfg.timezone;
if (!targetsEqual(existing.target, target)) patch.target = target;
if (!metadataEqual(existing.metadata, cfg.metadata)) patch.metadata = cfg.metadata;
if (cronChanged || timezoneChanged) {
patch.nextFireAt = computeNextFireAt(cfg.cron, { timezone: cfg.timezone, after: now });
}
if (Object.keys(patch).length > 0) {
await schedulesStore.updateSchedule(scheduleId, patch);
}
} catch (error) {
this.#logger?.error("Failed to register declarative schedule", { scheduleId, workflowId, error });
}
}
const allRows = await schedulesStore.listSchedules();
for (const row of allRows) {
if (declaredIds.has(row.id)) continue;
if (!row.id.startsWith("wf_")) continue;
const ownerWorkflowId = ownerWorkflowIdForRow(row.id, declaredIdsByWorkflow) ?? ownerWorkflowIdFromRowId(row.id);
if (!ownerWorkflowId) continue;
try {
await schedulesStore.deleteSchedule(row.id);
} catch (error) {
this.#logger?.error("Failed to delete orphaned declarative schedule", {
scheduleId: row.id,
workflowId: ownerWorkflowId,
error
});
}
}
}
/**
* Auto-enables the background task manager when an agent with sub-agents is
* registered. Sub-agent delegation runs in the background by default so the
* parent stream stays responsive; that requires the manager to be available.
* No-op when the user explicitly opted out via `backgroundTasks.enabled: false`.
*
* Eligible agents: any agent whose `agents` field is either a static record
* with at least one entry OR a dynamic (function-based) resolver. Function
* resolvers are evaluated per request, so we can't inspect their contents
* here — but if the caller bothered to wire one up, we enable defensively
* so those resolved sub-agents also dispatch in the background.
*/
#maybeEnableBackgroundTasksForAgent(agent) {
if (this.#backgroundTaskManager) return;
if (this.#backgroundTaskConfig?.enabled === false) return;
if (!agent.__hasSubAgentsConfigured?.()) return;
this.#backgroundTaskConfig = { ...this.#backgroundTaskConfig ?? {}, enabled: true };
this.#ensureBackgroundTaskManager();
}
getAgent(name, version) {
const agent = this.#agents?.[name];
if (!agent) {
const error = new MastraError({
id: "MASTRA_GET_AGENT_BY_NAME_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Agent with name ${String(name)} not found`,
details: {
status: 404,
agentName: String(name),
agents: Object.keys(this.#agents ?? {}).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
if (!version) {
return this.#agents[name];
}
return this.resolveVersionedAgent(agent, version);
}
/**
* Returns the `AgentChannels` instances for all registered agents.
* Keys are agent IDs.
*/
getChannels() {
const result = {};
for (const [agentKey, agent] of Object.entries(this.#agents ?? {})) {
const agentChannels = agent.getChannels();
if (agentChannels instanceof AgentChannels) {
result[agentKey] = agentChannels;
}
}
return result;
}
getAgentById(id, version) {
let agent = Object.values(this.#agents).find((a) => a.id === id);
if (!agent) {
try {
agent = this.getAgent(id);
} catch {
}
}
if (!agent) {
const error = new MastraError({
id: "MASTRA_GET_AGENT_BY_AGENT_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Agent with id ${String(id)} not found`,
details: {
status: 404,
agentId: String(id),
agents: Object.keys(this.#agents ?? {}).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
if (!version) {
return agent;
}
return this.resolveVersionedAgent(agent, version);
}
/**
* Resolve a versioned variant of an agent by applying stored overrides from the editor.
*
* Requires the editor package to be configured — throws
* `MASTRA_EDITOR_REQUIRED_FOR_VERSIONED_AGENT_LOOKUP` if it is not.
*
* @param agent - The code-defined agent to resolve a version for.
* @param version - Selects a version by ID or publication status.
* @returns A forked agent instance with the stored overrides applied.
*/
async resolveVersionedAgent(agent, version) {
const editor = this.getEditor();
if (!editor) {
const error = new MastraError({
id: "MASTRA_EDITOR_REQUIRED_FOR_VERSIONED_AGENT_LOOKUP",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: "Versioned agent lookup requires the editor package to be configured",
details: {
status: 400,
agentId: agent.id,
...version && "versionId" in version ? { versionId: version.versionId } : {},
...version && "status" in version && version.status ? { versionStatus: version.status } : {}
}
});
this.#logger?.trackException(error);
throw error;
}
return editor.agent.applyStoredOverrides(
agent,
"versionId" in version ? version : { status: version.status ?? "published" }
);
}
/**
* Returns all registered agents as a record keyed by their names.
*
* This method provides access to the complete registry of agents, allowing you to
* iterate over them, check what agents are available, or perform bulk operations.
*
* @example
* ```typescript
* const mastra = new Mastra({
* agents: {
* weatherAgent: new Agent({ id: 'weather-agent', name: 'weather', model: 'openai/gpt-4o' }),
* supportAgent: new Agent({ id: 'support-agent', name: 'support', model: 'openai/gpt-4o' })
* }
* });
*
* const allAgents = mastra.listAgents();
* console.log(Object.keys(allAgents)); // ['weatherAgent', 'supportAgent']
* ```
*/
listAgents() {
return this.#agents;
}
/**
* Adds a new agent to the Mastra instance.
*
* This method allows dynamic registration of agents after the Mastra instance
* has been created. The agent will be initialized with the current logger.
*
* @throws {MastraError} When an agent with the same key already exists
*
* @example
* ```typescript
* const mastra = new Mastra();
* const newAgent = new Agent({
* id: 'chat-agent',
* name: 'Chat Assistant',
* model: 'openai/gpt-4o'
* });
* mastra.addAgent(newAgent); // Uses agent.id as key
* // or
* mastra.addAgent(newAgent, 'customKey'); // Uses custom key
*
* // Durable agents (e.g., InngestAgent) are also supported:
* const durableAgent = createInngestAgent({ agent: newAgent, inngest });
* mastra.addAgent(durableAgent); // Auto-registers required workflows
* ```
*/
addAgent(agent, key, options) {
if (!agent) {
throw createUndefinedPrimitiveError("agent", agent, key);
}
if (isDurableAgentLike(agent)) {
const durableAgent = agent;
const underlyingAgent = durableAgent.agent;
const agentKey2 = key || durableAgent.id;
const agents2 = this.#agents;
if (agents2[agentKey2]) {
const logger = this.getLogger();
logger.debug(`Agent with key ${agentKey2} already exists. Skipping addition.`);
return;
}
durableAgent.__setMastra?.(this);
underlyingAgent.__setLogger(this.#logger);
underlyingAgent.__registerMastra(this);
underlyingAgent.__registerPrimitives({
logger: this.getLogger(),
storage: this.getStorage(),
agents: agents2,
tts: this.#tts,
vectors: this.#vectors
});
agents2[agentKey2] = durableAgent;
const durableWorkflows = durableAgent.getDurableWorkflows?.() ?? [];
for (const workflow of durableWorkflows) {
this.addWorkflow(workflow, workflow.id);
}
return;
}
let mastraAgent;
if (isToolLoopAgentLike(agent)) {
mastraAgent = toolLoopAgentToMastraAgent(agent, { fallbackName: key });
} else {
mastraAgent = agent;
}
const agentKey = key || mastraAgent.id;
const agents = this.#agents;
if (agents[agentKey]) {
return;
}
mastraAgent.__setLogger(this.#logger);
mastraAgent.__registerMastra(this);
mastraAgent.__registerPrimitives({
logger: this.getLogger(),
storage: this.getStorage(),
agents,
tts: this.#tts,
vectors: this.#vectors
});
if (options?.source) {
mastraAgent.source = options.source;
}
agents[agentKey] = mastraAgent;
mastraAgent.getConfiguredProcessorWorkflows().then((processorWorkflows) => {
for (const workflow of processorWorkflows) {
this.addWorkflow(workflow, workflow.id);
}
}).catch((err) => {
this.#logger?.debug(`Failed to register processor workflows for agent ${agentKey}:`, err);
});
if (mastraAgent.hasOwnWorkspace?.()) {
Promise.resolve(mastraAgent.getWorkspace?.()).then((workspace) => {
if (workspace) {
this.addWorkspace(workspace, void 0, {
source: "agent",
agentId: mastraAgent.id ?? agentKey,
agentName: mastraAgent.name
});
}
}).catch((err) => {
this.#logger?.debug(`Failed to register workspace for agent ${agentKey}:`, err);
});
}
mastraAgent.listScorers().then((scorers) => {
for (const [, entry] of Object.entries(scorers || {})) {
this.addScorer(entry.scorer, void 0, { source: "code" });
}
}).catch((err) => {
this.#logger?.debug(`Failed to register scorers from agent ${agentKey}:`, err);
});
const agentChannelsInstance = mastraAgent.getChannels();
if (agentChannelsInstance) {
agentChannelsInstance.__setLogger(this.#logger);
const channelRoutes = agentChannelsInstance.getWebhookRoutes();
if (channelRoutes.length > 0) {
this.#server = {
...this.#server,
apiRoutes: [...this.#server?.apiRoutes ?? [], ...channelRoutes]
};
}
void agentChannelsInstance.initialize(this);
}
}
/**
* Removes an agent from the Mastra instance by its key or ID.
* Used when stored agents are updated/deleted to allow fresh data to be loaded.
*
* @param keyOrId - The agent key or ID to remove
* @returns true if an agent was removed, false if no agent was found
*
* @example
* ```typescript
* // Remove by key
* mastra.removeAgent('myAgent');
*
* // Remove by ID
* mastra.removeAgent('agent-123');
* ```
*/
removeAgent(keyOrId) {
const agents = this.#agents;
if (agents[keyOrId]) {
const agentId = agents[keyOrId]?.id;
delete agents[keyOrId];
if (agentId) {
this.#storedAgentsCache.delete(agentId);
}
return true;
}
const key = Object.keys(agents).find((k) => agents[k]?.id === keyOrId);
if (key) {
const agentId = agents[key]?.id;
delete agents[key];
if (agentId) {
this.#storedAgentsCache.delete(agentId);
}
return true;
}
return false;
}
/**
* Retrieves a registered vector store by its name.
*
* @template TVectorName - The specific vector store name type from the registered vectors
* @throws {MastraError} When the vector store with the specified name is not found
*
* @example Using a vector store for semantic search
* ```typescript
* import { PineconeVector } from '@mastra/pinecone';
* import { OpenAIEmbedder } from '@mastra/embedders';
*
* const mastra = new Mastra({
* vectors: {
* knowledge: new PineconeVector({
* apiKey: process.env.PINECONE_API_KEY,
* indexName: 'knowledge-base',
* embedder: new OpenAIEmbedder({
* apiKey: process.env.OPENAI_API_KEY,
* model: 'text-embedding-3-small'
* })
* }),
* products: new PineconeVector({
* apiKey: process.env.PINECONE_API_KEY,
* indexName: 'product-catalog'
* })
* }
* });
*
* // Get a vector store and perform semantic search
* const knowledgeBase = mastra.getVector('knowledge');
* const results = await knowledgeBase.query({
* query: 'How to reset password?',
* topK: 5
* });
*
* console.log('Relevant documents:', results);
* ```
*/
getVector(name) {
const vector = this.#vectors?.[name];
if (!vector) {
const error = new MastraError({
id: "MASTRA_GET_VECTOR_BY_NAME_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Vector with name ${String(name)} not found`,
details: {
status: 404,
vectorName: String(name),
vectors: Object.keys(this.#vectors ?? {}).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
return vector;
}
/**
* Retrieves a specific vector store instance by its ID.
*
* This method searches for a vector store by its internal ID property.
* If not found by ID, it falls back to searching by registration key.
*
* @throws {MastraError} When the specified vector store is not found
*
* @example
* ```typescript
* const mastra = new Mastra({
* vectors: {
* embeddings: chromaVector
* }
* });
*
* const vectorStore = mastra.getVectorById('chroma-123');
* ```
*/
getVectorById(id) {
const allVectors = this.#vectors ?? {};
for (const vector of Object.values(allVectors)) {
if (vector.id === id) {
return vector;
}
}
const vectorByKey = allVectors[id];
if (vectorByKey) {
return vectorByKey;
}
const error = new MastraError({
id: "MASTRA_GET_VECTOR_BY_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Vector store with id ${id} not found`,
details: {
status: 404,
vectorId: String(id),
vectors: Object.keys(allVectors).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
/**
* Returns all registered vector stores as a record keyed by their names.
*
* @example Listing all vector stores
* ```typescript
* const mastra = new Mastra({
* vectors: {
* documents: new PineconeVector({ indexName: 'docs' }),
* images: new PineconeVector({ indexName: 'images' }),
* products: new ChromaVector({ collectionName: 'products' })
* }
* });
*
* const allVectors = mastra.getVectors();
* console.log(Object.keys(allVectors)); // ['documents', 'images', 'products']
*
* // Check vector store types and configurations
* for (const [name, vectorStore] of Object.entries(allVectors)) {
* console.log(`Vector store ${name}:`, vectorStore.constructor.name);
* }
* ```
*/
listVectors() {
return this.#vectors;
}
/**
* Adds a new vector store to the Mastra instance.
*
* This method allows dynamic registration of vector stores after the Mastra instance
* has been created. The vector store will be initialized with the current logger.
*
* @throws {MastraError} When a vector store with the same key already exists
*
* @example
* ```typescript
* const mastra = new Mastra();
* const newVector = new ChromaVector({ id: 'chroma-embeddings' });
* mastra.addVector(newVector); // Uses vector.id as key
* // or
* mastra.addVector(newVector, 'customKey'); // Uses custom key
* ```
*/
addVector(vector, key) {
if (!vector) {
throw createUndefinedPrimitiveError("vector", vector, key);
}
const vectorKey = key || vector.id;
const vectors = this.#vectors;
if (vectors[vectorKey]) {
return;
}
vector.__setLogger(this.#logger || this.getLogger());
vectors[vectorKey] = vector;
}
/**
* @deprecated Use listVectors() instead
*/
getVectors() {
console.warn("getVectors() is deprecated. Use listVectors() instead.");
return this.listVectors();
}
/**
* Gets the currently configured deployment provider.
*
* @example
* ```typescript
* const mastra = new Mastra({
* deployer: new VercelDeployer({
* token: process.env.VERCEL_TOKEN,
* projectId: process.env.VERCEL_PROJECT_ID
* })
* });
*
* const deployer = mastra.getDeployer();
* if (deployer) {
* await deployer.deploy({
* name: 'my-mastra-app',
* environment: 'production'
* });
* }
* ```
*/
getDeployer() {
return this.#deployer;
}
/**
* Gets the global workspace instance.
* Workspace provides file storage, skills, and code execution capabilities.
* Agents inherit this workspace unless they have their own configured.
*
* @example
* ```typescript
* const workspace = mastra.getWorkspace();
* if (workspace?.skills) {
* const skills = await workspace.skills.list();
* }
* ```
*/
getWorkspace() {
return this.#workspace;
}
/**
* Retrieves a registered workspace by its ID.
*
* @throws {MastraError} When the workspace with the specified ID is not found
*
* @example
* ```typescript
* const workspace = mastra.getWorkspaceById('workspace-123');
* const files = await workspace.filesystem.readdir('/');
* ```
*/
getWorkspaceById(id) {
const entry = this.#workspaces[id];
if (!entry) {
const error = new MastraError({
id: "MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Workspace with id ${id} not found`,
details: {
status: 404,
workspaceId: id,
availableIds: Object.keys(this.#workspaces).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
return entry.workspace;
}
/**
* Returns all registered workspaces as a record keyed by their IDs.
*
* @example
* ```typescript
* const workspaces = mastra.listWorkspaces();
* for (const [id, entry] of Object.entries(workspaces)) {
* console.log(`Workspace ${id}: ${entry.workspace.name} (source: ${entry.source})`);
* }
* ```
*/
listWorkspaces() {
return { ...this.#workspaces };
}
/**
* Adds a new workspace to the Mastra instance.
*
* This method allows dynamic registration of workspaces after the Mastra instance
* has been created. Workspaces are keyed by their ID.
*
* @example
* ```typescript
* const workspace = new Workspace({
* id: 'project-workspace',
* name: 'Project Workspace',
* filesystem: new LocalFilesystem({ rootPath: './workspace' })
* });
* mastra.addWorkspace(workspace);
* ```
*/
addWorkspace(workspace, key, metadata) {
if (!workspace) {
throw createUndefinedPrimitiveError("workspace", workspace, key);
}
const source = metadata?.source ?? (metadata?.agentId || metadata?.agentName ? "agent" : "mastra");
if (source === "agent" && (!metadata?.agentId || !metadata?.agentName)) {
throw new MastraError({
id: "MASTRA_ADD_WORKSPACE_MISSING_AGENT_METADATA",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: "Agent workspaces must include agentId and agentName.",
details: { status: 400, workspaceId: key || workspace.id }
});
}
const workspaceKey = key || workspace.id;
if (this.#workspaces[workspaceKey]) {
return;
}
this.#workspaces[workspaceKey] = {
workspace,
source,
...metadata?.agentId ? { agentId: metadata.agentId } : {},
...metadata?.agentName ? { agentName: metadata.agentName } : {}
};
}
/**
* Retrieves a registered workflow by its ID.
*
* @template TWorkflowId - The specific workflow ID type from the registered workflows
* @throws {MastraError} When the workflow with the specified ID is not found
*
* @example Getting and executing a workflow
* ```typescript
* import { createWorkflow, createStep } from '@mastra/core/workflows';
* import { z } from 'zod/v4';
*
* const processDataWorkflow = createWorkflow({
* name: 'process-data',
* triggerSchema: z.object({ input: z.string() })
* })
* .then(validateStep)
* .then(transformStep)
* .then(saveStep)
* .commit();
*
* const mastra = new Mastra({
* workflows: {
* dataProcessor: processDataWorkflow
* }
* });
* ```
*/
getWorkflow(id, { serialized } = {}) {
const workflow = this.#workflows?.[id];
if (!workflow) {
const error = new MastraError({
id: "MASTRA_GET_WORKFLOW_BY_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Workflow with ID ${String(id)} not found`,
details: {
status: 404,
workflowId: String(id),
workflows: Object.keys(this.#workflows ?? {}).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
if (serialized) {
return { name: workflow.name };
}
return workflow;
}
__registerInternalWorkflow(workflow) {
workflow.__registerMastra(this);
workflow.__registerPrimitives({
logger: this.getLogger()
});
this.#internalMastraWorkflows[workflow.id] = workflow;
}
__hasInternalWorkflow(id) {
return Object.values(this.#internalMastraWorkflows).some((workflow) => workflow.id === id);
}
__getInternalWorkflow(id) {
const workflow = Object.values(this.#internalMastraWorkflows).find((a) => a.id === id);
if (!workflow) {
throw new MastraError({
id: "MASTRA_GET_INTERNAL_WORKFLOW_BY_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "SYSTEM" /* SYSTEM */,
text: `Workflow with id ${String(id)} not found`,
details: {
status: 404,
workflowId: String(id)
}
});
}
return workflow;
}
/**
* Retrieves a registered workflow by its unique ID.
*
* This method searches for a workflow using its internal ID property. If no workflow
* is found with the given ID, it also attempts to find a workflow using the ID as
* a name.
*
* @throws {MastraError} When no workflow is found with the specified ID
*
* @example Finding a workflow by ID
* ```typescript
* const mastra = new Mastra({
* workflows: {
* dataProcessor: createWorkflow({
* name: 'process-data',
* triggerSchema: z.object({ input: z.string() })
* }).commit()
* }
* });
*
* // Get the workflow's ID
* const workflow = mastra.getWorkflow('dataProcessor');
* const workflowId = workflow.id;
*
* // Later, retrieve the workflow by ID
* const sameWorkflow = mastra.getWorkflowById(workflowId);
* console.log(sameWorkflow.name); // "process-data"
* ```
*/
getWorkflowById(id) {
let workflow = Object.values(this.#workflows).find((a) => a.id === id);
if (!workflow) {
try {
workflow = this.getWorkflow(id);
} catch {
}
}
if (!workflow) {
const error = new MastraError({
id: "MASTRA_GET_WORKFLOW_BY_ID_NOT_FOUND",
domain: "MASTRA" /* MASTRA */,
category: "USER" /* USER */,
text: `Workflow with id ${String(id)} not found`,
details: {
status: 404,
workflowId: String(id),
workflows: Object.keys(this.#workflows ?? {}).join(", ")
}
});
this.#logger?.trackException(error);
throw error;
}
return workflow;
}
async listActiveWorkflowRuns() {
const storage