hermes: two-way vault federation of core memory files - #181
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two-way federation between Hermes's core memory files (
SOUL.md,memories/MEMORY.md,memories/USER.md) and their materialized notes inthe Obsidian vault, so edits made in either place converge instead of
silently drifting apart.
vault (operator-edited) version is kept; the agent's version is never
silently discarded — it's copied into a timestamped conflict file first.
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).
previously-synced file, the corresponding volume file is moved to a
quarantine directory rather than deleted outright.
SOUL.mdis exempt from quarantine — if its vaultnote 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.
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.pyscript (delivered and unit-tested in earlier PRs inthis 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
hermesuser (viagosu), matching every other write this entrypoint makes under theagent'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)
a cadence (must be registered as the
hermesuser; the boot-time runhere only covers session start).
edits its identity file going forward.
Validation
pyteston the vault-federation suite: 30/30 passing.ruff check .across the whole repo: clean, no findings.docker build -t ordo/agent-hermes:memfed-validate services/hermes) — completedsuccessfully with the new
COPYlayer applied on top of cached layers.vault_federate.pyis present at itsbaked 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:
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
hermesuser (not root — root-owned cron output bricks laterhermes-user writes the same way root-owned memory files do).
Ordo/Context Files/. That tree is a pre-existing export unrelatedto this bridge; the bridge only reads/writes under
notes/. Thoseroot-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.
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