Memory
Session, project, and committed repo scopes, the curated repo graph, and pruning.
Captain stores graph relationships in three scopes. Session and project memory live outside the repository; repo memory is committed with the code and shared by the team.
<project>/.captain/graph.json # curated --scope repo facts, committed
~/.local/state/captain-barbossa/<hash of project path>/
graph.json # explicit --scope project facts
<OS temp>/captain-barbossa-<uid>/<hash of project path>/
sessions/<session-id>/
session.json # workspace and crew references
graph.json # this session's memory only
protocol.json # assignments, questions, acknowledgements
events/<crew>-<incarnation>.jsonl # native eventsThe split is deliberate: durable project facts survive OS temp cleanup while ephemeral session state does not.
The three scopes#
| Scope | Lives | Use it for |
|---|---|---|
session | OS temp, per session | The default. Working notes for this session only. |
project | State root, per project path | Facts that should survive into future sessions on this machine. |
repo | .captain/graph.json | Team decisions worth committing. Curated, never a dump. |
Repo scope#
--scope repo writes .captain/graph.json inside the checkout, beside .captain/settings.toml, so a decision recorded once reaches every teammate through version control. It holds architectural decisions, methods, conventions, and their rationale — nothing else.
captain memory add "placement" decided "declared tab shapes" \
--scope repo --because "even ratios must survive a dismissal"
captain memory show --scope repoYou choose the content; the code fixes the shape. Only an explicit memory add --scope repo writes it, with no inference, extraction, or summarising step anywhere in the write path. Session facts are never promoted into it, and crew reports, lifecycle events, timestamps, and machine paths stay out.
The closed relation vocabulary#
| Relation | Records |
|---|---|
decided | An architectural decision. |
method | How the team does something. |
convention | A rule the code follows. |
Anything else is refused with the allowed set. --because is required and carries the rationale onto the link. Every field is capped at 300 characters and refused rather than spilled to a note file, because a value that long is a transcript, not a fact.
Deterministic bytes#
Node IDs hash the label alone, nodes and links are sorted, and nothing timestamped, random, or machine-specific is stored. The same facts always produce byte-identical files regardless of the order they were written in, so re-adding a fact is an idempotent no-op rather than a second row — and a merge conflict is a real disagreement, not noise.
Supersede, never newest-wins#
A write whose subject and relation are already on record is refused. --supersede replaces that one row and drops the object it orphans, leaving exactly the bytes recording the new fact first would have produced.
captain memory add "placement" decided "free-form splits" \
--scope repo --because "the shape got in the way" --supersedeSeeding from the project rulebook#
captain memory init seeds the repo graph from the rules the project already states — the counterpart of captain init writing .captain/settings.toml. It reads AGENTS.md, else CLAUDE.md (--from PATH overrides), turns each bullet under a Rules or Key facts section into one convention fact, and previews them until you pass --apply. Re-running adds only what is new.
captain memory init
captain memory init --applyAdd, show, query#
captain memory add "rate limiter" "uses" "per-user windows"
captain memory add "test command" "is" "python -m unittest" --scope project
captain memory show
captain memory show --scope repo
captain memory query "rate limiter"
captain memory path
captain memory path --scope projectshowprints the most recent relationships as[scope] [subject, relation, object].--allshows every link,--jsondumps the raw graph, and--scopenarrows to one scope.- Text output caps each field at 300 characters, including with
--all. Stored values and explicit--jsonoutput remain intact. - Recent session rows fill the 25-row default, with 5 rows reserved for project and 5 for repo so durable facts are never crowded off the end.
- Default add scope is
session.--becauseand--supersedeapply to--scope repoalone and are refused elsewhere.
show and path are pure reads: no directory creation, chmod, locks, migration, snapshot, or graph rewrite. Both bypass Herdr pane lookup. When the durable project graph is absent, show reads a legacy temp-root project graph in place — it never copies it. For bounded inspection of the stored files, use inspect state SCOPE [PATH].
Identity and roots#
- Git repositories use their checkout root as project identity; other directories use the launch directory.
$XDG_STATE_HOMEis honoured in place of~/.local/statewhen set.- Crew inherit their captain's project and session. Only explicitly saved project facts carry into other sessions.
CAPTAIN_MEMORY_ROOT, set before starting captain, redirects both roots at once. It must point outside the project.
Graphify is optional#
Graphify enables memory query, which runs against an isolated snapshot of session, project, and repo memory that is removed afterwards. Install it with uv tool install graphifyy. Relationships can still be added and read with memory add and show without it.
Pruning#
Session directories accumulate as sessions end. captain memory prune — also run automatically, silently, and best-effort at every launch — removes directories where nothing has been touched for --older-than days (7 by default) and Herdr reports no live agent in their panes. When Herdr is unreachable, only directories twice that age are removed.
captain memory prune
captain memory prune --older-than 30