Skip to content

Build gated memory backend as a Hermes pluggable MemoryProvider (improve #141 wire demo) #147

@hanwencheng

Description

@hanwencheng

Summary

Build the gated memory backend decided in PR #146 (Position C — AgentKeys owns the encrypted, per-actor, cap-gated, audited store + gate; the ranking engine is pluggable) and expose it to Hermes as a pluggable MemoryProvider, so that Hermes recalls/persists memory through AgentKeys' IAM gate.

The deliverable improves the demo shipped in #141 (agentkeys wire + agentkeys hook — IAM-guarantee hooks for Hermes; harness/phase1-wire-demo.sh): the wire demo currently proves AgentKeys hooks fire around Hermes; this issue makes the memory data plane real and namespace-isolated, with Hermes' own MemoryProvider lifecycle hooks driving it.

Why now

The integration shape

Hermes agent loop
  │  prefetch(query)         ──►  AgentKeys memory.get(actor, namespace, query)   ──┐
  │  sync_turn(user, asst)   ──►  AgentKeys memory.put(actor, namespace, line)    ──┤  cap-token gated
  │  system_prompt_block()   ──►  AgentKeys profile snapshot                        ──┘  (op + namespace + audit)
  ▼
AgentKeys MemoryProvider plugin (thin, Hermes-side)
  → calls the AgentKeys MCP/HTTP surface (#141 wire) with a session cap-token
        │
        ▼
AgentKeys gated memory backend (Position C)
  GATE : verify cap → op-match → data_class=Memory → namespace filter → audit
  STORE: K3-encrypted per-line objects in S3, per-actor prefix, namespaced
  ENGINE: NOT built here — v0 retrieval is deterministic (namespace + recency/substring);
          a richer engine (mem0 / Hermes-native / vector) plugs in later, unchanged store.

Key property preserved: the LLM never sees the whole memory (prefetch returns top-K namespace-scoped snippets), and memory is portable + LLM-pluggable (swap the Hermes model, or swap Hermes for another runtime, the gated store is unchanged).

Deliverables

  1. Gated memory store endpoints (plan §9 stage M1): memory.put (append, per-line encrypted object), memory.get / memory.list (deterministic read), memory.snapshot. No ranking engine in the worker (plan invariant docs: human-readable keychain metadata in manual tests + field-name translation design note #1: worker never calls an LLM).
  2. Namespace gate (plan §9 stage M1.5): wire-format namespace field + cap-token namespaces_allowed claim; worker filters reads by deterministic string-set membership. v0 namespaces: personal / family / work / travel (per agent-iam-strategy.md §3.5).
  3. Hermes MemoryProvider plugin (new): a thin adapter implementing prefetchmemory.get, sync_turnmemory.put, system_prompt_block → profile snapshot, carrying a session cap-token. Reuses the feat(cli): agentkeys wire + hook — IAM-guarantee hooks for Hermes #141 wire/hook plumbing.
  4. Improve harness/phase1-wire-demo.sh to exercise the full loop end-to-end:
    • Session 1: agent learns a fact (e.g. "trip to Chengdu, May 25–29", namespace travel).
    • Session 2 (fresh context): agent answers "where am I going this weekend?" by recalling it through AgentKeys — the demo "wow moment".
    • Namespace isolation: a device/cap scoped to namespaces_allowed:["travel"] recalls the trip but is denied a personal-namespace secret (e.g. an allergy).

Acceptance criteria

  • memory.put then memory.get round-trips an encrypted per-line object through the gated backend (cap-verified, per-actor S3 prefix).
  • A cap with namespaces_allowed:["travel"] reads travel lines and is denied personal/family (403 / empty) — deterministic, no LLM.
  • Cross-data-class isolation still holds: a Credentials cap → memory endpoint → 403 cap_data_class_mismatch (extends the existing stage-3 negative tests; per CLAUDE.md test-discipline rule).
  • The Hermes MemoryProvider plugin drives prefetch/sync_turn against AgentKeys; Hermes recalls a prior-session memory with no manual add().
  • harness/phase1-wire-demo.sh demonstrates cross-session recall + namespace isolation end-to-end, exiting 0 on all-green (per the ok/skip/fail convention).
  • arch.md §15.2 / §17 updated for the namespace field (architecture-as-source-of-truth rule).

Explicitly out of scope (per Position C)

  • The ranking/extraction engine (vector index, BM25, consolidation, decay). v0 retrieval is deterministic namespace + recency/substring; a pluggable engine (mem0-self-hosted / Hermes-native / Claude memory tool backend) layers on later against the unchanged store. See memory-build-vs-gate-decision.md §6.
  • Cumulative/budget limits, graph queries, cross-actor sharing — deferred per the plan.

Implementation order

  1. Store endpoints (M1) + types (MemoryLine, ULID ids).
  2. Namespace gate (M1.5) + cap-token namespaces_allowed claim.
  3. Hermes MemoryProvider adapter on top of the feat(cli): agentkeys wire + hook — IAM-guarantee hooks for Hermes #141 wire surface.
  4. Demo + harness extension (cross-session recall + namespace isolation).
  5. arch.md update.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/mcpMCP server, MCP tool integration, MCP protocol workarea/memoryMemory worker, namespaces, semantic/episodic/profile/procedural storageenhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions