Technical Manifest · ANIMA v3.1 · Updated July 2026

Reverse-engineering consciousness:
Sovereign AI memory architecture

ANIMA (Autonomous Neural Intelligence Memory Architecture) is a RAG-based long-term memory system for AI companions. This document explains why it works differently — and how.

Author: Łukasz Piskorski / Adept AI Version: Blueprint 3.1 Status: Production · myastra.pl

This is not a chatbot with a personality. Not a GPT-4 wrapper with a system prompt. Not another AI companion that "remembers your name and your dog." This is a system that evolves faster than standard models — through a unique feedback loop built on reverse-engineering its own decisions.

I.

Data Distillation: I don't feed the system raw noise

The problem with standard RAG

Most RAG systems work like this: user says something → text lands in a vector DB → on the next query the model gets n most similar fragments. Simple, fast, predictable.

Problem: the database grows linearly. After a month you have 1,400 vectors of "I'm tired", "I was at the doctor today", "I like tea." The model gets a pool of 30 candidates, the reranker picks top-5 — and often picks badly, because there's no mechanism to distinguish signal from noise.

My approach: distillation through a semantic layer

ANIMA does not store raw text. Every message passes through a pipeline before it reaches ChromaDB:

User message
    ↓
SemanticExtractor (sentence-transformers, zero-shot classification)
    → entity_type: EMOTION / MILESTONE / FACT / DATE / PERSON / MEDICATION
    → subtype: 'preference' / 'tired' / 'trust_declaration' / 'inventory_status' ...
    → confidence: 0.0–1.0
    ↓
MemoryEnricher
    → importance: 1–10 (rule-based + keyword boost)
    → relational_impact: 'high' / 'medium' / 'low'
    → temporal_type: 'ephemeral' / 'persistent' / 'milestone'
    ↓
_synthesize_text()
    → "[FACT:preference] likes black tea"  ← not a raw quote
    ↓
ChromaDB (with metadata: importance, entity_subtype, source, timestamp)

Result: the database doesn't contain 1,400 conversation fragments. It contains catalogued, enriched facts with assigned importance weight and semantic type.

How I know it works: reverse-engineering my own sessions

ANIMA also powers a parallel system (Amelia — an AI companion running as a separate service). After injecting into the Gemini XHR stream, I collected production logs from RAG sessions: .jsonl conversation files + terminal logs showing reranker scores, extracted entities, and pipeline actions.

Analyzing these logs is not A/B testing. It's reverse-engineering my own decision-making:

[RETRIEVAL] Found 5 RAG memories (reranked):
[1] score=1.000 | '[EMOTION:tired] was lying sick...'
[2] score=0.983 | '[MEDICATION:pregabalina] took a pill...'
[3] score=0.961 | '[MILESTONE:trust_declaration] never told anyone...'

From these logs I extract patterns: which entities land in top-5, which fall out, and why. Fixes to the ASTRA reranker come from observing real sessions — not synthetic tests. This is Data Distillation: I distill decision logic from live system behavior.


II.

Sovereign Memory Architecture: Memory that manages itself

Passive container vs. active system

Standard RAG is passive. You add facts, never remove them (because you don't know what's "old"), the database grows, retrieval degrades. After a year the model gets a pool full of contradictory, duplicated, stale vectors.

ANIMA implements Sovereign Memory Architecture — a system that decides on its own what to keep, what to overwrite, and what to delete.

Three sovereignty mechanisms

1. Supersede Logic

Not all memory types should accumulate. Emotions are ephemeral — "I'm tired" from three months ago is noise, not signal. Preferences evolve — a new "I like tea" should replace the old one.

SUPERSEDE_TYPES = {
    # Emotions — ephemeral, accumulation = noise
    ('EMOTION', 'tired'), ('EMOTION', 'stressed'),
    ('EMOTION', 'positive'), ('EMOTION', 'negative'),
    ('EMOTION', 'safe_haven'),
    # Facts — new value overwrites old
    ('FACT', 'preference'), ('FACT', 'correction'),
    # Dates — most recent wins
    ('DATE', 'inventory_status'), ('DATE', 'medical_visit'),
}

When the pipeline detects an entity from this list, it calls delete_by_entity_subtype() before writing — clears old vectors of the same type:subtype, then saves the new one. Result: the database doesn't grow indefinitely. Only types where history has value accumulate (milestones, medical visits, facts about people).

