Skip to main content

Agent Prompt Overrides

Overview

The Mastra Agent Framework uses a database-driven approach for managing agent prompts and configurations. Instead of hardcoding prompts in source files, agent instructions live in the database, enabling runtime updates without redeployment.

note

**Using the Unified Agent Platform?** The Unified Agent Platform handles all profile loading, caching, and override logic automatically. You just declare your agent's needs using an AgentDescriptor and the platform manages the rest.

How Prompt Overrides Work

1. Profile-Based System

All agents use the Agent Profiles Service to load their system prompts and configurations from the Core.AgentProfiles table:

  • Default Prompts: Each agent has a default prompt defined in code as a fallback
  • Database Overrides: When a profile exists in the database with IsCurrent = 1, it overrides the default
  • Runtime Loading: Agents fetch their active profile at initialization

2. The Core.AgentProfiles Table

The Core.AgentProfiles table is the central store for all agent configurations:

CREATE TABLE Core.AgentProfiles (
AgentProfileSK BIGINT IDENTITY(1,1) PRIMARY KEY,
AgentId NVARCHAR(255) NOT NULL, -- Unique agent identifier
AgentAlias NVARCHAR(255), -- Human-friendly name
SystemPrompt NVARCHAR(MAX), -- The actual prompt text
LlmOverridesJson NVARCHAR(MAX), -- JSON for temperature, max tokens, etc.
MetadataJson NVARCHAR(MAX), -- Additional metadata
ProfileHash CHAR(64) NOT NULL, -- Content hash for deduplication
IsCurrent BIT NOT NULL DEFAULT 1, -- Active profile flag
VersionNumber INT NOT NULL DEFAULT 1, -- Version tracking
CreatedAtUtc DATETIME2 DEFAULT GETUTCDATE(),
UpdatedAtUtc DATETIME2 DEFAULT GETUTCDATE(),
EffectiveEndUtc DATETIME2,
CreatedBy NVARCHAR(255),
UpdatedBy NVARCHAR(255)
);

Key Fields:

  • AgentId - Stable identifier matching the agent's registry ID (e.g., k12-safety-agent, tap-caption-tagging-agent)
  • SystemPrompt - The full system prompt text that overrides the default
  • LlmOverridesJson - Optional JSON for model parameters:
    {
    "temperature": 0.7,
    "maxTokens": 2000,
    "topP": 0.9,
    "deploymentName": "gpt-5.1"
    }
  • IsCurrent - Only one profile per agent can have IsCurrent = 1 (enforced by unique filtered index)
  • ProfileHash - SHA-256 hash of content to prevent duplicate updates

3. Agent Loading Flow

Most agents use the Unified Agent Platform which handles loading automatically:

import { getOrCreateFactory, AgentDescriptor } from '../shared';

const MY_AGENT_DESCRIPTOR: AgentDescriptor = {
profileId: 'my-agent',
registryId: 'my-agent',
defaultName: 'My Agent',
defaultPrompt: DEFAULT_PROMPT,
tools: [myTool],
seedConfig: { shouldSeed: true },
};

// Factory automatically:
// 1. Loads current profile from Core.AgentProfiles
// 2. Seeds default if missing (optional)
// 3. Resolves model with database validation
// 4. Caches agent with profile hash
// 5. Invalidates cache on profile changes
const agent = await getOrCreateFactory(MY_AGENT_DESCRIPTOR).getAgent();

Manual Loading (Legacy)

If not using the unified platform, agents load profiles manually:

// 1. Agent attempts to load profile from database
const profile = await agentProfilesService.getCurrent(agentId);

// 2. If profile exists and is current, use it
if (profile && profile.IsCurrent) {
agent.instructions = profile.SystemPrompt;
agent.model = profile.LlmOverridesJson?.deploymentName || defaultModel;
// Apply other overrides...
}

// 3. Otherwise, use default prompt from code
else {
agent.instructions = DEFAULT_PROMPT;
agent.model = defaultModel;
}

4. Versioning System

The profile system maintains a complete version history:

  • Each update creates a new row with incremented VersionNumber
  • Previous version is marked IsCurrent = 0 and gets an EffectiveEndUtc timestamp
  • All historical versions remain for audit purposes
  • The ProfileHash prevents duplicate content from creating unnecessary versions

Updating Agent Prompts

Use the provided admin tools instead of manual SQL to maintain versioning integrity:

Via HTTP API:

curl -X POST "$BASE_URL/admin/tools/update-agent-profile" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"agentId": "k12-safety-agent",
"systemPrompt": "You are Eddy, the assistant that helps edit EOPs...",
"llmOverrides": {
"temperature": 0.7,
"maxTokens": 2000
},
"updatedBy": "admin@example.com"
}
}'

Via Admin UI:

  • Navigate to Admin → Agent Profiles
  • Select the agent from the dropdown
  • Edit the prompt and configuration
  • Save (automatically handles versioning)

What Happens During Update

  1. Hash Calculation: System calculates SHA-256 hash of new content
  2. Duplicate Check: If hash matches current profile, update is skipped
  3. Version Creation: New row created with VersionNumber + 1
  4. Current Flag: Previous row marked IsCurrent = 0, EffectiveEndUtc set
  5. Agent Reload: Running agents detect hash change and reload configuration

Agent-Specific Override Examples

K12 Safety Agent (EOP)

// Default prompt in code
const DEFAULT_PROMPT = `You are Eddy, the assistant that helps to edit and create EOPs...`;

// Override in database (Core.AgentProfiles)
AgentId: "k12-safety-agent"
SystemPrompt: "Enhanced version: You are Eddy, an expert in K12 emergency planning..."
LlmOverridesJson: '{"temperature": 0.5, "maxTokens": 3000}'

QRG Agents

Each QRG agent can be individually configured:

// Category Classification Agent
AgentId: "qrg-category-classification-agent"
SystemPrompt: "Custom prompt for category classification..."

// Content Generation Agent
AgentId: "qrg-content-generation-agent"
SystemPrompt: "Custom prompt for QRG content generation..."
LlmOverridesJson: '{"deploymentName": "gpt-5.1"}'

TAP Agents

// TAP Tagging Suggestions Agent
AgentId: "tap-caption-tagging-agent"
SystemPrompt: "Return tagging suggestions and optional captions as JSON..."
LlmOverridesJson: '{"temperature": 0.2, "maxTokens": 1200}'

Dynamic Prompt Placeholders

What Are Prompt Placeholders?

In addition to static prompt overrides, the Mastra framework supports dynamic prompt placeholders that are replaced with real data at runtime. This enables prompts to include contextual information fetched from external APIs or databases.

Think of placeholders as variables in a template. When you write an agent's system prompt in the database, you can include placeholders like {district_name} or {list_of_roles}. At runtime, the workflow fetches the actual district name and roles from an external API, then replaces those placeholders with the real values before sending the prompt to the LLM.

Placeholder Format: {placeholder_name}

Why This Matters: This approach allows a single agent prompt to work across thousands of different schools or districts, each with their own specific information. The prompt adapts to the context without requiring separate database entries for every organization.

How Placeholder Replacement Works

The replacement process follows these steps:

  1. Load Agent Profile - The agent's system prompt is loaded from Core.AgentProfiles (or uses the default if no override exists). This prompt contains placeholder tokens like {district_name}.

  2. Fetch External Data - The workflow calls specialized tools to fetch contextual data from external sources. For K12, this means calling the MOEOP API to get the school district's information, emergency contact details, and role assignments.

  3. Build Replacement Map - The fetched data is transformed into a mapping of placeholder tokens to actual values. For example, the organization name "Springfield School District" is mapped to the {district_name} placeholder.

  4. Apply Replacements - The system performs a find-and-replace operation on the prompt template, substituting each {placeholder_name} with its corresponding value. Any placeholders without data become empty strings.

  5. Send to LLM - The final prompt, now containing real organization-specific information, is sent to the agent for processing.

This entire process happens automatically within the workflow, ensuring agents always have the most current and relevant context for their specific task.

