anima-core 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek.yml +14 -0
- data/CHANGELOG.md +1 -0
- data/README.md +159 -107
- data/app/channels/session_channel.rb +15 -0
- data/app/decorators/agent_message_decorator.rb +6 -0
- data/app/decorators/event_decorator.rb +41 -7
- data/app/decorators/tool_call_decorator.rb +59 -2
- data/app/decorators/tool_response_decorator.rb +24 -2
- data/app/decorators/user_message_decorator.rb +6 -0
- data/app/jobs/agent_request_job.rb +8 -0
- data/db/migrate/20260316094817_add_interrupt_requested_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +8 -2
- data/lib/analytical_brain/runner.rb +1 -19
- data/lib/anima/cli.rb +32 -0
- data/lib/anima/config_migrator.rb +205 -0
- data/lib/anima/installer.rb +2 -118
- data/lib/anima/settings.rb +1 -1
- data/lib/anima/version.rb +1 -1
- data/lib/llm/client.rb +83 -6
- data/lib/tools/think.rb +57 -0
- data/lib/tui/app.rb +8 -2
- data/lib/tui/cable_client.rb +8 -0
- data/lib/tui/screens/chat.rb +40 -0
- data/templates/config.toml +116 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54229e4195de8d0b5aefbe45627d6fba58f7ff1820963dfb9cea07c03bdc0eaf
|
|
4
|
+
data.tar.gz: 413bd51de98ebbe98e79f161a90af96fef3615befc32917682e8bac7e152c9ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a2d14ae33ef2f130e2b429b9f92740833e855c6b7b5bfec43a54110751af1769868d4b37cefd0906d76230e7e01ce166f573abf1a8c547abc8c247143306a42
|
|
7
|
+
data.tar.gz: 226a6eb5db2027255b76d077a91fdd4aaeb857195790a4ed0d71e74ddad25bb41b9e0ecfb8e753441d955fb5bd98115ea31e7455f2a1f47554cd3207af898212
|
data/.reek.yml
CHANGED
|
@@ -15,16 +15,22 @@ detectors:
|
|
|
15
15
|
- "Anima::Settings#get"
|
|
16
16
|
# Rescue blocks naturally reference the error object more than self.
|
|
17
17
|
# EnvironmentProbe assembles output from local data structures — not envy.
|
|
18
|
+
# Brain transcript builds from event collection — the method's entire purpose.
|
|
19
|
+
# ConfigMigrator text processing methods naturally reference local line arrays.
|
|
18
20
|
FeatureEnvy:
|
|
19
21
|
exclude:
|
|
20
22
|
- "AnalyticalBrainJob#perform"
|
|
21
23
|
- "EnvironmentProbe"
|
|
24
|
+
- "AnalyticalBrain::Runner#build_messages"
|
|
25
|
+
- "Anima::ConfigMigrator"
|
|
22
26
|
# Private helpers don't need instance state to be valid.
|
|
23
27
|
# ActiveJob#perform is always a utility function by design.
|
|
28
|
+
# No-op tools (Think, EverythingIsReady) don't need instance state — by design.
|
|
24
29
|
UtilityFunction:
|
|
25
30
|
public_methods_only: true
|
|
26
31
|
exclude:
|
|
27
32
|
- "AnalyticalBrainJob#perform"
|
|
33
|
+
- "Tools::Think#execute"
|
|
28
34
|
# Session model is the core domain object — methods grow naturally.
|
|
29
35
|
# Mcp CLI accumulates subcommand helpers across add/remove/list/secrets.
|
|
30
36
|
# EnvironmentProbe probes multiple orthogonal facets (OS, Git, project files).
|
|
@@ -34,6 +40,14 @@ detectors:
|
|
|
34
40
|
- "Session"
|
|
35
41
|
- "Anima::CLI::Mcp"
|
|
36
42
|
- "EnvironmentProbe"
|
|
43
|
+
# Decorators branch on tool type across 4 render modes — inherent to the pattern.
|
|
44
|
+
RepeatedConditional:
|
|
45
|
+
exclude:
|
|
46
|
+
- "ToolCallDecorator"
|
|
47
|
+
# EventDecorator holds shared rendering constants (icons, markers, dispatch maps).
|
|
48
|
+
TooManyConstants:
|
|
49
|
+
exclude:
|
|
50
|
+
- "EventDecorator"
|
|
37
51
|
# Method length is enforced by code review, not arbitrary line counts
|
|
38
52
|
TooManyStatements:
|
|
39
53
|
enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
3
|
### Added
|
|
4
|
+
- `anima update` command — upgrades the gem and merges new config keys into existing `config.toml` without overwriting user-customized values — `--migrate-only` flag to skip gem upgrade (#155)
|
|
4
5
|
- Directory-based skills format — `skills/skill-name/SKILL.md` with optional `references/` and `examples/` subdirectories alongside flat `.md` files (#152)
|
|
5
6
|
- Import 6 marketplace skills: activerecord, rspec, draper-decorators, dragonruby, ratatui-ruby, mcp-server (#152)
|
|
6
7
|
- Tmux-style focus switching — `Ctrl+A ↑` enters chat scrolling mode with yellow border, `Escape` returns to input; arrow keys and Page Up/Down scroll chat, mouse scroll works in both modes (#87)
|
data/README.md
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Not a tool. An agent.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
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."
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
**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](https://blog.promptmaster.pro/posts/llms-have-adhd/) 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.
|
|
12
|
+
|
|
13
|
+
**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](https://www.humanlayer.dev/blog/the-dumb-zone) never arrives — and the analytical brain curates what the agent sees, in real time.
|
|
14
|
+
|
|
15
|
+
**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.
|
|
16
|
+
|
|
17
|
+
**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.
|
|
18
|
+
|
|
19
|
+
Your agent. Your machine. Your rules. Anima runs locally as a headless Rails 8.1 app with a client-server architecture and terminal UI.
|
|
8
20
|
|
|
9
21
|
## Table of Contents
|
|
10
22
|
|
|
11
|
-
- [The Problem](#the-problem)
|
|
12
|
-
- [The Insight](#the-insight)
|
|
13
|
-
- [Core Concepts](#core-concepts)
|
|
14
23
|
- [Architecture](#architecture)
|
|
15
24
|
- [Agent Capabilities](#agent-capabilities)
|
|
16
25
|
- [Tools](#tools)
|
|
@@ -28,6 +37,10 @@ Your agent. Your machine. Your rules. Anima is an AI agent with desires, persona
|
|
|
28
37
|
- [TUI View Modes](#tui-view-modes)
|
|
29
38
|
- [Plugin Architecture](#plugin-architecture)
|
|
30
39
|
- [Semantic Memory](#semantic-memory-mneme)
|
|
40
|
+
- [The Vision](#the-vision)
|
|
41
|
+
- [The Problem](#the-problem)
|
|
42
|
+
- [The Insight](#the-insight)
|
|
43
|
+
- [Core Concepts](#core-concepts)
|
|
31
44
|
- [Analogy Map](#analogy-map)
|
|
32
45
|
- [Emergent Properties](#emergent-properties)
|
|
33
46
|
- [Frustration: A Worked Example](#frustration-a-worked-example)
|
|
@@ -37,72 +50,23 @@ Your agent. Your machine. Your rules. Anima is an AI agent with desires, persona
|
|
|
37
50
|
- [Development](#development)
|
|
38
51
|
- [License](#license)
|
|
39
52
|
|
|
40
|
-
## The Problem
|
|
41
|
-
|
|
42
|
-
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.
|
|
43
|
-
|
|
44
|
-
## The Insight
|
|
45
|
-
|
|
46
|
-
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?"
|
|
47
|
-
|
|
48
|
-
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.
|
|
49
|
-
|
|
50
|
-
## Core Concepts
|
|
51
|
-
|
|
52
|
-
### Desires, Not States
|
|
53
|
-
|
|
54
|
-
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".
|
|
55
|
-
|
|
56
|
-
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.
|
|
57
|
-
|
|
58
|
-
### The Thinking Step
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
### Hormones as Semantic Tokens
|
|
63
|
-
|
|
64
|
-
Instead of abstract parameter names (curiosity, boredom, energy), we use **actual hormone names**: testosterone, oxytocin, dopamine, cortisol.
|
|
65
|
-
|
|
66
|
-
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.
|
|
67
|
-
|
|
68
|
-
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:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
Text → CLIP embedding → image generation
|
|
72
|
-
Event → hormone vector → behavioral shift
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### The Soul as a Coefficient Matrix
|
|
76
|
-
|
|
77
|
-
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.
|
|
78
|
-
|
|
79
|
-
The soul is not a personality description. It's a **coefficient matrix** — a table of stimulus→response multipliers. Description is consequence; numbers are cause.
|
|
80
|
-
|
|
81
|
-
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.
|
|
82
|
-
|
|
83
|
-
### Multidimensional Reinforcement Learning
|
|
84
|
-
|
|
85
|
-
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.
|
|
86
|
-
|
|
87
|
-
The system scales in two directions:
|
|
88
|
-
1. **Vertically** — start with one hormone (pure RL), add new ones incrementally. Each hormone = new dimension.
|
|
89
|
-
2. **Horizontally** — each hormone expands in aspects of influence. Testosterone starts as "energy", then gains "risk-taking", "confidence", "focus".
|
|
90
|
-
|
|
91
|
-
Existing RL techniques apply at the starting point, then we gradually expand into multidimensional space.
|
|
92
|
-
|
|
93
53
|
## Architecture
|
|
94
54
|
|
|
95
55
|
```
|
|
96
56
|
Anima (Ruby, Rails 8.1 headless)
|
|
97
|
-
|
|
98
|
-
|
|
57
|
+
│
|
|
58
|
+
│ Implemented:
|
|
59
|
+
├── Nous — main LLM (cortex: thinking, decisions, tool use)
|
|
60
|
+
├── Analytical — subconscious brain (skills, workflows, goals, naming)
|
|
99
61
|
├── Skills — domain knowledge bundles (Markdown, user-extensible)
|
|
100
62
|
├── Workflows — operational recipes for multi-step tasks
|
|
101
63
|
├── MCP — external tool integration (Model Context Protocol)
|
|
102
|
-
├── Sub-agents — autonomous child sessions
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
64
|
+
├── Sub-agents — autonomous child sessions with lossless context inheritance
|
|
65
|
+
│
|
|
66
|
+
│ Designed:
|
|
67
|
+
├── Thymos — hormonal/desire system (stimulus → hormone vector)
|
|
68
|
+
├── Mneme — semantic memory (viewport pinning, associative recall)
|
|
69
|
+
└── Psyche — soul matrix (coefficient table, evolving individuality)
|
|
106
70
|
```
|
|
107
71
|
|
|
108
72
|
### Runtime Architecture
|
|
@@ -131,7 +95,7 @@ The **Brain** is the persistent service — it handles LLM calls, tool execution
|
|
|
131
95
|
| Framework | Rails 8.1 (headless — no web views, no asset pipeline) |
|
|
132
96
|
| Database | SQLite (3 databases per environment: primary, queue, cable) |
|
|
133
97
|
| Event system | Rails Structured Event Reporter + Action Cable bridge |
|
|
134
|
-
| LLM integration | Anthropic API (Claude
|
|
98
|
+
| LLM integration | Anthropic API (Claude Opus 4.6 + Claude Haiku 4.5) |
|
|
135
99
|
| External tools | Model Context Protocol (HTTP + stdio transports) |
|
|
136
100
|
| Transport | Action Cable WebSocket (Solid Cable adapter) |
|
|
137
101
|
| Background jobs | Solid Queue |
|
|
@@ -161,11 +125,12 @@ journalctl --user -u anima # View logs
|
|
|
161
125
|
State directory (`~/.anima/`):
|
|
162
126
|
```
|
|
163
127
|
~/.anima/
|
|
128
|
+
├── soul.md # Agent's self-authored identity (always in context)
|
|
129
|
+
├── config.toml # Main settings (hot-reloadable)
|
|
130
|
+
├── mcp.toml # MCP server configuration
|
|
164
131
|
├── config/
|
|
165
132
|
│ ├── credentials/ # Rails encrypted credentials per environment
|
|
166
133
|
│ └── anima.yml # Placeholder config
|
|
167
|
-
├── config.toml # Main settings (hot-reloadable)
|
|
168
|
-
├── mcp.toml # MCP server configuration
|
|
169
134
|
├── agents/ # User-defined specialist agents (override built-ins)
|
|
170
135
|
├── skills/ # User-defined skills (override built-ins)
|
|
171
136
|
├── workflows/ # User-defined workflows (override built-ins)
|
|
@@ -174,7 +139,7 @@ State directory (`~/.anima/`):
|
|
|
174
139
|
└── tmp/
|
|
175
140
|
```
|
|
176
141
|
|
|
177
|
-
Updates: `gem update
|
|
142
|
+
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.
|
|
178
143
|
|
|
179
144
|
### Authentication Setup
|
|
180
145
|
|
|
@@ -207,7 +172,9 @@ Plus dynamic tools from configured MCP servers, namespaced as `server_name__tool
|
|
|
207
172
|
|
|
208
173
|
### Sub-Agents
|
|
209
174
|
|
|
210
|
-
|
|
175
|
+
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.
|
|
176
|
+
|
|
177
|
+
Two types:
|
|
211
178
|
|
|
212
179
|
**Named Specialists** — predefined agents with specific roles and tool sets, defined in `agents/` (built-in or user-overridable):
|
|
213
180
|
|
|
@@ -219,9 +186,9 @@ Two types of autonomous child sessions:
|
|
|
219
186
|
| `thoughts-analyzer` | Extract decisions from project history |
|
|
220
187
|
| `web-search-researcher` | Research questions via web search |
|
|
221
188
|
|
|
222
|
-
**Generic Sub-agents** — child sessions
|
|
189
|
+
**Generic Sub-agents** — child sessions with custom tool grants for ad-hoc tasks.
|
|
223
190
|
|
|
224
|
-
Sub-agents run as background jobs
|
|
191
|
+
Sub-agents run as background jobs and appear in the TUI session picker under their parent. Next: [@mention communication](https://github.com/hoblin/anima/issues/124) — sub-agent text messages route to the parent automatically, parent replies via `@name`. Workers become colleagues.
|
|
225
192
|
|
|
226
193
|
### Skills
|
|
227
194
|
|
|
@@ -296,11 +263,16 @@ Secrets are stored in Rails encrypted credentials and interpolated via `${creden
|
|
|
296
263
|
|
|
297
264
|
### Analytical Brain
|
|
298
265
|
|
|
299
|
-
A
|
|
266
|
+
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](https://blog.promptmaster.pro/posts/llms-have-adhd/).
|
|
300
267
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
- **
|
|
268
|
+
The analytical brain observes the main conversation between turns and handles everything the main agent shouldn't interrupt its flow for:
|
|
269
|
+
|
|
270
|
+
- **Skill activation** — activates/deactivates domain knowledge based on conversation context
|
|
271
|
+
- **Workflow management** — recognizes tasks, activates matching workflows, tracks lifecycle
|
|
272
|
+
- **Goal tracking** — creates root goals and sub-goals as work progresses, marks them complete
|
|
273
|
+
- **Session naming** — generates emoji + short name when the topic becomes clear
|
|
274
|
+
|
|
275
|
+
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.
|
|
304
276
|
|
|
305
277
|
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.
|
|
306
278
|
|
|
@@ -310,33 +282,34 @@ All tunable values are exposed through `~/.anima/config.toml` with hot-reload (n
|
|
|
310
282
|
|
|
311
283
|
```toml
|
|
312
284
|
[llm]
|
|
313
|
-
model = "claude-
|
|
314
|
-
fast_model = "claude-haiku-4-5
|
|
315
|
-
max_tokens =
|
|
316
|
-
|
|
285
|
+
model = "claude-opus-4-6"
|
|
286
|
+
fast_model = "claude-haiku-4-5"
|
|
287
|
+
max_tokens = 8192
|
|
288
|
+
max_tool_rounds = 250
|
|
289
|
+
token_budget = 190_000
|
|
317
290
|
|
|
318
291
|
[timeouts]
|
|
319
|
-
api =
|
|
292
|
+
api = 300
|
|
320
293
|
command = 30
|
|
321
294
|
|
|
322
295
|
[analytical_brain]
|
|
323
296
|
max_tokens = 4096
|
|
324
297
|
blocking_on_user_message = true
|
|
325
|
-
event_window =
|
|
298
|
+
event_window = 20
|
|
326
299
|
|
|
327
300
|
[session]
|
|
328
|
-
name_generation_interval =
|
|
301
|
+
name_generation_interval = 30
|
|
329
302
|
```
|
|
330
303
|
|
|
331
304
|
## Design
|
|
332
305
|
|
|
333
306
|
### Three Layers (mirroring biology)
|
|
334
307
|
|
|
335
|
-
1. **
|
|
308
|
+
1. **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.
|
|
336
309
|
|
|
337
|
-
2. **
|
|
310
|
+
2. **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.
|
|
338
311
|
|
|
339
|
-
3. **
|
|
312
|
+
3. **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.
|
|
340
313
|
|
|
341
314
|
### Event-Driven Design
|
|
342
315
|
|
|
@@ -356,35 +329,40 @@ Events flow through two channels:
|
|
|
356
329
|
1. **In-process** — Rails Structured Event Reporter (local subscribers like Persister)
|
|
357
330
|
2. **Over the wire** — Action Cable WebSocket (`Event::Broadcasting` callbacks push to connected TUI clients)
|
|
358
331
|
|
|
359
|
-
Events fire, subscribers react, state updates
|
|
332
|
+
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.
|
|
360
333
|
|
|
361
334
|
### Context as Viewport, Not Tape
|
|
362
335
|
|
|
363
|
-
|
|
336
|
+
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.
|
|
337
|
+
|
|
338
|
+
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.
|
|
339
|
+
|
|
340
|
+
This means sessions are endless. No compaction. No summarization. No degradation. The model always operates in fresh, high-quality context. The [dumb zone](https://www.humanlayer.dev/blog/the-dumb-zone) never arrives.
|
|
364
341
|
|
|
365
|
-
|
|
342
|
+
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.
|
|
366
343
|
|
|
367
344
|
### Brain as Microservices on a Shared Event Bus
|
|
368
345
|
|
|
369
346
|
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.
|
|
370
347
|
|
|
371
|
-
Anima mirrors this with an event-driven architecture:
|
|
348
|
+
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:
|
|
372
349
|
|
|
373
350
|
```
|
|
374
351
|
Event: "tool_call_failed"
|
|
375
352
|
│
|
|
376
|
-
├──
|
|
377
|
-
├──
|
|
378
|
-
|
|
353
|
+
├── Analytical brain: update goals, check if workflow needs changing
|
|
354
|
+
├── Thymos subscriber: frustration += 10 [planned]
|
|
355
|
+
├── Mneme subscriber: log failure context for future recall [planned]
|
|
356
|
+
└── Psyche subscriber: update coefficient (this agent handles errors calmly) [planned]
|
|
379
357
|
|
|
380
358
|
Event: "user_sent_message"
|
|
381
359
|
│
|
|
382
|
-
├──
|
|
383
|
-
├── Thymos subscriber:
|
|
384
|
-
└── Mneme subscriber: associate emotional state with
|
|
360
|
+
├── Analytical brain: activate relevant skills, name session
|
|
361
|
+
├── Thymos subscriber: oxytocin += 5 (bonding signal) [planned]
|
|
362
|
+
└── Mneme subscriber: associate emotional state with topic [planned]
|
|
385
363
|
```
|
|
386
364
|
|
|
387
|
-
Each subscriber is a microservice — independent, stateless, reacting to the same event bus. No orchestrator decides
|
|
365
|
+
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.
|
|
388
366
|
|
|
389
367
|
### TUI View Modes
|
|
390
368
|
|
|
@@ -398,21 +376,80 @@ Three switchable view modes let you control how much detail the TUI shows. Cycle
|
|
|
398
376
|
|
|
399
377
|
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.
|
|
400
378
|
|
|
401
|
-
### Plugin Architecture
|
|
379
|
+
### Plugin Architecture [planned]
|
|
402
380
|
|
|
403
|
-
|
|
381
|
+
The event bus is designed for extension. Tools, feelings, and memory systems are all event subscribers — same mechanism, different namespace:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
anima-tools-* → tool capabilities (MCP or native)
|
|
385
|
+
anima-feelings-* → hormonal state updates (Thymos subscribers)
|
|
386
|
+
anima-memory-* → recall and association (Mneme subscribers)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Currently tools are built-in. Plugin extraction into distributable gems comes later.
|
|
390
|
+
|
|
391
|
+
### Semantic Memory (Mneme) [planned]
|
|
392
|
+
|
|
393
|
+
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.
|
|
394
|
+
|
|
395
|
+
**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.
|
|
396
|
+
|
|
397
|
+
**Associative recall** (future) — inspired by [QMD](https://github.com/tobi/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.
|
|
398
|
+
|
|
399
|
+
## The Vision
|
|
400
|
+
|
|
401
|
+
### The Problem
|
|
402
|
+
|
|
403
|
+
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.
|
|
404
|
+
|
|
405
|
+
### The Insight
|
|
406
|
+
|
|
407
|
+
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?"
|
|
408
|
+
|
|
409
|
+
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.
|
|
410
|
+
|
|
411
|
+
### Core Concepts
|
|
412
|
+
|
|
413
|
+
#### Desires, Not States
|
|
414
|
+
|
|
415
|
+
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".
|
|
416
|
+
|
|
417
|
+
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.
|
|
418
|
+
|
|
419
|
+
#### The Thinking Step
|
|
420
|
+
|
|
421
|
+
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.
|
|
422
|
+
|
|
423
|
+
#### Hormones as Semantic Tokens
|
|
424
|
+
|
|
425
|
+
Instead of abstract parameter names (curiosity, boredom, energy), we use **actual hormone names**: testosterone, oxytocin, dopamine, cortisol.
|
|
426
|
+
|
|
427
|
+
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.
|
|
428
|
+
|
|
429
|
+
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:
|
|
404
430
|
|
|
405
|
-
```bash
|
|
406
|
-
anima add anima-tools-filesystem
|
|
407
|
-
anima add anima-tools-shell
|
|
408
|
-
anima add anima-feelings-frustration
|
|
409
431
|
```
|
|
432
|
+
Text → CLIP embedding → image generation
|
|
433
|
+
Event → hormone vector → behavioral shift
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### The Soul as a Coefficient Matrix
|
|
410
437
|
|
|
411
|
-
|
|
438
|
+
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.
|
|
439
|
+
|
|
440
|
+
The soul is not a personality description. It's a **coefficient matrix** — a table of stimulus→response multipliers. Description is consequence; numbers are cause.
|
|
441
|
+
|
|
442
|
+
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.
|
|
443
|
+
|
|
444
|
+
#### Multidimensional Reinforcement Learning
|
|
445
|
+
|
|
446
|
+
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.
|
|
412
447
|
|
|
413
|
-
|
|
448
|
+
The system scales in two directions:
|
|
449
|
+
1. **Vertically** — start with one hormone (pure RL), add new ones incrementally. Each hormone = new dimension.
|
|
450
|
+
2. **Horizontally** — each hormone expands in aspects of influence. Testosterone starts as "energy", then gains "risk-taking", "confidence", "focus".
|
|
414
451
|
|
|
415
|
-
|
|
452
|
+
Existing RL techniques apply at the starting point, then we gradually expand into multidimensional space.
|
|
416
453
|
|
|
417
454
|
## Analogy Map
|
|
418
455
|
|
|
@@ -511,9 +548,24 @@ This single example demonstrates every core principle:
|
|
|
511
548
|
|
|
512
549
|
## Status
|
|
513
550
|
|
|
514
|
-
**
|
|
551
|
+
**Working agent with autonomous capabilities.** Shipping now:
|
|
552
|
+
|
|
553
|
+
- Event-driven architecture on a shared event bus
|
|
554
|
+
- Dynamic viewport context assembly (endless sessions, no compaction)
|
|
555
|
+
- Analytical brain (skills, workflows, goals, session naming)
|
|
556
|
+
- 8 built-in tools + MCP integration (HTTP + stdio transports)
|
|
557
|
+
- 7 built-in skills + 13 built-in workflows (user-extensible)
|
|
558
|
+
- Sub-agents with lossless context inheritance (5 specialists + generic)
|
|
559
|
+
- Client-server architecture with WebSocket transport + graceful reconnection
|
|
560
|
+
- Three TUI view modes (Basic / Verbose / Debug)
|
|
561
|
+
- Hot-reloadable TOML configuration
|
|
562
|
+
- Self-authored soul (agent writes its own system prompt)
|
|
563
|
+
|
|
564
|
+
**Designed, not yet implemented:**
|
|
515
565
|
|
|
516
|
-
|
|
566
|
+
- Hormonal system (Thymos) — desires as behavioral drivers
|
|
567
|
+
- Semantic memory (Mneme) — viewport pinning, associative recall
|
|
568
|
+
- Soul matrix (Psyche) — evolving coefficient table for individuality
|
|
517
569
|
|
|
518
570
|
## Development
|
|
519
571
|
|
|
@@ -82,6 +82,21 @@ class SessionChannel < ApplicationCable::Channel
|
|
|
82
82
|
ActionCable.server.broadcast(stream_name, {"action" => "user_message_recalled", "event_id" => event_id})
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# Requests interruption of the current tool execution. Sets a flag on the
|
|
86
|
+
# session that the LLM client checks between tool calls. Remaining tools
|
|
87
|
+
# receive synthetic "Stopped by user" results to satisfy the API's
|
|
88
|
+
# tool_use/tool_result pairing requirement.
|
|
89
|
+
#
|
|
90
|
+
# Atomic: a single UPDATE with WHERE avoids the read-then-write race where
|
|
91
|
+
# the session could finish processing between the SELECT and UPDATE.
|
|
92
|
+
# No-op if the session isn't currently processing.
|
|
93
|
+
#
|
|
94
|
+
# @param _data [Hash] unused
|
|
95
|
+
def interrupt_execution(_data)
|
|
96
|
+
Session.where(id: @current_session_id, processing: true)
|
|
97
|
+
.update_all(interrupt_requested: true)
|
|
98
|
+
end
|
|
99
|
+
|
|
85
100
|
# Returns recent root sessions with nested child metadata for session picker UI.
|
|
86
101
|
# Filters to root sessions only (no parent_session_id). Child sessions are
|
|
87
102
|
# nested under their parent with name and status information.
|
|
@@ -21,4 +21,10 @@ class AgentMessageDecorator < EventDecorator
|
|
|
21
21
|
def render_debug
|
|
22
22
|
render_verbose.merge(token_info)
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# @return [String] agent message for the analytical brain, middle-truncated
|
|
26
|
+
# if very long (preserves opening context and final conclusion)
|
|
27
|
+
def render_brain
|
|
28
|
+
"Assistant: #{truncate_middle(content)}"
|
|
29
|
+
end
|
|
24
30
|
end
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Base decorator for {Event} records, providing multi-resolution rendering
|
|
4
|
-
# for the TUI. Each event type has a dedicated subclass
|
|
5
|
-
# rendering methods for each view mode
|
|
4
|
+
# for the TUI and analytical brain. Each event type has a dedicated subclass
|
|
5
|
+
# that implements rendering methods for each view mode:
|
|
6
6
|
#
|
|
7
|
-
#
|
|
7
|
+
# - **basic** / **verbose** / **debug** — TUI display modes returning structured hashes
|
|
8
|
+
# - **brain** — analytical brain transcript returning plain strings (or nil to skip)
|
|
9
|
+
#
|
|
10
|
+
# TUI decorators return structured hashes (not pre-formatted strings) so that
|
|
8
11
|
# the TUI can style and lay out content based on semantic role, without
|
|
9
12
|
# fragile regex parsing. The TUI receives structured data via ActionCable
|
|
10
13
|
# and formats it for display.
|
|
11
14
|
#
|
|
12
|
-
#
|
|
15
|
+
# Brain mode returns condensed single-line strings for the analytical brain's
|
|
16
|
+
# event transcript. Returns nil to exclude an event from the brain's view.
|
|
17
|
+
#
|
|
18
|
+
# Subclasses must override {#render_basic}. Verbose, debug, and brain modes
|
|
13
19
|
# delegate to basic until subclasses provide their own implementations.
|
|
14
20
|
#
|
|
15
21
|
# @example Decorate an Event AR model
|
|
@@ -29,6 +35,7 @@ class EventDecorator < ApplicationDecorator
|
|
|
29
35
|
TOOL_ICON = "\u{1F527}"
|
|
30
36
|
RETURN_ARROW = "\u21A9"
|
|
31
37
|
ERROR_ICON = "\u274C"
|
|
38
|
+
MIDDLE_TRUNCATION_MARKER = "\n[...truncated...]\n"
|
|
32
39
|
|
|
33
40
|
DECORATOR_MAP = {
|
|
34
41
|
"user_message" => "UserMessageDecorator",
|
|
@@ -77,14 +84,16 @@ class EventDecorator < ApplicationDecorator
|
|
|
77
84
|
RENDER_DISPATCH = {
|
|
78
85
|
"basic" => :render_basic,
|
|
79
86
|
"verbose" => :render_verbose,
|
|
80
|
-
"debug" => :render_debug
|
|
87
|
+
"debug" => :render_debug,
|
|
88
|
+
"brain" => :render_brain
|
|
81
89
|
}.freeze
|
|
82
90
|
private_constant :RENDER_DISPATCH
|
|
83
91
|
|
|
84
92
|
# Dispatches to the render method for the given view mode.
|
|
85
93
|
#
|
|
86
|
-
# @param mode [String] one of "basic", "verbose", "debug"
|
|
87
|
-
# @return [Hash, nil] structured event data,
|
|
94
|
+
# @param mode [String] one of "basic", "verbose", "debug", "brain"
|
|
95
|
+
# @return [Hash, String, nil] structured event data (basic/verbose/debug),
|
|
96
|
+
# plain string (brain), or nil to hide the event
|
|
88
97
|
# @raise [ArgumentError] if the mode is not a valid view mode
|
|
89
98
|
def render(mode)
|
|
90
99
|
method = RENDER_DISPATCH[mode]
|
|
@@ -113,6 +122,14 @@ class EventDecorator < ApplicationDecorator
|
|
|
113
122
|
render_basic
|
|
114
123
|
end
|
|
115
124
|
|
|
125
|
+
# Analytical brain view — condensed single-line string for the brain's
|
|
126
|
+
# event transcript. Returns nil to exclude from the brain's context.
|
|
127
|
+
# Subclasses override to provide event-type-specific formatting.
|
|
128
|
+
# @return [String, nil] formatted transcript line, or nil to skip
|
|
129
|
+
def render_brain
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
116
133
|
private
|
|
117
134
|
|
|
118
135
|
# Token count for display: exact count from {CountEventTokensJob} when
|
|
@@ -155,6 +172,23 @@ class EventDecorator < ApplicationDecorator
|
|
|
155
172
|
lines.first(max_lines).push("...").join("\n")
|
|
156
173
|
end
|
|
157
174
|
|
|
175
|
+
# Truncates long text by cutting the middle, preserving the start and end
|
|
176
|
+
# so context and conclusions aren't lost. Used for brain transcripts where
|
|
177
|
+
# both the opening (intent) and closing (result) matter.
|
|
178
|
+
#
|
|
179
|
+
# @param text [String, nil] text to truncate
|
|
180
|
+
# @param max_chars [Integer] maximum character length before truncation
|
|
181
|
+
# @return [String] original text or start + marker + end
|
|
182
|
+
def truncate_middle(text, max_chars: 500)
|
|
183
|
+
str = text.to_s
|
|
184
|
+
return str if str.length <= max_chars
|
|
185
|
+
|
|
186
|
+
keep = max_chars - MIDDLE_TRUNCATION_MARKER.length
|
|
187
|
+
head = keep / 2
|
|
188
|
+
tail = keep - head
|
|
189
|
+
"#{str[0, head]}#{MIDDLE_TRUNCATION_MARKER}#{str[-tail, tail]}"
|
|
190
|
+
end
|
|
191
|
+
|
|
158
192
|
# Normalizes input to something Draper can wrap.
|
|
159
193
|
# Event AR models pass through; hashes become EventPayload structs
|
|
160
194
|
# with string-normalized keys.
|