Skip to content

Memory

Every agent in Axocoatl has a four-tier memory system, modeled on the MemGPT/Letta split between a small curated context the agent edits itself and a larger lossless store it retrieves from. The tiers go from hot working state down to durable recall:

| Tier | Lives in | What it holds | |------|----------|---------------| | 1 — Session | In-process state | The live conversation, this turn | | 2 — Daily log | Append-only JSONL, one file per date | Everything that happened, by day | | 3 — Core memory | Named blocks, rendered into every prompt | Curated facts the agent keeps in context | | 4 — Semantic | On-device vector store | Lossless recall by meaning, not by key |

All four tiers are local-first. The semantic embedder runs on your own hardware (see below); nothing leaves the machine unless you point a provider at a remote LLM.

The session is the live conversation — the ordered list of messages for the current run, held in process. It’s the working set every LLM call sees. Source: session.rs.

The daily log is an append-only JSONL file per date ({base_dir}/{agent_id}/YYYY-MM-DD.jsonl). Every conversation turn, tool call, decision, and note is appended as one line — nothing is ever overwritten or evicted, so it’s the durable record of what the agent did.

The agent can read its own history back by date range with the recall_timeframe tool — “what did I do on the 12th?” reads that day’s log. Source: daily_log.rs.

Core memory is the curated top of the hierarchy — a small set of named blocks that are rendered into the system prompt on every turn, so they’re always in context. By default an agent gets three blocks:

  • persona — who the agent is and how it behaves.
  • human — what it knows about the person it’s working with.
  • project — durable facts about the work at hand.

You can override the default set in YAML:

memory:
core:
blocks:
- label: persona
value: "A terse senior Rust engineer."
limit: 2000
- label: project
description: "Facts about the repo under work."
shared: true

The agent maintains these blocks itself, using three tools exposed in its tool loop:

  • core_memory_append — add a line to a block.
  • core_memory_replace — find-and-replace within a block.
  • core_memory_set — overwrite a block’s whole value.

Edits persist per-agent as atomic, owner-only (0600) JSON. Each block has a character limit (default 2000; 0 means unlimited) the tools enforce, so the curated context can’t grow without bound.

A block marked shared: true is backed by a process-wide registry instead of the per-agent store. Every agent that declares that label gets the same block, so one agent’s edit is immediately visible to the others — team memory across a lattice of agents. Source: core_memory.rs.

Tier 4 is a lossless on-device vector store. Every stored memory is embedded into a vector; recall finds the nearest vectors by cosine similarity. Real recall — semantically related text with no shared words still scores high.

There are two ways the agent reaches it:

  • Passive injection. Each turn, the top-k most relevant memories above a score floor are injected into the prompt automatically — the agent doesn’t have to ask.
  • The recall_search tool. The agent can also query the store directly when it needs a specific fact that isn’t in the passive set.

Both paths share one relevance bar, tunable under memory.recall:

memory:
recall:
passive_inject: true # inject top-k each turn (default true)
top_k: 5 # how many hits (default 5)
min_score: 0.15 # cosine floor a hit must clear (default 0.15)

Embeddings come from all-MiniLM-L6-v2 run with Candle — pure-Rust, no ONNX, no C++ runtime, no external embedding API. The model weights are fetched once (~90 MB) the first time an agent needs them, then cached. The active embedder’s id is recorded in each store file; if it changes, every memory is automatically re-embedded so all vectors share one space (the original text is always kept).

If you build with --no-default-features (dropping neural-embeddings), Tier 4 falls back to a pure-Rust lexical embedder (signed feature hashing). Recall is weaker — it reflects word overlap rather than meaning — but there’s no model download and the binary stays small. Source: semantic.rs.

Tiers 3 and 4 are linked by a background “sleep-time” consolidation pass. A daemon loop periodically asks idle agents to run an LLM memory-manager turn that reads recent Tier-4 activity and promotes durable facts up into the core blocks — so the things that matter graduate into always-in-context memory on their own.

Two properties keep it safe:

  • Promotion-only. It reads Tier 4 and writes Tier 3. It never evicts the semantic store — the lossless record stays intact.
  • Idle-gated. Each agent decides whether it’s been idle long enough; the pass never fires mid-turn. A graceful stop runs one final pass.

Source: consolidation.rs (the loop) and on_consolidate in the actor behavior (the per-agent pass).

Checkpointing is separate from the memory tiers. It’s a crash-recovery snapshot: the session transcript serialized to disk with bincode, written atomically with owner-only (0600) permissions, keeping the last three versions per agent.

On restart the supervisor restores the agent from its latest checkpoint — the conversation transcript only. It does not restore token-usage accounting or tool/plan state; the agent resumes its conversation and rebuilds the rest. The checkpoint frequency follows the configured policy (EveryLlmCall, EveryNMessages(n), Manual, or None). Source: checkpoint.rs.

By default, under ./data/:

data/
memory/
core/
agent_{id}.json # Tier 3 — per-agent core blocks
shared/ # Tier 3 — shared (team) blocks
{agent_id}/
YYYY-MM-DD.jsonl # Tier 2 — daily logs
agent_{id}_semantic.json # Tier 4 — semantic store
checkpoints/ # crash-recovery snapshots
models/
all-MiniLM-L6-v2/ # cached embedder weights

The data/ directory is gitignored by default. Move it to your home, back it up, sync it — your call.