A Hermes Agent memory provider plugin that stores conversation context in a local Cashew thought graph with semantic search and automatic context recall. Get from zero to a working install in under five minutes.
The plugin auto-generates cashew.json with defaults on first load, can use an
explicit Hermes auxiliary.memory mapping for LLM-powered extraction, adds
forest-level insight extraction via on_pre_compress, and runs graph
consolidation on a persistent Hermes cron schedule.
Existing profiles can be inspected without opening a writable database:
python -m plugins.memory.cashew.integrity /path/to/brain.dbThe audit uses a canonical shared maintenance lease and SQLite URI read-only
mode. It reports schema and privacy-safe provider fingerprints, ordinary
embedding validity, vector-index parity when the installed extension can be
verified, orphan rows, referential graph defects, and permanence contradictions.
Large scans use fixed row, byte, and deadline budgets; a cutoff is reported as
audit_incomplete rather than presented as a complete audit. It never runs schema
migrations, decay, consolidation, embedding services, or repair as a side
effect. Reports include only bounded counts and reason codes; historical merge
intent remains an explicit manual-review item because it cannot be reconstructed
from the stored graph safely.
The explicit operator apply surface is opt-in and backup-backed:
python -m plugins.memory.cashew.integrity --apply --confirm /path/to/brain.dbThe command takes the canonical exclusive maintenance lease, reads the stored
model identity, writes and verifies a SQLite backup under the profile's
backups/ directory, starts a write-excluding transaction, delegates bounded
repairs to Cashew, verifies the result before and after commit, and reports the
backup path. A failure before commit rolls back the repair transaction while
retaining the backup. Missing profiles, missing model identity, incompatible
requested identity, unavailable upstream APIs, and lock/backup failures all
fail closed. The workflow does not touch HOME or change journal mode.
It preserves the maintenance epoch and does not lock or invalidate the separate
embedding cache: targeted repairs preserve the stored model identity, and that
cache maps content to same-model vectors rather than graph rows. A future repair
that changes model identity must use the existing graph-to-cache migration
boundary instead of this workflow.
By default Cashew attempts its information-preserving repairs. Operators can
restrict the work by repeating --action; use --max-items and --batch-size
to bound a run. Contradictory permanence state remains report-only unless
--permanence-policy preserve_permanent or preserve_decay is chosen
explicitly. Missing or invalid embeddings require a compatible embedding
callable through the Python API; the CLI does not construct one or perform
blanket re-embedding.
The selected immutable Cashew composite also provides the connection-owned API
for applications that already own the full maintenance boundary. Such callers
may use inspect_integrity(conn, ...) and, only after confirm=True,
apply_integrity_repairs(conn=conn, ...); they retain ownership of backup,
locking, transaction, commit, rollback, verification, and close. Older Cashew
installations without the API remain fail-closed. Neither mode reconstructs
merged facts or restores decayed nodes automatically; retain the original
database and generated backups for manual review.
- Hermes Agent installed
- Cashew engine included with the plugin; no separate
cashew-braininstall - SQLite 3.35 or newer, required for Cashew's legacy v1 schema migration
sqlite-vec— enables vector similarity search; installed as a runtime dependency
hermes plugins install magnus919/hermes-cashewThis installs into the active Hermes home under plugins/cashew/
(default: ~/.hermes/plugins/cashew/). Hermes installs ordinary index dependencies;
the reviewed Cashew engine ships inside the plugin. No manual direct-URL install
is needed after installation or a Hermes environment rebuild.
The bundled engine is the selected composite commit
fcb4919ac37144bfbeb822eaafc668a4bdceb791. Its schema, retrieval, session, sleep,
and integrity algorithms remain upstream-owned. A private namespace prevents an
older separately installed cashew-brain from replacing it. The bundle includes
the upstream MIT license, a pristine source snapshot, and a file-by-file provenance
inventory. Absolute intra-engine imports are mechanically relocated, and the standalone
migration script's sys.path bootstrap is removed.
See upstream compatibility for the source and
replacement policy.
SQLite 3.35 or newer is required for legacy v1 migrations. To audit the installed
bundle and linked SQLite runtime, run scripts/verify-cashew-baseline.py with the
Python interpreter selected by Hermes for the plugin. Version metadata alone does
not identify the selected engine.
Diagnostics remain disabled unless explicitly enabled. The normal plugin install uses runtime dependencies only. To enable either optional Sentry or OpenTelemetry diagnostics, install the tracing extra into Hermes's environment first:
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 \
'hermes-cashew[tracing]'Then set HERMES_CASHEW_SENTRY_DSN for Cashew's isolated diagnostics worker,
or set HERMES_CASHEW_OTEL_ENABLED=1 for Cashew spans. Generic SENTRY_* and
OTEL_* host settings do not enable Cashew diagnostics. Events and spans contain
only fixed operation codes and allowlisted bounded metadata.
After install, run setup and restart the gateway:
hermes memory setup
hermes gateway restartAfter installing, set cashew as the active memory provider:
hermes config set memory.provider cashew
hermes gateway restartOr use the interactive setup (v0.2.0 now includes cashew in the provider picker):
hermes memory setupUpdate through Hermes:
hermes plugins update cashew
hermes gateway restart
hermes memory statushermes update and environment rebuilds retain the engine as part of the plugin;
there is no separately installed source dependency to restore. A catalog pin may
need to advance before a catalog installation selects a newly merged fix. Updating
the separately installed PyPI cashew-brain does not update the bundled engine.
To roll back, select a previously tested plugin revision through Hermes. Keep profile configuration and graph backups; uninstalling the plugin is unnecessary for routine updates. Older plugin revisions may still require their documented manual dependency setup and should not be assumed compatible with current data.
hermes-cashew works out of the box — all 17 persisted configuration fields have
sane defaults and are backed by current runtime behavior. On first agent
startup, the plugin creates cashew.json in the active Hermes home with the full default
configuration. It never edits Hermes config.yaml; until an auxiliary role is
explicitly configured there, extraction remains heuristic-only.
Edit cashew.json under the active Hermes home only if you want to override specific defaults.
Automatic startup does not overwrite an existing file; an explicit
hermes memory setup save can update and normalize it:
# Optional: override individual defaults
cat > "${HERMES_HOME:-$HOME/.hermes}/cashew.json" << 'EOF'
{
"recall_k": 10,
"think_interval": 15,
"user_domain": "user"
}
EOF| Key | Default | Description |
|---|---|---|
cashew_db_path |
cashew/brain.db |
Path to SQLite DB, relative to hermes_home |
embedding_model |
thenlper/gte-large |
Sentence-transformers model for embeddings (1024-dim) |
embedding_device |
cpu |
Sentence-transformers device; cpu avoids unstable native accelerators |
llm_aux_role |
memory |
Hermes auxiliary role for LLM-powered extraction; requires auxiliary.memory in config.yaml |
auto_extraction |
true |
Auto-extract knowledge from conversation turns |
sync_queue_timeout |
30.0 |
Seconds to wait for sync worker drain on shutdown |
| Key | Default | Description |
|---|---|---|
recall_k |
5 |
Context fragments returned per query |
prefetch_k |
3 |
Nodes to pre-warm into context on each turn |
prefetch_cues |
3 |
Cue phrases to send to LLM for prefetch generation |
| Key | Default | Description |
|---|---|---|
user_domain |
user |
Domain label for user messages |
ai_domain |
ai |
Domain label for AI messages |
| Key | Default | Description |
|---|---|---|
sleep_cycles |
true |
Enable the upstream Cashew sleep cycle through the Hermes adapter (cross-linking, dedup, GC, dreams) |
sleep_schedule |
"every 12h" |
Cron schedule for sleep cycle |
sleep_max_nodes |
2000 |
Max nodes per sleep cycle tick |
think_cycles |
true |
Enable periodic insight generation (think cycle) |
think_interval |
10 |
Turns between think cycle runs (0 = disable) |
Cashew validates the effective JSON and CASHEW_* configuration before use.
recall_k and prefetch_k allow 1–20; prefetch_cues allows 0–20;
think_interval allows 0–10,000; sleep_max_nodes allows 1–2,000; and
sync_queue_timeout must be finite and between 0 and 300 seconds. Invalid
JSON or values are left in place so they can be corrected instead of being
silently overwritten. These numeric ceilings are a new validation policy.
embedding_device defaults to cpu. SentenceTransformer model loading and
encoding run in one provider-owned child process. A backend process exit cannot
terminate Hermes, and an interactive caller stops waiting after a bounded
interval if the worker hangs. Set the option to auto or an explicit device
such as mps, cuda, or cuda:0 after validating that backend. A non-CPU
startup failure retries once on CPU; Hermes never loads the model as a fallback.
This boundary is deliberately narrow. SQLite, sqlite-vec, NumPy operations in the adapter, Cashew graph algorithms, and auxiliary LLM calls still run in the Hermes process. The child adds startup and local IPC overhead; deterministic transport checks are recorded in CI, while real-model latency and memory use remain hardware/model dependent and are not represented by those fake-model measurements.
Starting with v0.11.0, the following deprecated parse-only settings are removed because cashew-brain 1.x has no supported runtime contract for them:
default_domain, auto_classify, domain_classifications,
domain_separation_enabled, token_budget, walk_depth,
similarity_threshold, access_weight, temporal_weight, clustering_eps,
clustering_min_samples, novelty_threshold, max_think_iterations,
gc_mode, gc_threshold, gc_grace_days,
gc_protect_types, gc_think_cycle_penalty, decay_pruning, and
pattern_detection.
These keys never changed provider behavior. Existing files continue to load;
removed keys are ignored and pruned the next time hermes memory setup saves
the provider configuration. Remove matching CASHEW_* environment variables,
because they are no longer part of the adapter's configuration surface.
Experimental features gated behind boolean toggles. All default to false.
Enable in cashew.json under the _features key:
{"_features": {"experimental_batch_sync": true}}| Key | Default | Description |
|---|---|---|
experimental_batch_sync |
false |
Drain up to 8 sync turns per worker iteration instead of one-at-a-time |
The former experimental_parallel_retrieval setting is retired. Existing
profiles still load; saving removes that setting. Recall always uses upstream
retrieval first, with keyword fallback for an empty result or failure. This
removes the competing retrieval threads and timing-dependent result selection;
it does not impose a deadline on the normal upstream call.
Environment variables override config values: prefix any key with CASHEW_
(e.g. CASHEW_RECALL_K=10).
hermes gateway restart # ensure gateway picks up the new plugin
hermes memory statusThe host can show Provider: cashew, Plugin: installed, and
Status: available. Those discovery results do not prove that a turn was
persisted or that semantic retrieval is ready. Availability is a cheap check of
configuration/dependencies; it does not open or repair the brain.
The provider's read-only health_status() diagnostic distinguishes runtime
readiness from discovery. It returns state, reason codes, fallback/cron status,
and bounded outcome counts without querying the database or loading a model.
Host versions differ in which diagnostics they display; do not interpret a
successful hermes memory status command as a full integrity check.
- Ready means initialization established the active runtime. It does not guarantee that every future extraction succeeds.
- Degraded means a capability is unavailable. Check the reported reason and fallback: keyword recall may remain usable while semantic retrieval or writes are disabled. A missing configured auxiliary model can leave heuristic extraction available.
- Failed, stopping, or stopped require attention to the reported lifecycle state; installed files alone do not establish an active worker.
Concurrent sessions coordinate access to the same profile through advisory leases. Contended maintenance can skip a cycle, and writer admission can time out. A lock warning is a reason to inspect competing sessions and maintenance, not to delete lock files or the SQLite journal. Stop competing processes before intentional model migration. These protections apply to participating clients; an unrelated program writing the database directly can bypass them.
sync_turn enqueues work and returns promptly; returning does not acknowledge
persistence. The queue holds at most 16 pending turns. When full, the oldest
pending turn is dropped with a warning to admit the newest; a remaining full
queue rejects the new turn. Shutdown rejects new work and gives accepted work a
bounded drain interval. The queue is in memory, so process termination can lose
pending turns. Outcome diagnostics distinguish accepted work, drops, and write
results; investigate warnings before assuming the conversation was stored.
Embedding model execution is isolated in a child process, but SQLite, sqlite-vec, graph operations, and auxiliary calls still run in the host process. The isolation does not make Hermes immune to every native crash or external termination. The original reported crash had no retained traceback establishing its cause; the regression tests prove specific failure boundaries, not a retrospective diagnosis.
hermes-cashew provides two LLM-accessible tools:
cashew_query— searches the local thought graph for context relevant to the current conversation. Uses sqlite-vec for semantic search; optionaldomain,tag, andexclude_tagsfilters narrow the result set.cashew_extract— explicitly persists a conversation turn into the graph. The agent can call this when it judges a turn contains worth-remembering knowledge.
Both tools are registered automatically when Hermes loads the plugin.
On each session start, prefetch() retrieves relevant context from the graph
and injects it into the system prompt.
Nodes in the thought graph can carry tags. The cashew_query tool accepts an
exclude_tags parameter to filter out nodes with specific tags from results:
{"query": "prior decisions", "exclude_tags": ["vault:private"]}This works in both the vector search and keyword fallback paths. Common use cases:
- Privacy: Tag sensitive nodes with
vault:privateto exclude them from group or shared contexts - Domain isolation: Exclude nodes from specific domains during broad queries
- Declassification: Remove exclusion to reveal previously private nodes
llm_aux_role selects a Hermes auxiliary role. Its default, "memory", is a
selector rather than an implicit configuration: add a complete
auxiliary.memory mapping to Hermes config.yaml to opt in. The plugin never
creates or changes that mapping.
auxiliary:
memory:
provider: your-provider
model: your-modelFor each extraction, Cashew uses Hermes' public auxiliary-client resolver for the selected profile's transport and authentication, then makes one bounded chat-completions request. Prompts are capped at 32,000 characters, responses at 1,024 tokens, and the caller waits up to 30 seconds. A timed-out provider call cannot be forcibly cancelled and may finish later; no more than four such backend calls may be outstanding process-wide, and later requests fail closed until a slot is released. Cashew intentionally does not use Hermes' task-level retry and fallback ladder for these calls.
The configured role is used when Cashew next performs extraction, insight, or dream work. If Hermes cannot resolve it, Cashew silently uses its heuristic extractor instead.
To disable LLM extraction (heuristic-only mode), set llm_aux_role to null in
cashew.json:
{"llm_aux_role": null}An absent, null, or incomplete role mapping also keeps extraction heuristic-only.
- LLM extraction — structured knowledge extraction with typed nodes, confidence scores, tags, and domain assignment
- Think cycles — cross-domain synthesis, generates
insightnodes from clusters of related knowledge. Runs everythink_intervalsync turns (default 10). Setthink_intervalto 0 to disable. - Sleep synthesis — Graph consolidation pipeline: cross-linking, dedup,
garbage collection, permanence evaluation, core memory promotion, and
LLM-powered dream generation. Runs as a Hermes cron job on a configurable
schedule (default: every 12 hours), not at session boundaries. The cron script
reads
cashew.jsonat runtime and uses the same explicit auxiliary role when one is configured; otherwise it runs without an LLM. Processes up tosleep_max_nodesper cycle (default 2,000). - Pre-compress insight extraction — Before context compression discards
old messages, extracts conversation-arc patterns (topic shifts, framing
changes, implicit decisions) using a dedicated LLM prompt. Creates
insight/observationnodes in the graph. Requiresllm_aux_roleconfiguration. Silent-degrades without LLM.
Without an explicitly enabled llm_aux_role mapping, the plugin uses
heuristic-only extraction — no API calls and no LLM cost.
Design note: The auxiliary.memory convention is provider-agnostic.
Any memory provider plugin can declare llm_aux_role and reference the
same auxiliary.memory section, making this a standard pattern across
the Hermes plugin ecosystem.
hermes-cashew runs its graph consolidation pipeline (cross-linking, dedup,
garbage collection, permanence evaluation, core memory promotion) as a
Hermes no_agent cron job, not at session boundaries. This means
/new returns instantly — no synchronous sleep cycle work blocks the
start of a new session.
The cron job is created during plugin initialization (initialize()) only
when all of the following are true:
| Condition | Config Key | Default | Behavior if false |
|---|---|---|---|
| Sleep cycles enabled | sleep_cycles |
true |
Cron not registered |
| Schedule non-empty | sleep_schedule |
\"every 12h\" |
Cron not registered; set to \"\" to disable |
| Provider init succeeds | — | — | Exception caught, _config set to None, cron never reached |
| Hermes cron module available | — | — | ImportError caught, WARNING logged |
create_job() succeeds |
— | — | Exception caught, WARNING logged |
| Matching profile-owned job already registered | — | — | Existing job is adopted without changing its schedule |
The job persists across ordinary provider shutdown and is adopted by a later initialize for the same Hermes profile. Reconciliation serializes concurrent initializers, tags ownership with an opaque profile token, and only replaces or disables a job carrying that token. It never deletes a similarly named job that does not prove it belongs to this profile.
On the configured schedule (default every 12h), the Hermes scheduler
executes $HERMES_HOME/scripts/cashew-sleep-cycle.py as a no_agent
script. Registration embeds the complete validated effective configuration,
including JSON/default values and any valid CASHEW_* overrides, so a cron
daemon cannot drift from its provider's DB, model, device, limits, or auxiliary
role. Changes to JSON or environment settings take effect on the next provider
initialize, which atomically refreshes the script and reconciles the job. The
cycle uses LLM dream synthesis only when that embedded configuration has an
explicit configured auxiliary role; otherwise it runs with no LLM overhead.
The generated script is pinned to the Cashew installation that registered the
job. This keeps one Hermes profile from loading another profile's provider.
After moving, reinstalling, or changing the plugin layout, reinitialize
Cashew to refresh the script and cron registration. Development installs may
use the documented $HERMES_HOME/hermes-agent/plugins/memory/cashew symlink
to an external checkout.
- Uses the validated effective configuration embedded when the job was
registered; it does not reread
cashew.jsonor ambientCASHEW_*values during a tick - Selects up to
sleep_max_nodes(default 2,000) eligible nodes - Computes pairwise cosine similarity (vectorized numpy)
- Creates and repairs cross-links using the configured model-profile thresholds
- Deduplicates near-identical nodes through maximal-clique consolidation
- Runs garbage collection on low-fitness isolated nodes
- Promotes frequently-accessed nodes to permanent / core memory status
- Prints a JSON summary (captured by the cron scheduler's output log)
Without an explicit auxiliary role, no LLM-powered dream generation occurs in cron mode. Cross-linking, dedup, and GC still provide the graph-maintenance benefit without a provider dependency in the subprocess.
Embedding-dimension migration and the synchronous portion of a sleep cycle use
the same nonblocking advisory lock derived from the configured
cashew_db_path. A contended migration defers and a contended cycle skips;
the lock file is retained because a process exit releases its flock
descriptor automatically. Do not delete an old lock file to recover a cycle:
unlinking it can let a second pathname refer to a different inode while the
original process still holds the lock.
The Hermes adapter rejects background_dream=True with
background_dream_unsupported. Supported cron runs pass false, so the
adapter keeps the entire accepted cycle inside the synchronous maintenance-lock
scope.
| Key | Default | Description |
|---|---|---|
sleep_schedule |
\"every 12h\" |
Cron expression or interval string. Set to \"\" to disable cron-based scheduling entirely. Examples: \"every 30m\", \"0 */2 * * *\", \"0 3 * * *\" (daily at 3am). |
sleep_max_nodes |
2000 |
Maximum number of eligible nodes considered in one sleep cycle. Higher values can increase consolidation work and tick time. |
sqlite-vec enables vector similarity search and is installed automatically as a
standard dependency. If your platform doesn't support sqlite-vec's native extension,
the plugin degrades gracefully to keyword-based retrieval — still functional,
but less precise.
sqlite-vec is a standard dependency, but loading its native extension can fail. Startup then records degraded vector availability and retains keyword + BFS retrieval.
Normal provider shutdown intentionally preserves the profile-owned cron job.
Before removing the plugin, disable sleep in Cashew setup (set
sleep_cycles to false or sleep_schedule to "") and initialize
the provider once. Reconciliation then removes only the job whose ownership
matches that Hermes profile. Afterwards use the host-supported removal flow:
hermes plugins remove cashew
hermes config set memory.provider built-in # revert to built-in memory
# Irreversible: back up first, then optionally remove the local graph data.
rm -rf "${HERMES_HOME:-$HOME/.hermes}/cashew"Check that Hermes installed and enabled the plugin for the active profile:
hermes plugins list
hermes plugins doctor
hermes memory statusUpdate or reinstall the intended plugin revision through Hermes if its files are
missing. Preserve cashew.json and the graph database. Do not install an unrelated
cashew-brain release to repair this plugin's bundled engine.
Check the active profile, declared dependency installation, and startup logs.
Run scripts/verify-cashew-baseline.py with Hermes' selected plugin interpreter to
verify the adjacent flat source (or add --installed to audit a wheel/sdist). A damaged or incomplete bundle requires reinstalling
the same reviewed plugin revision. Model availability and initialization health
are separate from dependency importability; see the health guidance above.
Hermes-agent creates a minimal venv without pip. Bootstrap it first:
~/.hermes/hermes-agent/venv/bin/python3 -m ensurepip
~/.hermes/hermes-agent/venv/bin/python3 -m pip install <package>Do not run pip install from outside the venv targeting the hermes python,
or the package will land in the wrong environment.
The plugin installs sentence-transformers. The first retrieval operation may trigger a ~500 MB embedding model download. To avoid this in automated environments:
HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 HF_DATASETS_OFFLINE=1 hermes ...When embedding_model changes, the provider compares both stored embedding
rows and the sqlite-vec table with the configured model's dimension during
initialization. A mismatch is repaired before background workers start:
- a consistent SQLite backup is written under
cashew/backups/; - cashew-brain re-embeds active nodes and recreates
vec_embeddingsat the configured dimension; - the provider validates the new dimensions before enabling retrieval.
If backup, migration, or validation fails, the provider logs a warning and
restores the backup. Thought nodes are not discarded. Stop other Hermes or
Cashew processes before deliberately changing embedding_model, then restart
Hermes and allow the one-time migration to finish before issuing queries.
Install uv, then use the committed lockfile for a reproducible development environment:
git clone https://github.com/magnus919/hermes-cashew
cd hermes-cashew
uv sync --frozen --extra dev
uv run --frozen --extra dev pytestTests require no network access and mock the embedding model automatically
(HF_HUB_OFFLINE=1 is set by tests/conftest.py).
The plugin uses a dual-path loading strategy to support both pip install -e .
(development) and hermes plugins install (flat-entry loader):
- pip / test path: Python's namespace package mechanism resolves
plugins.memory.cashewtoplugins/memory/cashew/__init__.pyviasys.path - flat-entry path: Hermes loads
~/.hermes/plugins/cashew/__init__.pyas_hermes_user_memory.cashew. The root__init__.pydetects this context and exec's the nested implementation withsys.modulespatched so relative imports resolve correctly
See LICENSE.