14 KiB
Live verification
Two end-to-end runs against an unmodified Hermes 0.9.0 install on Proxmox LXC 129. The Apr 14 run validated the v0.1 HTTP backend; the May 9 run validated the v0.2 embedded backend. Both run real DeepSeek-driven sessions; transcripts cited verbatim.
Reading order: the May 9 (v0.2) section is at the top because it's the current shipping default; the Apr 14 (v0.1) section is preserved below for the contrast and because the bug-it-caught is still worth documenting.
v0.2 verification — 2026-05-09 — embedded backend
Environment
-
Hermes version: 0.9.0 (existing clone on LXC 129, refreshed working tree).
-
Host: LXC 129 on Proxmox node2, IP 192.168.4.24 (lease changed since Apr 14), Ubuntu 24.04 LTS, Python 3.12.3,
uv0.11.6. -
Backend: embedded —
yantrikdb._yantrikdb_rust.YantrikDB.with_default(...)in-process. No yantrikdb-server, no Docker, no token, no URL, no cluster. -
pip install yantrikdbbrought:uuid-utils + click + yantrikdb. Total ~10 MB. Empirically slim (yantrikdb 0.7.6 moved heavy ML deps to extras). -
Hermes
.envis literally three lines:YANTRIKDB_MODE=embedded YANTRIKDB_DB_PATH=/root/.hermes/yantrikdb-memory.db YANTRIKDB_NAMESPACE=hermes-demo -
LLM backend: DeepSeek (
deepseek-chat,https://api.deepseek.com/v1).
Plugin discovery (embedded mode)
$ uv run hermes memory status
Memory status
────────────────────────────────────────
Built-in: always active
Provider: yantrikdb
Plugin: installed ✓
Status: available ✓
Installed plugins:
• yantrikdb (API key / local) ← active
is_available() returned True without a token, just because import yantrikdb._yantrikdb_rust succeeds.
Real session: 3 × remember + recall + stats
Same prompt as the Apr 14 session, for direct comparison:
🔧 API call #1/6 — 3 yantrikdb_remember calls in ONE response:
Tool 1: yantrikdb_remember({"text": "My name is Pranab Sarkar", ...})
→ {"rid": "019e0abf-b90b-..."} in 0.08s ← includes one-time
YantrikDB.with_default()
engine warmup
Tool 2: yantrikdb_remember({"text": "I prefer Rust for systems code", ...})
→ {"rid": "019e0abf-bcf5-..."} in 0.00s ← sub-ms
Tool 3: yantrikdb_remember({"text": "I am building YantrikDB ...", ...})
→ {"rid": "019e0abf-c0df-..."} in 0.00s ← sub-ms
🔧 API call #2/6 — recall + stats:
yantrikdb_recall({"query": "Pranab Rust", "top_k": 5})
→ {"count": 3, ...} in 0.00s
yantrikdb_stats({})
→ {"active_memories": 3, "edges": 0, "open_conflicts": 0, ...} in 0.00s
🎉 Conversation completed after 3 OpenAI-compatible API calls.
The whole flow finished in 3 API turns (vs 3 on Apr 14 HTTP — same conversational shape, but no token-mint preamble, no leader-failover, no 503 cascade).
why_retrieved flowing through DeepSeek's reasoning
The agent's natural-language summary, verbatim:
Recall (query: "Pranab Rust", top_k=5): All 3 memories returned, ranked by relevance × recency × importance. Your name ranked highest (semantic match + keyword + high importance + recency), followed by the Rust preference (keyword match), then the YantrikDB project (high importance but no direct keyword overlap).
The agent is reading the why_retrieved reason codes per result and reflecting them in its explanation. That's the explainability story working in the wild — embedded path has the same surface as HTTP, the model doesn't know or care which backend produced the response.
Steady-state latency (separate 100-iteration micro-benchmark)
Captured by running the plugin's own EmbeddedYantrikDBClient in a tight loop, post-warmup:
| Op | p50 | p95 | p99 |
|---|---|---|---|
record_text |
0.60 ms | 0.82 ms | 10.66 ms |
recall_text |
2.58 ms | 11.79 ms | 13.24 ms |
vs the v0.1 HTTP path against the homelab cluster (Apr 14 same machine, 100 ops):
| Op | p50 | p95 | p99 |
|---|---|---|---|
remember |
13.8 ms | 22.6 ms | 55.3 ms |
recall |
24.0 ms | 44.8 ms | 67.2 ms |
Embedded is ~23× faster on writes (p50) and ~9× faster on recall (p50). Cold start is one-time 77 ms when with_default() first loads the bundled potion-2M embedder.
What this proves for v0.2.0
- The plugin loads cleanly in an unmodified Hermes 0.9.0 install with
YANTRIKDB_MODE=embeddedand zero infrastructure. - All 8 tools (
remember,recall,forget,think,conflicts,resolve_conflict,relate,stats) work via the embedded backend identically to the HTTP backend. why_retrievedreason codes from the engine reach the model and show up in the model's reasoning.- Steady-state latency is ~23×/~9× faster than the HTTP path; cold start is one-time 77 ms.
- The whole install is
pip install yantrikdb-hermes-plugin(~10 MB total). No torch, no transformers, no scipy, no Docker, no token, no URL.
v0.1 verification — 2026-04-14 — HTTP backend
Real end-to-end verification of the plugin against a live Hermes install talking to a live yantrikdb-server cluster. Captured here so the PR body can cite concrete evidence rather than "tests pass in CI".
Environment
- Hermes version: 0.9.0 (
git clone --depth 1 https://github.com/NousResearch/hermes-agent.git, 2026-04-14) - Host: LXC 129 on Proxmox node1, IP 192.168.4.54, Ubuntu 24.04 LTS, Python 3.12.3,
uv0.11.6 - YantrikDB cluster: 3-node raft cluster on homelab (yantrikdb-1/140 leader, yantrikdb-2/141 follower, yantrikdb-witness/142), v0.5.x with encryption auto-generated
- Namespace used:
hermes-demo(isolated from Pranab's production memories) - LLM backend: DeepSeek (
deepseek-chat,https://api.deepseek.com/v1) via Hermes'--base_url/--modelflags
Bug caught by live testing
Running the first Hermes session surfaced a bug that all 95 unit tests had missed:
get_tool_schemas() guarded the schema list with self._client is None, returning [] before initialize() ran. Hermes calls get_tool_schemas() at provider register time (in MemoryManager._register_provider) to index tool_name → provider for routing, which happens strictly before initialize(). With the guard, Hermes never indexed our tool names, so every yantrikdb_* tool call from the agent resolved as "Unknown tool" and the model fell back to the built-in memory tool.
Fix: return list(ALL_TOOL_SCHEMAS) unconditionally (except for the cron-context skip, which is set inside initialize() — fine, since cron contexts don't register the provider for tool use anyway). Runtime readiness is enforced in handle_tool_call(), which is where it belongs.
Added test test_schemas_available_before_initialize that asserts the eight tool names are present before initialize() runs, so this regresses if anyone reintroduces a similar guard. This test alone would have caught the bug offline.
Verification 1 — plugin discovery
$ uv run hermes memory status
Memory status
────────────────────────────────────────
Built-in: always active
Provider: yantrikdb
Plugin: installed ✓
Status: available ✓
Installed plugins:
• byterover (requires API key)
• hindsight (API key / local)
• holographic (local)
• honcho (API key / local)
• mem0 (API key / local)
• openviking (API key / local)
• retaindb (API key / local)
• supermemory (requires API key)
• yantrikdb (API key / local) ← active
Hermes picked up plugin.yaml, ran is_available() (returned True because YANTRIKDB_TOKEN is set), and marked the plugin active.
Verification 2 — remember × 3 + stats
User query: "Use the yantrikdb_remember tool three separate times to store: (1) text=My name is Pranab Sarkar, domain=people, importance=0.9 (2) text=I prefer Rust for systems code, domain=preference, importance=0.8 (3) text=I am building YantrikDB a cognitive memory database for AI agents, domain=work, importance=0.9. Then call yantrikdb_stats."
🔄 API call #1/6
🤖 I'll store those three memories and then check the database stats.
🔧 3 tool calls:
yantrikdb_remember(text="My name is Pranab Sarkar", domain="people", importance=0.9)
→ {"rid": "019d8eac-f59b-712b-bae7-f7e38326a16e", "stored": true}
yantrikdb_remember(text="I prefer Rust for systems code", domain="preference", importance=0.8)
→ {"rid": "019d8eac-f999-7c24-b066-f2c710a38111", "stored": true}
yantrikdb_remember(text="I am building YantrikDB a cognitive memory database for AI agents", domain="work", importance=0.9)
→ {"rid": "019d8eac-fda4-710b-b84f-c6a427dcec4c", "stored": true}
🔄 API call #2/6
🔧 1 tool call:
yantrikdb_stats()
→ {"active_memories": 1795, "consolidated_memories": 7, "tombstoned_memories": 4,
"edges": 1, "entities": 2, "operations": 75068, "open_conflicts": 3, "pending_triggers": 98}
🎯 Final: Stored 3 memories with RIDs [...]. Database stats: 1795 active memories, 0 open conflicts, 98 pending triggers.
- Three distinct writes, three distinct rids, returning as-expected
{rid, stored}envelopes. yantrikdb_statsround-tripped with the full operational snapshot.- Active memories went from 1790 (pre-test cluster state) to 1795 = 3 writes from this session + 2 from earlier integration test. Counts line up exactly.
Verification 3 — explainable recall with why_retrieved
This is the differentiator. User query: "Call yantrikdb_recall with query='Pranab Sarkar Rust' and top_k=5. Show me the full JSON response verbatim, especially the why_retrieved field for each result."
🔄 API call #1/4
🔧 1 tool call:
yantrikdb_recall(query="Pranab Sarkar Rust", top_k=5)
→ {"count": 5, "results": [
{"rid": "019d8eac-f59b-712b-bae7-f7e38326a16e",
"text": "My name is Pranab Sarkar",
"score": 1.404,
"importance": 0.9,
"domain": "people",
"created_at": 1776215192.987,
"why_retrieved": ["semantically similar (0.59)", "recent",
"important (decay=0.76)", "keyword_match"]},
{"rid": "019d8eac-f999-7c24-b066-f2c710a38111",
"text": "I prefer Rust for systems code",
"score": 1.195,
"importance": 0.8,
"domain": "preference",
"created_at": 1776215194.009,
"why_retrieved": ["recent", "important (decay=0.68)", "keyword_match"]},
{"rid": "019d8ead-1f24-74c6-9580-a2b0f095c1bb",
"text": "Use the yantrikdb_remember tool three separate times ...",
"score": 1.017,
"importance": 0.6,
"domain": "",
"created_at": 1776215203.620,
"why_retrieved": ["recent", "important (decay=0.53)",
"keyword_match", "fts_sourced"]},
{"rid": "019d8902-0d94-79cb-a342-a76311a48ca6",
"text": "benchmark memory number 644 about topic 44 ...",
"score": 0.405,
"importance": 0.5,
"domain": "benchmark",
"why_retrieved": ["recent"]},
{"rid": "019d8902-125d-7721-97d1-6738e7294318",
"text": "benchmark memory number 663 about topic 13 ...",
"score": 0.394,
"importance": 0.5,
"domain": "benchmark",
"why_retrieved": ["recent"]}
]}
why_retrievedis a real array of reason codes per result, not a claim in our README.- Top 3 results rank by the dimensions our tool description promised: semantic similarity × recency × importance, with keyword/FTS signals layered in.
- Lower-ranked
benchmarkmemories from an earlier unrelated session are clearly distinguished — they only have"recent"as a reason. - The agent (DeepSeek) passed through the reason lists verbatim, confirming the provider's JSON shape reaches the model intact.
What this proves for the PR
- The plugin loads cleanly in an unmodified Hermes 0.9.0 install.
- The
MemoryProvidercontract is honored —is_available/initialize/get_tool_schemas/handle_tool_callall fire in the expected order. - The eight tools (
remember,recall,forget,think,conflicts,resolve_conflict,relate,stats) are registered with Hermes and routable from the model. - Wire protocol is correct against a real
yantrikdb-server— all response field names match (rid,why_retrieved,consolidation_count,active_memories, …). - The "explainable recall" differentiator is real, not marketing — the server returns reason codes and the plugin passes them through unchanged.
- The 95 unit tests plus the 2 live integration tests (now in
tests/integration/test_live.py) form a meaningful coverage lattice.
Known caveats surfaced during this run
- Token replication is node-local in the tested cluster build. A token minted against the leader (node 141 at term 39) was rejected by the new leader (node 140 at term 40) after a raft election. Re-minting on the current leader worked. This is a yantrikdb-server issue (control-plane replication), not a plugin issue — the plugin surfaces a clean
YantrikDBAuthErrorwith the 401, and the circuit breaker doesn't trip. Worth noting in the PR's troubleshooting section as a real failure mode operators may hit. - Auxiliary LLM not configured on the test box, so Hermes' context compression would drop middle turns without a summary. Irrelevant for this short demo, but worth flagging for anyone trying to run multi-hour sessions with yantrikdb as the only external memory.
Reproducing this
# From the workspace root, after a yantrikdb-server is running at $YDB_URL:
YANTRIKDB_INTEGRATION_URL=$YDB_URL \
YANTRIKDB_INTEGRATION_TOKEN=$YDB_TOKEN \
python -m pytest tests/integration/ -v
For the live Hermes session, see VERIFICATION.md prose above — that path requires an LXC with Hermes installed and is not wrapped into a one-liner (yet).