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

SystemWhat it isCanonical formGovernance
OmpKeepPersistent, auditable long-term memory for the OMP/pi coding agent (the pi-persistent-intelligence package).JSONL (L1/L2/L3) + SQLite FTS5 + optional qmd semantic indexPatch-governed; evidence + trust gated
HermesBuilt-in turn-budget memory for the Hermes agent (MEMORY.md / USER.md).Plain markdown, char-budget limitedFree-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.tsdefaultRoot() / resolveRoot()), highest → lowest precedence:

  1. OMP_MEMORY_ROOT
  2. OMPKEEP_ROOT
  3. PI_MEMORY_ROOT
  4. legacy ~/.pi/agent/pi-memory/ (used if it exists)
  5. 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 qmd binary via execFile (src/qmd.ts).
  • Deep / hybrid — Reciprocal Rank Fusion of FTS + semantic, weights FTS 0.45 · Semantic 0.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: true

Store: /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): builds cmd = [OMP_CMD, "acp"] (+ --model if set) and launches it with env={**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.md and USER.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_term so it lands in inbox/captured.jsonl as a governed candidate (trust class agent_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) / rendered MEMORY.md, then emit a budget-trimmed § digest back into ~/.hermes/memories/MEMORY.md honoring memory_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.

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 (or OMPKEEP_ROOT) for OMP sessions — already a verified first-class hook in src/paths.ts:22-23,43-44; no code change required for the OMP side.
  • Step 3 — Teach Hermes the JSONL contract. [INFERENCE] replace Hermes’ direct MEMORY.md/USER.md writes with calls into OmpKeep’s write path (memory_write semantics) so Hermes writes governed candidates instead of free markdown. user_profile_enabled maps 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 to memory_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 in inbox/captured.jsonl under 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 add OMPKEEP_HANDOFF=<path> alongside the existing OMP_HOME env, or stream it as the first ACP prompt turn.
  • Step 3 — Consume. The omp acp session 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 new long_term candidates 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.