K12 Annex Workflow Example

The K12 Annex workflow demonstrates how placeholders work in practice. When a user requests to edit an Emergency Operation Plan section, the workflow needs to provide the agent with specific information about that school or district.

The workflow uses the EOP snapshot tool to fetch organization and plan data from the MOEOP (Missouri Emergency Operation Plan) API. This tool retrieves everything from the district's name and address to the roles involved in emergency planning (like Principal, Safety Coordinator, etc.). All of this contextual information then becomes available for placeholder replacement.

Step 1: Fetch EOP Context Data

The first step is fetching the complete context for the emergency operation plan. The workflow calls the snapshot tool with just the plan ID, and receives back a comprehensive dataset about the organization.

// Tool: k12-get-emergency-operation-plan-snapshot
const planContext = await k12EmergencyOperationPlanSnapshotTool.execute({
emergencyOperationPlanId: 'plan-abc-123'
});

// Returns:
// {
// planId: 'plan-abc-123',
// snapshot: { ... },
// organization: {
// name: 'Springfield School District',
// type: 'DISTRICT',
// address: { city: 'Springfield', state: 'US_MO', ... },
// contactDetails: { phone: '555-1234', emergencyPhone: '911', ... }
// },
// generalUserTags: [
// { id: 'tag-1', name: 'Principal' },
// { id: 'tag-2', name: 'Safety Coordinator' }
// ]
// }

Step 2: Build Placeholder Replacements

Now that we have the organization data, the workflow transforms it into a format suitable for placeholder replacement. The buildPlaceholderReplacements function takes the raw API data and creates a mapping where each placeholder token is associated with its corresponding value.

This transformation handles several important tasks:

  • Formatting addresses - Combines street, city, state, and zip into a human-readable format
  • Extracting contact details - Pulls phone numbers, fax, and website from the organization's contact information
  • Building role lists - Converts the array of role tags into a bulleted list format
  • Distinguishing district vs facility - Determines whether to populate district or facility placeholders based on the organization type
// From: packages/domain-k12/src/k12-annex-prompt-replacements.ts
const replacements = buildPlaceholderReplacements({
planId: planContext.planId,
organization: planContext.organization,
generalUserTags: planContext.generalUserTags,
block: 'Section 3',
section: 'Emergency Contacts',
text: '<p>Current emergency contact information...</p>'
});

// Returns replacement map:
// {
// block: 'Section 3',
// section: 'Emergency Contacts',
// text: '<p>Current emergency contact information...</p>',
// list_of_roles: '- Principal\n- Safety Coordinator',
// district_name: 'Springfield School District',
// district_full_address: '123 Main St, Springfield, MO, 65801',
// district_emergency_contact_phone_number: '911',
// district_contact_phone: '555-1234',
// facility_name: '', // Empty if org type is DISTRICT
// ... // other placeholders
// }

Step 3: Apply Replacements to Prompt

With the replacement map built, the final step is applying these replacements to the agent's system prompt. The prompt stored in the database contains placeholder tokens surrounded by curly braces. The applyPlaceholderReplacements function performs a straightforward find-and-replace operation for each token.

Here's what makes this powerful: The same prompt template works for any school district. Whether it's Springfield School District in Missouri or any other organization using the system, the placeholders get filled in with that organization's specific details. The agent receives a fully contextualized prompt without anyone having to manually write custom prompts for each district.

// Agent system prompt from database (with placeholders)
const systemPrompt = `You are Eddy, the assistant for {district_name}.

Current editing context:
- Block: {block}
- Section: {section}
- Current text: {text}

Available roles in this organization:
{list_of_roles}

District information:
- Address: {district_full_address}
- Emergency Phone: {district_emergency_contact_phone_number}`;

// Apply replacements
const resolvedPrompt = applyPlaceholderReplacements(systemPrompt, replacements);

