Two memory systems run side-by-side on this host today and never share state. This page maps both with exact paths, identifies the single operational link that exists, and proposes three phased designs to unify them. Cross-refs: OMP · OmpKeep.
1. 🧭 Overview
| System | What it is | Canonical form | Governance |
|---|---|---|---|
| OmpKeep | Persistent, auditable long-term memory for the OMP/pi coding agent (the pi-persistent-intelligence package). | JSONL (L1/L2/L3) + SQLite FTS5 + optional qmd semantic index | Patch-governed; evidence + trust gated |
| Hermes | Built-in turn-budget memory for the Hermes agent (MEMORY.md / USER.md). | Plain markdown, char-budget limited | Free-write, flush-on-turns; no audit trail |
They occupy different roots (~/.pi/agent/pi-memory/ vs ~/.hermes/memories/), use different formats, and have no read/write path between them. The only connective tissue is Hermes’ omp-suite plugin dispatching omp acp subprocesses (see §4) — process orchestration, not memory sharing.
2. 🗃️ OmpKeep
Source repo: /home/dv/pi-persistent-intelligence (README.md, index.ts).
OMP integration: /home/dv/.omp/agent/extensions/ompkeep.ts — a TUI/extension layer that statically imports the core logic from ../../../pi-persistent-intelligence/src/* (paths, render, procedure-candidates, …; see ompkeep.ts:99-123).
Root resolution (src/paths.ts → defaultRoot() / resolveRoot()), highest → lowest precedence:
OMP_MEMORY_ROOTOMPKEEP_ROOTPI_MEMORY_ROOT- legacy
~/.pi/agent/pi-memory/(used if it exists) - fallback
~/.omp/agent/omp-memory/
The env-var precedence at the top of this list is the hook the §5 shared-store design relies on.
Live canonical store (verified): /home/dv/.pi/agent/pi-memory/
memory/
L1.identity.jsonl # identity records — never auto-applied
L2.playbooks.jsonl # patch-governed durable rules
profiles.jsonl
evidence.jsonl # content-addressed, bounded excerpts
reinforcement.jsonl
inquiries.jsonl
tombstones.jsonl # content-free deletion markers
projects/dv.jsonl # project-scoped records
daily/ YYYY-MM-DD.md # L3 session logs (freely writable)
inbox/ captured.jsonl # long_term candidates awaiting curation
patches/ patch_*.json # governed mutations
rendered/ MEMORY.md # markdown projection (NOT canonical)
runtime/ context.md, selected_memory.json, injection-stats.json
search/ memory-fts.db # SQLite FTS5 index
sessions/ session-index.jsonl + summaries/Layers (README.md): L1 Identity (ratification only), L2 Playbooks (confidence/evidence gated patches), L3 Session (daily logs, digest-injected). Markdown is a rendered projection — canonical memory is JSONL.
Search stack (verified):
- Keyword — built-in SQLite FTS5 over
bun:sqlite, zero external deps (src/search/fts.ts). - Semantic — delegates to the external
qmdbinary viaexecFile(src/qmd.ts). - Deep / hybrid — Reciprocal Rank Fusion of FTS + semantic, weights FTS
0.45· Semantic0.55,RRF_K = 60(src/search/hybrid.ts).
Tools / commands: memory_write (target=daily appends; target=long_term → inbox candidate), memory_read, memory_search (keyword|semantic|deep), scratchpad, session_*; /curate-memory, /render-memory, /memory-handoff [--goal], /memory-diagnostics.
⚠️ Distinct, do not confuse:
/home/dv/.omp/agent/memories/--home-dv--/(MEMORY.md,memory_summary.md,raw_memories.md,skills/,rollout_summaries/) is the OMP harness’ own built-in summary memory, a separate markdown store — not the OmpKeep JSONL root above.
3. 📓 Hermes
Root: /home/dv/.hermes/.
Config — config.yaml memory: block (lines 338-345, verified):
memory:
flush_min_turns: 6
memory_char_limit: 2200
memory_enabled: true
nudge_interval: 10
provider: ""
user_char_limit: 1375
user_profile_enabled: trueStore: /home/dv/.hermes/memories/
MEMORY.md(~2.1 KB) — operational learnings, records separated by a§delimiter.USER.md(~1.2 KB) — user profile / preferences, same§delimiter.
This is plain markdown under a hard char budget (memory_char_limit: 2200, user_char_limit: 1375), flushed every ≥6 turns. There is no JSONL canon, no FTS index, no evidence/trust/patch governance, and no audit trail — the inverse of OmpKeep’s model.
4. 🔌 Current Bridge
The only existing link is Hermes’ omp-suite plugin, which orchestrates OMP subprocesses through a Routa lane workflow:
- Plugin:
/home/dv/.hermes/plugins/omp-suite/· domain DB/home/dv/.hermes/omp-suite/routa.db(SQLite, WAL). - The Dev lane auto-dispatches with
dispatch_command: "omp acp"(plugins/omp-suite/dashboard/routes/lanes.py:29). - Spawn path
_spawn_omp_acp(plugins/omp-suite/dashboard/routes/acp.py:40-52): buildscmd = [OMP_CMD, "acp"](+--modelif set) and launches it withenv={**os.environ, "OMP_HOME": ...}.
This is process dispatch, not shared memory. The spawned omp acp session reads/writes its own OmpKeep store; Hermes passes no memory snapshot into the subprocess, and nothing flows back into ~/.hermes/memories/. The two memories stay siloed even while one agent drives the other.
5. 🧪 Proposals
Three designs, each expressed as ordered Steps (no durations). (b) is recommended.
5a. ↔️ Sync bridge
A periodic projector that keeps both stores loosely consistent without merging them.
- Step 1 — Extract. Parse the
§-delimited records from~/.hermes/memories/MEMORY.mdandUSER.md.[INFERENCE]a small adapter normalizes each§block into an OmpKeep candidate shape. - Step 2 — Project Hermes → OmpKeep. Feed each block through
memory_write target=long_termso it lands ininbox/captured.jsonlas a governed candidate (trust classagent_inference/repository_text) — never auto-applied, surfaced at next/curate-memory. - Step 3 — Render OmpKeep → Hermes. Read OmpKeep’s selected memory (
runtime/selected_memory.json) / renderedMEMORY.md, then emit a budget-trimmed§digest back into~/.hermes/memories/MEMORY.mdhonoringmemory_char_limit: 2200. - Step 4 — Schedule.
[INFERENCE]drive the projector from a Hermes cron tick (~/.hermes/cron/) or OmpKeep session-end hook; dedupe on normalized key so re-runs are idempotent.
Trade-off: lowest blast radius, but two stores of record persist and can drift between syncs.
5b. 🏆 Shared store (recommended)
Collapse the silo by pointing both agents at one governed JSONL root.
- Step 1 — Pick the canon. Use the existing OmpKeep root
/home/dv/.pi/agent/pi-memory/(or a neutral~/.shared-memory/). - Step 2 — Repoint OMP. Export
OMP_MEMORY_ROOT(orOMPKEEP_ROOT) for OMP sessions — already a verified first-class hook insrc/paths.ts:22-23,43-44; no code change required for the OMP side. - Step 3 — Teach Hermes the JSONL contract.
[INFERENCE]replace Hermes’ directMEMORY.md/USER.mdwrites with calls into OmpKeep’s write path (memory_writesemantics) so Hermes writes governed candidates instead of free markdown.user_profile_enabledmaps onto OmpKeep L1/profile records (review-gated). - Step 4 — Read via projection. Hermes loads its in-context budget from OmpKeep’s rendered projection (
rendered/MEMORY.md+runtime/selected_memory.json), still clamped tomemory_char_limit/user_char_limit. - Step 5 — Verify. Confirm both agents hit the same root (
/memory-doctor), FTS5 stays in sync after writes, and Hermes records appear ininbox/captured.jsonlunder curation.
Trade-off: one source of record, full audit + dedup + tombstones for both agents. Cost: Hermes must adopt the candidate/patch flow instead of free-writing markdown.
5c. 💉 ACP-time injection
Make the existing §4 bridge memory-aware at dispatch.
- Step 1 — Snapshot. Before spawning, generate an OmpKeep handoff via
/memory-handoff [--goal <task>](or the underlying snapshot API) scoped to the Dev-lane task. - Step 2 — Attach. In
_spawn_omp_acp(acp.py:40-52),[INFERENCE]pass the snapshot into the child — write it to a temp file and addOMPKEEP_HANDOFF=<path>alongside the existingOMP_HOMEenv, or stream it as the first ACP prompt turn. - Step 3 — Consume. The
omp acpsession loads the snapshot as task context so the dispatched worker starts with the parent’s relevant memory instead of a cold store. - Step 4 — Return path (optional).
[INFERENCE]on lane completion, fold the session’s newlong_termcandidates back through the §5a projector into Hermes.
Trade-off: smallest change to the live workflow and immediately useful for delegated tasks, but per-dispatch only — it doesn’t unify the resting stores.
6. 🗺️ Topology
flowchart LR subgraph OMP["OMP Agent + OmpKeep"] OK["OmpKeep JSONL canon (~/.pi/agent/pi-memory)"] FTS["SQLite FTS5 + qmd RRF"] OK -->|"indexes"| FTS end subgraph HERMES["Hermes Agent"] HM["MEMORY.md / USER.md (char-budget)"] SUITE["omp-suite (routa.db)"] end SUITE -->|"dispatch 'omp acp' (process only)"| OMP HM -. "5a: project to long_term candidates" .-> OK OK -. "5a: render budget digest back" .-> HM HM == "5b: repoint via OMP_MEMORY_ROOT" ==> OK SUITE -. "5c: attach /memory-handoff snapshot" .-> OMP
Solid arrow = the only link that exists today; dashed/thick = the three proposals above. See also OMP and OmpKeep.