Memobase Memory Provider for Hermes Agent
A Hermes Agent memory provider
plugin that connects the MemoryProvider lifecycle to a self-hosted
Memobase server
(FastAPI + PostgreSQL + pgvector). Memobase stores a user profile
(ontology-based topics) plus an append-only event timeline, extracted offline
by an LLM when the buffer flushes.
This plugin gives the agent automatic per-turn recall instead of model-discretion search: every turn, relevant memories are prefetched and injected into context, and the completed turn is written back for background profile extraction.
How it works
When the provider is active, Hermes automatically:
prefetch(query)— recalls the user profile + recent event timeline for the current user message and injects it into context (Memobase's/users/contextassembly; hot path < 100 ms).sync_turn(user, asst)— inserts the completed turn into Memobase's buffer for background LLM profile extraction (auto-flush at the buffer threshold).- Exposes
memobase_search/memobase_profiletools as an on-demand backstop.
Hybrid retrieval (v3)
Recall is not a single vector lookup. The context endpoint is queried with a fixed budget, and the plugin runs its own three-leg hybrid retrieval locally over the returned event window:
| Leg | Signal | Purpose |
|---|---|---|
bm25 |
Okapi BM25 over CJK-bigram + latin tokens | exact term / keyword match |
vec |
Memobase's own embeddings | semantic match |
entity |
inverted index over extracted entities | proper nouns, file paths, IPs, domains, IDs, quoted strings, book titles |
The three ranked lists are fused with Reciprocal Rank Fusion (RRF), then refined by two optional passes:
- Temporal boost — when the query contains a time expression
(
今天/昨天/上周/3天前/ …), events inside the parsed window are multiplied bytemporal_gain; events outside decay exponentially withtemporal_half_life_days, floored attemporal_floor. - Conditional rerank (opt-in) — a cross-encoder rerank pass fires only
when the fused score distribution is low-discrimination (i.e. the ranking
is too flat to trust), so a clear-cut ranking never pays the latency cost.
It runs only when you configure both
rerank_base_urlandrerank_api_key. There is no default endpoint, so an unconfigured install makes no outbound request to any host but your own Memobase server.
Tunable through $HERMES_HOME/memobase.json — see the module docstring in
__init__.py for the full list (hybrid_enabled, hybrid_keep,
temporal_*, entity_boost_enabled, rerank_*, timezone, …).
CJK proper nouns
The entity leg recognizes structured entities (paths, IPs, domains, camel case, quoted text) out of the box. Personal names are deployment-specific, so the public build ships no name list. Add your own:
{
"cjk_known_names": ["Alice", "Bob"]
}
Empty (the default) is fine — everything else still works.
Requirements
- A running Memobase server (>= 0.0.42) with a bearer token.
- Hermes Agent with the memory-provider plugin system (any recent build).
Install
Drop this repository's files into ~/.hermes/plugins/memobase/
(user-level memory provider discovery path), or clone it there:
mkdir -p ~/.hermes/plugins/memobase
git clone https://github.com/fred0m/hermes-memobase-provider.git ~/.hermes/plugins/memobase
Then configure:
# ~/.hermes/.env
MEMOBASE_BASE_URL=http://<your-memobase-host>:8019/api/v1
MEMOBASE_API_KEY=<your-memobase-token>
# optional: MEMOBASE_USER_NAME=user (maps to uuid5(NAMESPACE_DNS, name+"memobase_client"))
Enable the provider:
hermes config set memory.provider memobase
hermes gateway restart
hermes memory status # → Provider: memobase / Plugin: installed / Status: available
Behavioral settings may live in ~/.hermes/memobase.json:
{
"prefetch_max_tokens": 2000,
"prefetch_wait_secs": 5,
"fill_window_events": true,
"time_range_days": 180,
"hybrid_enabled": true,
"hybrid_keep": 10,
"temporal_boost_enabled": true,
"temporal_gain": 1.6,
"temporal_half_life_days": 30,
"temporal_floor": 0.6,
"timezone": "Asia/Shanghai",
"cjk_known_names": [],
"rerank_base_url": "",
"rerank_api_key": ""
}
API surface used
| Lifecycle | Endpoint |
|---|---|
| prefetch / memobase_search | GET /users/context/{user_id}?max_token_size=…&chats_str=… |
| sync_turn (write) | POST /blobs/insert/{user_id}?wait_process=false |
| on_session_end (flush) | POST /users/buffer/{user_id}/chat?wait_process=true |
| memobase_profile | GET /users/profile/{user_id} |
Safety & reliability
- Non-primary agents skip writes (subagent / cron / flush contexts cannot corrupt the user profile); recall still works.
- Fail-open: any backend error is logged and swallowed — the agent turn never breaks because memory died.
- Circuit breaker: after 5 consecutive failures the provider pauses for 60 s instead of hammering a down server; a success resets the counter.
- Fast hot path:
prefetch()consumes a background thread's cached result (wait ≤prefetch_wait_secs); a slow backend degrades to "no injection" and the search tool remains available. - Business errors are caught: Memobase may return HTTP 200 with a
non-zero
errnobody; those are treated as failures, not successes. - One outbound host by default: your Memobase server. Vector recall and the entity leg are computed locally from the context payload. The reranker is the only other path, and it stays off until you set both an endpoint and a key for it.
Development
Run the test suite (stdlib unittest + httpx.MockTransport, no extra deps):
python -m unittest discover -s tests -v
License
MIT