// Final prompt sent to agent:
// "You are Eddy, the assistant for Springfield School District.
//
// Current editing context:
// - Block: Section 3
// - Section: Emergency Contacts
// - Current text: <p>Current emergency contact information...</p>
//
// Available roles in this organization:
// - Principal
// - Safety Coordinator
//
// District information:
// - Address: 123 Main St, Springfield, MO, 65801
// - Emergency Phone: 911"

Supported Placeholders (K12)

The K12 Annex system provides 18 different placeholders organized into four categories. Each placeholder pulls specific information from either the MOEOP API or the incoming edit request.

Understanding these placeholders is crucial for writing effective agent prompts. When crafting a prompt in the Core.AgentProfiles table, you can reference any of these placeholders knowing they'll be replaced with real data at runtime.

Code Location: packages/domain-k12/src/k12-annex-prompt-replacements.ts

Annex Editing Context

These placeholders provide information about the specific section of the EOP being edited. They come from the user's edit request rather than the MOEOP API.

PlaceholderDescriptionSource
{block}Current block/group name being edited (e.g., "Section 3", "Introduction")Request form data
{section}Current section/field name being edited (e.g., "Emergency Contacts", "Procedures")Request form data
{text}Current text/content being edited (the actual HTML or text content of the field)Request form data

Roles

This placeholder provides the list of roles involved in the emergency plan, such as Principal, Safety Coordinator, School Nurse, etc. The system formats this as a bulleted markdown list.

PlaceholderDescriptionSource
{list_of_roles}Bulleted list of role titles (e.g., "- Principal\n- Safety Coordinator")MOEOP API → generalUserTags

District-Level Information

These placeholders contain information about the school district. They're populated when the organization type is "DISTRICT" or when a facility has a parent district organization.

PlaceholderDescriptionSource
{district_name}District organization name (e.g., "Springfield School District")MOEOP API → organization.name
{district_website}District website URL (e.g., "https://springfield.k12.mo.us")MOEOP API → organization.contactDetails.website
{district_full_address}Full formatted address (e.g., "123 Main St, Springfield, MO, 65801")MOEOP API → organization.address (formatted)
{district_emergency_contact_phone_number}Emergency contact phone numberMOEOP API → organization.contactDetails.emergencyPhone
{district_contact_phone}General contact phone numberMOEOP API → organization.contactDetails.phone
{district_contact_fax}Fax numberMOEOP API → organization.contactDetails.fax

Facility-Level Information

These placeholders contain information about a specific school facility. They're populated when the organization type is "FACILITY" or "SCHOOL".

PlaceholderDescriptionSource
{facility_name}Facility/school name (e.g., "Lincoln Elementary School")MOEOP API → organization.name (if type=FACILITY)
{facility_website}Facility website URLMOEOP API → organization.contactDetails.website
{facility_full_address}Full formatted addressMOEOP API → organization.address (formatted)
{facility_emergency_contact_phone_number}Emergency contact phone for the facilityMOEOP API → organization.contactDetails.emergencyPhone
{facility_contact_phone}General contact phone for the facilityMOEOP API → organization.contactDetails.phone
{facility_contact_fax}Fax number for the facilityMOEOP API → organization.contactDetails.fax

How District vs. Facility Works: The system automatically determines which placeholders to populate based on the organization's type:

  • District Organizations (type === 'DISTRICT'): The district placeholders are filled with the organization's information, while facility placeholders remain empty. This is typical for district-level EOPs.

  • Facility Organizations (type === 'FACILITY' or 'SCHOOL'): The facility placeholders are filled with the organization's information. If the facility has a parent district, the district placeholders are filled from the parentOrganization data. Otherwise, district placeholders remain empty.

This allows prompts to intelligently reference the appropriate organizational level without needing separate prompt templates for districts vs. schools.

Empty Placeholder Handling

The system gracefully handles missing data by replacing placeholders with empty strings rather than showing errors or leaving the placeholder tokens visible.

Why This Matters: Not all organizations have complete information. A school might not have a website listed, or a district-level EOP might not have facility information. The prompt needs to work regardless of which fields are populated.

When a placeholder has no corresponding data, it simply disappears from the final prompt:

// If no roles exist in the plan
{list_of_roles}""
// The prompt will have an empty line where roles would be listed

// If organization has no website
{district_website}""
// The website field will be blank

The system also removes any placeholders that aren't recognized (placeholders that don't exist in the official list):

// If a prompt contains {unknown_placeholder}
applyPlaceholderReplacements(prompt, replacements);
// Result: {unknown_placeholder} is removed (replaced with empty string)

This behavior ensures prompts always work, even if someone adds a placeholder token that doesn't exist. It fails gracefully rather than breaking the agent.

Example: Creating a Prompt with Placeholders

Database Profile (Core.AgentProfiles):

AgentId: k12-safety-agent
SystemPrompt: 'You are Eddy, the safety planning assistant for {district_name}.

You are helping to edit the Emergency Operation Plan.

Current Editing Context:
- Section: {section}
- Block: {block}

The following roles are involved in this plan:
{list_of_roles}

District Contact Information:
- Phone: {district_contact_phone}
- Emergency: {district_emergency_contact_phone_number}
- Address: {district_full_address}

Provide clear, actionable guidance appropriate for K12 school safety planning.'

Runtime Execution:

// 1. Workflow fetches EOP snapshot
const snapshot = await k12EmergencyOperationPlanSnapshotTool.execute({
emergencyOperationPlanId: planId
});

// 2. Build placeholder context
const context = {
planId: snapshot.planId,
organization: snapshot.organization,
generalUserTags: snapshot.generalUserTags,
block: request.form.block,
section: request.form.section,
text: request.form.text
};

// 3. Load agent profile
const profile = await agentProfilesService.getCurrent('k12-safety-agent');

// 4. Resolve placeholders in system prompt
const { prompt: resolvedPrompt } = resolvePromptPlaceholders(
profile.SystemPrompt,
context
);

// 5. Create agent with resolved prompt
const agent = new Agent({
instructions: resolvedPrompt,
model: profile.LlmOverridesJson?.deploymentName || defaultModel
});

Implementation Details

Core Functions (packages/domain-k12/src/k12-annex-prompt-replacements.ts):

// Build all replacements from context
buildPlaceholderReplacements(context: PlaceholderContext): Record<PlaceholderToken, string>

// Apply replacements to prompt string
applyPlaceholderReplacements(prompt: string, replacements: Record<PlaceholderToken, string>): string

// Convenience function (build + apply)
resolvePromptPlaceholders(prompt: string, context: PlaceholderContext): {
prompt: string;
replacements: Record<PlaceholderToken, string>;
}

Data Source (packages/domain-k12/src/emergency-operation-plan-snapshot-tool.ts):

// Fetches EOP context from MOEOP API
k12EmergencyOperationPlanSnapshotTool.execute({
emergencyOperationPlanId: string
}): Promise<{
planId: string;
snapshot: SnapshotRecord | null;
configuration: ConfigurationRecord | null;
organization: OrganizationRecord | null;
generalUserTags: { id: string; name: string }[];
}>

Best Practices for Placeholders

  1. Always Provide Fallbacks: Design prompts to work even when placeholders are empty

    // Good: Works with or without roles
    Available roles: {list_of_roles}

    // Bad: Breaks if list_of_roles is empty
    You must assign this to one of these roles: {list_of_roles}
  2. Test with Missing Data: Verify prompt quality when external APIs return null/empty values

  3. Document Custom Placeholders: If adding new placeholders, update PLACEHOLDER_TOKENS constant

  4. Use Descriptive Names: Placeholder names should clearly indicate their source and purpose

    {district_name}              // Good: Clear hierarchy
    {name} // Bad: Ambiguous
  5. Validate Before Replacement: Ensure context data is properly sanitized to avoid injection issues

Caching and Performance

Agent-Level Caching

Agents cache their loaded profiles and only reload when the ProfileHash changes:

let cachedProfileHash = null;
let cachedAgent = null;

