Skip to main content

Cognitive Memory Substrate

Most agents forget everything the moment a conversation ends. Thoth's substrate is a real memory layer that runs beneath the conversation: it records what the agent perceives, lets most of it fade, consolidates the salient parts into structured knowledge, and surfaces what's relevant on each turn. It's modeled loosely on how a mind keeps its own memory — a continuous perception → curation → recall pipeline rather than a "remember a few facts" bolt-on.

The substrate is additive and non-fatal: it sits below the conversation loop and is allowed to fail silently. Recall is off by default — until you opt in, Thoth behaves exactly as it would without the substrate.

Requirements

The substrate stores everything in one PostgreSQL 17 instance with the vector (pgvector), pg_trgm, and pgcrypto extensions — the same database that holds transcripts and kanban state. There is no SQLite. A standard Docker-Compose install already provisions this.


The mental model​

Slices, streams, families​

The atom of the substrate is the slice — one perceived event. Slices arrive on named streams, and every stream belongs to a family recording where the perception came from:

FamilyMeaningExamples
exteroceptiveSignals from outside the agentuser messages (CLI, Telegram, Discord…), sensor reads
self_actionThe agent's own outbound actionsassistant responses, tool calls/results, sub-agent spawn/return
self_stateThe agent's own internal state changessession lifecycle, cron dispatch, every sub-agent decision

This world/self split is what later lets the system reason about itself — its own maintenance decisions are recorded as self_state slices, so any decision can be replayed after the fact. Fifteen streams are auto-registered at boot.

Each slice carries a payload, a triage state (pending → passed | quarantined), a consolidation state (unconsolidated → … → released), a salience score that decays over time, and a 1536-dimension embedding backfilled asynchronously.

The layer stack (L0–L4)​

Meaning is distilled upward through five layers, each citing its source so it survives the raw event being released:

LayerNameContents
L0PerceptionRaw slices — every event, as perceived
L1Entities & relationshipsEntities + typed relationships, each citing the L0 slice they came from
L2Associative graphWeighted associations between L1 entities (co-occurrence / shared-neighbour structure)
L3Patterns & abstractionsGeneralizations and recurring themes across many extractions
L4Self-model & calibrationWhat the agent knows about its own knowing — per-agent calibration + a cross-layer coherence score

The key mechanic is the consolidation handshake: a raw L0 slice is only released after its meaning has been safely promoted to L1+. Memory isn't so much deleted as promoted — the event fades while its distilled meaning persists higher up. On a fresh install the upper layers fill in only once the agent has done enough work to consolidate.


The background crew​

Maintenance and cognition run as background workers, each gated and individually disableable.

Core workers (always on, in-process):

  • Sentinel (200 ms tick) — triages new slices pending → passed. Content defense (prompt-injection / poisoning screening) is staged behind a flag, off by default.
  • Curator — the decay + release + embedding loop (see below).
  • Force-reject (10 s) — drops un-triaged slices past their TTL so the queue stays bounded.
  • Partition-maintenance (24 h) — keeps monthly table partitions ahead of now().

Cognitive sub-agents (on by default, each gated by a THOTH_SUBSTRATE_* env var) run in a separate worker subprocess and build the upper layers. They no-op silently when no auxiliary model is configured, so they cost nothing until you wire up a model:

Env var (default 1)Sub-agentProduces
THOTH_SUBSTRATE_PARSERParserL1 entities / relationships
THOTH_SUBSTRATE_ASSOCIATORAssociatorL2 associations
THOTH_SUBSTRATE_PATTERNFINDERPattern-finderL3 patterns
THOTH_SUBSTRATE_CRITICCriticL4 calibration + coherence
THOTH_SUBSTRATE_REFLECTORReflectorL3/L4 synthesis
THOTH_SUBSTRATE_DREAMERDreamercounterfactual exploration log
THOTH_SUBSTRATE_CONDUCTORConductoradaptive intensity dialing
THOTH_SUBSTRATE_SUMMARIZERSummarizercompresses older context

