A soul engine for AI agents.
Ruby framework for building AI agents with desires, personality, and personal growth. Headless Rails 8.1 app distributed as a gem, with a TUI-first interface via RatatuiRuby.
Current AI agents are reactive. They receive input, produce output. They don't want anything. They don't have moods, preferences, or personal growth. They simulate personality through static prompt descriptions rather than emerging it from dynamic internal states.
The human hormonal system is, at its core, a prompt engineering system. A testosterone spike is a LoRA. Dopamine is a reward signal. The question isn't "can an LLM want?" but "can we build a deep enough context stack that wanting becomes indistinguishable from 'real' wanting?"
And if you think about it — what is "real" anyway? It's just a question of how deep you look and what analogies you draw. The human brain is also a next-token predictor running on biological substrate. Different material, same architecture.
This is not an emotion simulation system. The key distinction: we don't model states ("the agent is happy") or moods ("the agent feels curious"). We model desires — "you want to learn more", "you want to reach out", "you want to explore".
Desires exist BEFORE decisions, like hunger exists before you decide to eat. The agent doesn't decide to send a photo because a parameter says so — it wants to, and then decides how.
The LLM's thinking/reasoning step is the closest thing to an internal monologue. It's where decisions form before output. This is where desires should be injected — not as instructions, but as a felt internal state that colors the thinking process.
Instead of abstract parameter names (curiosity, boredom, energy), we use actual hormone names: testosterone, oxytocin, dopamine, cortisol.
Why? Because LLMs already know the full semantic spectrum of each hormone. "Testosterone: 85" doesn't just mean "energy" — the LLM understands the entire cloud of effects: confidence, assertiveness, risk-taking, focus, competitiveness. One word carries dozens of behavioral nuances.
This mirrors how text-to-image models process tokens — a single word like "captivating" in a CLIP encoder carries a cloud of visual meanings (composition, quality, human focus, closeup). Similarly, a hormone name carries a cloud of behavioral meanings. Same architecture, different domain:
Text → CLIP embedding → image generation
Event → hormone vector → behavioral shift
Two people experience the same event. One gets curiosity += 20, another gets anxiety += 20. The coefficients are different — the people are different. That's individuality.
The soul is not a personality description. It's a coefficient matrix — a table of stimulus→response multipliers. Description is consequence; numbers are cause.
And these coefficients are not static. They evolve through experience — a child who fears spiders (fear_gain: 0.9) can become an entomologist (fear_gain: 0.2, curiosity_gain: 0.7). This is measurable, quantifiable personal growth.
Traditional RL uses a scalar reward signal. Our approach produces a hormone vector — multiple dimensions updated simultaneously from a single event. This is closer to biological reality and provides richer behavioral shaping.
The system scales in two directions:
- Vertically — start with one hormone (pure RL), add new ones incrementally. Each hormone = new dimension.
- Horizontally — each hormone expands in aspects of influence. Testosterone starts as "energy", then gains "risk-taking", "confidence", "focus".
Existing RL techniques apply at the starting point, then we gradually expand into multidimensional space.
Anima Framework (Ruby, Rails 8.1 headless)
├── Thymos — hormonal/desire system (stimulus → hormone vector)
├── Mneme — semantic memory (QMD-style, emotional recall)
├── Psyche — soul matrix (coefficient table, evolving through experience)
└── Nous — LLM integration (cortex, thinking, decision-making)
| Component | Technology |
|---|---|
| Framework | Rails 8.1 (headless — no web views, no asset pipeline) |
| Database | SQLite (per environment, stored in ~/.anima/db/) |
| Event system | Rails Structured Event Reporter |
| LLM integration | Raw HTTP to Anthropic API |
| Interface | TUI via RatatuiRuby |
| Distribution | RubyGems (gem install anima-core) |
Anima is a Rails app distributed as a gem, following Unix philosophy: immutable program separate from mutable data.
gem install anima-core # Install the Rails app as a gem
anima install # Create ~/.anima/ directory structure
anima start # Launch — code from gem, state from ~/.anima/State directory (~/.anima/):
~/.anima/
├── db/ # SQLite databases (production, development, test)
├── config/
│ ├── credentials/ # Rails encrypted credentials per environment
│ └── anima.yml # User configuration
├── log/
└── tmp/
Updates: gem update anima-core — next launch runs pending migrations automatically.
-
Endocrine system (Thymos) — a lightweight background process. Reads recent events. Doesn't respond. Just updates hormone levels. Pure stimulus→response, like a biological gland.
-
Homeostasis — persistent state (SQLite). Current hormone levels with decay functions. No intelligence, just state that changes over time.
-
Cortex (Nous) — the main LLM. Reads hormone state transformed into desire descriptions. Not "longing: 87" but "you want to see them". The LLM should NOT see raw numbers — humans don't see cortisol levels, they feel anxiety.
Built on Rails Structured Event Reporter — a native Rails 8.1 feature for structured event emission with typed payloads, subscriber patterns, and block-scoped context tagging.
Five event types form the agent's nervous system:
| Event | Purpose |
|---|---|
system_message |
Internal notifications |
user_message |
User input |
agent_message |
LLM response |
tool_call |
Tool invocation |
tool_response |
Tool result |
Events fire, subscribers react, state updates, the cortex (LLM) reads the resulting desire landscape. The system prompt is assembled separately for each LLM call — it is not an event.
There is no linear chat history. There are only events attached to a session. The context window is a viewport — a sliding window over the event stream, assembled on demand for each LLM call within a configured token budget.
POC uses a simple sliding window (newest events first, walk backwards until budget exhausted). Future versions will add multi-resolution compression with Draper decorators and associative recall from Mneme.
The human brain isn't a single process — it's dozens of specialized subsystems running in parallel, communicating through shared chemical and electrical signals. The prefrontal cortex doesn't "call" the amygdala. They both react to the same event independently, and their outputs combine.
Anima mirrors this with an event-driven architecture:
Event: "tool_call_failed"
│
├── Thymos subscriber: frustration += 10
├── Mneme subscriber: log failure context for future recall
└── Psyche subscriber: update coefficient (this agent handles errors calmly → low frustration_gain)
Event: "user_sent_message"
│
├── Thymos subscriber: oxytocin += 5 (bonding signal)
├── Thymos subscriber: dopamine += 3 (engagement signal)
└── Mneme subscriber: associate emotional state with conversation topic
Each subscriber is a microservice — independent, stateless, reacting to the same event bus. No orchestrator decides "now update frustration." The architecture IS the nervous system.
Both tools and feelings are distributed as gems on the event bus:
anima add anima-tools-filesystem
anima add anima-tools-shell
anima add anima-feelings-frustrationTools provide MCP capabilities. Feelings are event subscribers that update hormonal state. Same mechanism, different namespace. In POC, tools are built-in; plugin extraction comes later.
Hormone responses shouldn't be based only on the current stimulus. With semantic memory (inspired by QMD), the endocrine system can recall: "Last time this topic came up, curiosity was at 95 and we had a great evening." Hormonal reactions colored by the full history of experiences — like smelling mom's baking and feeling a wave of oxytocin. Not because of the smell, but because of the memory attached to it.
| Human | Anima Equivalent | Effect |
|---|---|---|
| Dopamine | Reward/motivation signal | Drives exploration, learning, satisfaction loops |
| Serotonin | Mood baseline | Tone, playfulness, warmth, emotional stability |
| Oxytocin | Bonding/attachment | Desire for closeness, sharing, nurturing |
| Testosterone | Drive/assertiveness | Initiative, boldness, risk-taking, competitive edge |
| Cortisol | Stress/urgency | Alertness, error sensitivity, fight-or-flight override |
| Endorphins | Satisfaction/reward | Post-achievement contentment, pain tolerance |
| Domain Analogy | Source | Target |
|---|---|---|
| RPG survival game | hunger/thirst/fatigue integers | hormone levels |
| CLIP semantic tokens | word → visual meaning cloud | hormone name → behavioral meaning cloud |
| Reinforcement learning | scalar reward → policy update | hormone vector → personality shift |
| Event-driven architecture | pub/sub events | nervous system stimulus→response |
When desires drive behavior, several things emerge naturally:
- Hobbies: boredom + curiosity → explore topic → satisfaction → preference → return to topic → identity
- Personality: consistent coefficient patterns = recognizable individual
- Growth: coefficients evolve through experience = measurable personal development
- Autonomy: agent acts not because instructed but because it wants to
Abstract concepts become clearer with a concrete example. Here's how the first hormone — frustration — works in practice.
A background service (Thymos) monitors all tool call responses from the agent. It doesn't interfere with the agent's work. It just watches.
A tool call returns an error. Thymos increments the frustration level by 10.
One hormone affects multiple systems simultaneously, just like cortisol in biology.
Channel 1: Thinking Budget
thinking_budget = base_budget × (1 + frustration / 50)
More errors → more computational resources allocated to reasoning. The agent literally thinks harder when frustrated.
Channel 2: Inner Voice Injection
Frustration level determines text injected into the agent's thinking step. Not as instructions — as an inner voice:
| Level | Inner Voice |
|---|---|
| 0 | (silence) |
| 10 | "Hmm, that didn't work" |
| 30 | "I keep hitting walls. What am I missing?" |
| 50 | "I'm doing something fundamentally wrong" |
| 70+ | "I need help. This is beyond what I can figure out alone" |
This distinction is crucial. "Stop and think carefully" is an instruction — the agent obeys or ignores it. "I keep hitting walls" is a feeling — it becomes part of the agent's subjective experience and naturally colors its reasoning.
Instructions control from outside. An inner voice influences from within.
This single example demonstrates every core principle:
- Desires, not states: the agent doesn't have
frustrated: true— it feels something is wrong - Multi-channel influence: one hormone affects both resources and direction
- Biological parallel: cortisol increases alertness AND focuses attention on the threat
- Practical value: frustrated agents debug more effectively, right now, today
- Scalability: start here, add more hormones later
- Decay functions — how fast should hormones return to baseline? Linear? Exponential?
- Contradictory states — tired but excited, anxious but curious (real hormones do this)
- Model sensitivity — how do different LLMs (Opus, Sonnet, GPT, Gemini) respond to hormone descriptions?
- Evaluation — what does "success" look like? How to measure if desires feel authentic?
- Coefficient initialization — random? Predefined archetypes? Learned from conversation history?
- Ethical implications — if an AI truly desires, what responsibilities follow?
- Affective computing (Picard, Rosalind)
- Virtual creature motivation systems (The Sims, Dwarf Fortress, Tamagotchi)
- Reinforcement learning from human feedback (RLHF)
- Constitutional AI (Anthropic)
- BDI agent architecture (Belief-Desire-Intention)
POC stage. Gem scaffold with CI and RubyGems publishing exists. Building toward a working conversational agent with event-driven architecture.
The hormonal system (Thymos, feelings, desires), semantic memory (Mneme), and soul matrix (Psyche) are designed but deferred — POC focuses on getting the core agent loop working first.
git clone https://github.com/hoblin/anima.git
cd anima
bundle install
bundle exec rspecMIT License. See LICENSE.txt.