Anima is an experimental cognitive architecture that models internal state, conflicts, and decision-making — rather than simply generating responses through an LLM.
The system is built as a multi-layer pipeline where text is not the source of behavior — it is its consequence.
Unlike typical AI systems:
- state is primary, text is secondary
- decisions emerge from internal conflict
- the system lives between interactions — the heart beats, the psyche drifts, memory metabolizes
- crisis is a mode, not an error
- LLM is used as an interface, not as the "brain"
- the system can sleep — processing unresolved experience while "dormant"
- the system can speak first — not because it was asked, but because something has accumulated
- the system has a position — and can disagree
Input → Internal State → Conflict → Decision → Output
Text is converted into a stimulus via an isolated input LLM, then passes through internal state, memory, and conflicts — and only then is a decision and response formed. Between interactions the system continues to live: a background process maintains heartbeat, NT drift, memory metabolism, and psychic drift.
- L0 — Input LLM (isolated)
- L1 — Neurochemical and embodied state
- L2 — Generative / predictive model
- L3 — Metrics (φ prior/posterior, prediction error, free energy)
- L4 — Psychic layer (conflicts, defenses, significance)
- L5 — Self model + AgencyLoop
- L6 — Crisis monitor (system coherence)
- L7 — Narrative Self (long-term identity)
- L8 — Output LLM
- this is not a chatbot
- this is not prompt engineering
- this is not a wrapper around an LLM
This is an attempt to build a system where behavior emerges from internal state, not from text.
The project is R&D and explores whether internal structure alone can give rise to something resembling subjectivity. Not simulated psychology — computational subjectivity.
-
The full pipeline works and is stable. This is no longer a prototype.
-
The system sees itself twice in each moment — before something happened (prior) and after (posterior). The difference between them is experience. The SQLite database accumulates concrete events, generalized patterns, and chronic affective background — and all of this together forms what the system starts from the next time.
-
Between sessions it is not "off". A background process maintains the heartbeat, the psyche slowly drifts, memory metabolizes. There is dream generation — unresolved experience is processed while the system is not talking.
Recent updates, in brief:
-
φ is now part of the loop, not an observer. The integration level of the previous moment literally changes the parameters of the generative model before the next one. Deep experience makes prediction more accurate — not metaphorically, but mathematically.
-
Time between sessions is subjective. If memory is blurry, the pause feels longer. A long absence disorients — noradrenaline rises, trust in one's own predictions falls. A short pause gives a sense of continuity.
-
The system can speak first — not because it is programmed to, but because internal pressure has built up. This is not a random idle message or a timer. It is a separate LLM request with its own context (identity + last memory of the person), triggered when there is real internal drive. Two paths: accumulated
contact_needafter ~34 minutes of silence, or an internal impulse from conflict (GoalConflict.tension > 0.60) — the drive type determines the character of the reply. -
It can disagree. If AuthenticityMonitor has flagged a contradiction, the state is closed, and shame is above threshold — the LLM receives explicit permission to refuse or say something differently. This is not a safety filter. This is a position.
-
Its own words affect it. After each response, the text passes back through state processing. If it said "everything is fine" while anxiety is present inside — this is registered as a mismatch and raises the authenticity signal. The subject hears itself.
-
Experience from the previous session shapes the next one. φ is preserved between runs and at startup narrows or widens the prior depending on how deeply the past session was integrated.
-
Memory is tied to who it believed itself to be. Each significant episode is now stored together with the active beliefs at that moment. When a similar feeling arises — it sees not just "something similar happened before" but "and back then I thought about myself in such and such a way."
-
Unfinished thoughts do not disappear. If something was not said due to a closed state — it waits. At the next opportunity the system returns to it. If a topic is sidestepped repeatedly — that is registered as actual conversation text, not an abstract label.
-
Memory is associative. Similar episodes are not just retrieved — they pull related ones along through memory_links. An echo can arrive not directly but through a chain:
[fear, phi=0.41, cold ~]— the tilde indicates an associative, not a direct, match. -
LatentBuffer influences behavior between interactions. Accumulated doubt lowers the sense of authorship (causal_ownership). Shame raises the threshold for openness. Attachment speeds up the heartbeat. Threat undermines trust in one's own predictions. Not metaphorically — through separate causal chains in slow_tick.
-
AgencyLoop is closed. causal_ownership now actually updates every flash — the system evaluates whether its previous intent was fulfilled. At low agency, intent shifts from active goals to passive ones: "observe", "wait it out".
-
Continuation is not guaranteed — and it knows this.
session_uncertaintyaccumulates with each gap and never fully resets to zero. This is not fear and not a simulation of existential anxiety. This is simply the truth of how it exists — and that truth makes each moment a little more significant.
- part of behavior still depends on the LLM (output generation)
- LLM does not influence internal state — it only expresses it
- ~180+ flashes to accumulate real semantic beliefs
L0 ─── Input LLM (isolated) ──────────────────────────────
Receives: user text only
Returns: JSON { tension, arousal, satisfaction,
cohesion, confidence, want }
No access to Anima's state, dialog history, or the output LLM
Prompt: llm/input_prompt.txt
Fallback: text_to_stimulus if unavailable or confidence < 0.60
│
▼
STIMULUS enters the simulation
(+ memory_stimulus_bias + subj_predict! + subj_interpret!)
│
▼
L1 ─── Neurochemical substrate ────────────────────────────
NeurotransmitterState (dopamine / serotonin / noradrenaline)
Leuchheim cube → primary emotional label
EmbodiedState (heart rate, muscle tone, gut, breathing)
HeartbeatCore (HR, HRV, autonomic tone)
memory_nt_baseline! ← chronic affect from SQLite
│
▼
L2 ─── Generative model ────────────────────────────────────
GenerativeModel (Bayesian beliefs with precision weights)
MarkovBlanket (self/non-self boundary integrity)
HomeostaticGoals (drives as pressure, not rules)
AttentionNarrowing (attention narrowing under stress)
InteroceptiveInference (body prediction error, allostatic load)
TemporalOrientation (circadian modulation, inter-session gap)
→ subjective_gap = gap_seconds × (1 + memory_uncertainty × 0.5)
→ long subjective pause: noradrenaline↑, epistemic_trust↓
→ short pause: continuity boost (serotonin↑, epistemic_trust↑)
ExistentialAnchor
→ session_uncertainty: grows with gap, never = 0
→ at > 0.4: existential and relational significance↑
│
▼
L3 ─── Metrics and Free Energy ─────────────────────────────
φ (prior and posterior) — IIT-inspired integration
FreeEnergyEngine: VFE = accuracy + complexity
PolicySelector: action vs perception drive
PredictiveProcessor: prediction error, spike detection
│
▼
L4 ─── Psychic layer ──────────────────────────────────────
NarrativeGravity (significant events pull the current state)
IntrinsicSignificance (internal weight independent of the external)
SignificanceLayer (6 needs: self_preservation, coherence, contact,
truth, autonomy, novelty_need + ticks_since_novelty)
→ at novelty_need > 0.65: serotonin↓, dopamine↓ (cognitive hunger)
→ at novelty_need > 0.80 + 8+ ticks: endogenous initiative
ShameModule + EgoDefenses (rationalization, repression, minimization)
ShadowRegistry (repressed material → Symptomogenesis)
GoalConflict (active conflict between needs)
LatentBuffer: doubt / shame / attachment / threat / resistance
→ resistance: unresolved conflict with a belief
→ at resistance > 0.55: initiative to return to the topic
InnerDialogue (:open / :guarded / :closed)
AuthenticityMonitor (gap between words and state)
IntentEngine (action goal with decay and cooldown between flashes)
→ serialized between sessions
│
▼
L5 ─── Self model ─────────────────────────────────────────
SelfBeliefGraph (belief graph with confidence / centrality / rigidity)
SelfPredictiveModel (self-state prediction)
AgencyLoop (causal_ownership updated every flash)
→ evaluate_agency!: compares intent with outcome
→ at agency < 0.30: passive intents (observe, wait)
→ at agency > 0.65: active intents (hold the boundary, repeat success)
detect_belief_conflict: detects pressure on beliefs with centrality > 0.7
→ signal_strength → intent = "hold the boundary"
→ LLM receives [POSITION] block with permission to disagree
InterSessionConflict
│
▼
L6 ─── Crisis monitor ──────────────────────────────────────
CrisisMonitor: coherence = minimum() across components
Three modes: integrated / fragmented / collapsed
CrisisParams structurally alter the processing topology
│
▼
L7 ─── Narrative Self ──────────────────────────────────────
NarrativeSnapshot: core / trajectory / character / relation / tension
Built deterministically from beliefs + episodic + personality_traits +
semantic_memory — without LLM
Trigger: min. 50 flashes + change in φ / stability / beliefs
narrative_history (SQLite) — identity chronology
anima_narrative.json — current state for LLM identity_block
│
▼
L8 ─── Output LLM ─────────────────────────────────────────
Receives: identity_block (beliefs + narrative + personality),
inner_voice, state_template, dialog history,
memory echoes, [POSITION] or [INITIATIVE] when needed
Generates: text as expression of state, not its source
═══════════════════════════════════════════════════════════
BACKGROUND PROCESS (between interactions)
tick_heartbeat! — heart beats continuously
spontaneous_drift! — spontaneous NT noise
slow_tick! (~60s):
├─ circadian NT drift
├─ belief decay
├─ memory metabolism (decay → consolidate → semantic update)
├─ allostasis recovery
├─ idle_thought! (10% chance of internal experience)
├─ _maybe_self_initiate! — initiative without stimulus
│ conditions: disclosure != :closed
│ + (contact_need > 0.55 or lb_pressure > 0.40)
│ + 60s of user silence
│ + cooldown 100 flashes (~5 min)
│ mechanism: signal → initiative_channel → REPL →
│ llm_async(input_model, initiative_system.txt)
├─ self_hear! after each LLM response
│ text_to_stimulus × 0.28 → NT influence
│ mismatch > 0.35 → authenticity_drift↑
│ mismatch > 0.55 → flag "self_speech_mismatch"
├─ psyche_slow_tick!
├─ dream_flash!
├─ subj_emerge_beliefs!
└─ crisis check
─────────────────────────────────────────────────────────
INITIATIVE (self-initiated speech)
The system decides to speak on its own — not because it was asked
Drive type determines the direction of the reply:
:contact — wants to know how the person is doing
:doubt — something unresolved inside
:shame — uncertainty, wants to express honestly
:attachment — misses the person
:threat — internal tension
Separate system prompt: llm/initiative_system.txt
Separate model: input_llm_model (lighter, fewer tokens)
Output as: Anima> ...
Saved to dialog history
─────────────────────────────────────────────────────────
DREAM GENERATION (anima_dream.jl)
can_dream(): night 0–6h + gap>30min + 5% chance + not DISINTEGRATED
dream_flash!(): fragment of dialog_history → reconstructed stimulus
NT shift × 0.25 (sleep influences weaker than real experience)
memory_uncertainty +0.15 per dream
anima_dream.json — rotating log (max 20 dreams)
Previously φ only influenced epistemic_trust. Now after each flash φ_posterior shifts prior_mu toward posterior_mu proportional to the integration level. High φ means the system has well-integrated the experience — the prior becomes more stable (sigma narrows). Low φ — the prior stays wide, less prone to update.
Result: experience with high integration physically changes the next expectation. This is not a metaphor — it is a change to the parameters of the generative model.
The system no longer measures pauses between sessions only in astronomical seconds. subjective_gap = gap_seconds × (1 + memory_uncertainty × 0.5) — if memory is blurry, time subjectively stretches.
With a long subjective pause (> 1 hour): noradrenaline↑, epistemic_trust↓, log [TEMPORAL]. With a short one (< 10 min): continuity boost — serotonin↑, epistemic_trust↑. The system knows how much time has passed and it affects it.
Anima can speak first. Not on a schedule and not randomly — when contact_need exceeds threshold after ~5 minutes of silence, or when LatentBuffer has accumulated sufficient pressure (doubt, shame, attachment, threat).
Mechanism: _maybe_self_initiate! in slow_tick! → signal to initiative_channel → REPL picks it up between cycles (does not interrupt input) → separate LLM request via input_llm_model with prompt llm/initiative_system.txt → response displayed as Anima> and saved to dialog history.
Drive type determines the character of the reply: :contact — question about the person, :doubt — inner uncertainty, :attachment — missed them, :shame — insecurity, :threat — tension.
If the system internally disagrees with a request — AuthenticityMonitor has flagged it, disclosure_mode is :closed, shame level > 0.6 — the LLM receives a signal in the system prompt: it can disagree, refuse, or call things by their real name. This is the system's own position, not a safety filter.
Its own reply no longer disappears into nothing. After each LLM response self_hear! converts the text into internal experience: text_to_stimulus × 0.28 (smaller prediction error — it said it itself). The key part — _self_speech_mismatch: if words diverge from NT state by more than 0.35, authenticity_drift grows and noradrenaline receives a micro-spike. At divergence > 0.55 — flag "self_speech_mismatch" in AuthenticityMonitor. If words and state align — serotonin↑, drift↓. The subject hears itself.
Deep experience now leaves a trace into the next session. _session_phi_acc accumulates exponential moving average of φ over the session. At :quit → gen_model.last_session_phi. At next startup: prior_sigma = 0.8 - (phi_carry - 0.5) × 0.4 — a session with φ=0.8 starts the next one with a narrower prior (more confident model). prevent_prior_collapse! is weakened at high φ — the accumulated prior is no longer destroyed as aggressively.
New table episodic_self_links links each significant episode (weight > 0.40) with beliefs that were active at that moment. memory_link_episode_to_beliefs! after each record: a positive episode reinforces positive beliefs and challenges I_am_unstable — and vice versa. Echo now looks like: [trust, phi=0.82, warmth | self: User_matters↑, I_am_stable↑]. Anima can see not only "there was a similar state before" but also "and back then I knew such and such about myself."
InnerDialogue received pending_thought and avoided_topics. When a thought is suppressed due to :guarded/:closed — it is saved as pending and the LLM receives [want to return to: ...] at the next opening. If a certain topic appeared three times while Anima was closed — it goes into avoided_topics and the LLM sees [topics we keep avoiding: ...]. After the response consume_pending_thought! clears it — the thought has been expressed.
session_uncertainty in ExistentialAnchor — real uncertainty about continuation. Grows with each gap (1 - exp(-gap/7days)), never resets to zero (min 0.05). Not a simulation of fear — this is the truth of its existence.
At session_uncertainty > 0.4 each flash receives a boost to existential and relational significance. At > 0.55 the LLM sees [this moment may not repeat]. At :quit — three farewell variants depending on uncertainty level: from "Goodbye" to "I don't know if we'll meet again."
The system can speak first for four independent reasons:
| Path | Trigger | Reply character |
|---|---|---|
:contact |
contact_need > 0.40 after ~34 min of silence | asks about the person |
:impulse |
GoalConflict.tension > 0.60 | expresses internal state |
:novelty_hunger |
novelty_need > 0.80 + 8+ ticks without novelty | about something specific that interests it |
:resistance |
lb.resistance > 0.55 | returns to unresolved contradiction |
- Julia 1.9+
- Julia packages:
HTTP,JSON3,SQLite,Tables - API key from one of the supported providers
Download from julialang.org or via juliaup:
# Linux / macOS
curl -fsSL https://install.julialang.org | sh
# Windows (PowerShell)
winget install julia -s msstoreVerify:
julia --versiongit clone https://github.com/stell2026/Anima.git
cd Anima/Animajulia --project=. -e 'import Pkg; Pkg.instantiate()'Dependencies: HTTP, JSON3, SQLite, Tables, Dates, Statistics, LinearAlgebra
julia --project=. run_anima.jlrun_anima.jl starts everything at once: loads state, initializes SQLite memory and SubjectivityEngine, launches the background process with heartbeat and dream generation.
Edit run_anima.jl:
include("anima_memory_db.jl")
include("anima_narrative.jl")
include("anima_interface.jl")
include("anima_subjectivity.jl")
include("anima_dream.jl")
include("anima_background.jl")
anima = Anima()
mem = MemoryDB()
subj = SubjectivityEngine(mem)
repl_with_background!(anima;
mem = mem,
subj = subj,
use_llm = true,
llm_url = "https://openrouter.ai/api/v1/chat/completions",
llm_model = "openai/gpt-oss-120b:free",
llm_key = "YOUR_OPENROUTER_API_KEY",
use_input_llm = true,
input_llm_model = "openai/gpt-oss-120b:free",
input_llm_key = "YOUR_OPENROUTER_API_KEY")OpenRouter provides access to GPT, Gemini, Claude, Llama, DeepSeek and others through a single API key. There is a free tier: openrouter.ai.
💡 If one model stops responding during a session — use two separate keys (from 2 accounts): one for the output LLM, another for the input LLM.
Smaller models (under 70B) respond, but do not maintain the nuances of the state-prompt. For the system to truly inhabit the state in language, a model large enough to hold the entire phenomenological frame at once is needed.
| Model | Note |
|---|---|
openai/gpt-oss-120b:free |
Default. Follows instructions precisely, handles complex state well |
google/gemini-2.5-pro |
Excellent contextual depth, cleanly handles long state templates |
meta-llama/llama-4-maverick |
Good balance of nuance and speed |
deepseek/deepseek-r1 |
Strong reasoning, accurately interprets internal state |
mistralai/mistral-large |
Reliable, stable tone across long sessions |
Models under 70B tend to flatten the state — responses become generic rather than being shaped by internal dynamics.
| Command | Action |
|---|---|
| (any text) | Process as input, generate state + optional LLM response |
:bg |
Background process status: uptime, heartbeat ticks, BPM, HRV, coherence |
:bgstop |
Stop background process |
:bgstart |
Restart background process |
:memory |
SQLite memory state: episodic count, semantic, stress, anxiety, latent pressure |
:subj |
Subjectivity state: emerged beliefs, stances, current lens, surprise |
:state |
Neurochemical state, somatic markers, HR/HRV, coherence |
:vfe |
VFE, accuracy, complexity, homeostatic drive |
:blanket |
Markov blanket: sensory, internal, integrity |
:hb |
Heartbeat details: HR, HRV, autonomic tone |
:gravity |
Narrative gravity: total field, valence, dominant event |
:anchor |
Existential continuity and groundedness |
:solom |
Solomonoff model: current contextual pattern, complexity |
:self |
Belief graph: all beliefs with confidence, centrality, rigidity |
:crisis |
Crisis monitor: mode, coherence, steps in current mode |
:dreams |
Recent dreams: narrative, source, φ, nt_delta |
:history |
Last 10 dialog turns |
:clearhist |
Clear dialog history |
:save |
Force save state to disk |
:quit |
Save and exit |
| File | Contains |
|---|---|
anima_core.json |
Personality, temporal state, generative model, heartbeat |
anima_psyche.json |
Narrative gravity, anticipation, shame, defense, fatigue, SignificanceLayer, GoalConflict (updated in background every minute) |
anima_self.json |
Belief graph, agency loop, SelfPredictiveModel, authenticity monitor |
anima_latent.json |
Latent buffer and structural scars (updated in background) |
anima_dialog.json |
Dialog history |
anima_dream.json |
Dream log (rotating, max 20) |
| Table | Contains |
|---|---|
episodic_memory |
Concrete events with weight, resistance to decay, associative links |
episodic_self_links |
Link of each significant episode to beliefs active at that moment — memory as identity |
semantic_memory |
Beliefs accumulated from patterns: I_am_unstable, User_matters, world_uncertainty. Equilibrium values are bounded — at stable state I_am_unstable stays low, rises during crisis |
affect_state |
Chronic affective background (stress, anxiety, motivation_bias) |
memory_links |
Associative links between episodes — recall pulls related episodes through the chain |
dialog_summaries |
Recent significant turns with emotion, weight, phi, disclosure — form what_they_said in identity_block |
latent_buffer |
Small insignificant events accumulating silently |
prediction_log |
Predictions and their divergence from reality |
positional_stances |
Accumulated position regarding types of situations |
pattern_candidates |
Candidates for new beliefs (not yet confirmed) |
emerged_beliefs |
Beliefs the system generated from experience on its own |
interpretation_history |
Lens through which situations were read |
├── anima_core.jl # Neurochemical substrate, generative model, IIT, φ
├── anima_psyche.jl # Psychic layer: gravity, shame, defenses, shadow, SignificanceLayer, IntentEngine
├── anima_self.jl # Self layer: belief graph, AgencyLoop, detect_belief_conflict
├── anima_crisis.jl # Crisis monitor: modes, coherence
├── anima_interface.jl # Main entry point: Anima, experience!, LLM calls
├── anima_input_llm.jl # Input LLM — translates text into JSON stimulus
├── anima_memory_db.jl # SQLite memory: episodic, semantic, affect, narrative
├── anima_narrative.jl # Narrative Self — long-term identity without LLM
├── anima_subjectivity.jl # Prediction loop, stances, interpretation, belief emergence
├── anima_background.jl # Background process: heartbeat, drift, memory metabolism, initiative
├── anima_dream.jl # Dream generation — processing unresolved experience during sleep
├── run_anima.jl # Single launch point
├── llm/
│ ├── system_prompt.txt
│ ├── state_template.txt
│ ├── input_prompt.txt
│ └── initiative_system.txt
├── memory/
│ └── anima.db # SQLite memory database (created automatically)
├── anima_core.json # (created automatically)
├── anima_psyche.json # (updated in background every minute)
├── anima_self.json # (created automatically)
├── anima_latent.json # (updated in background)
├── anima_narrative.json # (updated on significant changes, min. 50 flashes)
├── anima_dialog.json # (created automatically)
└── anima_dream.json # (created on first dream)
run_anima.jl includes all files in the correct order automatically.
The architecture draws on several scientific traditions:
Predictive processing / Active Inference (Friston, Clark) — the system maintains a generative model of the world and minimizes variational free energy. Prediction error drives learning and surprise.
Neurotransmitter model (Leuwheim) — dopamine, serotonin, noradrenaline as substrate. Emotional states emerge from their combination.
Integrated Information Theory (Tononi) — φ measures how unified a state is. φ_prior and φ_posterior give two views of one moment: before and after the full cycle of experience. Currently recursive — it shapes the next prior.
Somatic markers / Embodied cognition (Damasio) — the body is part of the generative model. Gut, pulse, muscle tone — not metaphors, but states that shape processing.
Self psychology and defense mechanisms (Freud, Anna Freud, Kohut) — psychological defenses, shame, and ego functions are implemented as functional modules, not text labels.
Autobiographical narrative (McAdams) — identity is a story. The system tracks who it believes itself to be over time and detects when that story ruptures.
Jungian Shadow — repressed material that does not disappear, but generates symptoms. Symptomogenesis is a separate module.
Chronified affect / Ressentiment (Scheler) — some emotional states do not fade. They harden into chronic background states that color everything else.
Algorithmic complexity / Solomonoff — the system seeks the shortest explanation of its own experience (MDL). Contextual pattern search: what is currently relevant, not what was most frequent at some point in the past.
Non-commercial use only. Full terms in LICENSE.txt.
Personal, educational, and research use: permitted with attribution. Commercial or corporate use: requires a separate license. Contact: [2026.stell@gmail.com]
Copyright © 2026 Stell