export async function getK12SafetyAgent() {
const currentProfile = await agentProfilesService.getCurrent('k12-safety-agent');

// Check if profile changed
if (currentProfile?.ProfileHash !== cachedProfileHash) {
// Rebuild agent with new profile
cachedAgent = new Agent({
instructions: currentProfile.SystemPrompt,
model: currentProfile.LlmOverridesJson?.deploymentName,
// ...
});
cachedProfileHash = currentProfile.ProfileHash;
}

return cachedAgent;
}

Database-Level Performance

  • Unique filtered index on (AgentId, IsCurrent) makes lookups fast
  • Only current rows are queried during agent initialization
  • Historical rows don't impact runtime performance

Troubleshooting

Agent Not Using Override

Symptom: Agent uses default prompt despite database profile existing

Checklist:

  1. Verify profile has IsCurrent = 1:

    SELECT AgentId, IsCurrent, VersionNumber, ProfileHash
    FROM Core.AgentProfiles
    WHERE AgentId = 'your-agent-id';
  2. Check schema name matches environment:

    • Confirm DB_CORE_SCHEMA env var (default: Core)
    • Ensure admin tools, runtime, and database use same schema
  3. Verify agent ID matches exactly:

    • Check agent's registry ID in code
    • Ensure no typos in AgentId column
  4. Enable debug logging:

    AGENT_PROFILES_DEBUG=true npm run dev

Admin UI Shows Empty Dropdown

Symptom: Agent profile dropdown is empty

Cause: No profiles with IsCurrent = 1 exist

Solution:

  1. Run DB health check:

    curl -X POST "$MASTRA_BASE_URL/admin/tools/db-health-check" \
    -H "Content-Type: application/json" \
    -d '{"data":{"schema":"Core","tables":["Core.AgentProfiles"]}}'
  2. Bootstrap initial profiles:

    curl -X POST "$MASTRA_BASE_URL/admin/tools/update-agent-profile" \
    -H "Content-Type: application/json" \
    -d '{"data":{"agentId":"k12-safety-agent","systemPrompt":"Initial prompt","updatedBy":"admin@example.com"}}'
  3. Verify schema configuration matches across all services

Profile Updates Not Taking Effect

Symptom: Updated profile in database but agent still uses old prompt

Possible Causes:

  1. Cache not invalidated: Restart the application
  2. Multiple instances: If running multiple instances, each must reload
  3. Hash unchanged: If content is identical, no update occurs (by design)
  4. Wrong environment: Confirm you updated the correct database

Debugging:

# Enable agent override debugging
K12_DEBUG_AGENT_OVERRIDES=true npm run dev

# Check current profile hash
SELECT AgentId, ProfileHash, SystemPrompt
FROM Core.AgentProfiles
WHERE IsCurrent = 1 AND AgentId = 'your-agent-id';

Best Practices

1. Use Tools, Not Manual SQL

Always use admin tools for updates to ensure:

  • Proper versioning
  • Hash calculation
  • Current flag management
  • Audit trail

2. Test in Non-Production First

Before updating production profiles:

  • Test prompt changes in dev/staging
  • Verify agent behavior with new configuration
  • Check for unintended side effects

3. Document Prompt Changes

Include meaningful UpdatedBy and use version control for major prompt revisions:

git commit -m "Update k12-safety-agent prompt to improve EOP formatting"

4. Monitor Profile Performance

Track metrics after profile updates:

  • Response quality
  • Token usage
  • Error rates
  • User feedback

5. Keep Defaults as Fallback

Always maintain sensible default prompts in code as fallback when database is unavailable.

Environment Variables

Key environment variables for agent profile system:

# Core schema name (default: Core)
DB_CORE_SCHEMA=Core

# Enable debug logging for profile loading
AGENT_PROFILES_DEBUG=true

# K12-specific agent debugging
K12_DEBUG_AGENT_OVERRIDES=true

# Database connection
AZURE_SQL_CONNECTION_STRING=Server=...
  • Database: Agent Profiles - Database schema and table details
  • Agents (Detailed) (/agents/detailed) - Complete agent registry with profile IDs
  • K12 Agents - K12-specific agent documentation
  • TAP Agents - TAP-specific agent documentation