Skip to content

feat(memory): add per-user scoping to holographic memory plugin - #7256

Closed
nericervin wants to merge 10 commits into
NousResearch:mainfrom
waikiservicios:feat/holographic-user-scoping
Closed

feat(memory): add per-user scoping to holographic memory plugin#7256
nericervin wants to merge 10 commits into
NousResearch:mainfrom
waikiservicios:feat/holographic-user-scoping

Conversation

@nericervin

Copy link
Copy Markdown
Contributor

Summary

The holographic memory plugin stores all facts in a single global table without user isolation. When multiple users share a Hermes instance via the API server gateway, every user's facts are visible to every other user.

This PR adds a user_scope column to the facts table and threads the gateway user_id through AIAgent → HolographicMemoryProvider → MemoryStore → FactRetriever, completing the per-user memory scoping that #5895 started for Mem0 and Honcho.

Changes

  • store.py: user_scope column with composite unique index, auto-migration for existing DBs, scope_clause() helper
  • init.py: extract user_id from kwargs in initialize(), pass to MemoryStore, scope system_prompt_block count
  • retrieval.py: apply scope_clause() to all retrieval paths (FTS, probe, related, reason, contradict, vector scoring)
  • api_server.py: read X-Hermes-User-Id header, pass through _run_agent_create_agentAIAgent(user_id=...) for all endpoints

Backwards-compatible

When user_id is None (CLI sessions), all facts remain visible — no behaviour change for single-user setups. Existing databases are auto-migrated (new column + index added on first connection).

Context

Follow-up to #5895 which threaded user_id to Mem0 and Honcho but left Holographic unscoped. We hit this in production running a multi-tenant API server where different clients' facts were leaking across sessions.

Test plan

  • Verify CLI sessions (no user_id) still see all facts
  • Verify API server with X-Hermes-User-Id: alice only sees alice's facts + global
  • Verify API server with X-Hermes-User-Id: bob only sees bob's facts + global
  • Verify existing databases auto-migrate (new column added)
  • Verify probe/related/reason/contradict respect scoping

Los mensajes de status/lifecycle (connection dropped, reconnecting,
model failed, operation interrupted, etc.) solo se envian a
Mattermost y CLI. En WhatsApp, Telegram y otras plataformas
externas se suprimen y se loguean a debug.

Evita que clientes vean errores internos del gateway.
Red de seguridad: antes de enviar por WhatsApp, verifica que
el contenido no sea un mensaje interno del sistema (Memory
updated, No home channel, skill_view, razonamiento interno
como 'silencio absoluto', etc.).

Si matchea algun patron, se suprime y se loguea a debug.
Complementa el filtro de lifecycle en run.py como segunda
linea de defensa.
Tres funcionalidades en el adapter de WhatsApp:

1. Group debounce (WHATSAPP_GROUP_DEBOUNCE_SECONDS):
   En grupos, espera N segundos antes de procesar un mensaje.
   Si alguien del equipo (WHATSAPP_TEAM_NUMBERS) responde
   durante el delay, el bot cancela y no responde.
   Normaliza JIDs correctamente (quita +, @s.whatsapp.net, :lid).

2. Group block (WHATSAPP_ALLOWED_GROUPS):
   Bloquea envio de mensajes a grupos no autorizados.
   Solo permite enviar a JIDs en la whitelist.
   Proteccion contra baneo de Meta.

3. Silence filter (_SUPPRESS_PATTERNS):
   Red de seguridad que suprime mensajes internos antes de
   enviarlos (Memory updated, No home channel, skill_view,
   razonamiento interno como 'silencio absoluto', etc.).

Env vars:
  WHATSAPP_GROUP_DEBOUNCE_SECONDS=120
  WHATSAPP_TEAM_NUMBERS=5492996351947,5492995347180,...
  WHATSAPP_ALLOWED_GROUPS=jid1@g.us,jid2@g.us
Dos funcionalidades para el adapter de Mattermost:

1. MATTERMOST_IGNORE_DMS: si es 'true', ignora mensajes
   directos al bot. Util para bots que solo deben operar
   en canales (ej: Dewun en dev-*).

2. MATTERMOST_NO_MENTION_PREFIX: prefijos de nombre de canal
   donde el bot responde sin necesidad de @MENCION.
   Ej: 'dev-' para que Dewun responda en todos los canales
   dev-general, dev-kellu, etc.
WhatsApp no puede enumerar chats, action='list' retorna vacio.
Se actualiza la descripcion del schema para instruir al modelo
a enviar directamente con target='whatsapp:NUMERO@s.whatsapp.net'.
Align env var name with upstream PR convention:
MATTERMOST_FREE_RESPONSE_PREFIX (consistent with FREE_RESPONSE_CHANNELS)
When replying inside an existing thread, Hermes was using
event.message_id as root_id — which is a reply post, not the
thread root. Mattermost rejects this with root_id.app_error.

Now prefer metadata.thread_id (the actual thread root) over
reply_to when available.
The holographic memory plugin stores all facts in a single global
table without any user isolation.  When multiple users share a
Hermes instance via the API server gateway, every user's facts are
visible to every other user — a data leak.

This commit adds a `user_scope` column to the facts table and threads
the gateway `user_id` (from the `X-Hermes-User-Id` header) through
`AIAgent → HolographicMemoryProvider → MemoryStore → FactRetriever`.

Changes:
- store.py: `user_scope` column with composite unique index on
  (content, user_scope), auto-migration for existing databases,
  `scope_clause()` helper for consistent WHERE filtering
- __init__.py: extract `user_id` from kwargs in `initialize()`,
  pass as `user_scope` to MemoryStore; scope `system_prompt_block`
  count query
- retrieval.py: apply `scope_clause()` to all retrieval paths
  (FTS candidates, probe, related, reason, contradict, vector
  scoring)
- api_server.py: read `X-Hermes-User-Id` header, pass through
  `_run_agent` → `_create_agent` → `AIAgent(user_id=...)` for
  all three endpoints (chat completions, responses, streaming)

Backwards-compatible: when `user_id` is None (CLI sessions), all
facts remain visible — no behaviour change for single-user setups.

Follows up on NousResearch#5895 which threaded `user_id` to Mem0 and Honcho
but left Holographic unscoped.
The previous commit added user_id to _run_agent and the handler
calls but missed adding it to _create_agent's signature and to
the AIAgent constructor call. This caused:
  "unexpected keyword argument 'user_id'" at runtime.
@nericervin

Copy link
Copy Markdown
Contributor Author

Closing in favor of a new PR from the proper GitHub fork (nericervin/hermes-agent) with rebased commits.

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