mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
On a multiplexed gateway every served profile's agents share one process. Each agent's MemoryManager owns its own provider instance, but nothing stopped a tool call or lifecycle hook from another profile's context, or from a context naming no profile, from reaching it. Nothing stopped two agents being handed one shared instance either. The in-repo providers also bound their stores from the ambient home rather than the one the manager passed. MemoryManager.initialize_all records the profile home (and agent_identity) it bound its providers to. Then: - Tool calls and every lifecycle hook (prefetch, queue_prefetch, sync_turn, on_turn_start, on_memory_write, on_session_end, on_session_switch, the session boundary, on_pre_compress, on_delegation) run only when the caller explicitly names that profile: the context's HERMES_HOME override, or the home its secret scope was built for. On a multiplexed process a caller that names no profile is refused, never resolved to the process's own profile. A refused checkpoint compression raises, so the transcript stays uncompressed. - Background hooks run under the owning profile's HERMES_HOME, set explicitly in the worker. - An instance another profile's manager already bound is dropped, not re-initialized (logged as memory_provider_instance_shared). Claims are tracked by instance identity, never by the provider's own hash or equality; on a multiplexed process an instance that can't be tracked (no weak reference) is refused too. - handle_tool_call passes hermes_home / agent_identity next to the previous commit's caller_gsk. A provider with the two-argument handle_tool_call gets only what it declares; caller_gsk alone made every call to it a TypeError. - The model sees a generic "refused, check gateway.log". gateway.log keeps memory_caller_profile_unresolved and both homes. ByteRover and RetainDB now bind their store from initialize()'s hermes_home. Both already kept their bindings on the instance and hold no profile state at module level. The provider side of the rule is documented on MemoryProvider and in the memory-provider plugin guide: a new instance per load, every binding from initialize()'s hermes_home, no profile state at module or class level, no per-call rebinding. Core cannot see inside an external provider; one that keeps a process-wide binding still mixes profiles' memories. The previous commit's two MagicMock tests in test_run_agent.py move into tests/agent/test_memory_provider_caller_profile.py (test_run_agent.py is over its line cap and may not grow). That file drives both dispatch paths and every hook with real managers and two profiles interleaving on threads; tests/plugins/memory/test_provider_multiplex_contract.py does the same for ByteRover and RetainDB with a fake backend. Refs #134889 (PLG-11) Refs #140208 Refs #140058
ByteRover Memory Provider
Persistent memory via the brv CLI — hierarchical knowledge tree with tiered retrieval (fuzzy text → LLM-driven search).
Requirements
Install the ByteRover CLI:
curl -fsSL https://byterover.dev/install.sh | sh
# or
npm install -g byterover-cli
Setup
hermes memory setup # select "byterover"
Or manually:
hermes config set memory.provider byterover
# Optional cloud sync:
echo "BRV_API_KEY=your-key" >> ~/.hermes/.env
Config
| Env Var | Required | Description |
|---|---|---|
BRV_API_KEY |
No | Cloud sync key (optional, local-first by default) |
Working directory: $HERMES_HOME/byterover/ (profile-scoped).
Tools
| Tool | Description |
|---|---|
brv_query |
Search the knowledge tree |
brv_curate |
Store facts, decisions, patterns |
brv_status |
CLI version, tree stats, sync state |