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
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.
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).
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.
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.
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
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.
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/ ├── 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.
~/.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
- Frames: the scoping unit for memory, history, and config overrides.
- Sub-agents: each sub-agent writes its own event log; the parent reads projections.
- Config: where the vault, providers, and frame definitions live.
- Tools: including the memory-touching ones.