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:
- Read the current, non-retired row for the descriptor
profileId. - Resolve the name and prompt from the profile, falling back to descriptor defaults.
- Resolve a compatible model through the provider/capability stack.
- Apply supported numeric/reasoning settings.
- Create optional memory, browser, and voice integrations when both descriptor policy and injected dependencies allow them.
- Wrap the model for diagnostics and live prompt loading without replacing caller-provided runtime instructions.
- 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.tsfor the artifact loader;runtime-public.tsfor 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.