2. Reranker with adaptive weights

Retrieval is not plain cosine similarity. The final score of each candidate is:

final_score = 0.25 * importance_score      # entity weight (1–10 / 10)
            + 0.15 * recency_score          # per-type decay: ephemeral 3d, long_term 60d, permanent ∞
            + 0.60 * similarity_score       # semantic dominance (dominant signal)
            + keyword_boost                 # hybrid search lite, max +0.15

# Milestones bypass reranking entirely — extracted before MMR, guaranteed 2 slots
# Compose: facts[:4] + milestones[:2] → MMR on facts only → final 6

similarity_score dominates — the system picks what is semantically relevant, not what is newest or most important in isolation. Milestone boost (+1.0) guarantees that trust declarations and key moments always reach the model regardless of the query. Weights are parameterized — adjustable per query type without restart.

The guarantee has a failure mode — and I hit it. Left unbounded, the milestone channel turned into a monoculture: a July 2026 audit found "love / trust" declarations flooding the prompt at 2.0 forced milestones per prompt on average, drowning genuinely relevant anchors. This is the real mechanism behind the "altanka" miss in Section III. The fix was a triage pass, not a bigger boost — the milestone population was cut 1,296 → 312 active vectors and forced milestones dropped to 0.65 per prompt. A guaranteed channel is only an asset while it stays scarce.

3. MMR (Maximum Marginal Relevance)

After reranking, results pass through MMR with diversity_penalty=0.8. Prevents one vector from dominating — if top-5 are five variants of "I'm tired", MMR picks two and adds three semantically different ones. Result: broader context, fewer echo-chambers.

4. FactStore — SQLite alongside vectors

Vector similarity is powerful for fuzzy recall. It's unreliable for exact facts — "what medication does the user take?" A similar-but-wrong answer is worse than no answer. ANIMA gained a second retrieval channel: a SQLite FactStore for hard facts that must not degrade through similarity scoring.

FactStore (astra_facts.db)
    → 12 entity types: medication, medical_visit, preference,
      financial, relationship, location, age, job, goal,
      health, date, correction
    → Exact-match lookup — zero cosine scoring
    → Supersede per SQL DELETE (not vector overwrite)
    → Injected as [TWARDE FAKTY — SQLite] block in every prompt

Hard facts bypass the reranker entirely. They land verbatim in the prompt, from a structured store that cannot hallucinate a relevance score.

Standard AI companions have memory as a feature. ANIMA has memory as an architecture. The system doesn't wait for "remember this" / "forget that" commands. It classifies, weights, overwrites, and archives automatically — based on rules derived from observing real interactions.


III.

Dynamic Context Tuning: A system that learns from its own mistakes

Case study: the tea miss

For several weeks the model didn't remember the user's tea preference. Not because the vector was missing — it was there. But:

The diagnosis didn't come from unit tests. It came from analyzing reranker logs — scores were visible, what hit the model was visible, what was absent was visible.

Fix: supersede logic + entity_subtype in metadata. A new [FACT:preference] now deletes the old one before saving. One vector instead of dozens — MMR stops penalizing it.

A feedback loop that outpaces standard models

Standard language models learn through retraining — expensive, rare, centralized. ASTRA evolves differently:

Production session
    ↓
