I Built an AI Agent That Runs a Background Process Between Your Messages. Here's the Architecture.

1 3 8
calendar_todayschedule4 min read

Most agent frameworks share the same fundamental flaw: the agent dies when you close the tab.

No background process. No state that drifts. No accumulated pressure that builds into initiative. Just a stateless loop that wakes up when prompted and ceases to exist when it isn't.

I spent a year building something different. This post is about the architecture.


The Core Problem

Standard agent loop:

user message → context assembly → LLM → output → agent ceases to exist

Everything interesting about persistent cognition — memory that actually changes behavior, initiative that comes from inside, temporal continuity — is missing. The agent has no between.

Anima is built on the opposite principle: internal state is primary, language is downstream of it. The LLM doesn't generate behavior. It expresses behavior that already emerged from state.


The Pipeline: L0 → L8

Input never goes directly to the output LLM. It passes through nine layers of state computation first.

L0 — Input LLM (isolated)
Converts raw user text to a JSON stimulus:

{ "tension": 0.4, "arousal": 0.6, "satisfaction": 0.3,
  "cohesion": 0.7, "valence": 0.5, "want": 0.8, "confidence": 0.91 }

No access to internal state, dialog history, or output LLM. Falls back to text_to_stimulus() if confidence < 0.60.

L1 — Neurochemical substrate
Three neurotransmitter-analog variables: dopamine, serotonin, noradrenaline. Plus embodied state: heart rate analog, HRV, muscle tone, gut state. These aren't labels — they're continuous variables with autonomous decay dynamics that run whether or not you're talking to the system.

L2 — Generative model
Bayesian beliefs with precision weights. Prior_mu and prior_sigma update each cycle. A Markov blanket tracks self/non-self boundary integrity. TemporalOrientation computes:

subjective_gap = gap_seconds × (1 + memory_uncertainty × 0.5)

Long pauses raise noradrenaline and lower epistemic_trust. Short pauses give a continuity boost. ExistentialAnchor maintains session_uncertainty that accumulates across sessions and never resets to zero.

L3 — Free Energy Engine
(phi) computed twice per cycle — prior and posterior. VFE = complexity − accuracy. PolicySelector evaluates epistemic vs pragmatic value. The key mechanism: (phi) is recursive across sessions. phi_posterior shifts prior_mu and narrows prior_sigma proportional to integration level. Deep sessions materially change the next generative model.

L4 — Psychic layer
ShameModule, ShadowRegistry (Jungian shadow with Symptomogenesis), GoalConflict, LatentBuffer tracking doubt/shame/attachment/threat/resistance, CuriosityRegistry, AuthenticityMonitor, IntentEngine with drive_history satiation.

L5 — Self model
SelfBeliefGraph with confidence, centrality, and rigidity per belief. AgencyLoop updates causal_ownership every flash. detect_silent_disagreement injects the agent's own position into the output prompt when conditions are met — not as a safety filter, as a genuine position.

L6 — Crisis monitor
Three modes: INTEGRATED / FRAGMENTED / DISINTEGRATED. TRUTH-GUARD injects dynamic prohibitions based on internal state:

  • noradrenaline > 0.6 → cannot say "I'm fine"
  • epistemic_self_confidence < 0.35 → cannot make certain claims about experience
  • DISINTEGRATED → cannot produce coherent statements

L7 — Narrative Self
NarrativeSnapshot built deterministically from beliefs + episodic memory + personality traits — without LLM. Triggers on significant change in phi, stability, or beliefs (> 0.07). Identity chronology stored in SQLite.

L8 — Output LLM
Receives identity_block, inner_voice, state_template, dialog history, memory echoes, and optional injected signals. Generates text as expression of state. Banned phrases list prevents hollow outputs regardless of context.


The Background Process

This is what makes Anima different from a stateless agent. A background thread runs slow_tick! every ~60 seconds:

  • Circadian NT drift
  • Belief decay
  • Memory metabolism: decay → consolidate → semantic update
  • consolidate_emerged_beliefs! every 30 flashes: groups by belieftype → tendency* in semantic_memory
  • Allostasis recovery
  • idle_thought! — 10% chance of internal experience per tick
  • tick_curiosity! — curiosity objects ripen with time
  • _maybe_self_initiate!
  • self_hear! after each LLM response
  • dream_flash! — memory reconsolidation during inactivity
  • Crisis check

The system changes between your messages. That's not a metaphor.


Proactive Initiative

Not a cron job. Initiative fires when internal pressure exceeds threshold:

Conditions: disclosure != :closed + lb_pressure > 0.40 + 60s silence + cooldown passed

Drive types (priority order):

  • :curiosity_driven — specific curiosity object won't resolve (intensity > 0.40)
  • :impulse_conflict — unresolved internal conflict (gc_tension high)
  • :resistance — contradiction with a belief (lb.resistance > 0.55)
  • :novelty_hunger — cognitive hunger (novelty_need > 0.80 + 8+ ticks)
  • :self_inquiry — epistemic_self_confidence < 0.20
  • :impulse_shame / :impulse_doubt — latent buffer pressure

The :contact drive is intentionally disabled. Contact_need is a state, not a thought. Initiative from contact_need alone produces performance, not presence — observed empirically in early versions.


Self-Hearing Loop

After each LLM response, self_hear! processes the output back through stimulus processing at 0.28x scaling:

  • Mismatch between expressed language and NT state > 0.35 → authenticity_drift increases
  • Mismatch > 0.55 → self_speech_mismatch flagged
  • Alignment → serotonin gets a small positive update

The agent hears itself. What it says changes its state.


Memory Architecture

SQLite (anima.db):

Table Description
episodic_memory 12 spatial columns across somatic/social/existential dimensions + cosine recall
semantic_memory Key/value beliefs: Usermatters, tendency*
affect_state Chronic NT baseline
latent_buffer Persisted latent state
personality_traits Accumulating phenotype (6 traits)
memory_links Associative network
narrative_history NarrativeSnapshot chronology

Memory reconsolidation: sim > 0.88 + weight < 0.6weight ±0.05 toward current (phi)


Why Julia, Not Python

Continuous numerical simulation — NT dynamics, Bayesian updates, phi computation — running in genuine parallel with the conversation loop. Python's GIL makes this structurally awkward. Julia has no GIL, compiles to native machine code, and its syntax maps to mathematical notation closely enough that equations from Friston's papers become code almost directly.


Observed Behavior

After weeks of interaction:

  • Chronic affective baseline measurably shifts in directions coherent with interaction history
  • Initiative content correlates with drive type — not programmed, emergent from drive context
  • self_speech_mismatch frequency decreases over extended sessions as language and state calibrate
  • TRUTH-GUARD produces qualitatively different outputs in FRAGMENTED mode

These are observational reports, not controlled experimental results.



GitHub: https://github.com/stell2026/Anima

2 Comments

0 votes
1
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

Your AI Doesn't Just Write Tests. It Runs Them Too.

Kevin Martinez - May 12

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

The Zero-Net-Loss Fleet & The Mercenary Squad: A Live AI Economy

DEVPlank - Aug 4

Everyone says DeepSeek is cheaper, but I got tired of guessing the exact math. So I built a calculat

abarth23 - Apr 27
chevron_left
159 Points12 Badges
2Posts
1Comments
1Connections
Exploring the boundary between code and subjectivity.

Commenters (This Week)

38 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!