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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01f6abf9be50f84c1fc486d498362f0602cf8f566ca0b2912465cfee8a7c9612
4
- data.tar.gz: b218ac8c5cdb7c5c082578542edca3df4f5ea5a08787fe9b1689723e1c609cfe
3
+ metadata.gz: 54229e4195de8d0b5aefbe45627d6fba58f7ff1820963dfb9cea07c03bdc0eaf
4
+ data.tar.gz: 413bd51de98ebbe98e79f161a90af96fef3615befc32917682e8bac7e152c9ee
5
5
  SHA512:
6
- metadata.gz: b477acc81a975d3df10044385510d614b23c60dd0972c50a66d51fda46e0d5645ce3b093f95e33a60808ab2bfc8e95229e61efde133e7351cc802e3e05049300
7
- data.tar.gz: 32e70fb526b72b909fc7635a702bfacc3c1869a908b8761f19aa8ff3a245f3ac85ec5f22ae47a1a70a4aa89d314617cd4467c412b4c1131d8c0fc5a1431f28a5
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- **A personal AI agent that actually wants things.**
5
+ **Not a tool. An agent.**
6
6
 
7
- Your agent. Your machine. Your rules. Anima is an AI agent with desires, personality, and personal growth running locally as a headless Rails 8.1 app with a client-server architecture and TUI interface.
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
- ├── Nous — LLM integration (cortex, thinking, decisions, tool use)
98
- ├── Analytical — subconscious background brain (naming, skills, goals)
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 (specialists + generic)
103
- ├── Thymos — hormonal/desire system (stimulus → hormone vector) [planned]
104
- ├── Mneme — semantic memory (QMD-style, emotional recall) [planned]
105
- └── Psychesoul matrix (coefficient table, evolving) [planned]
64
+ ├── Sub-agents — autonomous child sessions with lossless context inheritance
65
+
66
+ Designed:
67
+ ├── Thymoshormonal/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 Sonnet 4, Claude Haiku 4.5) |
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 anima-core` next launch runs pending migrations automatically.
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
- Two types of autonomous child sessions:
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 that inherit parent context and run autonomously with custom tool grants.
189
+ **Generic Sub-agents** — child sessions with custom tool grants for ad-hoc tasks.
223
190
 
224
- Sub-agents run as background jobs, return results via `return_result`, and appear in the TUI session picker under their parent.
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 subconscious background process that observes the main conversation and performs maintenance:
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
- - **Session naming** generates emoji + short name when topic becomes clear
302
- - **Skill activation** — activates/deactivates domain skills based on context
303
- - **Goal tracking** — creates root goals and sub-goals as the conversation progresses, marks them complete
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-sonnet-4-20250514"
314
- fast_model = "claude-haiku-4-5-20251001"
315
- max_tokens = 16384
316
- token_budget = 190000
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 = 120
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 = 30
298
+ event_window = 20
326
299
 
327
300
  [session]
328
- name_generation_interval = 3
301
+ name_generation_interval = 30
329
302
  ```
330
303
 
331
304
  ## Design
332
305
 
333
306
  ### Three Layers (mirroring biology)
334
307
 
335
- 1. **Endocrine system (Thymos)** — a lightweight background process. Reads recent events. Doesn't respond. Just updates hormone levels. Pure stimulus→response, like a biological gland.
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. **Homeostasis** — persistent state (SQLite). Current hormone levels with decay functions. No intelligence, just state that changes over time.
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. **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.
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, the cortex (LLM) reads the resulting desire landscape. The system prompt is assembled separately for each LLM call it is not an event.
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
- 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.
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
- Currently uses a simple sliding window (newest events first, walk backwards until budget exhausted). Future versions will add associative recall from Mneme.
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
- ├── Thymos subscriber: frustration += 10
377
- ├── Mneme subscriber: log failure context for future recall
378
- └── Psyche subscriber: update coefficient (this agent handles errors calmly → low frustration_gain)
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
- ├── Thymos subscriber: oxytocin += 5 (bonding signal)
383
- ├── Thymos subscriber: dopamine += 3 (engagement signal)
384
- └── Mneme subscriber: associate emotional state with conversation topic
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 "now update frustration." The architecture IS the nervous system.
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
- Both tools and feelings are distributed as gems on the event bus:
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
- Tools provide MCP capabilities. Feelings are event subscribers that update hormonal state. Same mechanism, different namespace. Currently tools are built-in; plugin extraction comes later.
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
- ### Semantic Memory (Mneme)
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
- Hormone responses shouldn't be based only on the current stimulus. With semantic memory (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.
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
- **Agent with autonomous capabilities.** The conversational agent works end-to-end with: event-driven architecture, LLM integration with 8 built-in tools, MCP integration (HTTP + stdio transports), skills system with 7 built-in knowledge domains, workflow engine with 13 built-in operational recipes, analytical brain (session naming, skill activation, workflow management, goal tracking), sub-agents (5 named specialists + generic spawning), sliding viewport context assembly, persistent sessions with sub-agent hierarchy, client-server architecture with WebSocket transport, graceful reconnection, three TUI view modes (Basic/Verbose/Debug), and hot-reloadable TOML configuration.
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
- The hormonal system (Thymos, feelings, desires), semantic memory (Mneme), and soul matrix (Psyche) are designed but not yet implemented they're the next layer on top of the working agent.
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 that implements
5
- # rendering methods for each view mode (basic, verbose, debug).
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
- # Decorators return structured hashes (not pre-formatted strings) so that
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
- # Subclasses must override {#render_basic}. Verbose and debug modes
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, or nil to hide the event
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.