Skip to main content

Unified Agent Platform

Overview

The unified agent platform turns an AgentDescriptor and injected runtime dependencies into a profiled Mastra Agent. Domain code imports the contract through @maf/platform-runtime/agents/shared/*. That package surface currently bridges parts of src/mastra/agents/shared/**; consumers should not depend on where the implementation physically lives during the migration.

Descriptor contract

The current descriptor fields are:

interface AgentDescriptor {
profileId: string;
registryId: string;
defaultName: string;
defaultPrompt: string;
scopePrefix?: string;
domain?: string;
functionalGroup?: string;
projects?: ProjectScope;
tools?: Record<string, unknown>;
browser?: MastraBrowser | (() => MastraBrowser);
preferredModelType?: string;
memoryOptions?: AgentMemoryOptions | null | false;
}

There is no seedConfig field on the current descriptor. Profile seeding is handled by the target-aware descriptor registry and admin seed/reconcile routes, not as a side effect of constructing an agent.

scopePrefix affects the display name; it is not prepended to registryId. Keep both IDs stable because profiles, routes, tests, and external callers may refer to them.

Factory API

interface IAgentFactory<T = unknown> {
getAgent(): Promise<T>;
invalidateCache(): void;
buildFresh(): Promise<AgentBuildResult<T>>;
readonly descriptor: Readonly<AgentDescriptor>;
readonly deps: Readonly<AgentDependencies>;
}
  • getAgent() reads the current profile and reuses a build with the same effective content hash.
  • invalidateCache() makes the next getter rebuild.
  • buildFresh() bypasses the instance cache and returns the agent, profile, resolved model metadata, and build duration.

Older documentation referred to reload() and configureForTesting() methods. They are not part of the current interface. Tests inject dependencies through the module's configure function or by passing deps to getOrCreateFactory().

Defining an agent

import {
getOrCreateFactory,
type AgentDependencies,
type AgentDescriptor,
type IAgentFactory,
} from '@maf/platform-runtime/agents/shared/index';

export const REVIEW_AGENT_DESCRIPTOR: AgentDescriptor = {
profileId: 'example-review',
registryId: 'example-review-agent',
defaultName: 'Review Agent',
defaultPrompt: 'Review the supplied evidence and return structured findings.',
domain: 'Example',
functionalGroup: 'Review',
projects: ['testing'],
memoryOptions: null,
};

let factory: IAgentFactory | null = null;

export function configureReviewAgent(deps: AgentDependencies): void {
factory = getOrCreateFactory(REVIEW_AGENT_DESCRIPTOR, deps);
}

export function getReviewAgentFactory(): IAgentFactory {
factory ??= getOrCreateFactory(REVIEW_AGENT_DESCRIPTOR);
return factory;
}

export async function getReviewAgent() {
return getReviewAgentFactory().getAgent();
}

export async function buildReviewAgentFresh() {
return getReviewAgentFactory().buildFresh();
}

export function invalidateReviewAgentCache(): void {
getReviewAgentFactory().invalidateCache();
}

The runtime configures global dependencies before loaders build agents. Explicit module configuration is preferred for domain families because it makes tests and loader ownership clear.

Build behavior

The factory performs these operations:

  1. Read the current, non-retired row for the descriptor profileId.
  2. Resolve the name and prompt from the profile, falling back to descriptor defaults.
  3. Resolve a compatible model through the provider/capability stack.
  4. Apply supported numeric/reasoning settings.
  5. Create optional memory, browser, and voice integrations when both descriptor policy and injected dependencies allow them.
  6. Wrap the model for diagnostics and live prompt loading without replacing caller-provided runtime instructions.
  7. Cache the build promise under the effective profile hash.

If a database read fails, the factory logs a warning and builds from defaults. If an agent build fails, its cache entry is cleared so a later call may retry.

Model resolution

Profile overrides are validated by packages/platform-runtime/src/schemas/agent-profiles.schema.ts. The shared model resolver can use base configuration, database capability data, a direct override, or a fallback. The model data owner is Core.LLMModelCapabilities—not the older Core.ModelCapabilities name—and shared parameter rows live in Core.LLMParamsDim.

The common numeric settings are temperature, topP, frequencyPenalty, presencePenalty, and maxOutputTokens; reasoningEffort is supported where the selected provider/model accepts it. Do not copy the schema into an app or documentation client.

Memory

Memory requires both a compatible memoryStorage dependency and enabled descriptor/profile policy. Descriptor options include recent-message history, semantic recall, working-memory scope, and observational memory. Database fields can hot-toggle memory and select observational-memory deployment/scope.

Always make the code default explicit:

memoryOptions: null // caller owns history; factory memory is disabled

or:

memoryOptions: {
lastMessages: 20,
workingMemory: { scope: 'thread' },
}

Registration

Creating or exporting a factory does not register an agent. The owning loader must configure/build it and return it under descriptor.registryId. Domain packages expose:

  • runtime.ts for the artifact loader;
  • runtime-public.ts for the narrow descriptor/route/tool surface needed by runtime composition or the live admin registry;
  • normal package exports for application/library consumers.

Update the target-aware live descriptor registry and the transitional static test registry when adding, renaming, or retiring an agent.

Verification

npm run test:agents
npm run typecheck:domains
npm run contracts:check
npm run contracts:bruno

Also run the owning domain test and an applicable target build or npm run verify:runtime-smoke when loader/export wiring changes.