Skip to content

hermes: two-way vault federation of core memory files - #181

Merged
AlienWalker1995 merged 5 commits into
mainfrom
feat/memory-stack-overhaul
Aug 19, 2026
Merged

hermes: two-way vault federation of core memory files#181
AlienWalker1995 merged 5 commits into
mainfrom
feat/memory-stack-overhaul

Conversation

@AlienWalker1995

@AlienWalker1995 AlienWalker1995 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Summary

Adds two-way federation between Hermes's core memory files (SOUL.md,
memories/MEMORY.md, memories/USER.md) and their materialized notes in
the Obsidian vault, so edits made in either place converge instead of
silently drifting apart.

  • Operator wins: when both sides changed since the last sync, the
    vault (operator-edited) version is kept; the agent's version is never
    silently discarded — it's copied into a timestamped conflict file first.
  • Content-hash manifest: a small JSON manifest tracks the last-synced
    hash per file so the reconciler can tell "only the agent changed"
    (push) apart from "only the operator changed" (pull) apart from
    "both changed" (conflict — operator wins, agent version preserved).
  • Quarantine, not delete: if the operator deletes a vault note for a
    previously-synced file, the corresponding volume file is moved to a
    quarantine directory rather than deleted outright.
  • SOUL protection: SOUL.md is exempt from quarantine — if its vault
    note goes missing, the note is regenerated from the volume copy instead
    of the file being removed, since SOUL.md is the agent's core identity
    file and must never disappear from a missing-note race.
  • A fresh lock file (default: written within the last 5 minutes) on the
    volume side skips reconciliation for that pair entirely, so an in-flight
    agent write never gets clobbered mid-write.

This PR (task 3 of the memory-stack-overhaul series) bakes the
vault_federate.py script (delivered and unit-tested in earlier PRs in
this series) into the Hermes agent image and wires it into the container
entrypoint, so federation runs once at every gateway boot before the
long-running gateway process starts. The script is defensive by
construction: each file pair is reconciled independently (one bad pair
can't block the others) and the whole run is wrapped so it always exits 0
and never blocks container startup, even on unexpected errors.

Boot-time federation is invoked as the unprivileged hermes user (via
gosu), matching every other write this entrypoint makes under the
agent's home directory — the script writes memory files, state, and
manifest data there, and running it as root would leave root-owned files
that the agent process can no longer write to afterward.

Post-merge operator/orchestrator actions (not in this PR)

  • Registering the every-10-minute cron job that runs this same script on
    a cadence (must be registered as the hermes user; the boot-time run
    here only covers session start).
  • Applying memory-file size caps.
  • Adding the SOUL.md ledger directive that governs how the agent itself
    edits its identity file going forward.

Validation

  • pytest on the vault-federation suite: 30/30 passing.
  • ruff check . across the whole repo: clean, no findings.
  • Built the Hermes agent image with this change (docker build -t ordo/agent-hermes:memfed-validate services/hermes) — completed
    successfully with the new COPY layer applied on top of cached layers.
  • Ran the built image and confirmed vault_federate.py is present at its
    baked path and imports/executes cleanly, and confirmed running it
    exactly as the entrypoint does (python3 /opt/ordo/vault_federate.py)
    exits 0 with no vault mounted (the no-op path used before the vault
    bind mount exists on a given host). Validation image tag removed after
    the check.

Follow-ups & disclosures

This PR also lands a fix wave closing seam issues found in the final
whole-branch review (blank-note handling, lock-probe correctness,
quarantine namespacing, a mass-quarantine guard, SOUL-restore
disclosure, stale-manifest cleanup, boot ownership repair, and a boot
timeout ceiling) — see the latest commit for the itemized list.

A few things are called out explicitly rather than left implicit:

  • Runtime steps still land post-merge. As noted above, this PR does
    not register the recurring cron jobs or apply the memory-size/ledger
    policies. Specifically still outstanding: memory-file size caps, the
    SOUL.md ledger directive, the 10-minute federation cron, a nightly
    consolidation cron, and registering all of the above to run as the
    hermes user (not root — root-owned cron output bricks later
    hermes-user writes the same way root-owned memory files do).
  • Rollout must clean up orphaned static copies at the vault ROOT
    Ordo/Context Files/.
    That tree is a pre-existing export unrelated
    to this bridge; the bridge only reads/writes under notes/. Those
    root-level static files are not touched by federation and will look
    like drift if left in place — they should be removed as part of
    rollout, not by this script.
  • Failure posture deviates from spec on purpose, in the safer
    direction.
    Per-pair errors go to stderr (container logs) rather than
    being written into the vault, and any hash mismatch against an unknown
    prior state degrades to conflict_pull (preserve both, operator wins,
    agent version archived) rather than falling back to an mtime
    comparison. Both choices trade a bit of automation for never silently
    destroying an agent-authored file.

🤖 Generated with Claude Code

Hermes Bot and others added 5 commits August 19, 2026 15:45
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copies vault_federate.py into the agent image and invokes it (as the
hermes user, before the config seeding block hands off to the
long-running gateway process) so memory files reconcile with the
Obsidian vault on every boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI (Linux) exposed that the fixture's unheld lock file only satisfies the
Windows mtime fallback; the fcntl probe correctly reports it unlocked. The
test now holds an actual LOCK_EX on POSIX, exercising the real behavior on
both platforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlienWalker1995
AlienWalker1995 merged commit 140bf97 into main Aug 19, 2026
3 checks passed
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.

1 participant