A Secure Hermes and Mnemosyne Memory Architecture Hermes Agent and Mnemosyne's memory integration lacks ownership controls, so a new architecture assigns each system a narrow job and adds a bridge that verifies all data before storage. The design disables raw transcript capture, makes Mnemosyne the only durable fact store, and uses project binding, trajectory extraction, deterministic verification, mutation staging, and fingerprint deduplication to prevent failed commands, context leaks, and unverified prose from corrupting memory. Giving an AI agent long-term memory sounds simple: save conversations, embed them, and retrieve the nearest matches later. That design fails when the agent records a failed command as a solution, leaks context across projects, keeps both sides of a correction, or treats its own confident prose as verified truth. Hermes Agent and Mnemosyne already have most of the machinery needed to avoid those failures. Hermes has exact session history, tools, lifecycle hooks, skills, profiles, and provider plugins. Mnemosyne provides local SQLite-backed durable memory, hybrid retrieval, canonical facts, provenance, and consolidation. Hermes's provider contract can pass the completed turn, including tool calls and tool results, to sync turn ..., messages=... . 1 Mnemosyne can run locally in one SQLite database without requiring an external memory service. 4 The missing piece is ownership. Each system needs a narrow job, and the bridge between them must distrust anything that has not been verified. The architecture in one diagram The project binding, trajectory extraction, deterministic verification, mutation staging, and fingerprint deduplication in this diagram are custom bridge behavior. The stock Hermes and Mnemosyne integration does not add those controls by itself. The ownership rules are strict: | Information | Authoritative home | |---|---| | Agent behavior and policy | Profile instructions and Hermes configuration | | Project truth | Repository files, Git, plans, tests, and documentation | | Exact conversation history | Hermes session database and session search | | Durable facts and preferences | Mnemosyne | | Reusable verified experience | Mnemosyne execution episodes | | Procedures | Hermes skills | | Commitments and current progress | Task system or project tracker | | Temporary working notes | Session todo or scratchpad | | Credentials | Secret manager or environment, never memory | This separation matters more than the embedding model. A better retriever cannot repair a system that stores the wrong material or assigns authority to the wrong layer. Why raw transcript memory is the wrong default The easiest integration is to save every user and assistant turn. I disabled it. hermes config set memory.mnemosyne.sync roles ' ' Raw transcripts contain setup chatter, abandoned ideas, copied logs, test canaries, prompt injections, stale task state, and sometimes secrets. They are also poor memory units. A 4,000-word debugging turn may contain one useful fact: a particular failure had a particular cause, and one change fixed it. Hermes already keeps exact session messages in SQLite with FTS5 search. 2 Duplicating those messages in Mnemosyne adds noise without adding evidence. Durable memory should hold compact facts and outcomes. Session history should preserve the full record. Disabling transcript capture does create one gap. A durable no-tool statement such as: I now prefer Python over TypeScript for backend services. must not disappear because no tool trajectory occurred. The operating rule is narrow: This is intentional capture, not transcript ingestion. If the statement is ambiguous, temporary, or inferred, the agent should stage it or omit it. Make Mnemosyne the only durable fact store Running two general-purpose memory stores produces duplicate and eventually contradictory truth. After migration and recall verification, disable Hermes's built-in fact and profile files while keeping the memory toolset enabled: hermes config set memory.memory enabled false hermes config set memory.user profile enabled false hermes config set memory.mnemosyne.profile isolation true Do not run this during initial installation without a rollback. The safer sequence is: - Back up the existing MEMORY.md , USER.md , and Mnemosyne database. - Move stable one-current-value facts into canonical slots. - Move other curated durable facts into ordinary memory. - Verify recall from a fresh Hermes process using paraphrased queries. - Disable the legacy stores only after the new path works. Keep the Hermes memory toolset enabled. Mnemosyne's Hermes guide notes that disabling the memory toolset also removes provider tools. 3 The bridge should learn from evidence, not prose Hermes can give a memory provider the full completed turn: user message, assistant tool calls, tool results, and final response. 1 The bridge inspects that trajectory after the turn finishes and creates no more than one episode. A turn is worth keeping when it contains at least one of these: - a failure with a discovered cause; - a fix followed by verification; - a user correction; - an architectural decision; - a reusable constraint; - a materially verified outcome. Routine navigation, file listing, status chatter, and unverified success claims are discarded. A compact episode can use this shape: { "task": "Run the provider acceptance suite", "context": "Hermes memory-provider extension", "observation": "The clean-wheel test could not import the entry point", "decision": "Declare the provider under hermes agent.memory providers", "action": "Added the package entry point and rebuilt the wheel", "outcome": "The clean environment loaded the provider", "verification": { "type": "test result", "command": "pytest -q tests/test entrypoint.py", "exit code": 0, "predicate": "target test collected and passed" }, "applicability": "Packaged Hermes memory providers", "metadata": { "project id": "git:sha256:...", "source session": "...", "source turn": "...", "veracity": "tool", "verified": true, "fingerprint": "sha256:..." } } The bridge may summarize deterministic evidence, but it may not promote its own summary to truth. verified=true is allowed only when the trajectory contains evidence such as: - a process exit status; - a test assertion or test result; - an exact file read-back; - an authoritative API response. A zero exit code proves only that the process exited successfully. The claimed outcome also needs an objective-specific predicate: the named test passed, the expected artifact exists and matches, or the API read-back contains the expected state. A model saying "the deployment succeeded" is not verification. A subagent saying "all tests pass" is not verification either. The parent must inspect the actual result. Project isolation has to survive new sessions Plain session scope is too narrow for reusable experience. A lesson from today's repository should help tomorrow's session in the same repository. Global scope without filtering is too broad because it can inject one project's commands and assumptions into another. The workable design is: The project identity must come from trusted initialization context, not from model-controlled tool arguments. Select one repository identity at provider startup, normally a credential-free host and repository path from origin . Map supported SSH and HTTPS forms to the same host and path, remove a terminal .git , lowercase the host, and preserve repository-path bytes and case unless that host has an explicit policy. Forks remain separate because their paths differ. Worktrees use the common repository identity; submodules bind independently. Hash the resulting canonical bytes instead of storing the remote directly: php from hashlib import sha256 def project id canonical remote: bytes - str: digest = sha256 b"git-remote\0" + canonical remote .hexdigest return f"git:sha256:{digest}" Bind it once when the provider initializes. Do not rebind the active project because a later shell command happens to contain another path. Never retain credentials that appeared in a remote URL. If no trusted repository identity exists, leave the session unbound and skip project episode creation. Every retrieval path must enforce the same filter. Protecting silent prefetch while leaving explicit recall unfiltered still leaks memories. Filtering only the first line of a multiline result still leaks memories. The tests need same-project recall and foreign-project exclusion for both paths. Ordinary user facts are different. A communication preference may be globally relevant and should remain recallable across projects. Project filtering applies to execution episodes, not every global memory. Corrections need replacement semantics Append-only memory fails when facts change. If a user corrects a consulate preference, project milestone, dependency version, or deployment state, the old value should not remain equally current. Use canonical slots for facts that should have exactly one live value: preference.communication professional.active project professional.current milestone Keep values that change at different rates in different slots. Updating a milestone should not rewrite the identity of the project itself. A correction flow should: - Read the current target. - Confirm the user's exact correction. - Write the replacement with veracity="stated" and provenance. - Supersede or invalidate the old current value. - Read the new value back. Do not append a contradictory memory and hope vector ranking chooses the newer one. Durable mutation must fail closed A generic "write approval" switch is unsafe if it covers remember but misses canonical updates, invalidation, deletion, triples, shared memory, or pending-write application. Inventory every mutation route before claiming approval coverage. The policy I use is: | Mutation | Default policy | |---|---| | Raw conversation autosave | Disabled | | Verified project episode | Automatic after evidence, secret, scope, and dedup gates | | Direct durable user fact | Foreground write with read-back | | Inferred global or canonical fact | Stage | | Canonical supersession | Exact foreground request or stage | | Update, invalidate, or forget | Read target first; exact foreground approval | | Shared-memory write | Disabled | | Skill modification | Report and staged diff only | | Secret-bearing content | Reject | The secret gate is defense in depth, not a guarantee. Pattern matching will miss some credentials and sensitive data. Reject the whole episode when practical, redact only when the remaining record is still useful, and test database rows, logs, exports, pending writes, and error paths. I use the same trust-boundary principle described in my AgentMask field note /context-level-secret-isolation-for-ai-coding-agents-with-agentmask/ : keep secrets out of model and memory context instead of trying to clean them up later. Pending approvals should be owner-only, content-addressed, expiring, and single-use. The approval command should bind to an exact ID: APPLY