Concepts
Pibmo Recall is an append-only archive of AI coding sessions kept on machine-local storage. There is no server and no resident daemon: an hourly scheduler unit runs a stdlib Python script straight from the repo checkout, and that script copies what your coding tools already wrote into a dataset root it never shrinks. Everything below is a consequence of that single choice.
Read this page before any command. It fixes the vocabulary the rest of the docs use. When you are ready to run something, go to Getting started and the CLI reference.
Machines and roots
A machine is one computer contributing sessions. It is identified by its
hardware UUID and registered by the installer in {root}/machines.json with a
friendly name; the friendly name becomes that machine’s folder inside the
root, so the archive reads as moraba/, not as a UUID.
A dataset root is the directory the archive lives in. It is the only
machine-local configuration Recall has, and it travels on the scheduler
unit’s command line (--root, or the PIBMO_ROOT environment variable) —
no ~/.pibmo, no dotfile, nothing written into ~/.claude. This
is deliberate and it fails closed: with no root at all, sync refuses to run
rather than guess a location, because a guessed root would scatter archives
nobody knows exist.
$ python3 scripts/sync/sync-transcripts.pyError: no dataset root. Pass --root /path/to/dataset (or set PIBMO_ROOT). The installer prints the exact cron line.There are two kinds of root. The local root is where this machine
archives, and it must be genuinely local disk (e.g. ~/Library/Application Support/pibmo/recall) — the installer refuses a cloud-synced path for it,
because a sync client’s fences and evictions inside the write path are a
silent-failure class, not a convenience. The consolidated
root is the shared directory that joins machines, passed as
--consolidate, and it may sit on a cloud-synced folder — that placement
is replication, chosen with eyes open. A single-computer install needs only
the local root; consolidation never comes up. See
Configuration.
Raw and derived
Recall stores exactly two kinds of data. The split between them is what makes the archive trustworthy.
| Raw archive | Derived output | |
|---|---|---|
| Path | {root}/{machine}/... | {root}/processed/{key}/{profile}/ |
| Written | By sync, on the hour | By the converter, when you run it |
| Ever edited? | Extended on genuine appends, never rewritten | Freely regenerated |
| Ever deleted? | Never | Whenever you like |
| Holds | Each tool’s files exactly as it laid them out | Merged per-project Markdown |
The raw archive is a faithful per-machine mirror of each source tool’s native layout: no renames, no remapping, no reorganisation, ever. Derived output is whatever the deterministic processing layer projects out of raw.
Why the split matters: derived can always be rebuilt from raw, but raw can
never be rebuilt from derived. So the rules are asymmetric on purpose — raw
is accumulate-only and immutable, while everything under processed/ is
disposable. If a derived artifact is wrong, delete it and re-run the
converter; raw was never at risk. That is the whole trick.
What the root looks like
<root>/ machines.json machine registry (uuid -> friendly name) projects.json process-time grouping map profiles.json denylist processing profiles moraba/ one machine's faithful mirror machine.json this machine's identity record pibmo.db provenance: runs, observations, schema claude/projects/<encoded>/ <uuid>.jsonl one session <uuid>.fork-<sha8>.jsonl a divergent rewrite, kept as a sibling <uuid>/subagents/ subagent transcripts gemini/ Gemini CLI and Antigravity codex/ Codex CLI processed/ <key>/<profile>/ derived Markdown -- rebuildableThe root is an ordinary directory of plain files plus one SQLite database per machine. Nothing in it depends on Recall being installed: a reader with a text editor can walk it, and an analysis session can be pointed at it read-only.
The three sources
Sync mirrors the native data directories of three tool families. Exclusions are decided at sync time and remove whole files or directories by name — sync never edits the inside of a file it keeps.
| Source | Native home | Lands under | Excluded at sync |
|---|---|---|---|
| Claude Code | ~/.claude/projects/ | {machine}/claude/projects/ | auto-memory directories — mutable and derived, not raw |
| Gemini CLI + Antigravity | ~/.gemini/ | {machine}/gemini/ | oauth_creds.json, google_accounts.json, extensions/, Antigravity’s browser_recordings/ and bin/ — credentials, app code, media |
| Codex CLI | ~/.codex/ | {machine}/codex/ | auth.json, .tmp/, tmp/, cache/ — credentials and ephemera |
Credential files never enter the archive. Neither do caches, app binaries, or recordings — the archive is transcripts and session data, not a disk image of your home directory.
Appends, forks, and regressions
Each .jsonl file under claude/projects/ is one session, named by the
UUID the tool gave it. On every tick, sync compares each source file to its
archived copy line by line and takes exactly one of three actions:
- Extend. The archived file is a strict line-prefix of the source — a genuine append — so the archive grows by the new lines.
- Fork. The source diverges from the archive — the tool rewrote history,
compaction being the canonical case. The rewrite is preserved as a sibling
{uuid}.fork-{sha8}.jsonl, wheresha8hashes the first divergent line. The original is never written over, the fork name is stable so a growing fork extends rather than multiplying, and a fork that itself diverges chains another.fork-segment. - Skip. The source is shorter than the archive — a regression — and the archive never shrinks.
Which state of a forked lineage is canonical is decided at process time, not by the sync. Nothing in the raw archive is ever deleted.
Projects and profiles
The raw archive knows nothing about your projects — it stores Claude Code’s
encoded paths exactly as found, and the same logical project looks
different on every machine and every checkout. Grouping is a process-time
concern: {root}/projects.json maps encoded paths to logical project
keys, and sync auto-appends newly seen paths to it with process: false so
nothing is converted without a human turning it on. Editing a row’s key or
consolidate_into changes only how the converter groups output — the raw
archive is never rerouted or renamed.
A profile is a denylist the converter applies on the way to Markdown:
default-include, declared exclusions. A profile names what it
drops; anything it does not name is kept, so a new event type appears in
output until a rule excludes it — it never silently vanishes. The shipped
profiles are faithful and dialogue, and output lands at
{root}/processed/{key}/{profile}/. The converter is a format converter, not
an editor: it never summarises, rewrites, or copy-edits a kept block, and the
same input produces byte-identical output.
Provenance and the doctor
Every sync run writes its own paper trail into {root}/{machine}/pibmo.db:
the run itself (a heartbeat the health checks read), a per-file observation
for every action taken, and the JSONL event-type schema collected
incrementally from the lines actually read — so schema drift in a source
tool is recorded, not discovered by a broken converter months later.
The doctor answers one question — is this machine actually archiving? — and every failure it reports prints its exact fix. It checks machine registration, writes a canary through the real pipeline, verifies the scheduler unit and the paths embedded in it, and measures heartbeat age and replication lag. It exists because the failure it hunts is real: a swallowed one-line PATH error once kept a machine from archiving for five weeks while its cron line looked correct. The preservation path now fails loudly, and the doctor watches the watchers.
What happens on the hour
Recall does not schedule itself. The installer writes a native scheduler unit
— a LaunchAgent on macOS, a systemd user timer on Linux — and prints the
activation commands; it never edits your crontab, and it writes nothing
outside the dataset root and the unit file. The unit invokes the sync script
inside the repo checkout, so updating is git pull: the next tick runs the
new code. There is no deployed copy and no self-updater.
Running sync by hand is safe at any time — concurrent runs exit immediately
via a lock. On a multi-machine setup, a --consolidate flag on the same unit
line adds a second, separate step after the local write: an accumulate-only
copy of the machine’s subtree into the shared consolidated root. Local
preservation never waits on transport, and transport never touches the local
root’s contents.
What this does not give you
State it plainly, because each of these is a decision someone should make with open eyes:
- Faithful is not redacted. The exclusion lists strip known credential files by name; they cannot strip a secret pasted into a conversation. A transcript is archived exactly as the session produced it, so anything a session contained is in raw — and while a denylist profile can drop whole events or blocks from derived output by rule, raw keeps everything. Treat access to the archive like access to your source code and shell history.
- One copy is one copy. Recall writes to local disk and stops there. A local root on one drive is exactly one copy until you consolidate to a shared root or replicate it — and replication is deliberately delegated to tools built for it rather than reimplemented inside the preservation path.
- No LLM anywhere in the pipeline. Sync, consolidation, and processing are deterministic and make no API calls. Recall does not summarise your sessions; it preserves them, and projects them into Markdown by rule. Analysis with a model happens in sessions you point at the archive, read-only, on top of the faithful record.
Next
- Getting started — install a machine and watch the first sync land.
- Configuration — roots,
machines.json,projects.json, and profiles. - CLI reference — sync, doctor, and the processing commands.
- Architecture — why it is built this way; the design decisions and their history.
- Troubleshooting and the FAQ.