Adaptive Prompt Intelligence
Every turn, AgentOS reassembles the system prompt in local code from the GMI's current state. That reassembly is template merging and criteria matching — no LLM call. Metaprompts are a separate, conditional loop that runs on top: on most turns, nothing fires. When a trigger does fire (every Nth turn for periodic self-reflection, on a SentimentTracker event, or on a host-set flag), a single extra LLM call runs in parallel with the main turn and writes back changes to mood, inferred user skill, task complexity, working-memory imprints, or HEXACO traits. The next turn's local prompt assembly then reflects those changes.
The agent stays the same persona; how it sounds, what it remembers, and how confidently it speaks evolve across the messages where triggers actually fire.
The dollar math, in one line: the only adaptive surface that adds an LLM call on every user turn is sentimentTracking.method: 'llm', and that defaults to 'lexicon_based' (free). Everything else either costs nothing extra (contextual elements, lexicon sentiment) or fires occasionally (one extra call every N turns or on detected emotional thresholds). See Operational notes below for a concrete per-1000-turn cost table.
This page is the source-verified map of that loop. Every class, interface, trigger name, and handler ID below corresponds to a real surface in packages/agentos. If you only need one mental model: the persona definition is the static contract, and the metaprompt executor is the dynamic editor that mutates parts of that contract turn over turn.
What's actually adaptive
Three things change between turns without persona reload, model swap, or operator intervention:
| Surface | Where it lives | What edits it |
|---|---|---|
| Per-turn system prompt | Composed by PromptEngine.assemble() | ContextualPromptElement[] whose criteria match the current PromptExecutionContext |
| GMI state (mood, user context, task context, working memory) | The GMI coordinator | MetapromptExecutor callbacks (onMoodUpdate, onUserContextUpdate, onTaskContextUpdate, onMemoryImprint) |
| HEXACO traits | PersonaOverlayManager | The AdaptPersonalityTool (emergent, agent-driven) and the offline PersonaDriftMechanism (heuristic, consolidation-cycle) |
Each surface has its own latency profile and its own gate. The contextual prompt elements run on every turn and cost nothing extra. Metaprompts run when their trigger fires and cost one extra LLM call each. Trait drift requires explicit agent action (the tool) or a consolidation cycle (the mechanism) and is bounded by per-session budgets.
The shortest useful example
import { agent } from '@framers/agentos';
const tutor = agent({
provider: 'openai',
model: 'gpt-4o-mini',
instructions: 'You are a patient programming tutor.',
personality: { conscientiousness: 0.85, agreeableness: 0.8 },
sentimentTracking: {
enabled: true,
presets: [
'frustration_recovery',
'confusion_clarification',
'satisfaction_reinforcement',
],
},
metaPrompts: [{
id: 'gmi_self_trait_adjustment',
promptTemplate: `Review the recent conversation and decide if any adjustments are warranted.
Evidence: {{evidence}}
Current mood: {{current_mood}}
User skill: {{user_skill}}
Task complexity: {{task_complexity}}
Respond JSON with optional fields: updatedGmiMood, updatedUserSkillLevel, updatedTaskComplexity, adjustmentRationale, newMemoryImprints.`,
trigger: { type: 'turn_interval', intervalTurns: 5 },
temperature: 0.3,
maxOutputTokens: 512,
}],
});
const session = tutor.session('user-42');
await session.send('I keep getting confused about recursion.');
The persona ships with two adaptive surfaces wired up:
sentimentTracking.presetssubscribes the GMI to three of the five event-based preset metaprompts. TheSentimentTrackeranalyzes every user message and emitsUSER_FRUSTRATED,USER_CONFUSED, orUSER_SATISFIEDevents when score patterns cross thresholds. When one fires, the matching preset (gmi_frustration_recovery,gmi_confusion_clarification,gmi_satisfaction_reinforcement) executes and updates the GMI's mood, the inferred user skill level, or the task complexity.metaPrompts[].trigger.type === 'turn_interval'schedules a periodic self-reflection every five turns. After every five user messages, the GMI calls an internal LLM with the last ten conversation turns, the last twenty reasoning-trace entries, and its current mood and context, then applies any returned changes.
Both happen without the host code doing anything. The GMI integrates the changes into the next turn's prompt.
The metaprompt definition
The full interface lives at packages/agentos/src/cognition/substrate/personas/IPersonaDefinition.ts:323-336:
export interface MetaPromptDefinition {
/** Stable identifier. Built-in IDs route to dedicated handlers. */
id: string;
/** Human-readable description for tooling and trace logs. */
description?: string;
/** The prompt template, with `{{variable}}` placeholders. */
promptTemplate: string | { template: string; variables?: string[] };
/** Model override (falls back to persona.defaultModelId). */
modelId?: string;
/** Provider override (falls back to persona.defaultProviderId). */
providerId?: string;
/** Max tokens for the metaprompt response. Default 512. */
maxOutputTokens?: number;
/** Sampling temperature. Default 0.3 for consistent state edits. */
temperature?: number;
/** JSON schema for the response shape (advisory; runtime parses anyway). */
outputSchema?: Record<string, any>;
/** Trigger contract — present means active. */
trigger?:
| { type: 'turn_interval'; intervalTurns: number }
| { type: 'event_based'; eventName: string }
| { type: 'manual' };
}
A persona's metaPrompts?: MetaPromptDefinition[] lives at IPersonaDefinition.ts:438. The runtime merges these with the active preset list (see Built-in presets below); persona-defined entries override preset entries on matching ID.
The template uses {{variable}} placeholders that the executor substitutes with values from the running context. Every handler has its own variable set, documented inline alongside the preset definitions in metaprompt_presets.ts.