agents-cli · sessions · architecture

Not shared memory.
A local index + SSH query.

Agents (Claude, Codex, Grok, Kimi…) do not share a brain. Each conversation is a transcript file on disk. agents sessions builds a per-machine SQLite + FTS5 index of those files, then answers filtered queries — locally, or on other machines over SSH. This page is grounded in the agents-cli source (lib/session/*).

source phnx-labs/agents-cli core discover · db · remote-list as of 2026-07-20 bench yosemite-s0
SQLite
Local index
WAL · FTS5 · scan ledger
SSH
Cross-device
no shared RAM · no daemon required
~0.45s
Local query
JSON list 50 sessions
~0.75s
One host
SSH + remote index

01The myth vs. the model

When someone asks “how do agents share memory across devices?”, the honest answer is: they don’t. They query.

MYTH one shared agent memory joint embeddings · hive mind REALITY per-agent JSONL on each machine + local SQLite index query via agents sessions · optional SSH fan-out zion transcripts + sessions.db personal laptop yosemite-s0 transcripts + sessions.db worker mac-mini transcripts + sessions.db relay / openclaw SSH query only
Three isolated stores. Cross-device “memory” is running the same CLI query on the peer’s index over SSH.
One sentence An agent “recalling” prior work is agents sessions "topic" --since 7d (or reading a session id as markdown) — the same family of operation as grepping logs, not loading a shared embedding space.

02How indexing works

Every machine that runs agents sessions maintains its own index. Discovery walks agent homes; only changed files are re-parsed; results land in SQLite.

1 · Transcript roots (where files live)

Specs live in lib/session/discover.ts as SESSION_ROOT_SPECS: Claude → projects, Codex → sessions, Gemini → tmp, Kimi → sessions, Droid → sessions, etc. getAgentSessionDirs() expands each to:

Expose the live set with agents sessions --roots --json so external watchers stay in lockstep with the CLI.

2 · Incremental scan (don’t re-parse the world)

JSONL files scan_ledger mtime + size filterChanged only dirty files parse + upsert sessions + session_text FTS discoverSessions() · tryClaimScan(pid) · concurrency 2 · stagger 15ms Active appends are debounced so a growing JSONL is not re-streamed every invocation db.ts · scan_ledger · discover.ts filterChangedFiles / shouldDeferRecentAppend
Incremental indexing. Unchanged mtime/size → skip. Concurrent processes coordinate via a scan claim in the DB meta table.

3 · SQLite schema (the actual index)

Database path: ~/.agents/sessions/sessions.db (WAL, busy_timeout 30s). Defined in lib/session/db.ts.

TableRole
sessionsMetadata: id, agent, project, cwd, topic, label, cost, tokens, PR/ticket, file_path, timestamps
session_text (FTS5)Full-text: label, topic, project, content — BM25 weights label > topic > project > content
scan_ledgermtime/size/scanned_at per file — “did we already look at this?”
metaschema version, scan_in_progress claim, migration flags
// Query path after scan (discoverSessions)
getDB();                          // open/migrate sessions.db
tryClaimScan(pid) → scan agents  // incremental only
sessions = querySessions(filters) // SQL WHERE + ORDER + LIMIT
// Text search:
hits = ftsSearch(query)           // label tiers + FTS5 BM25
Why this is fast Listing is a SQL select on an already-warm index. FTS5 handles content search. The expensive parse only runs for files whose mtime/size changed — so repeated agents sessions calls stay sub-second once the ledger is hot.

03How cross-device works

There is no multi-master memory. Cross-device is run the same query on the peer, over SSH, and merge rows tagged by machine.

you / agent agents sessions --since 7d "auth" gatherRemoteList / runRemoteSessions peer A (e.g. zion) AGENTS_SESSIONS_LOCAL=1 agents sessions … --json peer B (yosemite-s1) own sessions.db same filters · --all peer C offline skipped / cache replay never fatal
Fan-out. Each peer answers for itself. Rows are tagged machine + _remote: true so resume/read route back over SSH.

Modes

--localThis machine’s disk + DB only
--host zionExplicit peer(s); strip --host, forward filters
default listOnline devices from agents devices
import --from-hostCopy transcripts into local mirror
sessions syncOptional R2 CRDT — opt-in beta

Hardening details (code)

  • No recursive fan-out — peer gets AGENTS_SESSIONS_LOCAL=1
  • Whole index over SSHensureWholeIndex adds --all (remote cwd is home, not your project)
  • 12s timeout per host; dead hosts skipped with a gray note
  • Offline cache~/.agents/.cache/remote-sessions/ for explicit --host replays
  • Skip control-only devices (phones/tablets)
  • Windows peers get PowerShell command form
# Explicit peer — runs peer's own index
agents sessions --all --since 7d --host zion --flat

# Fan-out (interactive default) — merge all online machines
agents sessions --all --since 1d --flat

# Stay local (fast scripts / JSON)
agents sessions --all --since 7d --local --json -n 50

# Copy a peer week into a local mirror (tagged by origin machine)
agents sessions import --from-host zion --since 7d
Auth is SSH If machine A’s key is not on machine B, that host is simply skipped (unreachable or no agents CLI). There is no alternate memory channel. Optional R2 session-sync is a separate opt-in path — off by default on most fleets.

04Filters (the recall API)

Filters compile to SQL WHERE clauses in buildSessionWhere and/or FTS MATCH. Same flags work local and remote (forwarded over SSH).

FilterEffectLayer
"search text"Label-first tiers + FTS5 BM25 over contentFTS
--since 7d / --untiltimestamp windowSQL
-a claude / --codexagent (optional @version)SQL
-p agents-cliproject substringSQL
--alldrop cwd scopingquery scope
--teamsinclude team-origin (hidden by default)SQL flag
--activelive processes (separate path + remote-active fan-out)process scan
-n 50 · --sort costlimit + orderSQL
--include user · --last 3when rendering one sessionrender
--json / --markdown / --flatoutput shapeCLI
--artifactsfiles written during the sessionparse
--host / --devicewhere the query executesremote
# Agent mid-turn recall pattern
agents sessions --all --since 7d "secrets unlock" --flat -n 20
agents sessions a1b2c3d4 --markdown --include user,assistant --last 5
agents sessions --active --host zion

05Speed — measured, not claimed

Wall-clock on yosemite-s0 (agents-cli 1.20.x), three warm runs each. Local = index hit. Host = SSH + peer index.

CallTimes (3×)
--local --json --since 7d -n 500.47 · 0.45 · 0.45 s~0.45s
--local --flat --since 7d -n 500.85 · 0.78 · 0.83 s~0.8s
search "auth" local 7d0.84 · 0.80 · 0.85 s~0.8s
--active --local0.63 · 0.62 · 0.64 s~0.6s
--markdown --last 3 one id0.50 · 0.50 · 0.50 s~0.5s
--host yosemite-s1 1d -n 200.74 · 0.77 · 0.74 s~0.75s
default multi-host fan-out 1d1.81 · 1.80 · 1.81 s~1.8s
So what? Local list/search is comfortably under a second. Single-host SSH stays under a second on a healthy tailnet. Full fan-out is ~2s of parallel SSH — still fine mid-conversation. That is why agents can “check sessions” without stalling a turn.

06SQLite index — live on a worker

Investigated on yosemite-s0 after a real agents sessions call. Path from lib/state.ts: ~/.agents/.history/sessions/sessions.db.

15.1 MB
DB size
sessions.db on disk
2,054
sessions rows
metadata index
2,046
FTS docs
session_text (FTS5)
2,039
scan_ledger
mtime/size stamps
FactValue
Path~/.agents/.history/sessions/sessions.db
Schema version12 (meta table)
JournalWAL · busy_timeout 30s · concurrent writers OK
Core tablessessions, session_text (FTS5 + shadow tables), scan_ledger, meta
sessions columnsid, short_id, agent, version, account, timestamp, last_activity, project, cwd, topic, label, message_count, token_count, cost_usd, duration_ms, file_path, pr_*, ticket_id, plan, …
# Inspect yourself
sqlite3 ~/.agents/.history/sessions/sessions.db \
  "SELECT COUNT(*) FROM sessions; SELECT value FROM meta WHERE key='schema_version';"

# FTS smoke test (same engine ftsSearch uses)
sqlite3 ~/.agents/.history/sessions/sessions.db \
  "SELECT session_id FROM session_text WHERE session_text MATCH 'auth*' LIMIT 5;"
What the DB is not It is not the source of truth for conversation content long-term — the JSONL (or agent session dir) is. The DB is a query cache / index: rebuildable by re-scanning roots. Export/import move the transcript files; the next agents sessions on the receiving machine re-indexes them.

07Export & import — portable bundles

Yes. Explicit hand-off without requiring R2 sync. Format: NDJSON bundle (lib/session/bundle.ts, RUSH-1710/1711).

export select → NDJSON bundle agents-session-bundle v1 header + one line per file · optional AES secrets redacted by default · mode 0600 import mirrorPath by origin machine Local always wins · byte-exact dups skipped · conflicts need --overwrite
Portable hand-off. Bundle carries transcript bodies; import places them under the sync mirror tree keyed by origin machine, then the local scanner indexes them.
CommandWhat it does
agents sessions export --since 7d -o week.bundleSelect via same filters as list; write NDJSON archive
export … --stdoutPipe over SSH
export … --encryptAES-256-GCM per body
export --host zion …Run export on peer, stream bundle back
import week.bundlePlace into backups/<agent>/<originMachine>/…
import --dry-runPlan only: new / dup / conflict
import --from-host peer --since 7dexport on peer + import here in one shot
import - --decryptstdin pipe + decrypt
# Proven on yosemite-s0 just now
agents sessions export --all --since 7d --claude -n 2 -o /tmp/demo.bundle
# → Exported 2 sessions (2 files, redacted)
# header: kind=agents-session-bundle version=1 redacted=true

agents sessions import /tmp/demo.bundle --dry-run
# → 2 sessions · status new · (dry run — nothing written)

# One-liner peer pull
agents sessions import --from-host zion --since 7d

# Encrypted pipe
agents sessions export --since 7d --stdout --encrypt \
  | agents ssh boxB 'agents sessions import - --decrypt'
vs. live --host query Query (--host / fan-out) never copies files — peer answers from its own index. Import materializes transcripts under a machine-tagged mirror so you can search them offline later. Neither is “shared memory”; both are deliberate tools.

08Code map (agents-cli)

FileResponsibility
commands/sessions.tsCLI entry, flags, listing UI, routing to local/remote
lib/session/discover.tsTranscript roots, per-agent scanners, incremental filter, machine tagging
lib/session/db.tsSQLite schema, scan ledger, querySessions, FTS5, scan claim
lib/session/remote-list.tsDefault listing fan-out: SSH peers, merge SessionMeta[], _remote
lib/session/remote.tsExplicit --host: forward args, ensureWholeIndex, cache, outcomes
lib/session/remote-active.tsFan-out for --active
lib/session/sync/*Optional R2 CRDT mirror (beta, off by default)
lib/session/bundle.tsexport / import portable archives
# Prove the index roots on any machine
agents sessions --roots --json --local

# Prove cross-device is live query, not sync
agents sessions sync --status
# → often: automatic sync disabled · no shared cloud brain
For friends who only want the takeaway Agents write files. Each computer indexes its own files into SQLite+FTS5. agents sessions is the query tool. Cross-device = SSH the same query to the peer’s index. Fast enough to use mid-turn. Not shared memory.
agents sessions · indexing & cross-device · grounded in phnx-labs/agents-cli lib/session/{discover,db,remote,remote-list}.ts · measured 2026-07-20 · public share for friends · ◐ theme toggle