The Conductor is a load balancer for the crew: it dials each agent's intensity (OFF → LOW → MEDIUM → FULL) based on observable load. When the consolidation backlog is high it raises the Parser and pauses the enrichment agents so scarce cycles go to parsing; when the backlog drains, everyone returns to baseline. It also reacts to the Critic's coherence vital, re-prioritizing corrective work when coherence drops (with hysteresis so it doesn't flap).


The memory lifecycle​

The Curator runs a continuous decay loop parameterised by decay profiles (natural half-life, consolidation window, pending TTL, tombstone policy). A slice's salience fades exponentially; when it falls below the retention threshold and its meaning has consolidated upward, the Curator releases it. This — not a hard size cap — is what keeps storage bounded under a busy session.

You can override the machine:

  • pin a slice so it's never decayed or released (and is boosted in recall).
  • forget a slice to drop its salience to zero for release on the next cycle.

Recall: how it feeds a turn​

When recall is enabled, the substrate composes each turn's <memory-context> from substrate slices. The per-turn pipeline is timeout-bounded (default 300 ms) and roughly:

  1. Embed the query (optional, separate budget).
  2. Rank a recall window by a composite score: pgvector cosine similarity + keyword overlap + current salience + recency, each weighted by a THOTH_RECALL_*_WEIGHT knob. When coherence is low, recall raises its relevance floor so only stronger candidates surface.
  3. Compose a token-budgeted projection (default 1500 tokens) — a greedy composer that adds candidates until the budget is hit.
  4. Reinforce hits — a small, rate-limited salience bump for surfaced slices.
  5. Log one audit row to substrate_recall_log.

Two properties make this safe to turn on:

  • Failures never reach the conversation. Recall always returns a projection — possibly empty, with an empty_reason (timeout, no_candidates, token_budget_exhausted, error).
  • Embeddings are an optimization, not a gate. Recall against not-yet-embedded slices falls back to keyword matching, so it works immediately and only sharpens as embedding coverage climbs.

Turning it on​

The substrate perceives out of the box (slices are recorded), but two things are opt-in: recall into the conversation, and the cognitive worker that builds L1–L4.

# 1. Enable recall (off by default). Set in your .env and restart —
# substrate config is read once at boot.
echo "THOTH_SUBSTRATE_RECALL=1" >> ~/.thoth/.env

# 2. Run the cognitive worker subprocess (builds L1–L4 in the background).
thoth substrate worker run

# 3. Inspect what it's doing.
thoth substrate health # layer counts, coherence, per-agent cost
thoth substrate parser recent # replay any sub-agent's recent decisions

For the full operator playbook — inspect commands, tuning knobs, and troubleshooting — load the bundled substrate skill in a session with /substrate.

Config is read once at boot

THOTH_SUBSTRATE_RECALL and the other THOTH_* substrate knobs are read at startup and are effectively fixed for the life of the process. To flip recall on/off or retune weights, edit .env and restart.


Cost & limitations​

The substrate is deployed and useful, but a few things are intentionally partial — worth knowing before you point it at a paid model:

  • The cognitive crew has a real cost surface. All eight cognitive sub-agents default to ON. They no-op with no auxiliary model — but the moment one is configured, the whole crew runs against it. Per-call token usage is recorded to substrate_agent_cost and shown in thoth substrate health, so you can attribute spend per sub-agent. What doesn't exist yet is a cost governor: nothing throttles the crew on token spend (the Conductor steers on backlog and coherence, not cost). The blunt controls are: don't run the worker, set per-agent THOTH_SUBSTRATE_*=0, or hold the crew at a lower intensity. Watch the per-agent cost and budget accordingly.
  • The Conductor is a load balancer, not yet an optimizer. It reacts to consolidation pressure and coherence; the learned executive (forecasting, policy learning) is deferred.
  • Coherence is acted on, but the signal is still a scalar. The Critic's coherence score drives the Conductor and recall's relevance floor today; a richer, L2-grounded measure is still outstanding.

None of these affect the conversation — they're limits on how autonomous the substrate is, not on its safety. Slice writes and recall are always best-effort and never block or break a turn.


See also​

  • Operate it: the bundled substrate skill (/substrate) — inspect, tune, troubleshoot.
  • Persistent Memory — the simpler MEMORY.md / session-search layer the substrate complements.
  • Memory Providers — external memory backends (Honcho, Mem0, …).
  • Architecture deep-dive: docs/architecture/substrate.md in the repository, for the full design rationale and contributor invariants.