An Upstash Redis memory provider for Hermes Agent, built on
the same Redis Search layout as @upstash/agentkit-sdk's AgentMemory and ChatHistory. A memory
your AI SDK app saves is recalled by your Hermes agent, and the other way round.
- Recall: before each turn, the memories closest to the user's message are added to the prompt. By keyword and by meaning; see Semantic recall.
- Chat history: after each turn, the turn is appended in place to the session's searchable transcript document, so a write stays the size of one turn however long the session runs.
- Mirroring: facts the built-in
memorytool adds are also saved to Redis, and when Hermes corrects (replace/patch) or removes one, the Redis copy is updated or deleted too. - Tools:
upstash_memory_save,upstash_memory_search,upstash_memory_forgetandupstash_chat_search(search past conversations).
Subagents, cron jobs and context flushes can read memory but never write it.
# into the Python environment Hermes runs in
pip install upstash-hermes-memoryThen select it in Hermes's config.yaml:
memory:
provider: upstashAnd give it a database, in your environment or ~/.hermes/.env (hermes memory setup asks for
these):
| Variable | |
|---|---|
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN |
Required. From the Upstash console. |
UPSTASH_HERMES_USER_ID |
Whose memories these are. Defaults to the gateway user, then the Hermes profile name, then default. |
UPSTASH_HERMES_NAMESPACE |
Key namespace. Default agentkit, so memories sit at the TS SDK's default keys and are shared with AgentKit apps on the same database. |
UPSTASH_HERMES_TELEMETRY |
Set to 1 to send telemetry. Off by default; see Telemetry. |
Keyword recall ($smart: phrase, term and fuzzy matching) only finds a memory that shares words
with the message: "where is the assistant hosted" misses "Hermes runs on the ops Box". So the
memory text field is a HYBRID field, which Upstash Redis Search embeds itself (no separate
vector database), and recall merges a keyword query with a semantic $ann query using $rrf
(Hybrid Search).
- Upgrading from 0.2.x: the old index has
textasTEXTand is not supported. Recall fails until you replace it: stop every client of the namespace (Hermes gateway, desktop app, cron jobs, other profiles on it) and runSEARCH.DROP agentkit(or your namespace's index). Your memories and chats are kept, and the next start creates the new index and indexes them again. - Automatic recall injects up to 3 memories: the semantic side has no relevance
threshold and returns up to its limit even for unrelated text, and in a small-store test its
similarity scores did not separate related from unrelated memories well enough for a cutoff.
upstash_memory_searchkeeps its limit. - Results carry
retrieval: "hybrid"or"keyword". Hybrid scores are rank-fusion values that only order one search's results;min_scoreapplies to keyword results only. - If a hybrid query fails, recall falls back to keyword search for that call (logged at most every 10 minutes).
- Chat history stays keyword-only: a
HYBRIDfield gets one embedding for its whole value, and a chat document holds a whole session. - Every memory in the index is embedded, whichever user or profile wrote it, and each recall sends the message to Upstash Redis Search to be embedded. In a small-store test a hybrid query took about 150 to 300 ms, against a few ms for keyword recall.
Off by default. Set UPSTASH_HERMES_TELEMETRY=1 to opt in: requests to your Upstash database then
carry upstash-hermes-memory@<version> in the Upstash-Telemetry-Sdk header, next to the
upstash-redis client's own telemetry headers (SDK version, Python version, platform). Nothing
else is collected and nothing goes anywhere but your database. Without the opt-in the provider
creates its Redis client with allow_telemetry=False, so none of these headers are sent.
UPSTASH_DISABLE_TELEMETRY=1 turns telemetry off even when opted in.
| Key | Value | |
|---|---|---|
| Memory | agentkit:memory:<user>:<id> |
JSON {text, userId, createdAt, ...metadata}, as the TS SDK writes it |
| Chat | agentkit:chat:<user>:<session> |
JSON {kind: "chat", userId, sessionId, userMessages, modelMessages, messages, createdAt, updatedAt} |
Memory and chats share one search index, agentkit, covering both prefixes, so the provider
works on Upstash's Free plan, which allows one index per database. An AgentKit TS app on the same
database still creates its own memory index, so using both needs a plan with more than one index.
User and session ids are used as they are, so the same id reads the same keys from Hermes and the
TS SDK. Like the TS SDK, the provider refuses a user id with : (the key separator), and keeps no
transcript for a session id with one. Indexes are created on first use. AgentMemory and ChatHistory are importable on their own for
use outside Hermes.
uv sync --all-packages && pnpm py:lint && pnpm py:test # Redis tests need UPSTASH_REDIS_REST_* set
uv run python python/hermes-memory/e2e/hermes_e2e.py # a real Hermes session, needs OPENAI_API_KEYThe e2e script installs the package into a temporary directory and runs Hermes with a temporary
HERMES_HOME, so your own Hermes install is not touched. Hermes's built-in memory is turned off
there. One session saves two facts; a fresh session started right after must name them without
calling any tool (checked in Hermes's state.db), so they can only come from the automatic recall;
and a control session as another user must not know them. The script then checks the keys and
transcripts in Redis (and, if packages/sdk is built, that the TS AgentMemory recalls them too)
before cleaning up.