Motivation interpreter for elizaOS agents — transforms raw drives and resources into prioritized needs, constraints, and opportunities.
elizaOS agents have internal state (via plugin-homeostasis) that tracks drives like security, social needs, and status. They may also have external situational awareness (via plugin-appraisal) that tracks things like financial position, power dynamics, and reputation.
But raw numbers don't tell a story. An agent with security: 25 and money: strained doesn't know what that means or what to do about it.
plugin-motivation bridges this gap. It interprets both internal and external state into actionable orientation:
- "What matters most right now?" → Priorities
- "What limits acceptable action?" → Constraints
- "What possibilities exist?" → Opportunities
Think of it as the agent's motivational compass — it doesn't decide what to do (that's plugin-autonomous), but it provides the context for making good decisions.
Internal state (how I feel) + External state (what's happening)
↓ ↓
homeostasis appraisals
↓ ↓
"I want recognition" "But money is tight"
↓ ↓
→ financial_constraint pattern
→ "manage resources carefully" constraint
This creates contextual motivation: "I want recognition, but I need to find low-cost ways to get it."
Early designs used mathematical formulas to calculate motivation intensity. We abandoned this because:
- Models are better at nuance — An LLM with good context can reason about motivation more naturally than any formula we'd write
- Formulas are brittle — Edge cases multiply; thresholds need constant tuning
- Patterns are interpretable — "survival_mode triggered by critical hunger" is debuggable;
intensity = 0.73 * (1 - x/100)is not
Instead, we use pattern recognition: identify meaningful state combinations, then let frames filter and prioritize them.
Different agents (or the same agent in different contexts) should interpret the same state differently:
- A security-focused agent sees
social: 30as "nice to have later" - A community-focused agent sees the same value as "urgent need"
Frames are interpretation lenses, not replacement algorithms. They:
- Filter what patterns surface (Maslow suppresses growth needs when foundation is shaky)
- Reorder patterns by the frame's worldview
- Color the narrative with frame-specific language
The frame doesn't compute new values — it shapes what the LLM sees.
Homeostasis gives us security: 37. We convert to security: 'low'.
Why lose precision?
- Humans think in categories — "I'm hungry" not "My hunger is 67.3%"
- Thresholds are fuzzy — Is 38 meaningfully different from 37? Buckets acknowledge this
- LLMs work with language — "low security" is more useful context than "security: 37"
We bucket into: critical | low | balanced | satisfied | abundant
Homeostasis State + Appraisals (optional)
(drives, physiological, (money, power, notoriety, etc.)
resources)
↓ ↓
Internal Signals Situational Signals
↓ ↓
→ Combined Signal State
↓
Internal Patterns + Situational Patterns
↓
Frame Filter (worldview)
↓
Output (priorities, constraints, opportunities, narrative)
Why two signal types?
- Internal signals come from homeostasis (how the agent feels)
- Situational signals come from appraisals (what's happening externally)
Both feed into pattern detection, creating integrated motivation.
Patterns are named states that emerge from signal combinations:
| Pattern | Triggers When | Why It Matters |
|---|---|---|
survival_mode |
Any physiological critical | Body needs dominate everything |
foundation_shaky |
Security critical/low | Can't build on unstable ground |
seeking_connection |
Social low + security balanced | Safe enough to reach out |
recognition_hungry |
Status low + social balanced | Have belonging, want esteem |
freedom_constrained |
Autonomy low | Agency is blocked |
purpose_seeking |
Meaning low + foundation stable | Ready for self-actualization |
stable_foundation |
Security + physiological satisfied | Growth is possible |
socially_resourced |
Social satisfied/abundant | Have relational capital |
Why these patterns? They're inspired by Maslow's hierarchy but aren't a strict implementation. Each represents a meaningful motivational state that should influence behavior.
When plugin-appraisal is loaded, additional patterns become available:
| Pattern | Triggers When | Why It Matters |
|---|---|---|
external_pressure |
Any domain strained/critical | Something external is constraining |
external_tailwind |
Any domain favorable + foundation stable | External conditions enable bold action |
| Pattern | Triggers When | Why It Matters |
|---|---|---|
financial_constraint |
Money strained + growth drive active | Can't afford risk-taking |
influence_position |
Power favorable + foundation stable | Position of strength to leverage |
visibility_exposure |
Notoriety strained + social/status drive | Reputation requires management |
Why both universal and specific?
- Universal patterns catch any external constraint/opportunity, even from domains we don't know about
- Specific patterns provide richer interpretation for common domains (money, power, notoriety)
When a specific pattern fires, it suppresses the universal pattern for that domain to avoid redundancy.
| Frame | Worldview | Effect |
|---|---|---|
| Maslow | Hierarchical needs | Lower needs suppress higher until satisfied |
| Power | Control and influence | Status/autonomy patterns surface first |
| Notoriety | Reputation and visibility | Social/status patterns surface first |
| Survival | Emergency mode | Only immediate survival matters |
Why frame auto-selection? The Survival frame auto-activates when physiological stress is critical. You can't think about meaning when you're starving. Other frames are selected by configuration or default to Maslow.
# From monorepo root
bun installimport { motivationPlugin } from '@elizaos/plugin-motivation';
import { homeostasisPlugin } from '@elizaos/plugin-homeostasis';
const runtime = new AgentRuntime({
character,
plugins: [
homeostasisPlugin, // Required: provides raw state
motivationPlugin, // Interprets state into motivation
// ... other plugins
],
});import type { MotivationService } from '@elizaos/plugin-motivation';
// Get the service
const motivation = runtime.getService('motivation') as MotivationService;
// Get complete state
const state = motivation.getState();
// {
// priorities: [{ need: 'restore_security', intensity: 0.8, drivers: ['security'] }],
// constraints: [{ type: 'risk_averse', because: 'security_unstable', guidance: '...' }],
// opportunities: [{ type: 'growth_available', because: 'foundation_solid', potential: 0.7 }],
// dominantFrame: 'maslow',
// narrative: 'My foundation needs attention...',
// timestamp: 1703123456789
// }
// Get just what you need
const priorities = motivation.getPriorities();
const narrative = motivation.getNarrative();
const frame = motivation.getDominantFrame();// Force power frame for this agent
motivation.setFrameOverride('power');
// Return to automatic frame selection
motivation.setFrameOverride(null);// When motivation changes
runtime.on('MOTIVATION_UPDATED', (payload) => {
console.log('New state:', payload.state);
});
// When priorities shift
runtime.on('MOTIVATION_PRIORITIES_CHANGED', (payload) => {
console.log('Priorities changed:', payload.priorities);
});
// When frame shifts (e.g., entering survival mode)
runtime.on('MOTIVATION_FRAME_SHIFTED', (payload) => {
console.log(`Frame: ${payload.previousFrame} → ${payload.newFrame}`);
});
// When a new constraint activates
runtime.on('MOTIVATION_CONSTRAINT_ACTIVATED', (payload) => {
console.log('New constraint:', payload.constraint);
});
// When a goal candidate is generated
runtime.on('MOTIVATION_GOAL_CANDIDATE', (payload) => {
console.log('Goal candidate:', payload.candidate.name);
console.log('Relevance:', payload.candidate.relevance);
// plugin-autonomous handles this automatically
});Set via environment variables or runtime settings:
| Setting | Default | Description |
|---|---|---|
MOTIVATION_DEFAULT_FRAME |
maslow |
Default interpretation frame |
MOTIVATION_SURVIVAL_THRESHOLD |
75 |
Physiological level to auto-activate survival frame |
MOTIVATION_UPDATE_THROTTLE_MS |
1000 |
Minimum ms between recalculations |
We read, they own. Homeostasis owns the raw state (drives, physiological, resources). We subscribe to their events and interpret what they emit. We never modify homeostasis state.
We read, they evaluate. If plugin-appraisal is loaded, we read situational appraisals to understand external context. We never modify appraisals — that's the evaluator plugins' job.
// Motivation gracefully handles missing appraisal plugin
const appraisalService = runtime.getService('appraisal');
if (appraisalService) {
// Incorporate situational awareness
const appraisals = appraisalService.getAll();
// Filter by confidence, convert to signals
} else {
// Works fine without it — just no situational patterns
}Why optional? Not all agents need situational awareness. An agent in a simple environment might only need internal motivation. By making appraisals optional, motivation works at multiple capability levels.
Confidence filtering: We only incorporate appraisals with confidence > 0.3. Low-confidence appraisals add noise without signal. If an evaluator isn't sure, we treat it as neutral.
We advise, they decide. We output priorities, constraints, and opportunities. Autonomous (or other decision-making plugins) uses this context to choose actions. We don't execute anything.
Integration points:
- Motivation state adapter:
getMotivationState()provides a format optimized for autonomous's planning - Goal candidate events: We emit
MOTIVATION_GOAL_CANDIDATE, autonomous listens and creates goals - Motivation hints: We push hints to the autonomous message service for relevance scoring
// Autonomous uses motivation for planning context
const motivationService = runtime.getService('motivation');
const motivationState = motivationService.getMotivationState();
// Returns: { priorities, constraints, opportunities, disposition }
// Autonomous listens for goal candidates
// (This happens automatically in plugin-autonomous)
runtime.on('MOTIVATION_GOAL_CANDIDATE', async (payload) => {
if (payload.candidate.relevance >= 0.7) {
await goalService.createGoal(...);
}
});We suggest, they persist. We generate goal candidates based on priorities and opportunities. Plugin-autonomous evaluates these candidates and creates goals via plugin-goals. We never create goals directly — that would mix interpretation with action.
Why this separation?
- Motivation says "security matters right now" (interpretation)
- Autonomous decides "yes, let's make that a goal" (decision)
- Goals tracks "restore security" as an active objective (persistence)
The getMotivationState() adapter method is specifically designed for this integration — it formats our internal state into the structure that autonomous and goals expect.
We inject context. The motivationProvider formats our state into the LLM prompt:
## Motivation
**Frame**: maslow - Hierarchical needs
**Priorities**:
- restore security (80%) [security]
- manage resources carefully (65%) [money, status]
- seek connection (50%) [social]
**Constraints**:
- risk_averse: Avoid high-variance actions until stability returns
- budget_conscious: Avoid resource-intensive actions; preserve financial runway
**Opportunities**:
- growth available (70% potential)
**Current Orientation**:
My foundation needs attention. Security feels unstable, pulling focus from higher pursuits.
External circumstances weigh on my foundation. Until the situation stabilizes, higher pursuits must wait.Note the situational context: The "manage resources carefully" priority and "budget_conscious" constraint come from the financial_constraint pattern, which fired because money is strained and the agent has an active status drive.
This gives the LLM complete context: both internal state and external circumstances.
Separation of concerns. Homeostasis is the "body" — it knows what the state is. Motivation is the "interpreter" — it knows what the state means. Mixing these creates coupling and confusion about where to make changes.
Single responsibility. We're an interpreter, not an executor. If we started executing, we'd need to know about available actions, handle failures, coordinate with other plugins — scope creep that belongs elsewhere.
Decoupling. Plugins that care about motivation changes subscribe to events. They don't need to know when or why we recalculate. This also allows multiple consumers without us knowing about them.
The homeostasis-types.ts and appraisal-types.ts files define types locally rather than importing from their source packages. This is because:
- The source packages may not have built type declarations
- We only need a subset of their types
- This documents exactly what interface we depend on
- It maintains soft dependencies — we can still build and run if the source package isn't available
For appraisals specifically: We define the minimal interface we need (get, getAll, getIds) and the Appraisal type. This ensures we can work with any evaluator plugin that publishes appraisals, without coupling to implementation details.
We don't hardcode knowledge about specific appraisal domains (money, power, etc.). Instead, we:
- Check common payload fields (
status,level,state,condition) - Map keywords to signals using
IndicatorMap - Default to "stable" for unknown values
This means motivation automatically works with evaluator plugins that don't exist yet, as long as they use standard terminology like { status: 'cautious' } or { level: 'high' }.
We have both:
- Universal patterns (
external_pressure,external_tailwind) that work with any domain - Specific patterns (
financial_constraint,influence_position,visibility_exposure) for known domains
Why both?
- Universal patterns ensure we handle unknown domains (plugin-relationships, plugin-trust, plugin-health, etc.)
- Specific patterns provide richer interpretation by combining external + internal state (e.g., "money strained AND status drive active")
When a specific pattern fires, it suppresses the universal pattern for that domain. This prevents redundancy (we don't want both "external_pressure: money" AND "financial_constraint" to fire simultaneously).
src/
├── index.ts # Public exports
├── plugin.ts # Plugin definition
├── types.ts # Type definitions
├── constants.ts # Events, thresholds, defaults
├── homeostasis-types.ts # Local homeostasis interface types
├── appraisal-types.ts # Local appraisal interface types (for situational awareness)
│
├── signals/ # State → Categorical buckets
│ └── index.ts # driveToSignal, stateToSignals, etc.
│
├── patterns/ # Pattern recognition
│ ├── definitions.ts # Pattern definitions (8 patterns)
│ └── index.ts
│
├── frames/ # Interpretation lenses
│ ├── types.ts # Frame interface
│ ├── maslow.ts # Hierarchical needs frame
│ ├── power.ts # Control/influence frame
│ ├── notoriety.ts # Reputation frame
│ ├── survival.ts # Emergency mode frame
│ └── index.ts # Frame registry
│
├── output/ # Pattern → Output mapping
│ ├── priorities.ts # Pattern → MotivationPriority
│ ├── constraints.ts # Pattern → MotivationConstraint
│ ├── opportunities.ts # Pattern → MotivationOpportunity
│ └── index.ts
│
├── narrative/ # Narrative generation
│ └── generator.ts # Frame-colored narratives
│
├── services/
│ └── motivation-service.ts # Main service
│
├── providers/
│ └── motivation-provider.ts # LLM context injection
│
└── __tests__/
└── motivation.test.ts # 29 tests
cd packages/plugin-motivation
bun testTests cover:
- Signal conversion (10 tests)
- Pattern detection (6 tests)
- Frame filtering (6 tests)
- Output generation (7 tests)
When motivation identifies urgent priorities or valuable opportunities, it generates goal candidates — suggestions that can become actual goals.
Motivation (interpreter) → emits candidates → Autonomous (decision-maker) → creates goals
Separation of concerns:
- Motivation interprets "what matters" — it shouldn't decide "what to do"
- Autonomous decides "what to do" — it's the action selector
- Goals persist "what to pursue" — managed by plugin-goals
This flow means:
- Motivation generates candidates based on priorities/opportunities
- It emits
MOTIVATION_GOAL_CANDIDATEevents - Plugin-autonomous listens, evaluates relevance (≥0.7 threshold)
- If relevant, autonomous creates a goal via plugin-goals
| Source | When Generated | Goal Type |
|---|---|---|
| Priority | Intensity ≥ 0.4 | objective (low), milestone (med), action (high) |
| Opportunity | Potential ≥ 0.5 | action |
| Constraint | Optional flag enabled | objective |
import { generateGoalCandidates, convertPriorityToGoal } from '@elizaos/plugin-motivation';
// Generate candidates from current state
const candidates = generateGoalCandidates(motivationState, {
minIntensity: 0.5, // Only urgent priorities
maxCandidates: 3, // Limit output
includeOpportunities: true,
});
// Convert a specific priority to goal creation params
const goalParams = convertPriorityToGoal(priority, agentId);
await goalService.createGoal(goalParams);| Function | Purpose |
|---|---|
generateGoalCandidates(state, options?) |
Generate candidates from motivation state |
convertPriorityToGoal(priority, agentId) |
Convert priority to goal params |
convertOpportunityToGoal(opportunity, agentId) |
Convert opportunity to goal params |
suggestGoalUpdate(goalMetadata, state) |
Suggest updates for existing goals |
- Appraisal integration (situational awareness from plugin-appraisal)
- Situational patterns (financial_constraint, influence_position, visibility_exposure)
- Goal candidate generation
- Integration with plugin-goals for goal suggestions
- Event emission for goal candidates (MOTIVATION_GOAL_CANDIDATE)
- Learning from outcomes (which priorities led to good results)
- Character-specific frame customization
- Trust/relationship integration for social pattern nuance
- Temporal patterns (chronic needs, anticipated needs)
- Additional evaluator domains (health, creativity, time/margin)
The current specific patterns handle money, power, and notoriety. Future evaluator plugins could add:
plugin-relationships: Trust, connection quality → affects social patternsplugin-health: Physical wellbeing context → affects physiological patternsplugin-creativity: Creative state → affects meaning/purpose patternsplugin-time: Margin/pressure → affects urgency calculations
These would automatically work with universal patterns. Specific patterns can be added if richer interpretation is needed.
MIT