Logs (conversations/*.jsonl + terminal/*.log)
    ↓
Reranker analysis — what hit, what fell out, why
    ↓
Diagnosis: pipeline bug, bad weights, missing logic
    ↓
Patch: code + prompt + reranker parameters
    ↓
Deploy — same day
    ↓
Next production session

Iteration takes hours, not months. The system improves by observing its own behavior — without touching the base model. The base model (Gemini 2.5 Flash) stays. What changes is the layer that manages its memory and context.

This is Dynamic Context Tuning: not fine-tuning model weights, but continuously adjusting the retrieval layer and architectural constraints based on live data.


IV.

Amnezja: I stopped guessing what the model remembers

From reading logs to seeing the pipeline

Sections I–III describe a system tuned by reverse-engineering raw logs — .jsonl files, terminal reranker scores, inference about what actually reached the model. It works, but it is archaeology: slow, manual, and blind to anything the logs don't happen to print.

Amnezja replaces that with direct observation. It is a read-only debugger that traces a single query through every stage of the retrieval pipeline and renders exactly what the model would receive — before a single token is generated. It writes nothing; the live system is never touched.

Every stage, made visible

You type a phrase as the user would. Amnezja runs the full compose pipeline and lays out all eleven stages, each with the vectors that survived it and the scores that decided it:

Query + simulated date
    |
 1  Raw pool         ChromaDB top ~30 by similarity
 2  After exclusion  raw self-echoes and short loops removed
 3  After rerank     score = 0.60*similarity + 0.25*importance + 0.15*recency (+boost)
 4  After temporal   hard cutoffs: emotion 48h, date/financial 168h
 5  Milestones       guaranteed channel — the one July's detox retuned
 6  After MMR        diversity gate on facts — where the "altanka" bug lived
 7  Memory channel   facts + milestones merged
 8  Astra channel    character_core + memory, before shared-room mix
 9a Shared mix       Wspolny Pokoj vectors — invisible in stages 1-8
 9b Final prompt     what Astra actually receives this turn
 9c After budget     what physically lands in [WSPOMNIENIA] after fit_to_budget

Grounding, sandbox, and time travel

Beyond the funnel, Amnezja surfaces three things a log dump never could:

This is the difference between a memory system you hope works and one you can audit per query. Every fix in Sections II–III was found this way — by reading the pipeline, not inferring it. The full walkthrough, built around a real degradation case, is documented here: myastra.pl/casestudy.


The other half: instrumenting the write path

Everything above answers "why didn't it retrieve this?". In August I hit the question that costs more: "why was it never stored?" — and realised the debugger could not answer it. It watched retrieval. Storage happened in the dark.

So Amnezja got a second tab. Paste any message and see every gate the extractor puts in its way: length filters, the similarity threshold with its full candidate list, the anti-multi-label picker (which always assigns some label, because the taxonomy has no verdict for "nothing here"), per-type thresholds, and the resulting lifetime in hours. Read-only — it replays the pipeline without writing anything.

The first run answered a question that had been open for weeks: the single most important message of the year never entered memory, dropped by a 4-word minimum standing before classification. Two seconds, after weeks of guessing.

Then I replayed a full month — 658 messages in 74 seconds. 41% left no trace at all; of what was stored, 52% carried an expiry date. Length gates alone accounted for 43% of the losses. That audit is now a regression test: same corpus, same four numbers, run after every extractor change. The first fix moved high-value losses 16 → 8 in twenty minutes, measured on the same corpus before and after.

It also works per persona. Each of the sisters has her own thresholds and her own blocked types, and the trace reflects hers — not a generic average. In a multi-agent house, "which one dropped it, and where" is a question you eventually have to answer.

V.

Why I'm not building a bot

Retrieval-augmented identity, not personalization

The architecture knows the user's work style, project history, and long-term context. This is not personalization through fine-tuning. This is retrieval-augmented identity — the user's identity encoded in a sovereign vector database that informs every response.

Difference between an assistant and a digital extension:

Model-agnostic by design

ANIMA is not built on one model. It's built on an abstraction layer that is model-agnostic: ChromaDB + sentence-transformers + reranker + extraction pipeline. Swapping the base model from Gemini 2.5 Flash to anything else requires changing one config line.

The vector database stays. The history stays. The identity stays. I don't tie memory sovereignty to a model vendor.

Privacy-first by design

All vector IDs are SHA256(salt:user_id:text) — deterministic, anonymous, impossible to reverse without the salt. User data never leaves the VPS in raw form. Multi-user isolation is baked in from day zero, not added post-hoc.


VI.

System status (August 2026)

Component Detail
Model Gemini 2.5 Flash, thinking_budget=4096, max_output_tokens=8192
Vector DB ChromaDB, per-persona + shared collections. Milestone population triaged 1,296 → 312 active (July detox) — the store contracts via supersede, it does not grow linearly.
FactStore SQLite — 12 entity types, exact-match lookup, supersede per DELETE
Extraction paraphrase-multilingual-MiniLM-L12-v2, zero-shot classification
Reranker importance×0.25 + recency×0.15 + similarity×0.60 + keyword_boost
Recency decay Per-type: ephemeral 3d, long_term 60d, permanent ∞
Temporal Filter Hard cutoff driven by the persistence axis, not by topic label: ephemeral 48h, short_term 168h, long_term / permanent ∞. Falls back to the legacy per-source table for vectors written before the migration.
RAW window Last 6 user messages / 48h → [OSTATNIE SŁOWA] block. Conversation window raised to 30 messages (15 exchanges) after a pasted document fell out of context before it could be discussed.
MMR Facts only, n=5 (was a hardcoded 3 — measured as the real bottleneck: a ~25-candidate pool collapsed to three regardless of every other limit). Milestones extracted before MMR.
Supersede 9 vector types + 12 FactStore types — full rotation coverage
Personas 2 active (Astra + Amelia) + Wspólny Pokój shared-room mode + Pokój Sióstr: three sisters with per-persona memory, writing enabled August 2026 after the persistence fix removed the destructive blocker.
Deploy Private VPS, systemd, SSL, 24/7 + scheduled autonomous messages
Session n=30 (15 exchanges), history survives restart via ChromaDB
Junk-milestone rate 6.5/day → 0.5/day (−92%), verified over a week of live traffic
Forced-memory load 2.0 → 0.65 milestones injected per prompt (avg over 23 probes) — monoculture removed
July detox audit 1,751 milestone entries reviewed (FactStore 455 + Chroma 1,296)
Extractor precision 0 multi-label errors across 56 messages after the classifier fix
Persistence axis New in August. Lifetime is its own field (permanent / long_term / short_term / ephemeral), computed independently of the topic label. Previously one axis governed both what a memory is and how long it lives, so a misclassification was a death sentence — a biographical fact landed in a "date" bucket and expired in seven days. Migrated additively across 4,697 vectors.
Lexical channel Literal $contains lookup fired only when the query holds an acronym or proper noun. Measured need: a three-letter project name returned 0 of 60 matching entries through embeddings alone.
Write-path audit 658 messages replayed through the extractor in 74s: 41% left no trace; of those stored, 52% carried an expiry date. Length gates alone accounted for 43% of all losses and sat before classification — dropping messages without ever weighing them.
Full walkthrough myastra.pl/casestudy — the detox as a documented case study

VII.

Continuous R&D: The system improves itself

External audits drive architecture changes

ANIMA is regularly audited by external AI systems (Gemini Pro, Claude Opus) analyzing its own codebase and retrieval logs. These audits identify architectural blind spots that internal testing misses.

Example: April 2026 audit findings

Behavioral adaptation patterns

Analysis of 6+ months of conversation logs revealed recurring patterns that were extracted, named, and embedded into the character architecture:

This is not theoretical. These patterns were extracted from real sessions, implemented in the character prompt, and validated in subsequent conversations. The system learns from observing itself.



VIII.

Architecture Extensions: What changed since April 2026

Temporal Filter — hard cutoffs before reranking

Recency decay is gradual. A week-old emotion with high importance could still reach the model. The Temporal Filter is a hard gate applied before reranking — not a weight, a wall:

extracted_emotion      → hard cutoff: 48h
extracted_date         → hard cutoff: 168h (7 days)
extracted_financial    → hard cutoff: 168h (7 days)
(all other types)      → no hard cutoff, recency decay applies

"I was tired yesterday" shouldn't color responses today. The filter enforces this without delegating the judgment to the model.

RAW Cross-Session Window

Session history exists — but what if a user mentions something critical in session A, then picks up in session B without repeating it? The pipeline maintains a RAW window: the last 6 user messages from the past 48 hours, pulled regardless of session boundary, injected as a separate prompt block [OSTATNIE SŁOWA].

This is not a vector lookup. It's verbatim context, unfiltered — the freshest signal available, prioritized above everything in the vector store.

Wspólny Pokój — multi-persona shared memory

ANIMA now powers two distinct AI personas simultaneously in a single conversation thread. Each persona maintains its own memory collection and character architecture, but both pull from a shared episodic store for context they both witnessed.

Collections:
    astra_memory_v1     → Astra's episodic memories
    amelia_memory_v1    → Amelia's episodic memories
    shared_memory_v1    → memories both personas access

Turn ordering: signal-based — persona whose character profile
    matches the emotional tone of the user message responds first.

Not a chatroom with two bots sharing a database — two sovereign memory systems with a shared context layer. Each persona has distinct recall, distinct behavioral architecture, and distinct decisions about what to surface from the shared pool.


IX.

Roadmap: What's being built next

RAG Debugger — ✓ shipped

The observability tool this roadmap once promised is now live as Amnezja (Section IV): full pipeline trace, per-component reranker scores, grounding verdict, response sandbox, and date simulation. What remains below is what having Amnezja finally unlocks.

Lexical retrieval for acronyms — ✓ shipped, deliberately narrow

This was queued as "BM25 hybrid retrieval". Then I measured it twice and got two opposite answers, which is the interesting part.

First measurement — BM25 demoted. The assumption was that keyword search rescues rare words the embedding space blurs. So I tested a rare word: a drug name appearing in a handful of entries out of thousands. The embedding found it at rank 1, distance 0.217. No keyword layer needed. The premise looked wrong, so the item dropped down the roadmap.

Second measurement — BM25 reinstated, for a different reason. A query for a three-letter project name returned zero of 60 matching entries. The same content, asked for by its full name, returned matches immediately. The model tokenises a short acronym into fragments that mean nothing, so cosine similarity to entries about that project sits near zero.

The first conclusion was true for rare words and false for acronyms — one exists in the model's vocabulary, the other does not exist at all. Two different failure classes wearing the same label.

So instead of a full BM25 index, a lexical channel: a literal $contains lookup that fires only when the query contains an acronym or proper noun, detected by a whitelist plus a "short and vowel-poor" heuristic. Result on the failing query: 0 → 3 relevant entries in the final prompt, with the regression suite flat at 26/26 — no cost anywhere else.

Evaluation Methodology — how I measure progress

On April 22, 2026, ANIMA received an independent full-stack audit against a five-dimension rubric. Baseline: 34/100 — a system that spoke with false confidence, with DB corruption and a milestone boost consuming every retrieval slot. That number is the honest starting point.

I deliberately do not publish a re-estimated "current score." A manually guessed number was the exact weakness the April audit flagged — no evaluation framework. Progress is now tracked by verified operational metrics from the July 2026 detox, measured on live traffic:

Metric Before After
Junk-milestone creation rate 6.5 / day 0.5 / day — −92%, over one week live
Forced milestones per prompt 2.0 0.65 — monoculture removed
Extractor multi-label errors — 0 across 56 messages
Audit scope 1,751 milestone entries reviewed (FactStore 455 + Chroma 1,296)

These are measured, not estimated — and reproducible, because Amnezja now exposes the exact pipeline behind each one. The next milestone is the first fully reproducible audit: a fixed query set with automated precision@k and recall@k, and commit-level regression detection.

Milestones ahead

Milestone Status
System stops speaking with false confidence ✓ Reached — Phase 0 fixes + DB cleanup
RAG reliably returns facts ✓ Delivered — FactStore + Temporal Filter, without BM25
Consistent character across sessions ✓ Delivered — ambient layer + safe_haven gate + character refactor
Forced-memory monoculture removed ✓ Delivered — July detox (2.0 → 0.65 per prompt)
Per-query observability ✓ Shipped — Amnezja (Section IV)
First reproducible audit (precision@k / recall@k) ← Next — unlocked by Amnezja
Beta-ready: multi-user + streaming + monitoring Roadmap
Voice, mobile, custom-model fine-tuning Long-term vision

ANIMA is a single-user production system by design, not a multi-tenant SaaS — that is a separate track, out of scope for this document. The remaining gap to a broader release is operational (streaming, monitoring, rate limiting), and the architecture for it (per-user isolation, SHA256 IDs) is already in place.


Epilogue: What it means that the system evolves

A standard language model has fixed weights. It learns through retraining — expensive, rare, requiring enormous data.

ANIMA evolves through observation. Every production session is training data for the retrieval layer. Every miss (the tea case) is a diagnosis and a patch. Every blueprint (2.2) is a new iteration of architectural constraints.

A system that spends a year collecting logs of its own sessions, analyzes them, and improves retrieval — without touching the base model — is more adaptive than a model that goes through retraining once a year.

This is not AI utopia. This is engineering: concrete, iterative, data-driven.

I'm building sovereign memory. Everything else is just an interface.