carlos

home · docs · memory

concepts · memory

Three tiers of memory.

carlos remembers in three layers. Every event in a durable log; summarized turns indexed for FTS5 search; a slow-moving user model that only changes with your review.

Three tiers

1 · episodic

Full conversation transcripts. Append-only event log in SQLite WAL mode at ~/.carlos/state.db. Source of truth; the TUI projection replays from it. Every action (user message, assistant message, tool call, tool result, sub-agent state transition, schedule fire) is an event.

2 · searchable

SQLite with FTS5 over summarized turns. An LLM summarizer (or naive summarizer when no provider is configured) runs at conversation close, producing markdown summaries indexed for search. Per-frame scoping via the summaries.frame column (Phase F-13). Surfaces via carlos memory search <query> (CLI) and /memory <query> (slash).

3 · user model

Slow-moving facts about the user: preferences, ongoing projects, recurring contacts. Hand-curated plus agent-proposed. Never silent-write: every proposed edit goes through the approval queue. Lives in usermodel.go.

why three, not one

An event log is too noisy to search; a search index is too lossy to replay; a user model is too small to hold a conversation. Each tier exists because the other two cannot do its job.

/compact is manual

/compact targets the current conversation. The summarizer (Naive or LLM) rebuilds history; emits EvtSessionReset + a synthetic recap. Frees tokens without losing the gist.

why manual-only

carlos's /compact is manual-only by design. The equivalent Claude Code surface auto-triggers when context fills, which means the user loses the boundary between "what I said" and "what got rewritten." carlos refuses to silently overwrite your conversation; if you want a compaction, ask for one.

CLI surface

shell
carlos memory search "<query>"               # whole-corpus
carlos memory search -f <frame> "<query>"    # per-frame

The -f flag uses SearchInFrame / RecentInFrame. Frame scope follows the active frame's vault subtree, not just the conversation history. If a frame writes notes into vault/personal/projects/<frame>/, those summaries are included in the per-frame search.

Slash surface

command what it does
/memory <query> Wraps the same FTS5 path as carlos memory search.
/insights [topic] What carlos has learned about you and your work; topical filter optional.
/compact Summarize and shed older context. Emits EvtSessionReset plus a synthetic recap.

What you choose to remember

The user model is the boundary between "carlos remembered this from the transcript" and "the user agreed to remember this." A user-model edit is a structured proposal in the approval queue. The diff is shown; the user accepts, edits, or rejects. The choice persists.

This is the same surface as skill proposals. carlos has one approval queue; user-model edits, skill PROPOSALs, plan diffs, and research outputs all flow through it.

tip

If you find yourself rejecting the same proposed user-model edit twice, that's a signal: either the summarizer prompt needs a tighten, or the fact genuinely doesn't belong in the slow-moving tier. Reject it once, then look at the underlying transcript with /memory.

On-disk layout

~/.carlos/
~/.carlos/
├── state.db            (SQLite event log + memory FTS5)
├── frames/<name>/
│   ├── research/       (cited reports)
│   ├── usershell/      (per-job logs)
│   ├── worktrees/      (sub-agent worktrees)
│   └── digest/         (scheduled-digest artifacts)
└── config.yaml         (user prefs, frames, providers, mcp_servers)

File modes: 0700 directories, 0600 secret-bearing files (config.yaml, state.db, trusted-workspaces.json, artifact blobs), 0644 elsewhere. Atomic writes (temp + fsync + rename) for any file containing user state.

don't put a credentials file next to state.db

~/.carlos/ is 0700 for a reason. If you add files by hand, keep the mode discipline; carlos doesn't re-chmod files it didn't write.

Related reading

go deeper