You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Hermes is pluggable by design. Hermes v0.15 accepts external memory via the MemoryProvider ABC (lifecycle hooks: prefetch, sync_turn, on_session_end, on_pre_compress, on_memory_write, system_prompt_block) — see docs/research/xiaozhi-hermes-architecture.md and the agent-role reference. Those hooks map 1:1 onto the AgentKeys memory store API.
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).
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).
Hermes MemoryProvider plugin (new): a thin adapter implementing prefetch → memory.get, sync_turn → memory.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.
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 deniedpersonal/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
Store endpoints (M1) + types (MemoryLine, ULID ids).
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' ownMemoryProviderlifecycle hooks driving it.Why now
docs/research/memory-build-vs-gate-decision.md(Position C) + the gated-backend specdocs/plan/agentkeys-memory-design.md+ the generalizationdocs/research/universal-gate-pattern.mdall merged in docs: memory build-vs-gate decision (Position C) + universal gate pattern + arch.md index #146.MemoryProviderABC (lifecycle hooks:prefetch,sync_turn,on_session_end,on_pre_compress,on_memory_write,system_prompt_block) — seedocs/research/xiaozhi-hermes-architecture.mdand the agent-role reference. Those hooks map 1:1 onto the AgentKeys memory store API.The integration shape
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
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).namespacefield + cap-tokennamespaces_allowedclaim; worker filters reads by deterministic string-set membership. v0 namespaces:personal / family / work / travel(peragent-iam-strategy.md§3.5).MemoryProviderplugin (new): a thin adapter implementingprefetch→memory.get,sync_turn→memory.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.harness/phase1-wire-demo.shto exercise the full loop end-to-end:travel).namespaces_allowed:["travel"]recalls the trip but is denied apersonal-namespace secret (e.g. an allergy).Acceptance criteria
memory.putthenmemory.getround-trips an encrypted per-line object through the gated backend (cap-verified, per-actor S3 prefix).namespaces_allowed:["travel"]readstravellines and is deniedpersonal/family(403 / empty) — deterministic, no LLM.Credentialscap → memory endpoint → 403cap_data_class_mismatch(extends the existing stage-3 negative tests; per CLAUDE.md test-discipline rule).MemoryProviderplugin drivesprefetch/sync_turnagainst AgentKeys; Hermes recalls a prior-session memory with no manualadd().harness/phase1-wire-demo.shdemonstrates cross-session recall + namespace isolation end-to-end, exiting 0 on all-green (per theok/skip/failconvention).Explicitly out of scope (per Position C)
memory-build-vs-gate-decision.md§6.Implementation order
MemoryLine, ULID ids).namespaces_allowedclaim.MemoryProvideradapter on top of the feat(cli): agentkeys wire + hook — IAM-guarantee hooks for Hermes #141 wire surface.References
docs/research/memory-build-vs-gate-decision.mddocs/plan/agentkeys-memory-design.mddocs/research/universal-gate-pattern.mddocs/research/xiaozhi-hermes-architecture.md,docs/wiki/agent-role-and-usage-hdkd-per-agent-omni.mdcrates/agentkeys-worker-memory,crates/agentkeys-mcp-server