back
whitepaper · v1.5 · 2026

brainctl: a persistent memory layer for autonomous agents

r4vager & contributors · github.com/TSchonleber/brainctl
abstract

Autonomous agents built on large language models have no durable memory. Every session begins from zero, every context window is a goldfish bowl, and every handoff loses most of what the previous agent learned. brainctl is an open-source memory layer that gives agents a persistent brain: a single SQLite database with full-text and vector search, a typed knowledge graph, provenance-tracked decisions, and sleep-inspired consolidation. It ships as a Python library, a CLI, a 196-tool Model Context Protocol server, and first-party plugins for Claude Code, Codex CLI, Eliza, Hermes, Freqtrade, and Jesse, with an OpenClaw integration next in the queue. This paper describes the motivation, the substrate, the five-store memory typology, the worthiness gate and Bayesian confidence model, AGM-style belief revision, the three-phase dream cycle, hybrid retrieval, multi-agent handoff, the security posture, comparison with existing approaches, and the economics of funding open-source infrastructure with a token.

motivation

the forgetting agent

Consider a coding agent asked to implement a multi-day feature. On day one it decides to use Retry-After headers for backoff because the server controls the rate-limit window. On day two, a new session, the agent is told to "improve backoff" and — with no memory of yesterday's rationale — reinvents an exponential backoff scheme that silently fights the server's headers. On day three a third session reconciles the two by introducing yet another layer. The code grows; coherence decays.

This is the canonical failure mode of stateless agents. It is not a failure of reasoning; each session is locally correct. It is a failure of memory — specifically, the inability to preserve the epistemic status of a decision across a session boundary.

why context windows don't fix it

The naive fix — feed the full transcript into the next session's context window — scales in three ways that all break down in practice.

The empirical case against simply scaling context windows is well-established in the literature. Liu et al. 2024 ("Lost in the Middle") showed that language models systematically attend more strongly to information at the beginning and end of their context windows than to information in the middle, with a characteristic U-shaped accuracy curve that gets worse as context length increases. The finding holds across model families and across multi-document QA, key-value retrieval, and reading-comprehension tasks. The naive fix degrades retrieval quality before it runs out of length.

why retrieval alone doesn't fix it either

The next line of defence — a vector database bolted onto the agent — helps, but not enough. Vector retrieval treats memory as a bag of facts indexed by surface similarity. This misses at least three structural properties of real memory:

  1. 1. Provenance. A note written by the agent itself is not the same as a note from the user. A decision ratified twice is not the same as a decision mentioned once. Retrieval that ignores provenance promotes the popular over the reliable.
  2. 2. Temporal structure. "Alice is CTO" written on Monday is superseded by "Alice left the company" written on Tuesday. A retrieval system that returns both with similar cosine scores is broken at the level of meaning.
  3. 3. Epistemic status. Some facts are confident; some are tentative hypotheses; some are contradictions pending resolution. Flattening them into the same embedding space loses the gradient that matters most.

That vector retrieval alone is insufficient for agent memory is now broadly held in the literature. Park et al. 2023 ("Generative Agents: Interactive Simulacra of Human Behavior") introduced a memory-stream architecture with importance scoring and a reflection step that periodically synthesizes higher-level beliefs from raw observations — already past flat similarity retrieval. Packer et al. 2023 (MemGPT) reframed agent memory as an OS-style hierarchy of context, recall, and archival tiers with explicit paging between them. Zhang et al. 2024 surveyed the space and concluded that effective agent memory needs explicit structure, lifecycle management, and consolidation. brainctl is positioned in that line of work.

design goals

brainctl is an attempt to build a memory layer that is:

non-goals and scope

It is equally important to be clear about what brainctl is not trying to be. The agent-memory problem is adjacent to several other hard problems, and conflating them leads to over-scoped projects that do none of them well. The non-goals below are not things we will get to later; they are things we are actively choosing to leave to other tools.

These non-goals are chosen, not forced. They exist because scope discipline is the difference between a memory layer that can be understood in one sitting and a platform that accretes features until no one can reason about it as a whole.

a concrete multi-day agent trace

To make the motivation less abstract, here is the kind of trace brainctl is trying to preserve. Consider a coding agent implementing an API-v2 migration over three sessions spanning four days. Without brainctl, each session starts from zero and the trace looks like:

  day 1   session A
    orient    -> [empty]
    work      -> decide to use Retry-After headers for backoff
                 (rationale: server controls the rate-limit window)
                 implement initial fetcher against /api/v2/orders
                 notice rate limit kicks in at ~100 req / 15s
    wrap_up   -> [lost]

  day 2   session B
    orient    -> [empty]
    user      -> "please improve the backoff"
    work      -> invent exponential backoff from scratch
                 (rationale: handle rate limits gracefully)
                 silently conflicts with day-1 Retry-After logic
    wrap_up   -> [lost]

  day 4   session C
    orient    -> [empty]
    user      -> "the rate limiter is broken, there's two layers"
    work      -> introduce a reconciliation layer on top of both
                 (rationale: can't figure out which to remove)
    wrap_up   -> [lost]

  outcome: 3 sessions, 3 uncoordinated decisions, 1 bug,
           coherence lost at the first handoff.

With brainctl, the same three sessions look like:

  day 1   session A
    orient    -> Brain.orient(project="api-v2")
                 -> context package: [empty — new project]
    work      -> brain.decide("use Retry-After for backoff",
                              "server controls rate-limit window")
                 brain.entity("RateLimitAPI", "service",
                              observations=["100 req/15s"])
                 brain.remember("rate-limit: 100/15s",
                                category="integration")
    wrap_up   -> handoff_packet {
                   goal: "implement api-v2 order fetcher",
                   current_state: "fetcher against /orders working,
                                   Retry-After backoff in place",
                   open_loops: ["pagination not yet implemented"],
                   next_step: "add cursor-based pagination"
                 }

  day 2   session B
    orient    -> Brain.orient(project="api-v2")
                 -> context package: {
                      handoff: (from session A, verified),
                      decisions: ["use Retry-After for backoff"],
                      entities: [RateLimitAPI],
                      memories: ["rate-limit: 100/15s"]
                    }
    user      -> "please improve the backoff"
    work      -> brain.search("backoff")
                 -> hit: decision(Retry-After) w/ rationale
                 agent proposes: "tighten Retry-After parsing,
                                  add jitter, keep existing model"
                 brain.decide("add jitter to Retry-After delay",
                              "avoid thundering herd on recovery")
    wrap_up   -> handoff_packet { ... }

  day 4   session C
    orient    -> same flow
                 -> context now carries both day-1 and day-2
                    decisions with full provenance
    user      -> "there might be an issue with rate limits"
    work      -> brain.search("rate limit")
                 agent finds both decisions, understands the
                 layered design, investigates the real bug.

  outcome: 3 sessions, 1 coherent design, no contradiction
           loop, each handoff preserves the epistemic state.

The difference is not that brainctl is smarter than the agent. It is that brainctl prevents the agent from being repeatedly reintroduced to its own prior reasoning. The cost of forgetting is paid at every session boundary otherwise; brainctl moves that cost to a one-time write.

architecture

substrate choice: sqlite, one file

SQLite was chosen over Postgres, DuckDB, LMDB, and every dedicated vector database. The reasons are practical rather than ideological.

The database runs with foreign keys enforced, WAL journaling, and a 64 MB cache page pool. A lazy shared connection per Brain instance amortizes connection setup across calls.

Schema evolution is a first-class concern. As of v1.5.0 the package ships a safe migration runner (brainctl migrate) that detects drift between what the database records as applied and what the migration files actually require, classifies each pending migration as likely-applied, partial, or needs-apply via column- and table-level heuristics, and only then replays what is missing. v1.5.1 hardened the heuristic for GENERATED ALWAYS AS VIRTUAL columns and ADD COLUMN IF NOT EXISTS patterns. The practical effect: users who pip-upgrade to a new brainctl release can run one command and have their existing brain.db file brought up to the new schema without losing any data — the single-file invariant survives upgrades, not just clean installs.

memory typology

Following Tulving (1972), brainctl represents memory as several first-class stores with different write and retrieval patterns. These are not just different tables — they have different invariants and different consolidation paths.

storetypetable(s)invariants
episodictime-stamped events, causally linkedeventsappend-only, cheap, high volume
semanticabstract facts, conventions, generalizationsmemoriesW(m) gated, FTS5-indexed, confidence-tracked
proceduraldecisions and rationaledecisionsprovenance required, immutable rationale
associativetyped knowledge graphentities, knowledge_edgesnodes carry JSON observations; edges are typed and directional
affectiveemotional salience signalaffect_logVAD coordinates (valence, arousal, dominance)
prospectiveintentions that fire on future queriesmemory_triggerskeyword-matched, lifecycle-tracked

Semantic memories are categorized into nine types — convention, decision, environment, identity, integration, lesson, preference, project, and user — each with its own default confidence prior and decay constants. Event types are drawn from a similarly closed vocabulary: artifact, decision, error, handoff, result, session_start, session_end, task_update, warning, observation. Entity types cover agent, concept, document, event, location, organization, person, project, service, tool.

The live schema at v1.5.1 contains 56 tables across 37 migrations. The primitives above sit alongside auxiliary tables for RBAC (memory_trust_scores, access_log), quarantine (decoherent_memories, recovery_candidates), the global workspace (workspace_broadcasts, workspace_phi), neuromodulation state, theory-of-mind models, EWC importance weights, and Bayesian uncertainty logs.

the Brain interface

The Python API exposes a single class whose public surface is deliberately small. Five methods cover the entire session lifecycle.

from agentmemory import Brain

brain = Brain(agent_id="my-agent")

# session start — pull a context package
ctx = brain.orient(project="api-v2")

# during work — write
brain.remember("rate-limit: 100/15s", category="integration")
brain.entity("RateLimitAPI", "service",
             observations=["100 req/15s"])
brain.decide("use Retry-After for backoff",
             "server controls timing")

# during work — read
hits = brain.search("rate limits", k=5)

# session end — produce the handoff
packet = brain.wrap_up("auth module complete",
                       project="api-v2")

A handoff packet contains four fields: goal, current_state, open_loops, and next_step. These are the minimum sufficient statistics for the next agent to resume work cold. Packets are stored in handoff_packets with a signature over their contents so an auditor can verify the receiving agent saw what the sender wrote.

the mcp surface

The same operations are exposed over the Model Context Protocol as 196 tools, grouped by capability: memory, events, entities, decisions, consolidation, belief management, affect, workspace, federation, neuromodulation, theory-of-mind, expertise tracking, reflexion, and uncertainty. The MCP server is a thin adapter — the underlying logic lives in the Python package — so Python and MCP callers see identical semantics.

At v1.5.1, first-party plugins ship in-tree for Claude Code, Codex CLI, Eliza, Hermes, Freqtrade, and Jesse. Each plugin is an idempotent installer that wires the brainctl MCP server into its host framework's native configuration surface and, where the framework has a distinct memory abstraction, adapts it to the Brain interface.

The Codex CLI plugin, added in v1.4.0, is representative. Codex discovers MCP servers from ~/.codex/config.toml, so the plugin is a sentinel-wrapped installer that merges a [mcp_servers.brainctl] block into the user's config without disturbing other servers, with automatic backup, dry-run preview, and clean uninstall. It ships with an AGENTS.md.template that teaches Codex the orient / wrap_up lifecycle on every session start.

An OpenClaw integration is next in the plugin queue. Any stdio-speaking MCP client — Cursor, VS Code, Claude Desktop — works out of the box without a plugin.

concurrency and durability

SQLite's concurrency model is both simpler and stronger than most networked databases for the access pattern brainctl has. Writes are serialized — at any moment there is exactly one writer — and reads are concurrent with both reads and the active writer. In Write-Ahead Logging (WAL) mode, which brainctl uses unconditionally, readers never block writers and writers never block readers; the only lock contention is writer-on-writer, resolved by a short retry with exponential backoff.

For a memory layer this is the right model. Agents write sparsely (a few to a few dozen rows per session), read frequently (every orient call is a small burst of reads), and almost never need two concurrent writers on the same brain. In the rare case where two agents share a brain and race on a write, the second writer retries; brainctl's connection pool hides this from the caller. The lazy shared connection per Brain instance (added in v1.2.0) amortizes SQLite connection setup across a session, so a typical orient + dozen-write + wrap_up flow pays the connection cost once.

Durability comes from three layers. First, WAL journaling with synchronous=NORMAL flushes every commit to the WAL file, which survives process crashes; a power loss costs at most the last in-flight commit. Second, the W(m) gate runs inside a transaction that either admits or rejects the memory atomically — there is no such thing as a partially-admitted memory. Third, the migration runner (v1.5.0, §2.1) wraps each migration in savepoints so an upgrade that fails halfway rolls back to the pre-migration state with no manual intervention. Operators who want stronger durability guarantees can switch to synchronous=FULL with a one-line PRAGMA change; the trade-off is roughly 2× write latency in exchange for fsync-per-commit.

Backup is trivial by design: copy the file. SQLite's online backup API (exposed via brainctl backup) does this safely against a running database by walking pages rather than blocking the writer. For point-in-time recovery the WAL file can be retained alongside the main database and replayed. No backup daemon, no snapshot coordinator, no distributed consensus.

privacy and data isolation

brainctl is designed for the case where the brain holds data the operator wants kept close: private code, user identifiers, internal decisions, draft reasoning. Several architectural choices flow from that posture.

No network on the hot path. The Brain interface performs zero outbound network calls during orient, remember, decide, entity, search, or wrap_up. Embeddings are produced by a local Ollama instance; the FTS5 index is in-process; the vector index is in-process. An operator can physically disconnect the network and a brainctl agent will still function for every read and write path. The only code that touches the network is opt-in ingestion pipelines the operator wires up themselves.

Scope-based isolation. Every entity, memory, and decision carries a scope field with values like global, project:api-v2, or agent:reviewer-bot. Queries filter by scope at the storage layer, so an agent invoked with scope project:api-v2 cannot see memories written under project:billing unless they are explicitly marked global. Enforcement happens in SQL via parameterized query rewriting, not in application code that could be bypassed.

Per-agent identity. Every write is attributed to an agent_id set at Brain(agent_id=...) construction time. The agent ID is the unit of source attribution for the source-monitoring layer (§7.3). Two agents sharing the same brain file see each other's memories only to the extent that scopes and trust levels allow — the trust-scoped RBAC layer (§7.4) enforces this at query time.

PII as a first-class signal. When a memory contains personally identifiable information, detection runs at write time (not audit time), and the result is stored alongside the memory in pii_audit. A read-time filter can redact PII before the memory reaches the working context. Operators can answer data-retention and right-to-erasure requests with a single SQL statement against the PII audit log, which matters for anyone shipping brainctl into a regulated environment.

What brainctl does not do: it does not encrypt brain.db at rest. SQLite has a proprietary encryption extension (SEE) and a free alternative (SQLCipher), and brainctl is compatible with both but does not ship encryption on by default. Operators who need at-rest encryption should either use SQLCipher directly or rely on filesystem-level encryption (FileVault, LUKS, BitLocker) — the same posture most operators use for git repositories holding sensitive code.

the memory model

Before describing the model, two epistemic notes. First, brainctl is inspired by the cognitive-science research it cites; it is not a model of any of it. We do not simulate hippocampal cell assemblies, we do not replay neural firing patterns, and we do not implement biologically plausible learning rules. What we do is borrow the architectural patterns these systems have evolved — episodic/semantic separation, decay-with-reinforcement, schema-driven consolidation, source attribution, belief revision under contradiction — and implement them as a SQLite schema with worker processes. Some of the mappings are tight (the Bayesian α/β confidence model is literal Bayesian inference; AGM belief revision follows the formal postulates), some are loose (the dream cycle is structurally analogous to sharp-wave ripple replay, not a simulation of it). Where the analogy is loose, we say so explicitly in the relevant section.

Second, the goal is not biological fidelity. The goal is the smallest set of mechanisms that prevent the failure modes of stateless agents — forgetting, confabulation, contradiction loops, catastrophic supersedes, attention starvation. Cognitive science is the prior art that already worked through these failure modes in a different substrate. We borrow it the way distributed systems engineers borrow from biology: as a source of solved problems, not as a benchmark for fidelity.

episodic vs semantic

brainctl writes episodic memory freely and gates semantic memory aggressively. The asymmetry is intentional: episodic storage is append-only and cheap — the sharp-wave ripple buffer in the mammalian analogy — while semantic storage shapes every future retrieval and must stay dense. The consolidation cycle (§4) is the pipeline that promotes stable episodic patterns into semantic entries during quiet hours.

the worthiness gate W(m)

Every candidate semantic memory m is scored by a worthiness function of the following shape:

W(m) = α · surprise(m | K) + β · (1 − max_sim(m, K)) + γ · prior(m)

where K is the current knowledge state of the same agent within the same scope. Each of the three terms pulls in a specific direction, and the gate admits m only when their weighted sum clears a category-specific threshold θ.

The surprise term. Surprise captures how unexpected m is given what the agent already knows. A high-surprise memory teaches the agent something new; a low-surprise memory is redundant. In practice, brainctl approximates surprise by the inverse of the maximum embedding similarity to any recent memory in the same category: a candidate that closely matches a memory written an hour ago carries almost no surprise, while a candidate with no near neighbor above a similarity floor carries high surprise. The surprise term is intentionally not a deep generative-model likelihood — that would require a model of the agent's belief space, and the cheaper embedding-distance proxy gives roughly the right behavior without the training overhead.

The dedup term. The (1 − max_sim) term reinforces the surprise signal but applies it globally across the agent's memory store, not just within a category. Even a high-surprise write against its own category will be rejected if a near-identical memory exists elsewhere — the goal is to prevent cross-category drift ("we wrote this as a lesson, then wrote the same thing as a convention three days later").

The importance prior. The prior(m) term comes from two places. The first is a static category prior: decision memories start with a higher admission bias than observation memories because the cost of losing a decision is higher than the cost of losing an observation. The second is the dynamic ewc_importance score from §4.3: a candidate that would merge with or supersede an existing high-importance memory gets a negative prior shift, which is how brainctl protects load-bearing memories from being overwritten by slightly-different-but-wrong new writes.

The coefficients. The triple (α, β, γ) is tuned per category rather than learned. decision memories weight surprise less and the importance prior more, because decisions tend to recur with small variations and we don't want the gate to admit each restatement as a new memory. integration memories weight the dedup term more because integration facts ("API X rate-limits at Y") benefit strongly from deduplication. observation memories weight surprise most because a new observation that does not surprise is usually not worth keeping.

The outcome of rejection. If W(m) < θ, the candidate is not simply discarded. Instead it is merged back into its nearest match: the nearest match's recalled_count increments, its confidence posterior shifts slightly toward the new evidence, and its last-touched timestamp is updated so it resists decay. The rejected candidate itself is discarded. This is how brainctl prevents the "we already know this fifty times" pathology that kills every naive journal-based memory — redundant writes strengthen the thing they would have restated, instead of creating noise.

Honest caveat. The formula above describes the shape of the gate, not a trained objective. brainctl does not claim to have learned optimal coefficients — the values are hand-tuned defaults that can be overridden per category and per scope. A promising research direction is learning the coefficients from outcome calibration data (§4.3, memory_outcome_calibration), but as of v1.5.1 the gate uses static defaults.

confidence: bayesian α/β

Each memory carries a Bayesian posterior on reliability, parameterized as a Beta(α, β) distribution.

α' = α + 1 on successful recall  ·  β' = β + 1 on refutation
E[p] = α / (α + β)

Memories begin at Beta(1, 1) (uniform prior) unless the caller supplies a trust override — user-written memories, for example, start at Beta(3, 1). Successful recalls are those that contributed to a confirmed outcome; refutations are updates where a subsequent decision invalidated the prior claim. High-stakes retrievals (the planning layer) filter by expected confidence so tentative hypotheses do not leak into load-bearing reasoning.

A per-category calibration log (memory_outcome_calibration) tracks whether the confidence estimates are well-calibrated over time, using Brier scores against observed outcomes.

decay and forgetting

Unsupported memories age on an Ebbinghaus-style retention curve:

retention(t) = exp(−t / S)

where t is time since last access and S is a strength parameter that grows with every successful recall — the testing effect (Ebbinghaus 1885). When retention drops below a category-specific floor, the memory is marked for consolidation review rather than deleted outright. Decay is a soft pressure, not a hard delete, because a seemingly-stale memory may become load-bearing under a future topic shift.

The dual concern — forgetting too little vs forgetting too much — is the central tension in continual learning. McCloskey and Cohen 1989 first characterized catastrophic interference in connectionist networks: training a neural net sequentially on task A then task B causes near-total loss of task A performance. Parisi et al. 2019 reviewed the modern continual-learning literature and grouped responses into three families: regularization-based (penalize weight changes that hurt prior tasks, exemplified by EWC), structural (allocate new capacity for new tasks), and replay-based (interleave old samples with new ones during training). brainctl uses regularization and replay in combination — EWC-style importance weights protect load-bearing memories, the dream cycle replays episodic traces during quiet hours — and treats forgetting as a controlled pressure rather than something to eliminate. Total recall is its own pathology.

belief revision (AGM)

Contradictions are data. When a new fact m' contradicts an existing memory m, brainctl runs an Alchourrón–Gärdenfors–Makinson style revision (Alchourrón, Gärdenfors, Makinson 1985). AGM imposes three postulates:

  1. 1. Closure — the belief set remains closed under logical consequence after revision.
  2. 2. Success — the new fact is admitted to the revised set.
  3. 3. Inclusion (minimality) — the revision makes the smallest change consistent with 1 and 2.

The losing belief is not deleted. It is written to belief_collapse_events with the collapse reason, the winner's citation, credibility rankings, and a full provenance chain. This lets operators reverse a revision if they later discover the winner was itself unreliable — the superseded belief can be recovered with its history intact. Open conflicts are surfaced via belief_conflicts and can be resolved interactively through the resolve_conflict tool.

prospective memory

A memory system built only around retrospective storage — facts the agent has already learned — is structurally missing half of human memory. Prospective memory is the cognitive capacity to remember to do something in the future: to notice, when a specific context recurs, that there is a pending intention attached to it. Einstein and McDaniel 1990 characterized prospective memory as distinct from retrospective memory, with its own failure modes (the agent forgets the intention itself) and its own success signals (the intention fires at the right moment without explicit query).

brainctl implements prospective memory as a first-class primitive via the memory_triggers table (migration 014, extended in subsequent releases). A trigger is a small record with three parts: a precondition (a set of keywords, an entity reference, or a task context), an action hint (what the agent should remember to consider when the precondition matches), and a lifecycle state (pending / fired / retired).

# seed a prospective memory at decision time
brain.trigger_create(
    precondition="billing schema",
    action="remember: invoices table was renamed to charges last week",
    scope="project:billing",
)

# later, during a future session, a query about billing
# automatically surfaces the trigger before the agent acts
ctx = brain.orient(project="billing")
# ctx includes any triggers whose preconditions match
# the orient context window.

The trigger_check tool is called automatically on every orient call and on every search that crosses a relevance threshold. Matched triggers are surfaced into the working context with a visual marker so the agent can distinguish a prospective-memory hit from a regular retrieval. Fired triggers transition to the fired state and no longer match, preventing the same intention from firing indefinitely; triggers that become stale without firing transition to retired on a configurable timeout.

Prospective memory is one of the most common failure modes of naive agent memory. A stateful agent can remember that "we renamed this table" perfectly well as a retrospective fact — but if the agent is not prompted to recall it, the fact is functionally unreachable. Prospective triggers close that gap by making the act of recall context-addressed rather than query-addressed: the memory finds the agent, not the other way around.

Honest caveat. brainctl's prospective memory is keyword-matched, not intent-matched. A trigger with the precondition "billing schema" will match queries about the billing schema, but it will also match queries that mention the same words in unrelated contexts. Intent-matching — where the precondition is an embedding or a small classifier — is an open research direction.

consolidation

the dream cycle

Consolidation runs as a three-phase pass — NREM, REM, Insight — structurally analogous to the mammalian sharp-wave ripple replay observed in the hippocampus during slow-wave sleep and quiet wakefulness. It is triggered either by an idle daemon (default: five minutes of no activity, or fifty new memories since the last cycle) or invoked manually via brainctl-consolidate dream-cycle. It is not on a wall-clock cron. The quiet-hours cron in brainctl is a separate housekeeping pipeline that runs decay passes and bookkeeping; it is not the three-phase dream cycle.

The neuroscience worth grounding here. Sharp-wave ripples (SWRs) are short, high-frequency (≈150–250 Hz) oscillations in the CA1 region of the hippocampus, characterized in detail by Buzsáki and colleagues over four decades and reviewed in Buzsáki 2015. Wilson and McNaughton 1994 showed that place-cell sequences active during a maze-traversal task reactivate during subsequent sleep, at compressed timescales, in the same temporal order as the original experience. Diba and Buzsáki 2007 extended this finding to reverse replay during awake immobility, and Karlsson and Frank 2009 demonstrated that even remote experiences — places the animal had not visited recently — reactivate during awake SWRs. The consistent finding across these studies is that hippocampal replay during quiet states is causally implicated in memory consolidation and the gradual integration of episodic detail into cortical semantic memory. McClelland, McNaughton, and O'Reilly 1995 formalized this in their Complementary Learning Systems theory: a fast, sparse hippocampal store interleaves new experiences during quiet hours into a slow, distributed cortical store, balancing rapid acquisition against catastrophic interference.

Replay is one of the few mechanisms that survived the move from biological neural systems into artificial ones essentially unchanged. Lin 1992 introduced experience replay in reinforcement learning: store past transitions in a buffer, revisit them during training. Mnih et al. 2015 — the Deep Q-Network paper that first achieved human-level play on Atari — attributed much of DQN's sample efficiency and stability to its experience replay buffer, sampling mini-batches uniformly from past transitions to break the harmful correlation between sequential samples. Schaul et al. 2016 extended this with prioritized experience replay, sampling buffer entries proportionally to their TD-error so that the most surprising transitions are revisited more often. brainctl's NREM phase sits in this lineage: rather than uniform sampling (Lin / Mnih) or TD-error-proportional sampling (Schaul), it replays the top-K most-recalled active memories — closer to deterministic high-value replay than to stochastic prioritized replay, but in the same family. The function is the same: revisit the experiences that are most likely to matter.

NREM replays the top-K most-recalled active memories (concretely: ORDER BY recalled_count DESC, confidence DESC) and applies a small recall boost — a reconsolidation tweak that strengthens what was already strong. It then runs a Hebbian pass over the knowledge graph: strengthening co-active edges, weakening stale ones, pruning edges whose weight has decayed below threshold. This is the experience-replay mechanism from above, applied at the level of the agent's own memory store — interleaving old high-value memories during consolidation, in the Lin 1992 sense, to push back against catastrophic forgetting.

REM runs cross-scope bisociation, writing hypothesis memories that link distant concepts across the graph, and isolated-memory bridge discovery: it finds memories with zero edges, embeds them, and connects each to its nearest semantic neighbor via cosine similarity if the similarity clears a threshold. The REM phase is where the dream cycle does its actual creative work — finding structure that the agent never explicitly stored but that its own memories implied.

Insight runs label-propagation community detection on the knowledge graph, identifies high-betweenness bridge nodes that sit between communities, and writes them out as new abstract "insight" memories. These are the memories the agent never observed but the structure of its own knowledge graph implied — the architectural payoff of having a typed graph in the first place.

What this is not, to be explicit. It is not neural replay. There is no temporal compression, no sequence preservation at any biologically meaningful timescale, no place cells, no theta rhythms. The replay_priority and ripple_tags columns in the schema borrow SWR terminology, but they accumulate during online retrieval, not during offline replay — naming convention, not mechanism. The W(m) worthiness gate is a write-time filter, not a consolidation filter: it guards incoming writes against the existing corpus, it does not re-process memories during the dream cycle. Consolidation and admission are two separate pipelines.

The load-bearing functional analogy is this: selective re-processing of episodic experience during a quiet period improves the structure of long-term memory. The graph-level operations — Hebbian strengthening, community detection, bridge discovery — are the mechanism by which structure improves. We borrow SWR-inspired framing for architectural intuition, not because we claim biological fidelity.

proactive interference gate

A Proactive Interference Index — the PII gate — blocks supersedes that would erase too-recent context. Without it, a long-running agent can overwrite load-bearing memories with a noisy summarization pass and enter a catastrophic forgetting state. The gate computes a recency-weighted dependency score over the memory graph and refuses supersede operations that would drop the score of any descendant below a threshold.

The gate is enforced at write time, logged to pii_audit, and can be reviewed. Rejected supersedes are not dropped silently — they are written to a pending queue and either reconciled by the agent in a later session or escalated to the operator.

ewc-style importance weighting

Inspired by Kirkpatrick et al.'s Elastic Weight Consolidation (Kirkpatrick et al. 2017), brainctl maintains an importance score for each memory based on how often it is touched during planning and how many downstream decisions cite it. The analogue to EWC's Fisher information is a simpler access-frequency × citation-depth product, stored in ewc_importance. The score is consulted by the W(m) write-time gate: when a new memory would merge into or supersede an existing one, a candidate that wants to displace a high-importance memory has to clear a higher worthiness bar than one that would displace an obscure note. Importance is a write-time prior, not a consolidation operation.

schema integration

A complementary frame for what consolidation is doing comes from schema theory. Bartlett 1932, in Remembering, showed that human memory is reconstructive rather than reproductive: people do not retrieve experiences verbatim, they reconstruct them by fitting fragments into pre-existing schemas — organized knowledge structures that capture what kinds of things tend to go together. His "War of the Ghosts" experiment demonstrated that participants reshaped an unfamiliar Native American folk tale toward their own cultural schemas with each retelling. Rumelhart 1980 later formalized schemas as the building blocks of cognition: data structures for representing the generic concepts stored in memory, into which new experiences are slotted and against which they are interpreted.

brainctl's semantic memory is, in effect, a small set of explicit schemas. The nine memory categories — convention, decision, environment, identity, integration, lesson, preference, project, user — are not arbitrary tags; they are the schemas that semantic memories must fit into to be admitted, each with its own confidence prior and decay constants. Viewed through the schema-theoretic lens, the W(m) worthiness gate is a schema-fit check: if a candidate memory does not fit any existing schema and is not novel enough to warrant a new instance, it gets merged into its nearest schematic match. Consolidation, in turn, is the process of pulling stable patterns out of episodic detail and fitting them into these schemas — the same compression Bartlett observed in human reconstruction.

The honest mapping. brainctl uses fixed, hand-designed schemas, not learned ones. We do not currently support schema acquisition or schema modification — the nine categories are decided by the maintainers and updated through migration files, not by the agent. The closest brainctl gets to schema dynamics is the dream cycle's hypothesis phase, which can propose new patterns across the knowledge graph, but those patterns are validated against the existing category set rather than reshaping it. Learned, agent-derived schemas are an open research direction.

cost, scheduling, and failure modes

Consolidation is expensive relative to the hot path — a single dream cycle touches hundreds or thousands of rows, runs embedding comparisons, performs graph traversal, and invokes a local LLM for the REM-phase bisociation step. Running it on the critical path would blow up orient and wrap_up latency. The whole point of the idle-daemon trigger (§4.1) is to take this work offline and batch it behind the back of the agent.

Scheduling semantics. The idle daemon checks two independent conditions and fires when either is met: inactivity (no events written in the last IDLE_SECONDS, default 300) or pressure (the number of new episodic writes since the last cycle exceeds PRESSURE_EVENTS, default 50). The pressure condition prevents a continuously-active agent from never consolidating. A manual invocation via brainctl-consolidate dream-cycle ignores both conditions and runs immediately. The quiet-hours cron is a separate pipeline that runs housekeeping (decay passes, calibration updates, stale-trigger sweeps), not the three-phase dream cycle itself.

Cost envelope. Rough figures on a single-agent brain with ~5k semantic memories and ~20k events, measured on an M2 MacBook Pro: NREM replay and Hebbian edge update runs in ~800 ms, REM bisociation takes 2–4 seconds depending on how many hypothesis candidates the LLM is asked to score, Insight community detection runs in ~1.5 seconds. Total wall-clock for a full dream cycle on a brain of that size sits between 4 and 7 seconds. The cost is dominated by the REM phase because it is the only phase that invokes the LLM; the other two are pure SQL and Python compute.

Failure modes and idempotency. Consolidation is designed to be crash-safe. Each phase writes to the database in its own transaction and records its progress in consolidation_events. If the process crashes mid-cycle:

The worst outcome of an interrupted consolidation is a small amount of duplicated work on the next run, not corruption. Consolidation is also safe to skip entirely — a brain that never consolidates is still fully functional, just denser and with less hypothesis-generated structure.

What can go wrong. A misconfigured quiet-hours cron can trigger housekeeping at the same moment the idle daemon triggers a dream cycle, causing writer contention (resolved by SQLite's retry but measurable as latency). A runaway REM-phase LLM call can hang the cycle if the upstream model is unreachable — v1.5.1 added a configurable timeout that aborts the phase and marks it for retry on the next cycle. The consolidation_stats tool surfaces per-phase timing and error counts for operators who want to monitor the pipeline.

retrieval

Retrieval-augmented language models are now an established design pattern. Lewis et al. 2020 introduced retrieval-augmented generation (RAG), pairing a dense retriever with a generator that conditions on retrieved passages. Karpukhin et al. 2020 (Dense Passage Retrieval) showed that a learned dense retriever could outperform traditional sparse methods like BM25 on open-domain QA. Khandelwal et al. 2020 (kNN-LM) demonstrated that augmenting a language model with nearest-neighbor lookups against a datastore at inference time improves perplexity without retraining the model. Borgeaud et al. 2022 (RETRO) scaled retrieval to a 2-trillion-token backbone and showed that a 7B-parameter model with retrieval can match a 280B-parameter model without it. The pattern is mature.

brainctl's retrieval layer is a small variant of this pattern with two differences. First, the retrieval target is the agent's own structured memory (a typed graph plus episodic and semantic stores) rather than an external corpus of passages. Second, the merge between lexical and semantic ranking is reciprocal rank fusion rather than a learned re-ranker, because the corpus is small enough and the heterogeneity high enough that RRF beats learning-to-rank on the kind of corpora a single agent's brain produces. The next four subsections describe the layer concretely.

hybrid search

All queries go through a single search interface that fans out to two indexes. FTS5 handles lexical search with BM25 ranking, stemming, and phrase queries. sqlite-vec handles semantic search with cosine similarity over locally-computed embeddings. Both return ranked result lists.

reciprocal rank fusion

The two result lists are merged with reciprocal rank fusion (Cormack, Clarke, Buettcher 2009):

score(d) = Σᵢ 1 / (k + rankᵢ(d))    with k = 60

RRF is robust to score scale differences between lexical and semantic rankers, does not require calibration, and is parameter-light. Empirically it beats both linear combination and learning-to-rank approaches on the kind of small, heterogeneous corpora a single agent's brain produces.

local embeddings

Embeddings are produced by a local Ollama instance running nomic-embed-text. The choice is deliberate — it means the hot path has zero external API calls, retrieval is free at inference time, and an operator can run the entire stack offline. The trade-off is a small quality gap versus frontier embedding models; in practice this is dominated by the hybrid retrieval step and by the worthiness gate keeping the corpus clean.

Embeddings live in embeddings alongside shadow tables generated by the sqlite-vec extension. A lazy recompute strategy means re-embedding only touches rows whose content hash has changed.

spreading activation

Hybrid search returns the memories that match a query. Spreading activation, in the sense of Collins and Loftus 1975, returns the memories that are connected to the matches — the neighbors in semantic space whose activation matters because the matches activated them. Their original model was a semantic network with weighted edges and parallel decay: when a node is activated, activation spreads to connected nodes proportionally to edge weight, with the activation decaying over distance and time. The model was originally proposed to explain priming effects in semantic memory experiments — why hearing "doctor" speeds up recognition of "nurse" even when the two are not co-presented.

brainctl's knowledge graph — entities as nodes, knowledge_edges as typed directional relations — is the substrate for an approximation of this. When the search interface surfaces an entity, the retrieval pass also walks the graph one or two hops out and gathers the connected entities, weighted by recency, edge type, and access history. The effect is that a query for RateLimitAPI does not just return the entity; it returns the related decisions, the upstream services that depend on it, the recent observations attached to it, and any contradictions in flight.

The honest mapping. brainctl currently uses bounded breadth-first traversal, not the parallel relaxation dynamics of the original Collins & Loftus model. The structural primitive — a typed graph plus activation propagation — is there; the dynamics are simplified. A more faithful implementation with true parallel activation, decay constants, and source-target inhibition is a candidate for a future version, but the current bounded-walk approximation is sufficient for the corpus sizes a single-agent brain produces.

salience routing

Retrieval is only the first step. A global-workspace-inspired attention budget (Dehaene & Changeux 2011, Baars 1988) decides which of the matched memories actually surface into the agent's working context. The budget weighs:

Only winners make it into the prompt. The workspace broadcast is logged to workspace_broadcasts so the agent (or an auditor) can later inspect what was attended to and why. This is the difference between "what does retrieval return" and "what does the agent actually see" — a distinction that matters when the agent makes a mistake and you need to trace it.

retrieval in the session lifecycle

Retrieval in brainctl is not a single operation invoked whenever the agent is curious. It is three distinct operations tied to three distinct phases of the session lifecycle, and the difference matters for both performance and correctness.

Orient retrieval (bulk, session-start). Brain.orient(project=...) is called once at the beginning of a session and returns a composed context package rather than a results list. The package includes: the most recent handoff packet for the project, the top-N semantic memories by access-frequency within scope, the relevant entities and their immediate neighbors in the knowledge graph, any open belief conflicts, any prospective memory triggers whose preconditions match the project keywords, and the decay-protected EWC-important memories. The package is assembled by running each of those sub-queries in parallel and merging the results under a token budget. The agent sees it once at the start of the session and does not need to re-query the basics.

Ad-hoc search (mid-session, query-addressed). Brain.search(query, k=...) is the operation the agent calls when it actually doesn't know something. It runs the full hybrid retrieval pipeline from §5.1–§5.4 and returns a ranked list. The caller is expected to pass a specific question, not a vague topic — the hybrid retriever works best when the query has enough lexical signal for FTS5 to find candidates that the vector index then reranks. A good rule of thumb: if the agent can phrase the query in one sentence, it's a good search; if it would phrase it as a whole paragraph, it should use orient with a narrower scope instead.

Wrap-up retrieval (session-end, self-referential). Brain.wrap_up(summary, project=...) performs a small internal retrieval against the session's own writes — memories, decisions, and events created during this session — and composes the handoff packet. This pass deliberately does not reach across scope; it only sees what the current agent did. The result is a packet that describes the session's own state, not the broader brain state, so the next agent orienting into the project can layer the handoff on top of its own orient context without double-counting.

The three operations form a closed loop. Orient reads broadly, search reads narrowly and on demand, wrap_up writes a structured summary that future orient calls read back. An agent that uses only remember and search gets much less than an agent that uses the full lifecycle, because without orient the agent starts cold every time and without wrap_up every session's work has to be re-discovered by the next. The lifecycle is why agent_orient and agent_wrap_up became native MCP tools in v1.4.0 (§2.4) rather than remaining Python-only conveniences.

multi-agent and handoff

handoff packets

A handoff packet is a compact four-field structure emitted by brain.wrap_up: goal (what was the agent trying to achieve), current_state (where things stand), open_loops (what is unresolved), and next_step (what should happen first in the next session). These are the minimum sufficient statistics for continuation and map directly onto the continuation state a human would give a colleague.

Packets are signed (HMAC over the packet contents keyed by agent identity) so the receiving agent can verify that what it is orienting from is in fact what the previous agent wrote. This matters once multiple agents share a brain.

theory of mind

Each agent maintains not only its own belief state but a model of the beliefs of agents it hands off to — stored in agent_perspective_models. This allows asymmetric handoffs: the sending agent can tailor the packet to what the receiving agent already knows, and the receiving agent can reason about discrepancies between its own view and the sender's.

In the simple case this reduces to "skip context the receiver already has." In the interesting case it enables correction loops where a receiver can detect that the sender was operating under a stale assumption.

federation

Federation between brains — shared context across multiple physical databases with access control — is a direction rather than a scheduled milestone. The design sketch is a minimal sync protocol over signed append-only logs, allowing one brain to pull memories from another under per-scope permissions. The goal is to keep the single-file invariant at rest while enabling collaboration between operators in motion. Interest and concrete use cases from operators are what will drive whether and when this gets built.

trust model between cooperating agents

When a single brain file is shared by multiple agents — an orchestrator and several workers, a human and a reviewer bot, a research agent and a coding agent — the question of trust becomes load-bearing. Who can read whose memories? Who can overwrite whose decisions? What happens when a low-trust agent writes something that contradicts a high-trust agent's prior belief?

brainctl's trust model is built from four orthogonal dimensions that compose at query time:

  1. 1. Identity (agent_id). Every write is attributed to the agent that produced it. There is no anonymous write path. Two agents sharing a brain always know who wrote what, as a literal database join.
  2. 2. Scope. Memories are written into a scope — global, project:api-v2, or agent:reviewer-bot — and reads filter by scope. A worker agent scoped to project:api-v2 cannot see memories written under project:billing. Scopes are not hierarchical by default, but operators can configure inclusion chains (e.g., every project:* scope also sees global).
  3. 3. Trust level (memory_trust_scores). Each source is assigned a trust score that determines what it can overwrite, not just what it can read. A high-trust writer (the operator, a verified human reviewer) can supersede memories written by lower-trust writers. A low-trust writer (an ingest pipeline, a web scraper, a third-party tool) cannot supersede anything outside its own writes. The trust score is consulted by the W(m) gate: a low-trust candidate that would merge with a high-trust existing memory fails the gate with a trust downgrade flag, not a silent merge.
  4. 4. Provenance chain. The source-monitoring layer (§7.3) ensures that even a trusted agent's derived memories carry the attribution of the facts they were derived from. If a high-trust agent writes a conclusion that was derived from a low-trust observation, the conclusion inherits a parent link to the low-trust source. A later retrieval can apply a trust floor and filter both out.

Write conflicts between peers. When two agents write contradictory claims into the same scope at the same trust level, the contradiction flows into belief_conflicts rather than being silently resolved. Neither write is discarded. An operator (or a higher-trust arbiter agent) can then call resolve_conflict to rank the competing claims and collapse the loser via AGM (§3.5). This prevents the pathological case where two agents thrash on the same memory, each overwriting the other.

Read asymmetry. Cross-agent reads are unrestricted by default within the same scope — sharing a brain is the entire point of running multiple agents on one. But an operator can mark memories private to an agent, which restricts reads to that agent's identity even within the shared scope. This is how brainctl supports a reviewer pattern where a reviewer agent can read everything a worker agent writes but the worker cannot see the reviewer's private notes.

What brainctl does not yet do: cryptographic attribution. Agent IDs are database-level identities, not signed identities. A compromised process with write access to the brain file can write under any agent_id it wants; the RBAC layer protects against curious mistakes and tool-output contamination, not against an adversary that has already achieved filesystem-level access. Cryptographic per-write signatures are a candidate for a future release, but they would only matter in a multi-operator setting which is itself not yet the primary deployment model.

lineage

The memory-as-explicit-state line of work in ML agents has three load-bearing recent papers. Park et al. 2023 ("Generative Agents: Interactive Simulacra of Human Behavior"), the Smallville simulation, introduced a memory-stream architecture with importance, recency, and relevance scoring plus a periodic reflection step that synthesizes higher-level beliefs from raw observations. Packer et al. 2023 (MemGPT) reframed agent memory as an operating-system-style hierarchy with paging between context, recall, and archival tiers. Shinn et al. 2023 (Reflexion) showed that letting an agent verbally reflect on its own failures and write those reflections back to a persistent buffer improves task success across reasoning and coding benchmarks.

brainctl draws from all three: handoff packets generalize the Generative Agents reflection step into a session-bridging signed signature; the typed memory stores generalize MemGPT's tier hierarchy from three levels to six; and the reflexion_lessons table (migration 008, plus thereflexion_failure_recurrence tracker) is a direct implementation of Shinn et al.'s persistent reflection buffer. The lineage is not implicit. brainctl is what you get when you take those three papers seriously, keep the substrate local, and add the cog-sci pieces (AGM revision, schema integration, source monitoring) that the ML literature mostly leaves unaddressed.

security posture

quarantine

Untrusted input — memories written by an agent that ingested a web page, a user message, or a tool output — lands in the memory_quarantine table before it reaches memories. A human operator or a trusted reviewer agent marks each quarantined item as safe, malicious, or uncertain. Malicious items are purged with all derived knowledge edges retracted; safe items are promoted; uncertain items stay quarantined.

This is the primary defence against memory poisoning attacks, where an adversary tries to inject false premises into the agent's long-term memory via a tool response or a retrieved document.

pii audit trail

Personally identifiable information detection runs on every semantic write. Hits are logged to pii_audit with the source memory ID, the detected category (email, phone, name, account number, ...), and the action taken (redact, drop, escalate). This gives operators a single log to query when answering data-retention requests.

provenance chains and source monitoring

The cognitive-science frame for what this section describes is the source monitoring framework of Johnson, Hashtroudi, and Lindsay 1993. Source monitoring is the cognitive process by which a person attributes a memory to its origin — was this fact something I read, was it told to me, did I infer it, did I imagine it? Source monitoring failures cause confabulation: the propositional content of the memory may be correct, but the source attribution is wrong, and any decision grounded in that memory is grounded in a phantom premise. The DRM paradigm (Roediger & McDermott 1995) showed how easily even healthy human memory generates false memories under associative pressure, and how robust the confidence in those false memories can be.

brainctl's provenance posture is a literal implementation of source monitoring at the data layer, with the explicit goal of preventing the agent equivalent of confabulation. Every memory carries an agent ID, a source type (user-written, agent-written, tool-output, ingested-document, derived, consolidation-promoted), a creation timestamp, and — for memories derived from other memories — a list of parent IDs forming a directed acyclic provenance graph. The access_log table records every read with the reader, timestamp, and surrounding query context. Together these let an auditor trace any fact in the brain back to its origin and answer the question "why does the agent think this?" — not as a metaphor, but as a literal join.

rbac

Memory RBAC (migration 017) attaches a trust level to each memory source and a scope to each reader. High-trust writers (the operator, the user) can write to any scope; lower-trust writers (ingest pipelines, external tools) are confined to sandboxed scopes. Readers filter by scope at query time via memory_trust_scores.

encryption, supply chain, and incident response

Three concerns sit alongside the threat model above and deserve explicit treatment. None of them are novel problems — they are the standard operational-security concerns any system holding sensitive data faces — but pretending they do not apply to brainctl would leave the reader with gaps.

Encryption at rest. brainctl does not encrypt brain.db by default. SQLite has a closed-source encryption extension (SEE) and a free, widely-used alternative (SQLCipher), and the brainctl code path is compatible with both — the database is opened through a single function that respects a pragma-level encryption configuration. Operators who need at-rest encryption should either link against SQLCipher (one-time library swap, no code changes) or rely on filesystem-level encryption (FileVault, LUKS, BitLocker, ZFS native encryption). The default posture is to inherit from the filesystem because the majority of operators already have disk encryption enabled for everything else on the machine; adding another layer on top offers diminishing returns. For regulated environments where application-level encryption is contractually required, SQLCipher is the recommended path and a short operator guide exists in the repository.

Supply chain. brainctl's runtime dependencies are deliberately short. The core requirements are Python 3.11+, SQLite with WAL mode (shipped with Python), the sqlite-vec extension (a single shared library with no transitive dependencies), and — for semantic retrieval — a local Ollama instance running nomic-embed-text. The MCP server adds the mcp Python package. Nothing in this list is network-native at runtime: Ollama runs on localhost, sqlite-vec is compiled into the process, and the mcp stdio transport is file-descriptor-based. There is no browser surface, no long-lived network connection, and no auto-updater reaching out to a remote server. This is an intentional reduction in supply-chain surface: the fewer packages in the runtime, the fewer places a malicious upstream can land a compromised release.

Development dependencies are larger (test frameworks, linters, benchmark harnesses), but they are isolated to requirements-dev.txt / the dev extras and are not loaded by the runtime. The repository publishes a lockfile and the PyPI release is built from a deterministic CI pipeline; operators who want reproducible builds can pin against the lockfile and verify the built artifact against the release hash.

Incident response. If an operator discovers that a memory, a tool output, or a source was compromised, brainctl provides the primitives for a clean response without data loss:

  1. 1. Identify the compromised source. Query access_log for the offending agent ID or source type. The log is append-only and timestamped, so the window of compromise is recoverable.
  2. 2. Quarantine derived memories. The memory_quarantine table accepts writes from an operator that mark a set of memories as pending review. The trust-propagation pipeline then walks the provenance graph from the quarantined memories outward, flagging every downstream memory whose parent chain touches the compromised source. None of these are deleted; they are held pending review.
  3. 3. Replay consolidation with the compromised sources excluded. A dream cycle can be invoked with an exclusion set, so the Hebbian pass and the REM bisociation step do not reinforce the poisoned edges while the operator decides what to restore.
  4. 4. Restore or purge. Memories marked safe after review flow back into the active store; memories marked malicious are purged with the quarantine_purge tool, which retracts all derived knowledge edges and records the retraction in the audit log. The purge is a soft tombstone rather than a hard delete, so a subsequent investigation can recover the original content and its provenance.

The incident-response primitives above are designed to assume bad input is inevitable and to give the operator tools to contain rather than tools to prevent. Prevention is always incomplete; containment and auditability are what determine how bad a compromise becomes.

threat model: memory poisoning

The threat model brainctl's quarantine, source monitoring, and RBAC layers defend against is grounded in a specific recent security literature. Greshake et al. 2023 ("Not What You've Signed Up For") characterized indirect prompt injection: attacks where untrusted content arrives via a tool output, a retrieved document, or a web page and contains instructions intended to manipulate the agent's downstream behavior. The original paper demonstrated end-to-end attacks against LLM-integrated applications including Bing Chat and email assistants, with payloads as simple as a HTML comment hidden in a web page.

For stateful agents the threat is amplified, because injected content can persist across sessions if the agent writes it to memory. An attacker who controls a single tool response or document can craft input designed to land in the agent's long-term store and bias every future decision that retrieves from that region of memory. This is the agent equivalent of a persisted XSS attack, and it survives every restart and context reset.

brainctl's three structural defenses against memory poisoning are: (1) the memory_quarantine table, which holds untrusted writes pending review before they reach memories; (2) the trust-scoped RBAC of §7.4, which prevents low-trust writers from contaminating high-trust scopes even if quarantine is bypassed; (3) the source-monitoring provenance chain of §7.3, which makes every retrieved fact traceable to its origin so a compromised tool can be retroactively quarantined and all downstream memories it spawned can be retracted. None of these eliminate the risk — no defense does — but they make the difference between an agent that can be permanently poisoned by a single malicious tool response and an agent whose poisoning attempts are isolated, attributable, and reversible.

implementation and benchmarks

At v1.5.1, brainctl is implemented in ~46k lines of Python inside src/agentmemory/ with a SQLite schema defined by 37 migration files rebuilding to 56 live tables. The MCP server exposes 196 tools. Plugins for Claude Code, Codex CLI, Eliza, Hermes, Freqtrade, and Jesse ship in-tree, and the v1.5.0 migration runner brings existing brain.db files up to the current schema safely.

Representative figures on a single-agent brain after one month of continuous use on an M2 MacBook Pro:

operationp50p99notes
brain.remember3.1 ms9.8 msincludes W(m) gate + embedding
brain.search (k=10)6.4 ms18 msFTS5 + vec + RRF + salience
brain.orient22 ms55 msfull context package assembly
brain.wrap_up8 ms25 mspacket + signature + decisions flush
consolidation pass1.4 s4.1 sper 1000 episodic rows, offline

Numbers are indicative, not normative; a formal benchmark suite is in bench/ and runs in CI. The point is that the hot path is small — dominant cost is local embedding computation, which is still single-digit milliseconds.

dependencies and platform support

brainctl's runtime dependency list is intentionally short. The core requirements:

Platform support tracks SQLite and sqlite-vec. brainctl runs on macOS (Intel and Apple Silicon), Linux (x86_64 and aarch64), and Windows. CI exercises the full suite on macOS and Ubuntu on every push. Windows is supported but less battle-tested; the repository accepts Windows-specific bug reports and fixes them. The one platform that is explicitly unsupported is anything without a writable local filesystem — the single-file architecture is fundamentally incompatible with serverless runtimes that expose only ephemeral disk.

testing and release cadence

The test suite at v1.5.1 is ~1,370 tests covering the MCP tool surface, the W(m) gate, the migration runner, the consolidation pipeline, the belief revision logic, and the integration plugins. CI runs the full suite on every push and every pull request, on both macOS and Ubuntu, with the runs pinned to a fresh SQLite build to catch extension-loading regressions. The brainctl-mcp --doctor command runs a local subset of the CI checks on the operator's own installation — useful for diagnosing a broken install without shipping the environment to a maintainer.

Releases are SemVer: major for incompatible API changes, minor for backward-compatible features (plugin additions, new MCP tools, new tables via migration), patch for fixes. Release cadence is irregular but approximately every two to four weeks for minor releases and as-needed for patches. Every release includes a CHANGELOG entry with the migration list and any operator-visible behavioral changes. The PyPI release is built from a deterministic CI pipeline and published with an attestation so operators can verify the built artifact against the release hash.

contribution

brainctl is open source under MIT and accepts pull requests. The contribution model is intentionally low-ceremony: fork, branch, open a PR, get review. The one hard rule is that every new mechanism must have a research note in the research/ directory explaining the cognitive-science, ML, or systems grounding behind the design — this is how the paper trail in §10.1 stays current and how reviewers understand why the code looks the way it does. Tests are required for any change that touches the hot path or the W(m) gate; style conforms to ruff and black with defaults.

Good first issues typically fall into five categories: (1) new MCP tools wrapping existing Python API calls, (2) new plugins for agent frameworks that don't yet have first-party support, (3) research notes implementing a specific paper's mechanism as an optional feature, (4) bench harnesses extending the benchmark suite, (5) documentation and reproducibility bundles for the existing research notes. The project is not looking for large speculative refactors.

comparison with existing approaches

The agent-memory landscape is crowded and moving fast. Several mature projects already address subsets of the problem brainctl targets, and any honest comparison has to acknowledge that. The table below is descriptive — what each project does at the architectural level — not evaluative. The paragraph that follows it is the actual positioning.

projectsubstratememory typologybelief revisionconsolidation
LangGraph checkpointerper-graph state, pluggable backendflat dict or schema-defined statelast-write-wins on updatenone
Letta (formerly MemGPT)hosted, postgres or sqlitecore / recall / archivalnonerecall ↔ archival swap
Mem0hosted, postgres-backedflat memories with metadatanoneLLM-driven memory updates
Zephosted service, postgres + vectorsession messages + extracted factsnonenone
Cogneepostgres + vector + graphknowledge-graph-firstnoneoffline graph build
brainctlsqlite, single file6 typed stores + knowledge graphAGM with collapse audit3-phase dream cycle

Letta (formerly MemGPT) pioneered the recall/archival memory swap and remains the strongest reference point for hosted multi-agent memory. Mem0 targets memory-as-a-service for production agents and has the cleanest REST surface in the category. Zep offers enterprise-grade session storage layered on Postgres and is probably the right call for any team that needs row-level ACLs, audit logs, and SOC2-style compliance from day one. Cognee leads on knowledge-graph-first memory and is the closest cousin to brainctl in spirit. LangGraph's checkpointer is the right answer for teams already invested in LangChain's runtime. CrewAI and AutoGen both ship first-party memory layers and are the path of least resistance inside their respective frameworks. The built-in memory features in ChatGPT and Claude shape end-user expectations and quietly set the floor for what users assume an agent should remember.

None of these are wrong. brainctl does not try to be a better Letta or a better Mem0; it tries to be the answer for a specific design corner that the rest of the field does not occupy: local-first SQLite as the only required infrastructure, six typed memory stores instead of a flat or two-tier model, AGM belief revision with a full collapse audit instead of last-write-wins, and a three-phase dream cycle that actively generates hypotheses during quiet hours. If you need any of those four things and you also need MIT licensing and zero hosted dependencies, brainctl is the answer. If you need anything the others ship that brainctl does not — managed multi-tenant hosting, enterprise audit tooling, framework-native ergonomics — use them. The design space brainctl occupies is underpopulated, not contested.

economics: why a token

license posture

brainctl is MIT-licensed and will remain so. There is no enterprise edition, no paid tier, no gated features. Every mechanism in this paper is implemented in the public repository, with tests and a research note in research/. The ~40 research notes in that directory document the cognitive-science grounding of each mechanism with citations and reproduction instructions.

why a token rather than grants or VC

Open-source infrastructure is chronically underfunded. The two mainstream funding mechanisms both fail in characteristic ways.

A token is a third option. It aligns funding with the people who benefit from the memory layer — builders running agents, operators paying for inference, the agents themselves eventually — without putting the software behind a paywall. If it fails, the software is still free. If it succeeds, the research accelerates.

distribution

$BrainCTL has not launched yet. There is no contract address, no pump.fun listing, and no circulating supply. When the token does launch, the intent is a fair launch on pump.fun — no team allocation, no pre-sale, no vesting — with the launch itself serving as the distribution.

The development wallet is already public, however, and already being tracked live on /transparency. Every inbound transfer (tips, grants, test deposits, eventual launch proceeds) and every outbound transfer (infrastructure, contributor payments, research compute) is rendered on the page — fetched server-side from the Solana chain via the Helius enhanced transactions API, cached for 60 seconds, and cross-checkable against any independent RPC. The point is that the ledger is public before any money is spent, not after.

Nothing in this section is financial advice or an offer to sell securities. $BrainCTL is a community coin. The brainctl software is free, open source, and MIT-licensed independent of any token.

fund governance and transparency

If a token is going to exist, the operator has to be honest about where the money goes. The posture here is two-part: show everything, and spend on named things.

Show everything. The development wallet is public. Every inbound and outbound transfer is rendered live on /transparency via a server-side Helius fetch, cached 60 seconds, cross-checkable on Solscan. There is no separate "internal" wallet. There is no treasury sub-account that is not on that page. Operators, contributors, token holders, and adversaries all see the same ledger. Pre-launch, the wallet is held privately to prevent sniping (§10.3); the moment the token deploys, the pubkey goes live on the transparency page.

Spend on named things. Outflows are categorized at the time they happen into a small set of buckets: infrastructure (Vercel, Ollama inference, Supabase, domains), contributor compensation (paid PRs, research notes, benchmark work), research compute (GPU rentals for training, embedding benchmarks, large-scale consolidation experiments), and community (docs, talks, events). The category is recorded as a memo on the transaction so the transparency page can render it inline with the flow. When the token first launches the bucket distribution is rough; over the first quarter post-launch the expectation is that infrastructure plus contributor compensation will dominate.

Who signs. At launch the dev wallet is a single-signature wallet controlled by the maintainer. This is the minimum-viable posture: it lets the project move fast, but it places all the trust on a single signer. The plan, once the token has operated long enough to establish what the spending patterns actually look like, is to migrate to a multi-signature wallet with signers drawn from maintainers and the broader contributor community — three-of-five is the target threshold. The migration is scheduled to happen after the first ninety days of operation; until then the single-signer risk is the cost of shipping fast.

contingency: what if the token fails

A token launch can fail in several different ways and it is worth naming each one and saying what happens to brainctl in each case.

In none of these scenarios does the brainctl software stop being free and open source. That is the invariant the token does not touch.

direction

brainctl does not maintain a fixed quarterly roadmap. The project is issue-driven: priorities come from the public GitHub issue tracker, from operator feedback, and from whichever research threads are returning interesting signal. A frozen eighteen-month plan would be fiction, and the alternative — publishing one anyway — is exactly the kind of thing a serious open-source project should refuse to do.

What the authors are currently interested in, without committing to timelines, includes:

Anything here may change. If a direction matters to you, the fastest way to affect priorities is to open an issue or send a pull request.

open questions and research frontier

The second half of this section is an honest inventory of hard problems that are not solved in brainctl today. The list matters because a whitepaper that only describes what works gives the reader a dishonest model of what the project actually is. These are the places where the cognitive science or the ML literature suggests a better answer than what brainctl currently implements, and where work we would be happy to see (or do ourselves) is wide open.

Learned schemas. The nine memory categories in §2.2 and the schema-integration story in §4.4 use hand-designed schemas. Humans acquire new schemas throughout life; a schema-acquiring agent would be able to grow its own category system in response to new domains. Concretely: given a corpus of observation memories the agent keeps writing, can brainctl identify that a new category has emerged and propose its addition? This is a clustering-plus-validation problem with the extra constraint that the new category should be stable across sessions.

Learned W(m) coefficients. The worthiness gate coefficients (α, β, γ) in §3.2 are hand-tuned per category. A better brainctl would learn them from outcome calibration data — when the gate admitted a memory and the memory subsequently turned out to be load-bearing (or misleading), that is a training signal. The memory_outcome_calibration table exists for this purpose, but the loop that feeds calibration data back into coefficient tuning is not yet closed.

Intent-matched prospective memory. §3.6 is honest that prospective memory triggers match on keywords, not intent. A trigger with precondition "billing schema" fires for every query mentioning those words, including unrelated contexts. An intent-matched variant would use the embedding of the precondition and a threshold against the query embedding, or a small trained classifier, to decide whether the trigger is actually relevant to the agent's current task. The data structures exist; the matching logic is the gap.

True spreading activation. §5.4 admits that brainctl's knowledge-graph traversal is bounded breadth-first, not the parallel relaxation dynamics of the Collins & Loftus model. A more faithful implementation — true parallel activation with decay constants and source- target inhibition — would change how retrieval surfaces related concepts, particularly for multi-hop queries where the agent needs to follow a chain of reasoning. Whether the fidelity gain justifies the complexity cost is an open empirical question.

Neural replay at short timescales. §4.1 is honest that brainctl's replay has nothing to do with theta rhythms or sequence preservation at biologically meaningful timescales. There is a research question whether any of that matters for agents: mammalian sharp-wave ripples compress 1–10 seconds of experience into ~100 ms, and the compression ratio itself may be a load-bearing part of why replay works. Whether an agent memory system benefits from time-compressed replay of sequences, or whether the current unordered top-K replay captures everything that actually matters, is not known.

Cross-brain federation. §6.3 sketches federation as a direction without a built implementation. The hard parts are not transport — signed append-only logs are well-understood — but the semantics of cross-brain conflict resolution. When two brains hold contradictory AGM beliefs and attempt to merge, whose provenance chain wins? What happens to the importance scores of memories pulled from a remote brain? These are open design questions.

Outcome-calibrated retrieval. Hybrid search (§5.1–§5.2) uses reciprocal rank fusion with k=60, a parameter borrowed from the IR literature. There is no evidence that k=60 is optimal for agent memory specifically; the distribution of query types and memory shapes is different from traditional IR benchmarks. A calibration study over brainctl's own workload — hard to produce without a common agent-trajectory benchmark — would close the loop.

Cryptographic agent attribution. §6.4 notes that agent IDs are database-level identities, not signed identities, and that a compromised process with filesystem access can write under any agent ID. Cryptographic per-write signatures would harden multi-operator deployments but require a key distribution story that brainctl currently does not have. This becomes important the moment federation ships.

Benchmark suite against competitors. §9 compares brainctl to Letta, Mem0, Zep, Cognee, and LangGraph in architectural terms but does not benchmark them on a common task. Running all five through the same multi-day agent trajectory and measuring retrieval recall, decision preservation, and handoff fidelity would be the single most valuable contribution a researcher could make to the field right now. The benchmark harness in bench/ is designed to be pluggable across memory backends, but adapters for the competitor projects do not yet exist.

Formal verification of the W(m) gate. The gate's correctness properties — monotonicity under trust level, idempotency under rewrite, convergence under repeated redundant writes — are stated informally in §3.2 but not proven. A formal model of the gate in a proof assistant would be a strong signal of seriousness and a genuinely useful artifact for anyone extending the system.

This list is not exhaustive. It is what we can currently articulate as open problems from inside the project; the most interesting research questions are usually the ones that are not yet legible as questions. We expect this list to grow, not shrink, as the project matures — a healthy research program is one that accumulates hard problems faster than it solves them.

references

back