hermes_cli/memory_setup.py::_write_env_vars() wrote provider-controlled .env entries with a direct Path.write_text() + post-hoc chmod, bypassing the denylist/regex/CRLF-stripping/atomic-replace validation that hermes_cli/config.py::save_env_value() already provides for every other .env writer in the codebase. A malicious or buggy memory-provider plugin declaring a crafted env-var name/value in its setup schema could inject arbitrary lines into .env. Routes memory-provider env writes through save_env_value(), and fixes a regression this surfaced in plugins/memory/supermemory/__init__.py:: post_setup(), which called the old two-parameter _write_env_vars(env_path, values) signature — restores the caller via context-local hermes_constants.set_hermes_home_override()/reset_hermes_home_override() instead of a removed env_path parameter, so explicit HERMES_HOME overrides during setup still resolve correctly. Adds test_env_file_created_with_secure_permissions, guarded on Windows (POSIX mode bits aren't enforced there, mirroring the existing skip in test_openviking_provider.py / test_supermemory_provider.py) since save_env_value's atomic-replace path creates the temp file at 0o600 before writing content, closing the TOCTOU window the old direct-write + chmod implementation had.
Supermemory Memory Provider
Semantic long-term memory with profile recall, semantic search, explicit memory tools, and full-session conversation ingest (one ingest per session) for richer profiles.
Requirements
pip install supermemory- Hosted: API key from app.supermemory.ai/integrations?connect=hermes
- Self-hosted: a running Supermemory local server and the API key it prints on first boot
Setup
hermes memory setup # select "supermemory"
Or manually:
hermes config set memory.provider supermemory
echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env
For a fully self-hosted setup, start Supermemory local and note the API key it prints on first boot:
npx supermemory local
Before running hermes memory setup, add the local endpoint to
$HERMES_HOME/supermemory.json:
{
"base_url": "http://localhost:6767"
}
Then run hermes memory setup and enter the local server's API key. Configuring
the endpoint first ensures the setup connection probe also stays local.
Config
Config file: $HERMES_HOME/supermemory.json
| Key | Default | Description |
|---|---|---|
base_url |
https://api.supermemory.ai |
API endpoint for hosted or self-hosted Supermemory. Takes priority over SUPERMEMORY_BASE_URL. |
container_tag |
hermes |
Container tag used for search and writes. Supports {identity} template for profile-scoped tags (e.g. hermes-{identity} → hermes-coder). |
auto_recall |
true |
Inject relevant memory context before turns |
auto_capture |
true |
Store cleaned user-assistant turns after each response |
max_recall_results |
10 |
Max recalled items to format into context |
profile_frequency |
50 |
Include profile facts on first turn and every N turns |
capture_mode |
all |
Skip tiny or trivial turns by default |
search_mode |
hybrid |
Search mode: hybrid (profile + memories), memories (memories only), documents (documents only) |
entity_context |
built-in default | Extraction guidance passed to Supermemory |
api_timeout |
5.0 |
Timeout for SDK and ingest requests |
Environment Variables
| Variable | Description |
|---|---|
SUPERMEMORY_API_KEY |
API key (required) |
SUPERMEMORY_BASE_URL |
Compatibility fallback for the API endpoint when base_url is not configured |
SUPERMEMORY_CONTAINER_TAG |
Override container tag (takes priority over config file) |
Base URL precedence is supermemory.json → SUPERMEMORY_BASE_URL →
https://api.supermemory.ai. Hermes resolves it once and uses the same endpoint
for SDK operations, setup/status probes, and full-session conversation ingest.
Tools
Kebab-case names are registered for the agent; snake_case aliases remain supported.
| Tool | Alias | Description |
|---|---|---|
supermemory-save |
supermemory_store |
Store an explicit memory |
supermemory-search |
supermemory_search |
Search memories by semantic similarity |
supermemory-forget |
supermemory_forget |
Forget a memory by ID or best-match query |
supermemory-profile |
supermemory_profile |
Retrieve persistent profile and recent context |
Source attribution
All Supermemory API calls send x-sm-source: hermes, and document writes stamp
metadata.sm_source: hermes. This is a functional routing key, not telemetry:
it groups Hermes-written memories into a dedicated "Hermes" Space in the
Supermemory app, so you can filter, browse, and bulk-manage them per source agent
(alongside Codex, Claude Code, etc.) from the Supermemory UI.
Behavior
When enabled, Hermes can:
- prefetch relevant memory context before each turn
- buffer the full conversation and ingest it as one session at session end (or on
/reset, branch, compression, or shutdown) - ingest the full session to the conversations endpoint for richer profile/graph updates
- route every SDK, probe, and conversation-ingest request through the configured hosted or self-hosted endpoint
- expose explicit tools for search, store, forget, and profile access
The session is written once via the conversations endpoint, which drives Supermemory's entity extraction and profile building while keeping a clean, retrievable full transcript.
Profile-Scoped Containers
Use {identity} in the container_tag to scope memories per Hermes profile:
{
"container_tag": "hermes-{identity}"
}
For a profile named coder, this resolves to hermes-coder. The default profile resolves to hermes-default. Without {identity}, all profiles share the same container.
Multi-Container Mode
For advanced setups (e.g. OpenClaw-style multi-workspace), you can enable custom container tags so the agent can read/write across multiple named containers:
{
"container_tag": "hermes",
"enable_custom_container_tags": true,
"custom_containers": ["project-alpha", "project-beta", "shared-knowledge"],
"custom_container_instructions": "Use project-alpha for coding tasks, project-beta for research, and shared-knowledge for team-wide facts."
}
When enabled:
supermemory-search,supermemory-save,supermemory-forget, andsupermemory-profileaccept an optionalcontainer_tagparameter- The tag must be in the whitelist: primary container +
custom_containers - Automatic operations (turn sync, prefetch, memory write mirroring, session ingest) always use the primary container only
- Custom container instructions are injected into the system prompt