Skip to content

feat(integration): hermes backfill, install command, and docs - #1

Closed
raman325 wants to merge 13 commits into
feat/hermes-provider-corefrom
feat/hermes-backfill-install
Closed

feat(integration): hermes backfill, install command, and docs#1
raman325 wants to merge 13 commits into
feat/hermes-provider-corefrom
feat/hermes-backfill-install

Conversation

@raman325

@raman325 raman325 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Second half of the MemPalace#1684 split per @igorls — backfill, the mempalace hermes install command, and docs. Stacks on MemPalace#1915 (feat/hermes-provider-core); only the last two commits are new here, and the base will be retargeted to develop once that one merges.

What this adds

One command after pip install mempalace:

mempalace hermes install

Files land in ~/.hermes/plugins/mempalace/ (the canonical user-installed plugin directory Hermes' discover_memory_providers() scans) with a plugin.yaml manifest. ~/.hermes/config.yaml gets memory.provider: mempalace. The command pip-installs mempalace into the Hermes venv, and optionally backfills existing Hermes session exports into the palace (--skip-backfill to opt out, --yes for scripted installs).

Also: mempalace/integrations/hermes/README.md (install, hooks, tool surface, and why the ChromaBackend path avoids dimension mismatches) and a top-level README pointer. pyyaml becomes a runtime dependency for the config.yaml edit.

How the review blockers were addressed

  • Live/historical ingest paritybackfill.file_exchange routes through convo_miner.file_conversation_exchange(), the exact write path the live provider's _file_turn uses, so historical and live drawers carry identical routing, normalization, and metadata. authored_at is stamped from the session file's mtime, so date filters reflect when the conversation happened, not when the backfill ran.
  • One wing-routing implementationbackfill.classify_wing delegates to the provider's _match_wing_by_keywords instead of maintaining a hand-synced copy; a parity test guards that the delegation survives the spec_from_file_location loading mode the install flow uses.
  • No backend bypass — backfill opens its collection once via ChromaBackend.get_or_create_collection() (same construction path as the runtime provider), never a raw PersistentClient.
  • Re-run idempotency — exchange drawer ids are salted with filed_at, so re-filing mints fresh ids rather than overwriting. Each session file is guarded with palace.file_already_mined(..., extract_mode="exchange") — the same per-source check the convo miner uses — so re-running mempalace hermes install doesn't duplicate previously backfilled exchanges. Regression test runs backfill twice and asserts the drawer count holds.

Known trade-off

Live turns use source_file="hermes-session:{id}" while backfill uses the real session file path, so a session that was partially live-filed and later backfilled would not cross-dedup. Backfill targets pre-install history, so overlap should be rare — but it's a deliberate trade-off, not an oversight. Happy to tighten this if you'd rather key both on one convention.

Tests

24 tests in tests/test_hermes_install_cli.py: the path-resolution helpers (--hermes-home guardrails, venv interpreter discovery, palace path precedence), atomic config.yaml editing (including the scalar-memory: and malformed-YAML cases), wing-routing parity, and the backfill re-run guard.

Copilot AI review requested due to automatic review settings July 2, 2026 18:49
@raman325 raman325 changed the title feat(hermes): backfill, install command, and docs feat(integration): hermes backfill, install command, and docs Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the Hermes integration “second half” by shipping a one-command installer (mempalace hermes install), a historical-session backfill script, and end-user documentation so Hermes can discover MemPalace as a native memory provider.

Changes:

  • Add mempalace hermes install command helpers to install the Hermes plugin, update ~/.hermes/config.yaml, and optionally run backfill.
  • Add Hermes backfill implementation + a comprehensive unit test suite for the install helpers and idempotent backfill behavior.
  • Add Hermes integration documentation (top-level README pointer + integration README), and bump dev ruff version in uv.lock.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
uv.lock Bumps dev ruff pin from 0.15.180.15.20.
tests/test_hermes_install_cli.py New tests covering Hermes install helper behavior and backfill idempotency/parity.
README.md Adds a top-level Hermes integration section and pointer to integration docs.
mempalace/integrations/hermes/README.md New integration guide: install, hooks, tool surface, configuration, and rationale for ChromaBackend usage.
mempalace/integrations/hermes/backfill.py New backfill CLI/script for mining Hermes session exports into the palace using the canonical convo exchange path.
mempalace/integrations/hermes/__init__.py Updates wing-routing helper docstring to reflect shared implementation used by backfill.
mempalace/cli.py Adds Hermes install helpers + hermes install subcommand wiring and YAML-based config editing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mempalace/cli.py
Comment thread mempalace/cli.py
Comment thread mempalace/integrations/hermes/backfill.py
Comment thread mempalace/integrations/hermes/backfill.py
Comment thread mempalace/integrations/hermes/backfill.py
@raman325
raman325 force-pushed the feat/hermes-backfill-install branch from 385f9ee to 3c7cf91 Compare July 2, 2026 19:04
raman325 and others added 13 commits July 6, 2026 10:21
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add _is_real_user_message, _turn_fingerprint_from_messages, and
_segment_turns module-scope helpers to the Hermes provider. These
helpers distinguish Anthropic tool-result user messages from real
user turns, fingerprint turn anchors using make_turn_fingerprint,
and segment raw message lists into (user, assistant, turn_fp) dicts
for the dedup safety-net scan path in on_session_end/on_pre_compress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When `messages` is passed to `sync_turn`, compute `_turn_fingerprint_from_messages`
and carry the result as `turn_fp` in the worker payload. `_file_turn` then
writes `turn_fp` into the drawer's extra metadata so future session_end /
pre_compress safety nets can correlate already-filed turns by fingerprint
without re-filing duplicates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add _session_lineage to MempalaceProvider to record ancestor session ids
whose drawers cover an inherited transcript. Grows on /branch (parent
carries forward), cleared on /reset and /resume (stale lineage would
false-dedup against unrelated drawers — failure direction is always
duplicate, never lose), and preserved through rewinds (same conversation).

Also applies four small review-feedback items: empty-list guard in
_is_real_user_message test, fingerprint call-site consistency test,
clarifying comment above the empty-anchor early return in
_turn_fingerprint_from_messages, and a separator comment in _segment_turns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…press

on_session_end and on_pre_compress now enqueue _file_missing_exchanges,
which scans the session's existing drawers (plus /branch ancestors) and
files only turns sync_turn missed. Coverage tracked by turn_fp fingerprint
(survives representation differences) with exact-text fallback. Empty
session_id or scan failure disables dedup — failure direction is always
duplication, never data loss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d tests

The dedup scan now tracks (turn_fp, text) pairs per physical drawer and
every skip consumes one whole drawer atomically across both match
indexes. Independent fp/text counters let a single drawer protect two
window occurrences of the same turn (fp match leaving an orphaned text
count, or vice versa) — under-filing, i.e. a lost turn occurrence.

Also: document the lineage snapshot-at-enqueue invariant and the Hermes
reason-string coupling; cover branch-without-parent lineage clearing;
de-StopIteration the fingerprint consistency test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A greedy single-pass match let an earlier segment's fingerprint-donor
arm steal the drawer a later segment would have exact-matched —
re-filing the covered turn while silently losing the missed one
(interrupted 'continue' followed by a retried 'continue' reproduced
it). Matching now runs exact-pair matches for every segment first,
then fingerprint groups, then text groups; when same-fingerprint
occurrences outnumber their drawers we file them all — bounded
duplication of a covered turn over any possible loss. Loss paths now
log at warning like the duplication paths, and session_end uses a
bounded blocking enqueue since the session offers no later retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fingerprint coverage cannot see occurrences compression removed from
the window: a byte-identical prompt recurring after its synced
occurrence was compressed away gets fp-skipped, and if the recurrence
was interrupted its partial assistant output is not captured (the
user's words are already stored). Undecidable with occurrence counting
alone — Hermes message dicts carry no per-message identity — and
dropping fp-skip would re-file every tool-shaped turn. Documented in
_file_missing_exchanges and pinned by test so any future correlation
improvement surfaces loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fety net

Windows CI caught the safety net losing a turn: on_session_end spawned
the wake-up-cache refresh (a ChromaDB reader) concurrently with the
worker's safety-net writes, sqlite locked, and the upsert failed with
only a warning. Two defenses: _file_turn retries 'database is locked'
with backoff before giving up, and the wake-up refresh is routed
through the worker queue BEHIND the safety-net task so the provider's
own reader can never race its writes (direct-thread fallback only when
the queue is full, where the retries absorb the race).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second half of the MemPalace#1684 split — stacks on the provider core branch.

Review-driven changes vs the original branch:
- backfill.file_exchange routes through
  convo_miner.file_conversation_exchange, the same canonical write path
  live _file_turn uses, so historical and live drawers carry identical
  routing, normalization, and metadata (and authored_at now reflects
  the session file's mtime instead of the backfill run time).
- backfill.classify_wing delegates to the provider's
  _match_wing_by_keywords instead of maintaining a synced copy; the
  parity test now guards the delegation surviving path-based loading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exchange drawer ids are salted with filed_at, so re-filing a session
mints fresh ids instead of overwriting — every re-run of
'mempalace hermes install' duplicated all previously backfilled
exchanges. Guard each session file with palace.file_already_mined
(extract_mode="exchange"), the same per-source idempotency check the
convo miner uses.

Also extracts the by-path backfill module loader in the install CLI
tests into a shared helper for the new re-run regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@raman325

raman325 commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Moved upstream as MemPalace#1942 (rebased onto feat/hermes-filing-dedup, draft while the stack under it merges).

@raman325 raman325 closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants