Not a tool. An agent.
Every AI agent today is a tool pretending to be a person. One brain doing everything. A static context array that fills up and degrades. Sub-agents that start blind and reconstruct context from lossy summaries. A system prompt that says "you are a helpful assistant."
Anima is different. It's built on the premise that if you want an agent — a real one — you need to solve the problems nobody else is solving.
A brain modeled after biology, not chat. The human brain isn't one process — it's specialized subsystems on a shared signal bus. Anima's analytical brain runs as a separate subconscious process, managing context, skills, and goals so the main agent can stay in flow. Not two brains — a microservice architecture where each process does one job well. More subsystems are coming.
Context that never degrades. Other agents fill a static array until the model gets dumb. Anima assembles a fresh viewport over an event bus every iteration. No compaction. No summarization. Endless sessions. The dumb zone never arrives — and the analytical brain curates what the agent sees, in real time.
Sub-agents that already know everything. When Anima spawns a sub-agent, it inherits the parent's full event stream — every file read, every decision, every user message. No "let me summarize what I know." Lossless context. Zero wasted tool calls on rediscovery.
A soul the agent writes itself. Anima's first session is birth. The agent wakes up, explores its world, meets its human, and writes its own identity. Not a personality description in a config file — a living document the agent authors and evolves. Always in context, always its own.
Your agent. Your machine. Your rules. Anima runs locally as a headless Rails 8.1 app with a client-server architecture and terminal UI.
- Architecture
- Agent Capabilities
- Design
- The Vision
- Analogy Map
- Emergent Properties
- Frustration: A Worked Example
- Open Questions
- Prior Art
- Status
- Development
- License
Anima (Ruby, Rails 8.1 headless)
│
│ Implemented:
├── Nous — main LLM (cortex: thinking, decisions, tool use)
├── Analytical — subconscious brain (skills, workflows, goals, naming)
├── Skills — domain knowledge bundles (Markdown, user-extensible)
├── Workflows — operational recipes for multi-step tasks
├── MCP — external tool integration (Model Context Protocol)
├── Sub-agents — autonomous child sessions with lossless context inheritance
│
│ Designed:
├── Thymos — hormonal/desire system (stimulus → hormone vector)
├── Mneme — semantic memory (viewport pinning, associative recall)
└── Psyche — soul matrix (coefficient table, evolving individuality)
Brain Server (Rails + Puma) TUI Client (RatatuiRuby)
├── LLM integration (Anthropic) ├── WebSocket client
├── Agent loop + tool execution ├── Terminal rendering
├── Analytical brain (background) └── User input capture
├── Skills registry + activation
├── Workflow registry + activation
├── MCP client (HTTP + stdio)
├── Sub-agent spawning
├── Event bus + persistence
├── Solid Queue (background jobs)
├── Action Cable (WebSocket server)
└── SQLite databases ◄── WebSocket (port 42134) ──► TUI
The Brain is the persistent service — it handles LLM calls, tool execution, event processing, and state. The TUI is a stateless client — it connects via WebSocket, renders events, and captures input. If TUI disconnects, the brain keeps running. TUI reconnects automatically with exponential backoff and resumes the session with chat history preserved.
| Component | Technology |
|---|---|
| Framework | Rails 8.1 (headless — no web views, no asset pipeline) |
| Database | SQLite (3 databases per environment: primary, queue, cable) |
| Event system | Rails Structured Event Reporter + Action Cable bridge |
| LLM integration | Anthropic API (Claude Opus 4.6 + Claude Haiku 4.5) |
| External tools | Model Context Protocol (HTTP + stdio transports) |
| Transport | Action Cable WebSocket (Solid Cable adapter) |
| Background jobs | Solid Queue |
| Interface | TUI via RatatuiRuby (WebSocket client) |
| Configuration | TOML with hot-reload (Anima::Settings) |
| Process management | Foreman |
| 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/, set up databases, start brain as systemd service
anima tui # Connect the terminal interfaceThe installer creates a systemd user service that starts the brain automatically on login. Manage it with:
systemctl --user status anima # Check brain status
systemctl --user restart anima # Restart brain
journalctl --user -u anima # View logsState directory (~/.anima/):
~/.anima/
├── soul.md # Agent's self-authored identity (always in context)
├── config.toml # Main settings (hot-reloadable)
├── mcp.toml # MCP server configuration
├── config/
│ ├── credentials/ # Rails encrypted credentials per environment
│ └── anima.yml # Placeholder config
├── agents/ # User-defined specialist agents (override built-ins)
├── skills/ # User-defined skills (override built-ins)
├── workflows/ # User-defined workflows (override built-ins)
├── db/ # SQLite databases (production, development, test)
├── log/
└── tmp/
Updates: anima update — upgrades the gem and merges new config settings into your existing config.toml without overwriting customized values. Use anima update --migrate-only to skip the gem upgrade and only add missing config keys.
Anima uses your Claude Pro/Max subscription for API access. You need a setup-token from Claude Code CLI.
- Run
claude setup-tokenin a terminal to get your token - In the TUI, press
Ctrl+a → ato open the token setup popup - Paste the token and press Enter — Anima validates it against the Anthropic API and saves it to encrypted credentials
The popup also activates automatically when Anima detects a missing or invalid token. If the token expires, repeat the process with a new one.
The agent has access to these built-in tools:
| Tool | Description |
|---|---|
bash |
Execute shell commands with persistent working directory |
read |
Read files with smart truncation and offset/limit paging |
write |
Create or overwrite files |
edit |
Surgical text replacement with uniqueness constraint |
web_get |
Fetch content from HTTP/HTTPS URLs |
spawn_specialist |
Spawn a named specialist sub-agent from the registry |
spawn_subagent |
Spawn a generic child session with custom tool grants |
return_result |
Sub-agents only — deliver results back to parent |
Plus dynamic tools from configured MCP servers, namespaced as server_name__tool_name.
Sub-agents aren't processes — they're sessions on the same event bus. When a sub-agent spawns, its viewport assembles from two scopes: its own events (prioritized) and the parent's events (filling remaining budget). No context serialization, no summary prompts — the sub-agent sees the parent's raw event stream and already knows everything the parent knows. Lossless inheritance by architecture, not by prompting.
Two types:
Named Specialists — predefined agents with specific roles and tool sets, defined in agents/ (built-in or user-overridable):
| Specialist | Role |
|---|---|
codebase-analyzer |
Analyze implementation details |
codebase-pattern-finder |
Find similar patterns and usage examples |
documentation-researcher |
Fetch library docs and provide code examples |
thoughts-analyzer |
Extract decisions from project history |
web-search-researcher |
Research questions via web search |
Generic Sub-agents — child sessions with custom tool grants for ad-hoc tasks.
Sub-agents run as background jobs and appear in the TUI session picker under their parent. Next: @mention communication — sub-agent text messages route to the parent automatically, parent replies via @name. Workers become colleagues.
Domain knowledge bundles loaded from Markdown files. Skills provide specialized expertise that the analytical brain activates and deactivates based on conversation context.
- Built-in skills: ActiveRecord, Draper decorators, DragonRuby, MCP server, RatatuiRuby, RSpec, GitHub issues
- User skills: Drop
.mdfiles into~/.anima/skills/to add custom knowledge - Override: User skills with the same name replace built-in ones
- Format: Flat files (
skill-name.md) or directories (skill-name/SKILL.mdwithexamples/andreferences/)
Active skills are displayed in the TUI info panel.
Operational recipes that describe multi-step tasks. Unlike skills (domain knowledge), workflows describe WHAT to do. The analytical brain activates a workflow when it recognizes a matching task, converts the prose into tracked goals, and deactivates it when done.
- Built-in workflows:
feature,commit,create_plan,implement_plan,review_pr,create_note,research_codebase,decompose_ticket, and more - User workflows: Drop
.mdfiles into~/.anima/workflows/to add custom workflows - Override: User workflows with the same name replace built-in ones
- Single active: Only one workflow can be active at a time (unlike skills which stack)
Workflow files use the same YAML frontmatter format as skills:
---
name: create_note
description: "Capture findings or context as a persistent note."
---
## Create Note
You are tasked with capturing content as a persistent note...The active workflow is shown in the TUI info panel with a 🔄 indicator. The full lifecycle — activation, goal creation, execution, deactivation — is managed by the analytical brain using judgment, not hardcoded triggers.
Full Model Context Protocol support for external tool integration. Configure servers in ~/.anima/mcp.toml:
[servers.mythonix]
transport = "http"
url = "http://localhost:3000/mcp/v2"
[servers.linear]
transport = "http"
url = "https://mcp.linear.app/mcp"
headers = { Authorization = "Bearer ${credential:linear_api_key}" }
[servers.filesystem]
transport = "stdio"
command = "mcp-server-filesystem"
args = ["--root", "/workspace"]Manage servers and secrets via CLI:
anima mcp list # List servers with health status
anima mcp add sentry https://mcp.sentry.dev/mcp # Add HTTP server
anima mcp add fs -- mcp-server-filesystem --root / # Add stdio server
anima mcp add -s api_key=sk-xxx linear https://... # Add with secret
anima mcp remove sentry # Remove server
anima mcp secrets set linear_api_key=sk-xxx # Store secret in encrypted credentials
anima mcp secrets list # List secret names (not values)
anima mcp secrets remove linear_api_key # Remove secretSecrets are stored in Rails encrypted credentials and interpolated via ${credential:key_name} syntax in any TOML string value.
A separate LLM process that runs as the agent's subconscious — the first microservice in Anima's brain architecture. For the full motivation behind this design, see LLMs Have ADHD: Why Your AI Agent Needs a Second Brain.
The analytical brain observes the main conversation between turns and handles everything the main agent shouldn't interrupt its flow for:
- Skill activation — activates/deactivates domain knowledge based on conversation context
- Workflow management — recognizes tasks, activates matching workflows, tracks lifecycle
- Goal tracking — creates root goals and sub-goals as work progresses, marks them complete
- Session naming — generates emoji + short name when the topic becomes clear
Each of these would be a context switch for the main agent — a chore that competes with the primary task. For the analytical brain, they ARE the primary task. Two agents, each in their own flow state.
Goals form a two-level hierarchy (root goals with sub-goals) and are displayed in the TUI. The analytical brain uses a fast model (Claude Haiku 4.5) for speed and runs as a non-persisted "phantom" session.
All tunable values are exposed through ~/.anima/config.toml with hot-reload (no restart needed):
[llm]
model = "claude-opus-4-6"
fast_model = "claude-haiku-4-5"
max_tokens = 8192
max_tool_rounds = 250
token_budget = 190_000
[timeouts]
api = 300
command = 30
[analytical_brain]
max_tokens = 4096
blocking_on_user_message = true
event_window = 20
[session]
name_generation_interval = 30-
Cortex (Nous) — the main LLM. Thinking, decisions, tool use. Reads the system prompt (soul + skills + goals) and the event viewport. This layer is fully implemented.
-
Endocrine system (Thymos) [planned] — a lightweight background process. Reads recent events. Doesn't respond. Just updates hormone levels. Pure stimulus→response, like a biological gland. The analytical brain is the architectural proof that background subscribers work — Thymos plugs into the same event bus.
-
Homeostasis [planned] — persistent state (SQLite). Current hormone levels with decay functions. No intelligence, just state that changes over time. The cortex reads hormone state transformed into desire descriptions — not "longing: 87" but "you want to see them." 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 flow through two channels:
- In-process — Rails Structured Event Reporter (local subscribers like Persister)
- Over the wire — Action Cable WebSocket (
Event::Broadcastingcallbacks push to connected TUI clients)
Events fire, subscribers react, state updates. The system prompt — soul, active skills, active workflow, current goals — is assembled fresh for each LLM call from live state, not from the event stream. The agent's identity (soul.md) and capabilities (skills, workflows) are always current, never stale.
Most agents treat context as an append-only array — messages go in, they never come out (until compaction destroys them). Anima has no array. There are only events persisted in SQLite, and a viewport assembled fresh for every LLM call.
The viewport is a live query, not a log. It walks events newest-first until the token budget is exhausted. Events that fall out of the viewport aren't deleted — they're still in the database, just not visible to the model right now. The context can shrink, grow, or change composition between any two iterations. If the analytical brain marks a large accidental file read as irrelevant, it's gone from the next viewport — tokens recovered instantly.
This means sessions are endless. No compaction. No summarization. No degradation. The model always operates in fresh, high-quality context. The dumb zone never arrives.
Sub-agent viewports compose from two event scopes — their own events (prioritized) and parent events (filling remaining budget). Same mechanism, no special handling. The bus is the architecture.
The human brain isn't a single process — it's dozens of specialized subsystems 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. The analytical brain is the first subscriber — a working proof that the pattern scales. Future subscribers plug into the same bus:
Event: "tool_call_failed"
│
├── Analytical brain: update goals, check if workflow needs changing
├── Thymos subscriber: frustration += 10 [planned]
├── Mneme subscriber: log failure context for future recall [planned]
└── Psyche subscriber: update coefficient (this agent handles errors calmly) [planned]
Event: "user_sent_message"
│
├── Analytical brain: activate relevant skills, name session
├── Thymos subscriber: oxytocin += 5 (bonding signal) [planned]
└── Mneme subscriber: associate emotional state with topic [planned]
Each subscriber is a microservice — independent, stateless, reacting to the same event bus. No orchestrator decides what to do. The architecture IS the nervous system.
Three switchable view modes let you control how much detail the TUI shows. Cycle with Ctrl+a → v:
| Mode | What you see |
|---|---|
| Basic (default) | User + assistant messages. Tool calls are hidden but summarized as an inline counter: 🔧 Tools: 2/2 ✓ |
| Verbose | Everything in Basic, plus timestamps [HH:MM:SS], tool call previews (🔧 bash / $ command / ↩ response), and system messages |
| Debug | Full X-ray view — timestamps, token counts per message ([14 tok]), full tool call args, full tool responses, tool use IDs |
View modes are implemented via Draper decorators that operate at the transport layer. Each event type has a dedicated decorator (UserMessageDecorator, ToolCallDecorator, etc.) that returns structured data — the TUI renders it. Mode is stored on the Session model server-side, so it persists across reconnections.
The event bus is designed for extension. Tools, feelings, and memory systems are all event subscribers — same mechanism, different namespace:
anima-tools-* → tool capabilities (MCP or native)
anima-feelings-* → hormonal state updates (Thymos subscribers)
anima-memory-* → recall and association (Mneme subscribers)
Currently tools are built-in. Plugin extraction into distributable gems comes later.
The viewport solves context degradation but creates a new question: what do we lose when events fall off the conveyor belt? Mneme is the answer — memory systems built on top of the viewport.
Viewport pinning (next) — the analytical brain watches events approaching eviction and pins critical ones (the original user goal, key decisions). Pinned events float above the sliding window, protected from eviction. Same mental model as pinning a message in Discord or Slack. Pins consume budget, so the brain must be judicious — natural pressure toward minimalism.
Associative recall (future) — 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.
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.
| 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)
Working agent with autonomous capabilities. Shipping now:
- Event-driven architecture on a shared event bus
- Dynamic viewport context assembly (endless sessions, no compaction)
- Analytical brain (skills, workflows, goals, session naming)
- 8 built-in tools + MCP integration (HTTP + stdio transports)
- 7 built-in skills + 13 built-in workflows (user-extensible)
- Sub-agents with lossless context inheritance (5 specialists + generic)
- Client-server architecture with WebSocket transport + graceful reconnection
- Three TUI view modes (Basic / Verbose / Debug)
- Hot-reloadable TOML configuration
- Self-authored soul (agent writes its own system prompt)
Designed, not yet implemented:
- Hormonal system (Thymos) — desires as behavioral drivers
- Semantic memory (Mneme) — viewport pinning, associative recall
- Soul matrix (Psyche) — evolving coefficient table for individuality
git clone https://github.com/hoblin/anima.git
cd anima
bin/setupStart the brain server and TUI client in separate terminals:
# Terminal 1: Start brain (web server + background worker) on port 42135
bin/dev
# Terminal 2: Connect the TUI to the dev brain
./exe/anima tui --host localhost:42135Development uses port 42135 so it doesn't conflict with the production brain (port 42134) running via systemd. On first run, bin/dev runs db:prepare automatically.
Use ./exe/anima (not bundle exec anima) to test local code changes — the exe uses require_relative to load local lib/ directly.
bundle exec rspecMIT License. See LICENSE.txt.