同步完整源码 - 2026-05-24
This commit is contained in:
commit
47ae137ac0
|
|
@ -0,0 +1,68 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
jobs:
|
||||
# Catches the v0.4.13 release-day miss: pyproject.toml + plugin.yaml +
|
||||
# CHANGELOG header must all carry the same version, or PyPI ships with
|
||||
# a stale Hermes-side manifest. Cheap static check, runs once per PR.
|
||||
version-sync:
|
||||
name: Version sync
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Verify version surfaces match
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PYPROJECT=$(grep -E '^version = ' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
||||
PLUGIN_YAML=$(grep -E '^version: ' yantrikdb/plugin.yaml | head -1 | sed 's/version: //')
|
||||
CHANGELOG=$(grep -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' yantrikdb/CHANGELOG.md | head -1 | sed 's/## \[\(.*\)\]/\1/')
|
||||
echo "pyproject.toml = $PYPROJECT"
|
||||
echo "plugin.yaml = $PLUGIN_YAML"
|
||||
echo "CHANGELOG.md = $CHANGELOG"
|
||||
if [ "$PYPROJECT" != "$PLUGIN_YAML" ] || [ "$PYPROJECT" != "$CHANGELOG" ]; then
|
||||
echo "::error::Version drift — pyproject.toml=$PYPROJECT, plugin.yaml=$PLUGIN_YAML, CHANGELOG=$CHANGELOG"
|
||||
echo ""
|
||||
echo "Bump all three together. The repo has bump-my-version configured:"
|
||||
echo " pipx run bump-my-version bump patch # 0.4.14 -> 0.4.15"
|
||||
echo " pipx run bump-my-version bump minor # 0.4.14 -> 0.5.0"
|
||||
echo " pipx run bump-my-version bump major # 0.4.14 -> 1.0.0"
|
||||
echo "Then add a matching CHANGELOG entry."
|
||||
exit 1
|
||||
fi
|
||||
echo "All three version sources match: $PYPROJECT"
|
||||
|
||||
test:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests pytest ruff mypy types-requests
|
||||
|
||||
- name: Lint (ruff)
|
||||
run: ruff check yantrikdb/ tests/
|
||||
|
||||
- name: Type check (mypy)
|
||||
run: mypy --ignore-missing-imports yantrikdb/
|
||||
|
||||
- name: Run tests
|
||||
run: python -m pytest tests/ -v
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
name: Publish to PyPI
|
||||
|
||||
# Triggered when a GitHub Release is *published* (not on raw tag pushes).
|
||||
# This is the deliberate-release gate: pushing a `vX.Y.Z` tag now creates
|
||||
# the tag but does NOT ship to PyPI until a Release is also published from
|
||||
# that tag — either via the GitHub web UI or `gh release create`.
|
||||
#
|
||||
# Rationale: tags are cheap to push by accident or for in-progress work;
|
||||
# Releases are an explicit "this is the version we want users to install"
|
||||
# signal. The pre-v0.4.7 workflow fired on every tag, which works but
|
||||
# loses that distinction.
|
||||
#
|
||||
# Pipeline remains: ruff + mypy + pytest gate FIRST; PyPI upload only
|
||||
# on pass. Uses PyPI Trusted Publisher (no API token in repo secrets) —
|
||||
# configure once via PyPI's web UI:
|
||||
# https://pypi.org/manage/project/yantrikdb-hermes-plugin/settings/publishing/
|
||||
#
|
||||
# Trusted Publisher config to set on PyPI:
|
||||
# Owner: yantrikos
|
||||
# Repo: yantrikdb-hermes-plugin
|
||||
# Workflow: publish.yml
|
||||
# Environment: pypi
|
||||
#
|
||||
# Ship sequence going forward:
|
||||
# git tag vX.Y.Z
|
||||
# git push origin main vX.Y.Z
|
||||
# gh release create vX.Y.Z --notes-from-tag # → triggers PyPI publish
|
||||
#
|
||||
# Or do tag + release in one shot:
|
||||
# gh release create vX.Y.Z --target main --title "vX.Y.Z" --notes "…"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build distributions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install build deps + test deps
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build ruff mypy types-requests pytest requests
|
||||
|
||||
- name: Read version from tag
|
||||
id: version
|
||||
run: |
|
||||
tag="${GITHUB_REF##*/}"
|
||||
version="${tag#v}"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
# Sanity check: tag must match pyproject.toml version
|
||||
pyproject_version=$(grep -E '^version = ' pyproject.toml | head -1 | awk -F'"' '{print $2}')
|
||||
if [ "$version" != "$pyproject_version" ]; then
|
||||
echo "::error::tag $tag does not match pyproject.toml version $pyproject_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Lint (ruff)
|
||||
run: ruff check yantrikdb/ tests/
|
||||
|
||||
- name: Type check (mypy)
|
||||
run: mypy --ignore-missing-imports yantrikdb/
|
||||
|
||||
- name: Run tests
|
||||
run: python -m pytest tests/ -v
|
||||
|
||||
- name: Build wheel + sdist
|
||||
run: python -m build
|
||||
|
||||
- name: Inspect distributions
|
||||
run: |
|
||||
ls -la dist/
|
||||
# Sanity: wheel filename should embed the right version
|
||||
ls dist/yantrikdb_hermes_plugin-${{ steps.version.outputs.version }}*.whl >/dev/null
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
publish:
|
||||
name: Publish to PyPI
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write # required for PyPI Trusted Publisher (OIDC)
|
||||
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
# No password/token — Trusted Publisher exchanges the OIDC token
|
||||
# from GitHub Actions for a short-lived PyPI scoped token.
|
||||
# Configure on PyPI: https://docs.pypi.org/trusted-publishers/
|
||||
packages-dir: dist/
|
||||
skip-existing: true # idempotent re-runs of the same tag don't fail
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.DS_Store
|
||||
*.log
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Pranab Sarkar and yantrikdb-hermes-plugin contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
# yantrikdb-hermes-plugin
|
||||
|
||||
[](https://github.com/yantrikos/yantrikdb-hermes-plugin/actions/workflows/ci.yml)
|
||||
[](https://github.com/yantrikos/yantrikdb-hermes-plugin/actions)
|
||||
[](https://github.com/yantrikos/yantrikdb-hermes-plugin)
|
||||
[](LICENSE)
|
||||
[](https://github.com/yantrikos/yantrikdb-server)
|
||||
[](https://github.com/NousResearch/hermes-agent)
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://mypy-lang.org/)
|
||||
|
||||
> **YantrikDB as a memory provider for [Hermes Agent](https://github.com/NousResearch/hermes-agent).** Self-maintaining memory — canonicalizes duplicates, surfaces contradictions, explains recall — in a drop-in plugin. As of **v0.2.0** the default backend is **in-process** (`pip install` and go, no separate server).
|
||||
|
||||
This repository **is** the canonical distribution. Per Hermes maintainer guidance, new memory providers aren't being merged upstream — the recommended pattern is standalone plugins that users install via `pip` and register with their Hermes home directory. That keeps the version cadence, CI gating, issue triage, and review cycle on the plugin author's side, so fixes ship the same day they're ready instead of waiting on upstream review bandwidth.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Two recurring observations from the Hermes community map directly to what yantrikdb does:
|
||||
|
||||
> "Compression was silently dropping earlier constraints by turn 50." — Hermes developer building a long-running coding agent ([user-stories](https://hermes-agent.nousresearch.com/docs/user-stories))
|
||||
|
||||
The `on_pre_compress` hook injects the highest-salience memories before Hermes compresses, so constraints survive long sessions. Recency-aware ranking + `conflicts()` makes superseded claims visible instead of letting them silently outrank their replacements.
|
||||
|
||||
> "Spent 200-400 hours building a memory kernel because standard vector approaches dropped important constraints; successful implementations used temporal context graphs with lifecycle management — promotion / demotion / supersession — rather than vector similarity alone." — Hermes developer who built their own memory layer after vector approaches failed ([user-stories](https://hermes-agent.nousresearch.com/docs/user-stories))
|
||||
|
||||
This is the substrate yantrikdb already ships: temporal context graph via `relate()`, lifecycle via `consolidation_status` + `forget()` + the `think()` maintenance pass, recency ranking, first-class conflicts/canonicalization. Drop-in via `hermes plugins install`. The 200-400 hours are someone else's; you get the substrate.
|
||||
|
||||
### And what other Hermes memory providers don't have
|
||||
|
||||
| Capability | yantrikdb-hermes-plugin | Most others |
|
||||
|---|---|---|
|
||||
| Agent-authored skills with outcome ledger (`yantrikdb_skill_define` / `_search` / `_outcome`) | ✓ first-class, DB-native peer to Hermes' filesystem Markdown skills | filesystem-only (Hermes built-in) |
|
||||
| Contradiction tracking (`conflicts()` + `resolve_conflict()`) | ✓ first-class primitive | not in [mem0's 2026 taxonomy](https://docs.mem0.ai) |
|
||||
| Explainable recall (`why_retrieved` per result) | ✓ list of scoring reasons returned with every result | rarely surfaced |
|
||||
| Owner-scoping for multi-platform Hermes (Telegram + WhatsApp + Discord routed by canonical owner) | ✓ v0.4.10 identity-map + v0.4.11 shared group spaces | one shared namespace, manual scoping |
|
||||
| Embedded mode default (no server, no token, no GPU) | ✓ v0.2.0+ | varies |
|
||||
| HTTP backend for HA clusters | ✓ v0.5.0 (against yantrikdb-server) | varies |
|
||||
|
||||
## End-to-end demo — substrate growing through the skill lifecycle
|
||||
|
||||

|
||||
|
||||
Six skills from prior sessions, color-coded by type. Session 1: the agent adds a 7th (pink, just-created). Session 2 search highlights the relevant node, outcome recorded turns it green. Source: [`demo_visual.py`](./assets/demos/skill-lifecycle/demo_visual.py).
|
||||
|
||||
### What's actually running underneath
|
||||
|
||||

|
||||
|
||||
`gpt-4o-mini` receives the plugin's 15 tool schemas via OpenAI's chat-completions API and chooses when to call each one. In session 1 it autonomously picks the `skill_id` (`release.yantrikos.clean`), `applies_to` tags, and body for a workflow it just learned. In session 2 — fresh provider instance, same substrate — it searches the substrate, finds the skill, follows it, and records an outcome. Two real rids land. The autonomy loop closes in ~10 seconds.
|
||||
|
||||
Sources: [`demo_llm.py`](./assets/demos/skill-lifecycle/demo_llm.py) + [`transcript-llm.txt`](./assets/demos/skill-lifecycle/transcript-llm.txt) + [`demo_llm.tape`](./assets/demos/skill-lifecycle/demo_llm.tape) for rendering. A scripted (no API key required) deterministic version is also included: [`demo.py`](./assets/demos/skill-lifecycle/demo.py) / [`demo.gif`](./assets/demos/skill-lifecycle/demo.gif).
|
||||
|
||||
The plugin's `handle_tool_call` dispatch path you see in both demos is the same entry point Hermes invokes internally. For larger-scale evidence of LLM-driven autonomy: [`yantrikdb.com/guides/autonomous-skills/`](https://yantrikdb.com/guides/autonomous-skills/) documents 17 skills authored by Claude across many sessions on one production substrate, with 9 showing cross-session reuse via the outcome ledger.
|
||||
|
||||
## Install (default — embedded backend)
|
||||
|
||||
The v0.2.0 default backend is **in-process**: no separate server, no token, no GPU, no network. Bundled `potion-base-2M` static embedder (~8 MB, dim=64) loads on first call (~80 ms one-time warmup) and stays in-process.
|
||||
|
||||
### Option A — `hermes plugins install` (v0.4.5+, one command for the plugin source)
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
hermes plugins install yantrikos/yantrikdb-hermes-plugin
|
||||
pip install yantrikdb # ~10 MB; in the same Python env as Hermes
|
||||
hermes memory setup # → Select "yantrikdb" and press Enter
|
||||
hermes memory status # → Provider: yantrikdb Status: available ✓
|
||||
```
|
||||
|
||||
`hermes plugins install` clones the repo into `~/.hermes/plugins/yantrikdb/` based on `plugin.yaml`'s `name:` field. The `pip install yantrikdb` step gets the engine — `hermes plugins install` doesn't auto-install pip dependencies, so this is a separate step. **Crucially: pip-install into the same Python environment Hermes runs from.** If Hermes was installed via `pipx`, use `pipx inject hermes-agent yantrikdb`. If you're using a regular venv, source it first.
|
||||
|
||||
If your Hermes environment uses `uv` and does not have `pip` available, install with the Hermes Python explicitly:
|
||||
|
||||
```bash
|
||||
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python yantrikdb
|
||||
```
|
||||
|
||||
### Option B — `pip install yantrikdb-hermes-plugin` (bundled package path)
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
pip install yantrikdb-hermes-plugin # pulls yantrikdb engine + the provider source
|
||||
yantrikdb-hermes install # registers ~/.hermes/plugins/yantrikdb
|
||||
hermes memory setup # → Select "yantrikdb" and press Enter
|
||||
hermes memory status # → Provider: yantrikdb Status: available ✓
|
||||
```
|
||||
|
||||
If your Hermes environment uses `uv` and does not have `pip` available, install with the Hermes Python explicitly:
|
||||
|
||||
```bash
|
||||
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python yantrikdb-hermes-plugin
|
||||
~/.hermes/hermes-agent/venv/bin/yantrikdb-hermes install
|
||||
```
|
||||
|
||||
`yantrikdb-hermes install` registers the pip-installed provider with Hermes by creating a lightweight shim at `~/.hermes/plugins/yantrikdb` (or `$HERMES_HOME/plugins/yantrikdb`). The shim imports the real provider from the installed `yantrikdb-hermes-plugin` package, so future package upgrades are picked up without copying the whole provider tree. Use `yantrikdb-hermes install --copy` if your environment prefers a physical copy instead of the default shim.
|
||||
|
||||
### Updating
|
||||
|
||||
Option A updates the plugin checkout and the engine dependency separately:
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
hermes plugins update yantrikdb
|
||||
pip install --upgrade yantrikdb
|
||||
hermes gateway restart # if Hermes is running as a gateway/service
|
||||
hermes memory status
|
||||
```
|
||||
|
||||
If your Hermes CLI does not have `hermes plugins update`, reinstall the plugin source in place:
|
||||
|
||||
```bash
|
||||
hermes plugins install yantrikos/yantrikdb-hermes-plugin --force
|
||||
```
|
||||
|
||||
Option B updates the pip package, then refreshes the registered shim:
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
pip install --upgrade yantrikdb-hermes-plugin
|
||||
yantrikdb-hermes install --force
|
||||
hermes gateway restart # if Hermes is running as a gateway/service
|
||||
hermes memory status
|
||||
```
|
||||
|
||||
For uv-only environments, target the Hermes Python explicitly:
|
||||
|
||||
```bash
|
||||
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python --upgrade yantrikdb
|
||||
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python --upgrade yantrikdb-hermes-plugin
|
||||
```
|
||||
|
||||
`--force` replaces the registered plugin directory. Back up any plugin-local files first if you keep custom files under `~/.hermes/plugins/yantrikdb/`; normal YantrikDB settings belong in `~/.hermes/.env` and are not touched.
|
||||
|
||||
### Uninstalling
|
||||
|
||||
Option A uses Hermes' plugin manager for the plugin source, plus pip for the engine dependency:
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
hermes plugins remove yantrikdb
|
||||
pip uninstall yantrikdb
|
||||
hermes memory setup # choose another provider, or disable external memory
|
||||
hermes gateway restart # if Hermes is running as a gateway/service
|
||||
```
|
||||
|
||||
Option B removes the user-plugin registration, then optionally removes the pip packages:
|
||||
|
||||
```bash
|
||||
source ~/.hermes/hermes-agent/venv/bin/activate
|
||||
yantrikdb-hermes uninstall
|
||||
pip uninstall yantrikdb-hermes-plugin yantrikdb
|
||||
hermes memory setup # choose another provider, or disable external memory
|
||||
hermes gateway restart # if Hermes is running as a gateway/service
|
||||
```
|
||||
|
||||
If your installed version does not yet have `yantrikdb-hermes uninstall`, remove the registration manually:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.hermes/plugins/yantrikdb
|
||||
pip uninstall yantrikdb-hermes-plugin yantrikdb
|
||||
```
|
||||
|
||||
### Same-venv guidance (both options)
|
||||
|
||||
`yantrikdb` and `yantrikdb-hermes-plugin` must be importable from whatever Python interpreter Hermes uses:
|
||||
|
||||
- **`pipx install hermes-agent`** → `pipx inject hermes-agent yantrikdb yantrikdb-hermes-plugin`
|
||||
- **Default Hermes venv** → `source ~/.hermes/hermes-agent/venv/bin/activate` first, then `pip install ...`
|
||||
- **Other plain venv** → `source path/to/hermes-venv/bin/activate` first, then `pip install ...`
|
||||
- **uv-only venv** → `uv pip install --python path/to/hermes-venv/bin/python ...`
|
||||
- **System Python** → just `pip install`
|
||||
|
||||
If `hermes memory status` shows `Status: not available ✗` after install, the most common cause is the plugin landed in a different Python than Hermes is using. `which hermes && which python` will confirm.
|
||||
|
||||
### Optional: tier up the embedder
|
||||
|
||||
```bash
|
||||
echo "YANTRIKDB_EMBEDDER=potion-base-8M" >> ~/.hermes/.env # 28 MB, dim=256, ~92% MiniLM
|
||||
# or potion-base-32M for 121 MB, dim=512, ~95% MiniLM
|
||||
# or multilingual via the v0.4.2 model2vec path:
|
||||
echo "YANTRIKDB_EMBEDDER_MODEL2VEC=minishlab/potion-multilingual-128M" >> ~/.hermes/.env
|
||||
# or the broader HF ecosystem via sentence-transformers:
|
||||
echo "YANTRIKDB_EMBEDDER_HF=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" >> ~/.hermes/.env
|
||||
```
|
||||
|
||||
### Optional: quiet the HuggingFace embedder
|
||||
|
||||
`YANTRIKDB_EMBEDDER_HF` uses `sentence-transformers`, which by default emits noise to stdout — tqdm progress bars on every encode and a one-time HF Hub auth warning at startup. The plugin disables the per-encode progress bars internally (v0.4.12+). For the rest, add to your `.env`:
|
||||
|
||||
```bash
|
||||
HF_HUB_DISABLE_PROGRESS_BARS=1
|
||||
TRANSFORMERS_VERBOSITY=error
|
||||
# Optional, when running fully offline after the first download:
|
||||
HF_HUB_OFFLINE=1
|
||||
```
|
||||
|
||||
Without these, sentence-transformers / huggingface_hub output can pollute the agent's own stdout stream.
|
||||
|
||||
## Install (alternative — HTTP backend, for HA cluster setups)
|
||||
|
||||
If you run multiple Hermes instances that need to share one memory store, or you want HA via raft:
|
||||
|
||||
```bash
|
||||
docker run -d -p 7438:7438 -v yantrikdb-data:/var/lib/yantrikdb \
|
||||
--name yantrikdb ghcr.io/yantrikos/yantrikdb:latest
|
||||
docker exec yantrikdb yantrikdb token --data-dir /var/lib/yantrikdb \
|
||||
create --db default --label hermes
|
||||
# → ydb_abc123...
|
||||
|
||||
cat >> ~/.hermes/.env <<EOF
|
||||
YANTRIKDB_MODE=http
|
||||
YANTRIKDB_URL=http://localhost:7438
|
||||
YANTRIKDB_TOKEN=ydb_abc123...
|
||||
EOF
|
||||
```
|
||||
|
||||
Same plugin, same 8 tools, same hooks, same provider contract — just talks HTTP to a separately-managed server instead of running the engine in-process.
|
||||
|
||||
Full config, tool reference, troubleshooting: **[yantrikdb/README.md](yantrikdb/README.md)**.
|
||||
|
||||
## What it does
|
||||
|
||||
The differentiator versus other Hermes memory plugins is not the vector store — it's what happens *after* the write:
|
||||
|
||||
| Feature | Plain vector memory | YantrikDB |
|
||||
|---|---|---|
|
||||
| Duplicate facts | pile up | canonicalized by `think()` |
|
||||
| Contradictions | silently overwrite | surfaced via `conflicts()`, closed via `resolve_conflict()` |
|
||||
| Stale facts | outrank fresh ones | recency-aware ranking without deletion |
|
||||
| Why did a memory rank? | ¯\\_(ツ)_/¯ | every `recall()` result carries a `why_retrieved` reason list |
|
||||
| Cross-entity recall | semantic-only | graph edges from `relate()` boost related memories |
|
||||
|
||||
Twelve tools exposed to the agent by default: `yantrikdb_remember`, `_recall`, `_forget`, `_think`, `_conflicts`, `_resolve_conflict`, `_relate`, `_stats`, plus the trigger-lifecycle consumers `_pending_triggers`, `_acknowledge_trigger`, `_dismiss_trigger`, `_act_on_trigger` (v0.4.13+). Three additional **opt-in** skill tools (v0.3.0+): `_skill_search`, `_skill_define`, `_skill_outcome` — see [Skills](#skills-opt-in-v030) below.
|
||||
|
||||
### Trigger lifecycle (v0.4.13+)
|
||||
|
||||
`yantrikdb_think` flags redundancies, conflicts, and surprise cross-domain connections as **triggers** — substrate signals the agent can inspect and close out:
|
||||
|
||||
- `yantrikdb_pending_triggers` — list what's waiting (with `urgency`, `reason`, `suggested_action`, and the `source_rids` that produced it).
|
||||
- `yantrikdb_acknowledge_trigger` — agent saw it, no follow-up needed.
|
||||
- `yantrikdb_dismiss_trigger` — false positive or out of scope.
|
||||
- `yantrikdb_act_on_trigger` — agent took action; records an audit-trail entry.
|
||||
|
||||
All three closers remove the trigger from `pending_triggers`. Without these tools (pre-v0.4.13) the pending queue grew indefinitely because the producer (`think()`) had no matching consumer surface.
|
||||
|
||||
### Compared to other Hermes memory providers
|
||||
|
||||
Each row in the table below is backed by [`tests/comparison/findings_scale_lxc/<provider>/`](tests/comparison/findings_scale_lxc/) — the actual `findings_scale.yaml`, `transcript.md`, and `raw/` response capture from running a 1000-fact + 20-query probe against that provider on a real Hermes 0.9.0 install (LXC 129, commit `4610551`). The corpus is deterministic (`fixtures/corpus_1k.json`, seed=20260512: 600 realistic agent-memory facts + 300 noise + 50 planted duplicates + 50 planted contradictions); the probe is provider-agnostic and reproducible. Methodology details in [`tests/comparison/README.md`](tests/comparison/README.md).
|
||||
|
||||
| Provider | Hosting | Verified at 1000 scale | Writes (ok/attempted; latency) | Recall latency | Precision@5 | `why_retrieved` field | Maintenance behaviour observed |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **yantrikdb** (this) | embedded | yes — 256/1000 writes [^queuecap] | 256/1000; p50 0.48 ms / p99 5.13 ms | p50 3.78 ms / p99 32.94 ms | **0.80** (16/20) | yes — `why_retrieved` per result | contradiction API: `yantrikdb_conflicts`; duplicates kept separate (canonicalisation via explicit `think()`) |
|
||||
| [byterover](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/byterover) | cloud | couldn't verify — requires `brv` CLI auth | — | — | — | — | — |
|
||||
| [hindsight](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/hindsight) | cloud-default (local-stub mode used) | yes — 1000/1000 writes | 1000/1000; p50 0.27 ms / p99 0.31 ms | p50 0.28 ms / p99 0.30 ms | **0.00** (0/20) [^localstub] | no | — |
|
||||
| [holographic](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/holographic) | embedded (SQLite + FTS5) | yes — 1000/1000 writes [^hrrcap] | 1000/1000; p50 23.43 ms / p99 68.52 ms | p50 0.06 ms / p99 0.23 ms | **0.00** (0/20) [^keyword] | no | — |
|
||||
| [honcho](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/honcho) | self-hosted | couldn't verify — requires honcho-server URL or api key | — | — | — | — | — |
|
||||
| [mem0](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/mem0) | cloud or self-host | couldn't verify — requires `mem0.api_key` | — | — | — | — | — |
|
||||
| [openviking](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/openviking) | self-hosted | couldn't verify — requires `OPENVIKING_ENDPOINT` | — | — | — | — | — |
|
||||
| [retaindb](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/retaindb) | cloud | couldn't verify — requires `RETAINDB_API_KEY` | — | — | — | — | — |
|
||||
| [supermemory](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/supermemory) | cloud | couldn't verify — requires `SUPERMEMORY_API_KEY` | — | — | — | — | — |
|
||||
|
||||
[^queuecap]: yantrikdb v0.4.2 plugin against yantrikdb engine 0.7.8 on Linux: the engine's ingest queue is bounded at 256 pending ops and didn't drain during the 1000-fact burst (probe hit `RuntimeError('ingest queue full ...; retry after 50ms')` from fact 257 onward, even with 60-attempt × 100 ms backoff). Recall on the 256 stored facts is solid (P@5 = 0.80). Surfaced upstream as a likely queue-drain regression in the manylinux build of 0.7.8 — the Hermes plugin itself doesn't loop the writes.
|
||||
|
||||
[^localstub]: `HINDSIGHT_MODE=local_embedded` was set so `is_available()` returns true without an API key, but in this configuration writes return immediately (sub-millisecond) and recall returns no results — the local mode appears to be a no-op stub rather than a real local backend. Full retrieval almost certainly requires the cloud account.
|
||||
|
||||
[^hrrcap]: At ~256 stored items, the engine emits `HRR storage near capacity: SNR=2.00 (dim=1024, n_items=...)` warnings on every subsequent write. The capacity warning is part of holographic's normal output; it's not an error and writes continue to succeed, but retrieval quality is expected to degrade past that point.
|
||||
|
||||
[^keyword]: Holographic's recall is keyword-based (FTS5 + HRR cleanup); the probe's queries are full sentences (`"What color scheme does the user prefer in VS Code?"`). The 0/20 result is a query-format mismatch, not a retrieval failure — keyword-shaped queries probably hit. The honest takeaway is that holographic and yantrikdb target different query shapes, not that one is "better".
|
||||
|
||||
**Where the verified data lives** — every cell in the table maps to a file:
|
||||
|
||||
- `findings_scale.yaml` — structured cells (the table is generated from these by [`compare.py`](tests/comparison/compare.py))
|
||||
- `transcript.md` — human-readable session log with timing
|
||||
- `raw/recall-Q*.json` — captured raw recall responses for every query
|
||||
- `fixtures/corpus_1k.json`, `fixtures/queries_1k.json` — the deterministic corpus + queries used
|
||||
|
||||
**How to re-run it** — clone the repo, `scp tests/comparison/` to a Hermes-installed machine, `python3 runner_scale.py --all`. The harness will skip-with-honest-reason for any provider whose `is_available()` returns False (e.g. missing API key); for the ones that initialise, it produces a fresh `findings_scale.yaml`. Pull requests welcomed when accounts unlock more rows.
|
||||
|
||||
Three optional lifecycle hooks: `on_session_end` auto-consolidates, `on_pre_compress` preserves high-salience memories through context compression, `on_memory_write` mirrors built-in `MEMORY.md` / `USER.md` additions.
|
||||
|
||||
## Skills (opt-in, v0.3.0+)
|
||||
|
||||
Skills are **procedural memory**: reusable patterns the agent distills from observed success and pulls back next session. They live in YantrikDB's shared `skill_substrate` namespace alongside skills authored by other consumers (Lane B SDK, server handlers, WisePick). Hermes-authored skills are tagged `metadata.source=hermes` so any downstream consumer can filter them in or out cleanly.
|
||||
|
||||
**Disabled by default.** Adding the plugin to an existing Hermes install doesn't change the tool schema the model sees. Enable explicitly when you want the agentic skill loop:
|
||||
|
||||
```bash
|
||||
echo "YANTRIKDB_SKILLS_ENABLED=true" >> ~/.hermes/.env
|
||||
```
|
||||
|
||||
When enabled, three new tools join the schema:
|
||||
|
||||
| Tool | Purpose |
|
||||
|---|---|
|
||||
| `yantrikdb_skill_search` | Semantic search over agent-authored skills, namespace-isolated from regular memory recall. |
|
||||
| `yantrikdb_skill_define` | Distill a procedural pattern into a reusable skill (`skill_id`, `body`, `skill_type`, `applies_to`). Client-side validation reproduces yantrikdb-server's wrapper checks. |
|
||||
| `yantrikdb_skill_outcome` | Record success/failure for a skill after it's used. Append-only event log; rollup is the agent's call, not the substrate's. |
|
||||
|
||||
The agentic loop closes: agent observes a successful sequence → distills it via `define` → next session pulls it via `search` → records outcome via `outcome` → over time, ranking reflects what actually works.
|
||||
|
||||
**Lifecycle distinction worth understanding.** Hermes' own filesystem skills (`$HERMES_HOME/skills/*.md`) are *human-authored, durable, version-controlled*. YantrikDB skills are *agent-authored, runtime-evolving, semantic-search-queryable*. Different kinds of canonical, not competing authorities. The model picks by lifecycle.
|
||||
|
||||
### Explainability is a side effect, not a bolt-on
|
||||
|
||||
Every `recall()` result already carries the structured ranking-reason list — that's the engine's standard response shape. The model can *read* it without prompt engineering. From the live Hermes session captured in `VERIFICATION.md`, DeepSeek's natural-language summary of the recall:
|
||||
|
||||
> *"All 3 memories returned, ranked by relevance × recency × importance. The top result ranked highest (semantic match + keyword + high importance + recency), followed by [...] (keyword match), then [...] (high importance but no direct keyword overlap)."*
|
||||
|
||||
DeepSeek wasn't told the reason codes existed; it parsed them from the tool response and reflected them in its explanation. That's the architectural shape we wanted: the explainability surface is the recall response itself, transport-agnostic, model-agnostic, and visible to anyone who looks at the JSON. No separate "explain" tool. No second LLM call. The cost of explainability is zero because it was never separate.
|
||||
|
||||
## Verification
|
||||
|
||||
- **96 unit tests** covering request formation, error taxonomy, provider contract, hook semantics, circuit breaker, text truncation, mode-aware availability — all mocked, no network required.
|
||||
- **2 live integration tests** (`tests/integration/test_live.py`) that exercise the full flow against a real `yantrikdb-server`. Skipped by default; run with `YANTRIKDB_INTEGRATION_URL` + `YANTRIKDB_INTEGRATION_TOKEN` set.
|
||||
- **End-to-end Hermes demos** against an unmodified Hermes 0.9.0 install for both backends, captured in **[VERIFICATION.md](VERIFICATION.md)** — DeepSeek-driven sessions calling all 8 tools, with `why_retrieved` reason codes flowing through the model's reasoning verbatim.
|
||||
|
||||
### Performance (steady-state, post-warmup)
|
||||
|
||||
| Op | v0.1 HTTP (Apr 14) | v0.2 Embedded (May 9) |
|
||||
|---|---|---|
|
||||
| `record_text` p50 | 13.8 ms | **0.60 ms** |
|
||||
| `recall_text` p50 | 24.0 ms | **2.58 ms** |
|
||||
| `record_text` p99 | 55.3 ms | 10.66 ms |
|
||||
| `recall_text` p99 | 67.2 ms | 13.24 ms |
|
||||
| Cold start | n/a | 77 ms (one-time) |
|
||||
| Required infrastructure | yantrikdb-server + token | none |
|
||||
| `pip install` footprint | wheel + requests | wheel + 2 small libs (~10 MB total) |
|
||||
|
||||
Even embedded p99 tail latency is faster than HTTP p50 — bad-case embedded beats typical-case HTTP. Long-running soak validation is in progress upstream ([yantrikos/yantrikdb saga task #2](https://github.com/yantrikos/yantrikdb)); these numbers are 100-iteration micro-benchmarks, not 24-hour production traces.
|
||||
|
||||
### About the embedder quality claims
|
||||
|
||||
Tier 1 (`with_default()`, ~8 MB) uses [`potion-base-2M`](https://huggingface.co/minishlab/potion-base-2M) via [`model2vec-rs`](https://github.com/MinishLab/model2vec-rs) — a pure-Rust static embedding (lookup table + mean-pool + L2-normalize), no transformer forward pass. Tier 2 (`potion-base-8M`, 28 MB) and Tier 3 (`potion-base-32M`, 121 MB) trade larger model files for higher recall and live behind `set_embedder_named()` (downloaded on first use, cached under user data dir).
|
||||
|
||||
**Quality numbers cited in this README are R@5 vs `sentence-transformers/all-MiniLM-L6-v2` (dim=384) on the upstream [evaluation corpus](https://github.com/yantrikos/yantrikdb/blob/main/scratch/eval_potion_2m.py).** The "~89% / ~92% / ~95% of MiniLM" approximations are from that specific eval; your mileage will vary on a different corpus or task. Semantic separation is also corpus-size dependent — at 3 records all vectors look similar (top score ~0.58); at 8+ with real diversity the score range opens up (top score ~0.84). If you're evaluating, run against your own data.
|
||||
|
||||
CI runs ruff + mypy + pytest on Python 3.11 / 3.12 / 3.13 / 3.14 on every push.
|
||||
|
||||
## Running the tests
|
||||
|
||||
```bash
|
||||
python -m pytest tests/ # unit tests
|
||||
YANTRIKDB_INTEGRATION_URL=http://localhost:7438 \
|
||||
YANTRIKDB_INTEGRATION_TOKEN=ydb_... \
|
||||
python -m pytest tests/integration/ -v # live integration
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
**v0.4.2** (current) — first-class embedder loaders for the `model2vec` family and the HF `sentence-transformers` ecosystem; embedding dim auto-probed; default install stays slim via optional `[model2vec]` and `[sentence-transformers]` pip extras. 151 tests passing on Python 3.11/3.12/3.13. **Standalone-by-design** per Hermes maintainer guidance — Hermes is not accepting new memory providers upstream; standalone plugins installed via `pip` are the recommended pattern. PR [#9989](https://github.com/NousResearch/hermes-agent/pull/9989) closed 2026-05-13 with that resolution.
|
||||
|
||||
### Release cadence
|
||||
|
||||
| Version | Date | Highlight |
|
||||
|---|---|---|
|
||||
| v0.1.0 | 2026-04-14 | HTTP backend, 8 tools, 96 tests |
|
||||
| v0.2.0 | 2026-05-09 | Embedded backend default, ~10 MB install, sub-ms recall |
|
||||
| v0.3.0 | 2026-05-09 | Skill substrate bridge (opt-in) |
|
||||
| v0.3.1 | 2026-05-09 | PyPI distribution + `yantrikdb-hermes` CLI installer |
|
||||
| v0.4.1 | 2026-05-12 | Pluggable embedders (custom Python class via `YANTRIKDB_EMBEDDER_CLASS`) |
|
||||
| v0.4.2 | 2026-05-12 | First-class `model2vec` + `sentence-transformers` loaders, auto-probed dim |
|
||||
|
||||
### Durability signals
|
||||
|
||||
The maintainer doesn't promise "I won't quit" — promises like that aren't testable. What's testable:
|
||||
|
||||
- Every release ships with tests + CI (Python 3.11 / 3.12 / 3.13) + tagged CHANGELOG + a publish gate where 151 tests + ruff + mypy must pass before the wheel uploads to PyPI.
|
||||
- First user issue on this repo (multilingual embedding support) was filed and shipped to PyPI the same day — 25 minutes from raised to released.
|
||||
- Underlying yantrikdb engine: ~5.2k/mo PyPI downloads; flagship server repo has 141 GitHub stars; broader yantrikos namespace ~13.5k/mo combined PyPI+npm. Cross-stack ownership (engine + HTTP server + MCP server + this plugin) — 14+ months of parallel maintenance, not a one-week hobby.
|
||||
- Independent recognition: accepted into the [Cursor Directory](https://cursor.directory/plugins/yantrikdb) (300k+ developer reach) and (sibling project) the Anthropic MCP Directory.
|
||||
- Substrate design deposited as a peer-citable preprint: [10.5281/zenodo.20128887](https://doi.org/10.5281/zenodo.20128887).
|
||||
|
||||
That's what I can give you. The technical merits are above; the maintenance shape is here so you can audit before adopting.
|
||||
|
||||
See [yantrikdb/CHANGELOG.md](yantrikdb/CHANGELOG.md) for full release notes and [yantrikdb/ARCHITECTURE.md](yantrikdb/ARCHITECTURE.md) for the control flow, error taxonomy, and threading model (covering both backends).
|
||||
|
||||
## License
|
||||
|
||||
This plugin is **MIT** (matching Hermes — the code is intended for upstream contribution). The [YantrikDB server](https://github.com/yantrikos/yantrikdb-server) itself is AGPL-3.0; the plugin only talks to it over HTTP and does not embed or redistribute any server code, so the boundary is the same as any MIT client talking to an AGPL service. See [yantrikdb/SECURITY.md](yantrikdb/SECURITY.md#license-boundary-agpl-vs-mit) for the full note.
|
||||
|
||||
## Links
|
||||
|
||||
- **Plugin docs**: [yantrikdb/README.md](yantrikdb/README.md)
|
||||
- **Architecture**: [yantrikdb/ARCHITECTURE.md](yantrikdb/ARCHITECTURE.md)
|
||||
- **Verification transcripts**: [VERIFICATION.md](VERIFICATION.md)
|
||||
- **Hermes Agent**: <https://github.com/NousResearch/hermes-agent>
|
||||
- **YantrikDB server**: <https://github.com/yantrikos/yantrikdb-server>
|
||||
- **YantrikDB docs**: <https://yantrikdb.com>
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
# 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, `uv` 0.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 yantrikdb` brought**: `uuid-utils + click + yantrikdb`. Total ~10 MB. Empirically slim (yantrikdb 0.7.6 moved heavy ML deps to extras).
|
||||
- **Hermes `.env` is 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
|
||||
|
||||
1. The plugin loads cleanly in an unmodified Hermes 0.9.0 install with **`YANTRIKDB_MODE=embedded`** and zero infrastructure.
|
||||
2. All 8 tools (`remember`, `recall`, `forget`, `think`, `conflicts`, `resolve_conflict`, `relate`, `stats`) work via the embedded backend identically to the HTTP backend.
|
||||
3. `why_retrieved` reason codes from the engine reach the model and show up in the model's reasoning.
|
||||
4. Steady-state latency is ~23×/~9× faster than the HTTP path; cold start is one-time 77 ms.
|
||||
5. 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, `uv` 0.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`/`--model` flags
|
||||
|
||||
## 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`](tests/test_provider.py) 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_stats` round-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_retrieved` is 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 `benchmark` memories 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
|
||||
|
||||
1. The plugin loads cleanly in an unmodified Hermes 0.9.0 install.
|
||||
2. The `MemoryProvider` contract is honored — `is_available` / `initialize` / `get_tool_schemas` / `handle_tool_call` all fire in the expected order.
|
||||
3. The eight tools (`remember`, `recall`, `forget`, `think`, `conflicts`, `resolve_conflict`, `relate`, `stats`) are registered with Hermes and routable from the model.
|
||||
4. Wire protocol is correct against a real `yantrikdb-server` — all response field names match (`rid`, `why_retrieved`, `consolidation_count`, `active_memories`, …).
|
||||
5. The "explainable recall" differentiator is real, not marketing — the server returns reason codes and the plugin passes them through unchanged.
|
||||
6. 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 `YantrikDBAuthError` with 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
|
||||
|
||||
```bash
|
||||
# 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).
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
"""Top-level Hermes plugin entry point — for ``hermes plugins install``.
|
||||
|
||||
When users install via ``hermes plugins install yantrikos/yantrikdb-hermes-plugin``,
|
||||
Hermes' user-plugin loader clones this repo into ``~/.hermes/plugins/yantrikdb/``
|
||||
(target name comes from ``plugin.yaml.name`` at the repo root). The loader then
|
||||
imports ``__init__.py`` from that directory and looks for ``register`` or a
|
||||
``MemoryProvider`` subclass. This file is that entry point — it loads the real
|
||||
plugin module from the ``yantrikdb/`` subfolder and re-exports both.
|
||||
|
||||
The two install paths coexist intentionally:
|
||||
|
||||
* ``pip install yantrikdb-hermes-plugin`` + ``yantrikdb-hermes install <hermes>``
|
||||
copies the contents of the ``yantrikdb/`` subfolder into
|
||||
``<hermes>/plugins/memory/yantrikdb/`` (bundled-discovery path). This file
|
||||
is NOT used on that path — the inner ``yantrikdb/__init__.py`` is the entry.
|
||||
|
||||
* ``hermes plugins install yantrikos/yantrikdb-hermes-plugin`` clones the whole
|
||||
repo into ``~/.hermes/plugins/yantrikdb/`` (user-discovery path). This file
|
||||
IS used; it dynamic-loads the inner subfolder so the actual provider code is
|
||||
shared between both paths instead of duplicated.
|
||||
|
||||
We use absolute file-path loading (``importlib.util.spec_from_file_location``)
|
||||
rather than a relative import (``from .yantrikdb import ...``) because Hermes'
|
||||
user-installed-plugin loader doesn't register a parent package for
|
||||
``_hermes_user_memory.yantrikdb``, so relative imports inside this file fail
|
||||
silently and the provider is discovered as "loaded but no instance found".
|
||||
Absolute file-path loading sidesteps that.
|
||||
|
||||
If you opened this file looking for the implementation, see ``yantrikdb/``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
# This file is the entry point for the ``hermes plugins install`` path ONLY.
|
||||
# It is intentionally a no-op when loaded in any other context (pytest
|
||||
# collection, direct ``import``, etc.) so it doesn't create a second copy of
|
||||
# the inner package and break ``isinstance`` checks across test fixtures.
|
||||
#
|
||||
# Hermes' user-installed-plugin loader loads this file under a module name
|
||||
# starting with ``_hermes_user_memory.`` — that's the discriminator we use to
|
||||
# decide whether to do the inner load. Bundled discovery (which fires after
|
||||
# ``yantrikdb-hermes install`` copies the inner ``yantrikdb/`` contents into
|
||||
# ``<hermes>/plugins/memory/yantrikdb/``) doesn't go through this file at all.
|
||||
if __name__.startswith("_hermes_user_memory"):
|
||||
|
||||
# Workaround for a Hermes bug in user-installed plugin discovery: the loader
|
||||
# registers the module under a dotted name (e.g. ``_hermes_user_memory.yantrikdb``)
|
||||
# but never registers the parent package (``_hermes_user_memory``). Python's
|
||||
# import machinery then fails when our ``__init__.py`` tries to register a
|
||||
# child module under our own dotted name. Pre-register a synthetic parent
|
||||
# if it's missing.
|
||||
if "." in __name__:
|
||||
_parent = __name__.split(".", 1)[0]
|
||||
if _parent not in sys.modules:
|
||||
sys.modules[_parent] = types.ModuleType(_parent)
|
||||
|
||||
_INNER_DIR = Path(__file__).parent / "yantrikdb"
|
||||
_INNER_INIT = _INNER_DIR / "__init__.py"
|
||||
_INNER_MOD_NAME = f"{__name__}._yantrikdb_inner"
|
||||
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
_INNER_MOD_NAME,
|
||||
str(_INNER_INIT),
|
||||
submodule_search_locations=[str(_INNER_DIR)],
|
||||
)
|
||||
if _spec is None or _spec.loader is None: # pragma: no cover — defensive
|
||||
raise ImportError(
|
||||
f"yantrikdb-hermes-plugin: could not locate inner package at {_INNER_DIR}"
|
||||
)
|
||||
_inner = importlib.util.module_from_spec(_spec)
|
||||
sys.modules[_INNER_MOD_NAME] = _inner
|
||||
_spec.loader.exec_module(_inner)
|
||||
|
||||
# Re-export the names Hermes' user-plugin loader looks for.
|
||||
register = _inner.register
|
||||
YantrikDBMemoryProvider = _inner.YantrikDBMemoryProvider
|
||||
|
||||
__all__ = ["YantrikDBMemoryProvider", "register"]
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# Skill Lifecycle Demo
|
||||
|
||||
End-to-end demo showing the [`yantrikdb-hermes-plugin`](https://github.com/yantrikos/yantrikdb-hermes-plugin) skill substrate handling the **define → restart → search → outcome** loop — the autonomy loop described in [`yantrikdb/README.md`](../../../yantrikdb/README.md) and on [`yantrikdb.com/guides/autonomous-skills/`](https://yantrikdb.com/guides/autonomous-skills/).
|
||||
|
||||
## Constellation animation (the fireworks)
|
||||
|
||||

|
||||
|
||||
A visual time-lapse of the substrate growing. 6 seed skills appear as glowing nodes color-coded by skill_type (procedure, reference, lesson, rule). The agent adds a 7th. A search query in session 2 highlights the relevant node. The outcome recorded against it turns green with a ✓. Source: [`demo_visual.py`](./demo_visual.py) — renders with `matplotlib` + `networkx` + `imageio`, no external dependencies on the running engine. Renders in ~5s on any machine.
|
||||
|
||||
## LLM-driven recording (what's actually running)
|
||||
|
||||

|
||||
|
||||
`gpt-4o-mini` receives the plugin's 11 tool schemas via OpenAI's chat-completions API and chooses when to call each one. The model picked the `skill_id` (`release.yantrikos.clean`), `applies_to` tags, body text, search query, and outcome note autonomously. Two real rids land in the substrate; the autonomy loop closes in ~10 seconds. Source: [`demo_llm.py`](./demo_llm.py) + [`transcript-llm.txt`](./transcript-llm.txt).
|
||||
|
||||
## Scripted recording (deterministic plumbing demo)
|
||||
|
||||

|
||||
|
||||
The same `handle_tool_call` path, with the agent's tool-call decisions hard-coded for reproducibility. Useful for verifying the plugin works without needing an API key. Source: [`demo.py`](./demo.py) + [`transcript.txt`](./transcript.txt).
|
||||
|
||||
## Two demos, two levels of evidence
|
||||
|
||||
| Script | What's live | What's scripted | Captured run |
|
||||
|---|---|---|---|
|
||||
| **`demo.py`** | Plugin + engine + substrate + `handle_tool_call` dispatch | Agent's decision *when* to call each tool (deterministic for reproducibility) | [`transcript.txt`](./transcript.txt) |
|
||||
| **`demo_llm.py`** | Plugin + engine + substrate + `handle_tool_call` dispatch + **the LLM** (gpt-4o-mini) emitting tool calls from the plugin's actual tool schemas via OpenAI's chat-completions API | Nothing — the model chose the `skill_id`, `applies_to` tags, body text, search query, outcome note, and *when* to call each tool. | [`transcript-llm.txt`](./transcript-llm.txt) |
|
||||
|
||||
The LLM-driven run took ~10 seconds end-to-end and shipped two real rids:
|
||||
- `019e4788-6bce…` — the skill the model chose to define (id `release.yantrikos.clean`, applies_to `["release", "git", "python", "ci"]`)
|
||||
- `019e4788-8154…` — the outcome the model recorded after using the skill in a fresh session
|
||||
|
||||
`demo_llm.py` is the architecture Hermes wraps in its full agent loop — the plugin's `get_tool_schemas()` returns 11 OpenAI-tool-compatible schemas, they go into the chat completion call, the model emits tool calls, we dispatch via the same `handle_tool_call` entry point Hermes uses internally, and the result feeds back into the conversation. Hermes adds session management, multi-turn orchestration, and provider routing on top.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```bash
|
||||
pip install yantrikdb yantrikdb-hermes-plugin openai
|
||||
|
||||
# Scripted (no API key, deterministic, ~25s):
|
||||
python assets/demos/skill-lifecycle/demo.py
|
||||
|
||||
# LLM-driven (needs OPENAI_API_KEY, ~10s):
|
||||
export OPENAI_API_KEY=sk-...
|
||||
python assets/demos/skill-lifecycle/demo_llm.py
|
||||
```
|
||||
|
||||
## On animated GIFs
|
||||
|
||||
A [`demo.tape`](./demo.tape) script is included for [`vhs`](https://github.com/charmbracelet/vhs) rendering. The Windows VHS path (v0.11.0) hangs indefinitely on `Set Shell` directives — known limitation, see [VHS issues](https://github.com/charmbracelet/vhs/issues). The tape script renders on macOS/Linux. Captured text transcripts above are the canonical artifacts until that's resolved.
|
||||
|
||||
## What's shown
|
||||
|
||||
| Step | What the plugin does | What you see |
|
||||
|---|---|---|
|
||||
| 1 | Fresh ephemeral substrate, 0 skills | `yantrikdb_stats` returns zero memories, zero operations |
|
||||
| 2 | Agent observes a repeated pattern, calls `yantrikdb_skill_define` for a release-workflow procedure | rid returned, `stored: true`, substrate operations count ticks up |
|
||||
| 3 | Simulated session restart — provider torn down + a fresh instance created | new `YantrikDBMemoryProvider()` instance, same SQLite file underneath |
|
||||
| 4 | Session 2's agent calls `yantrikdb_skill_search("how to ship a release")` | the skill from session 1 returned, ranked by relevance |
|
||||
| 5 | Agent follows the procedure, calls `yantrikdb_skill_outcome(succeeded=True, note=…)` | rid returned, `recorded: true`, outcome ledger appended |
|
||||
|
||||
The demo runs in ~25 seconds against a fresh ephemeral SQLite DB. No LLM in the loop. The LLM-driven part — "agent decides to call skill_define / skill_search" — is scripted here so the recording is deterministic; everything below that line (the plugin's `handle_tool_call` dispatch, the engine's `yantrikdb` Rust core, the SQLite writes, the embedding+search, the response shapes) is live code.
|
||||
|
||||
## What this is and isn't
|
||||
|
||||
**This is**: the same `handle_tool_call` entry point Hermes uses to invoke yantrikdb tools when its agent's LLM emits a tool call. The plugin code is real ([`yantrikdb_hermes_plugin.YantrikDBMemoryProvider`](../../../yantrikdb/__init__.py)). The engine is real (`yantrikdb` on PyPI). The substrate is real (SQLite under the temp dir).
|
||||
|
||||
**This is not**: a recording of Claude / GPT / Qwen deciding to call these tools in response to a natural-language prompt. That part is scripted for the recording to run cleanly in <30s — the demo proves the plugin's plumbing, not the LLM's autonomy.
|
||||
|
||||
For evidence of LLM-driven autonomy, see [`yantrikdb.com/guides/autonomous-skills/`](https://yantrikdb.com/guides/autonomous-skills/), which documents 17 skills authored by Claude (via the `yantrikdb-mcp` server) on one production substrate, with 9 of them showing cross-session reuse via the outcome ledger.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```bash
|
||||
# In any environment where yantrikdb-hermes-plugin is installed:
|
||||
pip install yantrikdb-hermes-plugin yantrikdb
|
||||
|
||||
# Run the demo (Windows shown — on POSIX, just use `python`):
|
||||
python assets/demos/skill-lifecycle/demo.py
|
||||
```
|
||||
|
||||
The script creates a fresh temp dir for `YANTRIKDB_DB_PATH`, walks through the five steps, and prints what each `handle_tool_call` returns. Output is structured JSON from the plugin (truncated in the recording for readability).
|
||||
|
||||
## Re-rendering the GIF
|
||||
|
||||
Install [`vhs`](https://github.com/charmbracelet/vhs) — `winget install charmbracelet.vhs` on Windows, or follow the repo for macOS/Linux. Then:
|
||||
|
||||
```bash
|
||||
cd assets/demos/skill-lifecycle
|
||||
vhs demo.tape
|
||||
```
|
||||
|
||||
Outputs `demo.gif` (~800 KB, embeddable in READMEs) and `demo.mp4`.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
|
@ -0,0 +1,187 @@
|
|||
#!/usr/bin/env python3
|
||||
"""End-to-end demo of the skill lifecycle through the Hermes plugin.
|
||||
|
||||
What this script shows: the SAME tool-call entry point Hermes invokes
|
||||
when the agent's LLM emits a tool call. We bypass the LLM here — the
|
||||
"agent" is scripted — so the demo runs deterministically in <60s.
|
||||
The plugin code, the engine, and the substrate are real.
|
||||
|
||||
Lifecycle:
|
||||
1. Session 1 — agent observes a useful pattern, calls skill_define
|
||||
2. Session 2 (after a restart) — fresh agent searches, finds the
|
||||
skill, follows it, records the outcome via skill_outcome
|
||||
3. Substrate reflects both events: 1 new skill + 1 outcome row
|
||||
|
||||
Designed to be driven by VHS (charmbracelet.com/vhs) — the .tape file
|
||||
sitting next to this script paces the output for a clean recording.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Make the demo self-contained: fresh, ephemeral home so the dashboard
|
||||
# state we show is what THIS demo produced, not pre-existing data.
|
||||
DEMO_HOME = Path(tempfile.mkdtemp(prefix="yantrikdb_hermes_demo_"))
|
||||
os.environ["YANTRIKDB_DB_PATH"] = str(DEMO_HOME / "memory.db")
|
||||
os.environ["YANTRIKDB_MODE"] = "embedded"
|
||||
os.environ["YANTRIKDB_NAMESPACE"] = "demo"
|
||||
os.environ["YANTRIKDB_SKILLS_ENABLED"] = "true"
|
||||
|
||||
# Suppress HuggingFace tqdm noise.
|
||||
os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
|
||||
os.environ["TRANSFORMERS_VERBOSITY"] = "error"
|
||||
|
||||
|
||||
def banner(text: str) -> None:
|
||||
width = max(60, len(text) + 4)
|
||||
print()
|
||||
print("─" * width)
|
||||
print(f" {text}")
|
||||
print("─" * width)
|
||||
|
||||
|
||||
def pause(seconds: float = 0.6) -> None:
|
||||
sys.stdout.flush()
|
||||
time.sleep(seconds)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
banner("Step 1 — fresh substrate, 0 skills")
|
||||
print(f" YANTRIKDB_DB_PATH = {DEMO_HOME / 'memory.db'}")
|
||||
print(f" YANTRIKDB_NAMESPACE = demo")
|
||||
print(f" YANTRIKDB_SKILLS_ENABLED = true")
|
||||
pause(1.2)
|
||||
|
||||
# Lazy import so the env vars above are picked up.
|
||||
from yantrikdb_hermes_plugin import YantrikDBMemoryProvider # noqa: E402
|
||||
|
||||
provider = YantrikDBMemoryProvider()
|
||||
provider.initialize("demo-session-1", hermes_home=str(DEMO_HOME))
|
||||
|
||||
def tool(name: str, **args):
|
||||
"""Call a plugin tool the way Hermes does (via handle_tool_call)."""
|
||||
return provider.handle_tool_call(name, args)
|
||||
|
||||
pause(0.5)
|
||||
print()
|
||||
print(" $ provider.handle_tool_call('yantrikdb_stats', {'namespace': 'demo'})")
|
||||
pause(0.4)
|
||||
print(" →", tool("yantrikdb_stats", namespace="demo")[:140], "…")
|
||||
pause(1.0)
|
||||
|
||||
banner("Step 2 — Session 1: agent observes a pattern, defines a skill")
|
||||
print()
|
||||
print(" The agent has just shipped a clean release. It noticed the same")
|
||||
print(" sequence worked three times: feature branch → CI → squash-merge")
|
||||
print(" → tag → GH release → PyPI verify. It chooses to crystallize.")
|
||||
pause(2.5)
|
||||
print()
|
||||
print(" $ provider.handle_tool_call('yantrikdb_skill_define', { … })")
|
||||
pause(0.6)
|
||||
result = tool(
|
||||
"yantrikdb_skill_define",
|
||||
skill_id="workflow.release.yantrikos_repo",
|
||||
skill_type="procedure",
|
||||
applies_to=["release", "workflow", "yantrikos"],
|
||||
body=(
|
||||
"For every release on a yantrikos repo with branch protection: "
|
||||
"(1) feature branch + PR with CI green on Python 3.11/3.12/3.13/3.14, "
|
||||
"(2) squash-merge to main with version-bumped CHANGELOG entry, "
|
||||
"(3) tag vX.Y.Z + push, "
|
||||
"(4) gh release create vX.Y.Z (fires the gated Publish workflow), "
|
||||
"(5) verify https://pypi.org/pypi/<name>/json shows the new version, "
|
||||
"(6) close referenced issues with credit + install command in the comment."
|
||||
),
|
||||
triggers=["release", "ship", "publish to pypi"],
|
||||
)
|
||||
print(" →", result[:200])
|
||||
pause(2.0)
|
||||
|
||||
print()
|
||||
print(" $ provider.handle_tool_call('yantrikdb_stats', {'namespace': 'skill_substrate'})")
|
||||
pause(0.4)
|
||||
print(" →", tool("yantrikdb_stats", namespace="skill_substrate")[:140], "…")
|
||||
pause(1.2)
|
||||
|
||||
banner("Step 3 — simulated session restart (fresh agent state)")
|
||||
print()
|
||||
print(" Tearing down the agent's in-memory state. The substrate persists.")
|
||||
pause(1.5)
|
||||
provider.shutdown()
|
||||
del provider
|
||||
pause(0.8)
|
||||
|
||||
print()
|
||||
print(" $ provider = YantrikDBMemoryProvider() # new instance")
|
||||
pause(0.4)
|
||||
provider2 = YantrikDBMemoryProvider()
|
||||
provider2.initialize("demo-session-2", hermes_home=str(DEMO_HOME))
|
||||
print(" → provider ready, substrate has the skill from session 1")
|
||||
pause(1.5)
|
||||
|
||||
banner("Step 4 — Session 2: fresh agent searches before acting")
|
||||
print()
|
||||
print(" The agent gets a new request: 'ship v0.4.13 of the plugin.'")
|
||||
print(" Before doing anything, it searches the skill substrate.")
|
||||
pause(2.0)
|
||||
print()
|
||||
print(" $ provider.handle_tool_call('yantrikdb_skill_search', {'query': 'how to ship a release', 'top_k': 3})")
|
||||
pause(0.6)
|
||||
|
||||
def tool2(name: str, **args):
|
||||
return provider2.handle_tool_call(name, args)
|
||||
|
||||
search_result = tool2("yantrikdb_skill_search", query="how to ship a release", top_k=3)
|
||||
print(" →", search_result[:280], "…")
|
||||
pause(3.0)
|
||||
|
||||
banner("Step 5 — agent follows the skill, reports outcome")
|
||||
print()
|
||||
print(" The agent reads the skill body, ships the release following the")
|
||||
print(" 6-step procedure, succeeds, and records the outcome.")
|
||||
pause(2.0)
|
||||
print()
|
||||
print(" $ provider.handle_tool_call('yantrikdb_skill_outcome', { … })")
|
||||
pause(0.6)
|
||||
outcome = tool2(
|
||||
"yantrikdb_skill_outcome",
|
||||
skill_id="workflow.release.yantrikos_repo",
|
||||
succeeded=True,
|
||||
note="shipped v0.4.13 cleanly — PyPI verified within 3min of tag push",
|
||||
)
|
||||
print(" →", outcome[:200])
|
||||
pause(2.0)
|
||||
|
||||
banner("Done — the autonomy loop closed")
|
||||
print()
|
||||
print(" • Substrate now holds 1 skill (workflow.release.yantrikos_repo)")
|
||||
print(" • Outcome ledger has 1 success row, agent's access_count = 1")
|
||||
print(" • Next session's agent will see this skill ranked higher")
|
||||
print()
|
||||
print(" The plugin's code path you just saw is the same one Hermes invokes")
|
||||
print(" when its agent's LLM emits a tool call. The LLM is omitted here for")
|
||||
print(" determinism; everything else is the live plugin + live engine.")
|
||||
print()
|
||||
print(" Plugin: yantrikdb-hermes-plugin v" + getattr(__import__("yantrikdb_hermes_plugin"), "__version__", "0.4.12"))
|
||||
print(" Substrate: " + str(DEMO_HOME / "memory.db"))
|
||||
pause(3.0)
|
||||
provider2.shutdown()
|
||||
|
||||
# Tidy up. Keep the .db around for an optional dashboard snapshot
|
||||
# but the temp dir gets removed on subsequent demo runs.
|
||||
print()
|
||||
print(" (Ephemeral demo home left at " + str(DEMO_HOME) + " for inspection)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print(f"\n ! demo failed: {type(e).__name__}: {e}")
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Skill-lifecycle demo for yantrikdb-hermes-plugin.
|
||||
#
|
||||
# Render: vhs demo.tape (produces demo.gif + demo.mp4)
|
||||
# Requires: vhs >= v0.10, python3, yantrikdb + yantrikdb-hermes-plugin installed.
|
||||
#
|
||||
# Tested on Linux (Ubuntu via WSL works); Windows VHS v0.11 has known
|
||||
# shell-detection issues that hang the render — use WSL or render on a
|
||||
# Linux/macOS box.
|
||||
|
||||
Output demo.gif
|
||||
Output demo.mp4
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1280
|
||||
Set Height 800
|
||||
Set Padding 12
|
||||
Set Theme "Catppuccin Mocha"
|
||||
|
||||
# Hide the setup, show only the demo.
|
||||
Hide
|
||||
Type "cd /mnt/c/Users/sync/codes/yantrikdb-hermes-plugin && clear"
|
||||
Enter
|
||||
Sleep 200ms
|
||||
Show
|
||||
|
||||
Type "python3 assets/demos/skill-lifecycle/demo.py"
|
||||
Sleep 600ms
|
||||
Enter
|
||||
|
||||
# demo.py walks 5 lifecycle steps with built-in pause() calls — ~25s total.
|
||||
Sleep 30s
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 MiB |
Binary file not shown.
|
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env python3
|
||||
"""LLM-driven demo: agent triages an incident using the substrate, then crystallizes a new lesson.
|
||||
|
||||
A real LLM (OpenAI gpt-4o-mini) gets a concrete incident report. It uses
|
||||
the substrate as working memory — searching for relevant past procedures,
|
||||
references, and lessons; composing a multi-skill response; recording
|
||||
outcomes; and crystallizing one new insight from the experience.
|
||||
|
||||
Session 2 then proves the substrate carried session 1's learning
|
||||
forward: a fresh agent with no chat context gets a similar incident,
|
||||
finds the new lesson via search, and applies it directly.
|
||||
|
||||
Only the user prompts are scripted. Every tool call (which tool, what
|
||||
arguments, which skills to consult, what to record, what to write as
|
||||
the new lesson) is the model's autonomous choice.
|
||||
|
||||
End-to-end ~75-90 seconds at readable pacing.
|
||||
|
||||
Requires:
|
||||
pip install openai yantrikdb yantrikdb-hermes-plugin
|
||||
OPENAI_API_KEY in env
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from textwrap import indent, wrap
|
||||
|
||||
DEMO_HOME = Path(tempfile.mkdtemp(prefix="yantrikdb_hermes_llm_demo_"))
|
||||
os.environ["YANTRIKDB_DB_PATH"] = str(DEMO_HOME / "memory.db")
|
||||
os.environ["YANTRIKDB_MODE"] = "embedded"
|
||||
os.environ["YANTRIKDB_NAMESPACE"] = "demo"
|
||||
os.environ["YANTRIKDB_SKILLS_ENABLED"] = "true"
|
||||
os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
|
||||
os.environ["TRANSFORMERS_VERBOSITY"] = "error"
|
||||
|
||||
MODEL = os.environ.get("DEMO_LLM_MODEL", "gpt-4o-mini")
|
||||
MAX_TOOL_ITERATIONS = 10
|
||||
|
||||
BEAT_SHORT = 0.8
|
||||
BEAT_MED = 1.4
|
||||
BEAT_LONG = 2.2
|
||||
|
||||
|
||||
def banner(text: str) -> None:
|
||||
line = "─" * max(64, len(text) + 6)
|
||||
print(f"\n{line}\n {text}\n{line}")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def narrate(text: str) -> None:
|
||||
for line in wrap(text, width=78):
|
||||
print(f" · {line}")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def pretty_args(args: dict, max_val: int = 140) -> str:
|
||||
out_parts = []
|
||||
for k, v in args.items():
|
||||
s = json.dumps(v) if not isinstance(v, str) else json.dumps(v)
|
||||
if len(s) > max_val:
|
||||
s = s[: max_val - 3] + "..."
|
||||
out_parts.append(f"{k}={s}")
|
||||
return ", ".join(out_parts)
|
||||
|
||||
|
||||
def pretty_result(result_json: str, max_len: int = 500) -> str:
|
||||
try:
|
||||
obj = json.loads(result_json)
|
||||
except Exception:
|
||||
return result_json[:max_len]
|
||||
s = json.dumps(obj, indent=2)
|
||||
return s if len(s) <= max_len else s[:max_len] + " …"
|
||||
|
||||
|
||||
def pause(s: float = BEAT_MED) -> None:
|
||||
sys.stdout.flush()
|
||||
time.sleep(s)
|
||||
|
||||
|
||||
def to_openai_tools(plugin_schemas: list[dict]) -> list[dict]:
|
||||
return [{"type": "function", "function": s} for s in plugin_schemas]
|
||||
|
||||
|
||||
def run_agent_turn(client, provider, system: str, user: str) -> list:
|
||||
tools = to_openai_tools(provider.get_tool_schemas())
|
||||
messages = [
|
||||
{"role": "system", "content": system},
|
||||
{"role": "user", "content": user},
|
||||
]
|
||||
print()
|
||||
narrate("USER → agent:")
|
||||
for line in wrap(user, width=76):
|
||||
print(f" {line}")
|
||||
pause(BEAT_MED)
|
||||
|
||||
for step in range(MAX_TOOL_ITERATIONS):
|
||||
resp = client.chat.completions.create(
|
||||
model=MODEL, messages=messages, tools=tools,
|
||||
tool_choice="auto", temperature=0.1,
|
||||
)
|
||||
msg = resp.choices[0].message
|
||||
messages.append({
|
||||
"role": "assistant", "content": msg.content,
|
||||
"tool_calls": [
|
||||
{"id": tc.id, "type": "function",
|
||||
"function": {"name": tc.function.name,
|
||||
"arguments": tc.function.arguments}}
|
||||
for tc in (msg.tool_calls or [])
|
||||
] or None,
|
||||
})
|
||||
|
||||
if not msg.tool_calls:
|
||||
if msg.content:
|
||||
print()
|
||||
narrate(f"{MODEL} replies:")
|
||||
for line in wrap(msg.content, width=76):
|
||||
print(f" {line}")
|
||||
pause(BEAT_MED)
|
||||
return messages
|
||||
|
||||
for tc in msg.tool_calls:
|
||||
name = tc.function.name
|
||||
args = json.loads(tc.function.arguments)
|
||||
print()
|
||||
print(f" ⚙ {MODEL} → {name}(")
|
||||
for line in pretty_args(args, max_val=140).split(", "):
|
||||
print(f" {line}")
|
||||
print(f" )")
|
||||
pause(BEAT_SHORT)
|
||||
result = provider.handle_tool_call(name, args)
|
||||
print(f" ← plugin:")
|
||||
print(indent(pretty_result(result), " "))
|
||||
messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
|
||||
pause(BEAT_MED)
|
||||
return messages
|
||||
|
||||
|
||||
def show_substrate(provider, label: str, outcomes: int = 0) -> None:
|
||||
"""Substrate state line. Outcome count is tracked by the caller since
|
||||
there's no skill_outcome list tool — we count from what we've seen."""
|
||||
search_raw = provider.handle_tool_call(
|
||||
"yantrikdb_skill_search", {"query": "skill", "top_k": 100}
|
||||
)
|
||||
try:
|
||||
d = json.loads(search_raw)
|
||||
n = d.get("count", 0)
|
||||
except Exception:
|
||||
n = 0
|
||||
print(f" ▸ skill_substrate ({label}): skills={n}, outcomes={outcomes}, conflicts=0")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def count_outcomes_in_messages(messages: list) -> int:
|
||||
"""Counts skill_outcome tool calls we've seen in the conversation."""
|
||||
count = 0
|
||||
for m in messages:
|
||||
for tc in (m.get("tool_calls") or []):
|
||||
if tc.get("function", {}).get("name") == "yantrikdb_skill_outcome":
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
print(" ! openai SDK not installed. pip install openai", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print(" ! OPENAI_API_KEY not set", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
from yantrikdb_hermes_plugin import YantrikDBMemoryProvider
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from seed_skills import load_seed_skills, SEED_SKILLS # noqa: E402
|
||||
|
||||
banner("LLM-driven skill-lifecycle — agent triages an incident using the substrate")
|
||||
print(f" model: {MODEL}")
|
||||
print(f" substrate: ephemeral SQLite, pre-seeded with {len(SEED_SKILLS)} skills from past sessions")
|
||||
print(f" tools: {len(YantrikDBMemoryProvider().get_tool_schemas())} (the plugin's full surface)")
|
||||
pause(BEAT_LONG)
|
||||
|
||||
client = OpenAI()
|
||||
|
||||
# Seed.
|
||||
seed_provider = YantrikDBMemoryProvider()
|
||||
seed_provider.initialize("demo-llm-seed", hermes_home=str(DEMO_HOME))
|
||||
print()
|
||||
print(f" ▸ seeding {len(SEED_SKILLS)} skills:")
|
||||
for entry in SEED_SKILLS:
|
||||
print(f" • {entry['skill_id']:48s} ({entry['skill_type']})")
|
||||
loaded = load_seed_skills(seed_provider)
|
||||
print(f" ▸ {loaded} skills loaded — substrate is now lived-in")
|
||||
seed_provider.shutdown()
|
||||
pause(BEAT_LONG)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# SESSION 1 — incident triage, agent composes a response from
|
||||
# multiple substrate skills, then crystallizes a new lesson.
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
banner("Session 1 — agent triages a real incident using the substrate")
|
||||
provider1 = YantrikDBMemoryProvider()
|
||||
provider1.initialize("demo-llm-s1", hermes_home=str(DEMO_HOME))
|
||||
show_substrate(provider1, "before triage")
|
||||
pause(BEAT_MED)
|
||||
|
||||
narrate(
|
||||
"The agent gets a concrete incident report. Before responding, it "
|
||||
"uses yantrikdb_skill_search to gather context from past sessions — "
|
||||
"the substrate as working memory under pressure."
|
||||
)
|
||||
pause(BEAT_LONG)
|
||||
|
||||
system_1 = (
|
||||
"You are a Hermes Agent with yantrikdb tools for persistent skills and memory.\n\n"
|
||||
"When a user reports an incident, BEFORE responding:\n"
|
||||
"1. Use yantrikdb_skill_search to find any procedures, references, or lessons\n"
|
||||
" from past sessions that apply. Search broadly (try 2-3 different queries\n"
|
||||
" if needed — debugging shape, deployment shape, etc.).\n"
|
||||
"2. Read what you find. Compose your diagnosis using the relevant past lessons.\n"
|
||||
"3. After responding, call yantrikdb_skill_outcome for each skill you actually\n"
|
||||
" leaned on, with a brief note about how it helped.\n\n"
|
||||
"Be concise in your diagnosis. The substrate work — search and outcomes — is "
|
||||
"the load-bearing part of your value here."
|
||||
)
|
||||
user_1 = (
|
||||
"Our staging service stopped responding to /v1/* endpoints around 03:00 UTC "
|
||||
"after a deploy that extended ALLOWED_KINDS to include a new event type. "
|
||||
"/v1/health is still returning 200 but every operational endpoint hangs. "
|
||||
"We deployed both the polling watcher and the ingest service this morning. "
|
||||
"What's going on, and what should we check first?"
|
||||
)
|
||||
s1_messages = run_agent_turn(client, provider1, system_1, user_1)
|
||||
s1_outcomes = count_outcomes_in_messages(s1_messages)
|
||||
|
||||
pause(BEAT_MED)
|
||||
show_substrate(provider1, "after session 1", outcomes=s1_outcomes)
|
||||
pause(BEAT_LONG)
|
||||
provider1.shutdown()
|
||||
|
||||
banner("[ session 1 ended — agent state torn down — substrate persists ]")
|
||||
pause(BEAT_MED)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# SESSION 2 — different agent, similar incident, finds + applies
|
||||
# the lessons from session 1, then crystallizes the recurring pattern
|
||||
# as a NEW skill (since we've now seen it twice).
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
banner("Session 2 — fresh agent, similar-shape incident days later")
|
||||
provider2 = YantrikDBMemoryProvider()
|
||||
provider2.initialize("demo-llm-s2", hermes_home=str(DEMO_HOME))
|
||||
show_substrate(provider2, "session 2 begins — substrate carries session 1's outcomes",
|
||||
outcomes=s1_outcomes)
|
||||
pause(BEAT_MED)
|
||||
|
||||
narrate(
|
||||
"Different agent instance. Zero chat context. Similar-shape incident. "
|
||||
"The model searches the substrate first, applies what fits, and — because "
|
||||
"this incident shape has now recurred — crystallizes the pattern as a "
|
||||
"concrete lesson so the next session doesn't have to re-derive it."
|
||||
)
|
||||
pause(BEAT_LONG)
|
||||
|
||||
system_2 = (
|
||||
"You are a Hermes Agent. Before diagnosing an incident, use "
|
||||
"yantrikdb_skill_search to check for relevant past procedures and lessons. "
|
||||
"Apply them. Call yantrikdb_skill_outcome for each you leaned on.\n\n"
|
||||
"IMPORTANT: If you observe that this incident shape has the SAME root cause "
|
||||
"as patterns the existing skills warn about, that's evidence the pattern is "
|
||||
"recurring — call yantrikdb_skill_define to crystallize a concrete, specific "
|
||||
"lesson tailored to THIS exact symptom + cause (not a generic restatement). "
|
||||
"Use a clear skill_id like 'incident.ingest.allowed_kinds_deploy_race' or "
|
||||
"similar. Be concise in the diagnosis text — the substrate work is the value."
|
||||
)
|
||||
user_2 = (
|
||||
"We just deployed a new event type to our pipeline. The polling watcher "
|
||||
"started emitting the new kind at 14:30, ingest service deploy completed "
|
||||
"at 14:35. Now the ingest endpoint hangs on requests touching that event "
|
||||
"type. Where do I look first?"
|
||||
)
|
||||
s2_messages = run_agent_turn(client, provider2, system_2, user_2)
|
||||
s2_outcomes = count_outcomes_in_messages(s2_messages)
|
||||
total_outcomes = s1_outcomes + s2_outcomes
|
||||
|
||||
pause(BEAT_MED)
|
||||
show_substrate(provider2, "after session 2 — outcomes accrue, new lesson lands",
|
||||
outcomes=total_outcomes)
|
||||
pause(BEAT_LONG)
|
||||
provider2.shutdown()
|
||||
|
||||
banner("Substrate as working memory — composed, applied, refined")
|
||||
print(f" ▸ session 1: agent searched the substrate, composed a multi-source")
|
||||
print(f" diagnosis from {len(SEED_SKILLS)} prior-session skills, recorded")
|
||||
print(f" {s1_outcomes} outcome(s) against the skills it leaned on")
|
||||
print(f" ▸ session 2: fresh agent + similar incident → found relevant skills →")
|
||||
print(f" applied them → recorded {s2_outcomes} outcome(s) → crystallized")
|
||||
print(f" a new lesson when the pattern recurred")
|
||||
print(f" ▸ what this is: the substrate doing real work across real tasks,")
|
||||
print(f" not store-and-retrieve API theatre")
|
||||
print(f" ▸ docs: https://yantrikdb.com/guides/autonomous-skills/")
|
||||
pause(BEAT_LONG)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"\n ! demo failed: {type(e).__name__}: {e}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# LLM-driven skill-lifecycle demo for yantrikdb-hermes-plugin.
|
||||
#
|
||||
# Render: vhs demo_llm.tape (produces demo_llm.gif + demo_llm.mp4)
|
||||
# Requires: vhs >= v0.10, python3, yantrikdb + yantrikdb-hermes-plugin +
|
||||
# openai installed, OPENAI_API_KEY in env.
|
||||
#
|
||||
# Tested on Linux (Ubuntu via WSL works). Windows VHS v0.11 has known
|
||||
# shell-detection issues — use WSL on Windows.
|
||||
|
||||
Output demo_llm.gif
|
||||
Output demo_llm.mp4
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1280
|
||||
Set Height 800
|
||||
Set Padding 12
|
||||
Set Theme "Catppuccin Mocha"
|
||||
|
||||
Hide
|
||||
Type "cd /mnt/c/Users/sync/codes/yantrikdb-hermes-plugin && clear"
|
||||
Enter
|
||||
Sleep 200ms
|
||||
Show
|
||||
|
||||
Type "python3 assets/demos/skill-lifecycle/demo_llm.py"
|
||||
Sleep 600ms
|
||||
Enter
|
||||
|
||||
# demo_llm.py: ~80s end-to-end — two sessions, multi-source diagnosis
|
||||
# composed from seed skills, outcomes recorded, new lesson crystallized
|
||||
# when the pattern recurs.
|
||||
Sleep 85s
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
|
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Constellation-style animation of the skill substrate growing.
|
||||
|
||||
Visual companion to demo_llm.py — renders the substrate as a dark-themed
|
||||
animated GIF where each skill is a glowing node, edges are semantic
|
||||
similarity links, and the agent's contributions animate in over time.
|
||||
|
||||
Inspired by the constellation visualizer in wysie's yantrikdb-hermes-
|
||||
dashboard. Standalone (doesn't require the dashboard); uses NetworkX +
|
||||
matplotlib to render frames, imageio to assemble.
|
||||
|
||||
Run:
|
||||
pip install matplotlib networkx imageio pillow
|
||||
python3 demo_visual.py
|
||||
|
||||
Output: demo_visual.gif (~3-5 MB, ~12s loop)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from io import BytesIO
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Circle, FancyBboxPatch
|
||||
import matplotlib.patheffects as path_effects
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import imageio.v2 as imageio
|
||||
|
||||
OUTPUT = Path(__file__).parent / "demo_visual.gif"
|
||||
|
||||
# ── visual identity ──────────────────────────────────────────────────
|
||||
BG = "#0a0e1a" # deep space
|
||||
GRID = "#1a2030"
|
||||
TEXT = "#cdd6f4"
|
||||
DIM = "#6c7086"
|
||||
COLORS = {
|
||||
"procedure": "#94e2d5", # teal
|
||||
"reference": "#89b4fa", # blue
|
||||
"lesson": "#f9e2af", # gold (insight-shaped)
|
||||
"rule": "#cba6f7", # purple (norm-shaped)
|
||||
"new": "#f38ba8", # pink (just-created)
|
||||
"outcome": "#a6e3a1", # green (successful outcome)
|
||||
}
|
||||
|
||||
# Frame parameters
|
||||
W, H = 1280, 800
|
||||
FPS = 12
|
||||
HOLD_FRAMES = 18 # ~1.5s per key state
|
||||
|
||||
# Skills shape — same six seed entries from seed_skills.py
|
||||
SEED = [
|
||||
("research.preregistration.protocol", "procedure"),
|
||||
("deploy.allowed_kinds.extension_order", "lesson"),
|
||||
("incident.service.silent_deadlock_check", "reference"),
|
||||
("workflow.upstream_block.escalation", "procedure"),
|
||||
("review.user_visible_change.no_marketing", "rule"),
|
||||
("workflow.session_handoff.context_distillation", "procedure"),
|
||||
]
|
||||
# Skill added by the agent in session 1
|
||||
NEW_SKILL = ("release.yantrikos_clean", "procedure")
|
||||
|
||||
|
||||
def fig_to_array(fig) -> np.ndarray:
|
||||
buf = BytesIO()
|
||||
fig.savefig(buf, format="png", facecolor=BG, dpi=100, bbox_inches="tight", pad_inches=0)
|
||||
buf.seek(0)
|
||||
img = Image.open(buf).convert("RGB")
|
||||
# Resize to fixed canvas (matplotlib tight bbox varies frame to frame)
|
||||
img = img.resize((W, H), Image.LANCZOS)
|
||||
return np.array(img)
|
||||
|
||||
|
||||
def build_graph(skill_ids: list[tuple[str, str]]) -> nx.Graph:
|
||||
"""Build a graph where edges represent applies_to overlap."""
|
||||
g = nx.Graph()
|
||||
# Synthesized similarity via shared keywords in skill_id parts.
|
||||
parts = {sid: set(sid.split(".")) for sid, _ in skill_ids}
|
||||
for sid, stype in skill_ids:
|
||||
g.add_node(sid, skill_type=stype)
|
||||
for i, (a, _) in enumerate(skill_ids):
|
||||
for b, _ in skill_ids[i+1:]:
|
||||
shared = parts[a] & parts[b]
|
||||
if shared:
|
||||
g.add_edge(a, b, weight=len(shared))
|
||||
return g
|
||||
|
||||
|
||||
def render_frame(
|
||||
g: nx.Graph,
|
||||
*,
|
||||
title: str,
|
||||
subtitle: str = "",
|
||||
highlight: str | None = None,
|
||||
new_node: str | None = None,
|
||||
show_search: bool = False,
|
||||
outcome_node: str | None = None,
|
||||
stats_line: str = "",
|
||||
) -> np.ndarray:
|
||||
fig, ax = plt.subplots(figsize=(W/100, H/100), facecolor=BG)
|
||||
ax.set_facecolor(BG)
|
||||
ax.set_xlim(-1.2, 1.2)
|
||||
ax.set_ylim(-1.0, 1.0)
|
||||
ax.axis("off")
|
||||
|
||||
# Title (top left).
|
||||
ax.text(-1.15, 0.92, title, color=TEXT, fontsize=20, fontweight="bold",
|
||||
family="monospace", va="top", ha="left")
|
||||
if subtitle:
|
||||
ax.text(-1.15, 0.83, subtitle, color=DIM, fontsize=12,
|
||||
family="monospace", va="top", ha="left")
|
||||
if stats_line:
|
||||
ax.text(-1.15, -0.94, stats_line, color=TEXT, fontsize=12,
|
||||
family="monospace", va="bottom", ha="left")
|
||||
|
||||
# Plugin tag (bottom right).
|
||||
ax.text(1.15, -0.94, "yantrikdb-hermes-plugin · skill_substrate",
|
||||
color=DIM, fontsize=10, family="monospace", va="bottom", ha="right")
|
||||
|
||||
# Layout — circular gives a constellation feel and stays stable
|
||||
# as nodes are added (we render with the SAME positions seeded).
|
||||
nodes = list(g.nodes())
|
||||
n = len(nodes)
|
||||
pos = {}
|
||||
for i, node in enumerate(nodes):
|
||||
angle = 2 * math.pi * i / max(n, 1) - math.pi / 2
|
||||
radius = 0.55 if n <= 1 else 0.55
|
||||
pos[node] = (radius * math.cos(angle), radius * math.sin(angle) * 0.75)
|
||||
|
||||
# Edges first (under nodes).
|
||||
for u, v, data in g.edges(data=True):
|
||||
x = [pos[u][0], pos[v][0]]
|
||||
y = [pos[u][1], pos[v][1]]
|
||||
ax.plot(x, y, color="#3b4252", linewidth=1.2, alpha=0.6, zorder=1)
|
||||
|
||||
# Nodes with glow.
|
||||
for node in nodes:
|
||||
x, y = pos[node]
|
||||
stype = g.nodes[node].get("skill_type", "procedure")
|
||||
color = COLORS.get(stype, COLORS["procedure"])
|
||||
|
||||
is_new = node == new_node
|
||||
is_hit = node == highlight
|
||||
is_outcome = node == outcome_node
|
||||
|
||||
if is_new:
|
||||
color = COLORS["new"]
|
||||
size = 320
|
||||
elif is_outcome:
|
||||
color = COLORS["outcome"]
|
||||
size = 280
|
||||
elif is_hit:
|
||||
size = 280
|
||||
else:
|
||||
size = 200
|
||||
|
||||
# Glow halo.
|
||||
for r, a in [(0.085, 0.10), (0.06, 0.18), (0.04, 0.32)]:
|
||||
halo = Circle((x, y), r, color=color, alpha=a, zorder=2)
|
||||
ax.add_patch(halo)
|
||||
# Core node.
|
||||
ax.scatter(x, y, s=size, c=color, edgecolors="white",
|
||||
linewidths=1.4, zorder=3)
|
||||
# Label.
|
||||
label = node.split(".", 1)[-1] if "." in node else node
|
||||
if len(label) > 28:
|
||||
label = label[:26] + "…"
|
||||
ax.text(x, y - 0.10, label, color=TEXT, fontsize=8,
|
||||
family="monospace", ha="center", va="top", zorder=4,
|
||||
path_effects=[path_effects.withStroke(linewidth=2, foreground=BG)])
|
||||
# Skill-type chip.
|
||||
ax.text(x, y + 0.075, stype, color=DIM, fontsize=7,
|
||||
family="monospace", ha="center", va="bottom", zorder=4)
|
||||
|
||||
# Outcome checkmark.
|
||||
if is_outcome:
|
||||
ax.text(x + 0.05, y + 0.05, "✓", color=COLORS["outcome"],
|
||||
fontsize=18, fontweight="bold", ha="center", va="center",
|
||||
zorder=5,
|
||||
path_effects=[path_effects.withStroke(linewidth=3, foreground=BG)])
|
||||
|
||||
# Search-query overlay.
|
||||
if show_search:
|
||||
ax.text(0, -0.85, "search: \"ship to PyPI\" → 1 match",
|
||||
color=COLORS["procedure"], fontsize=14, family="monospace",
|
||||
ha="center", va="center",
|
||||
bbox=dict(boxstyle="round,pad=0.5", fc="#11151f",
|
||||
ec=COLORS["procedure"], lw=1.5))
|
||||
|
||||
plt.tight_layout(pad=0)
|
||||
arr = fig_to_array(fig)
|
||||
plt.close(fig)
|
||||
return arr
|
||||
|
||||
|
||||
def main() -> None:
|
||||
frames: list[np.ndarray] = []
|
||||
|
||||
# Stage 1 — title card / empty substrate (3s).
|
||||
g0 = nx.Graph()
|
||||
f = render_frame(
|
||||
g0,
|
||||
title="yantrikdb-hermes-plugin · LLM-driven skill lifecycle",
|
||||
subtitle="agent-authored procedures with outcome tracking",
|
||||
stats_line="skills=0, outcomes=0, conflicts=0",
|
||||
)
|
||||
frames.extend([f] * (HOLD_FRAMES * 2))
|
||||
|
||||
# Stage 2 — seed appears one by one (1s each).
|
||||
current = []
|
||||
for sid, stype in SEED:
|
||||
current.append((sid, stype))
|
||||
g = build_graph(current)
|
||||
f = render_frame(
|
||||
g,
|
||||
title="Seed — prior sessions",
|
||||
subtitle=f"loading skill #{len(current)}: {sid}",
|
||||
new_node=sid,
|
||||
stats_line=f"skills={len(current)}, outcomes=0, conflicts=0",
|
||||
)
|
||||
frames.extend([f] * (FPS // 2)) # ~0.5s
|
||||
|
||||
# Hold the full seed view (1.5s).
|
||||
g_seed = build_graph(SEED)
|
||||
f = render_frame(
|
||||
g_seed,
|
||||
title="Seed — 6 skills from past sessions",
|
||||
subtitle="agent inherits a lived-in substrate",
|
||||
stats_line="skills=6, outcomes=0, conflicts=0",
|
||||
)
|
||||
frames.extend([f] * HOLD_FRAMES)
|
||||
|
||||
# Stage 3 — session 1: agent defines a new skill (2.5s).
|
||||
all_skills = SEED + [NEW_SKILL]
|
||||
g_after_define = build_graph(all_skills)
|
||||
for _ in range(HOLD_FRAMES + 6):
|
||||
f = render_frame(
|
||||
g_after_define,
|
||||
title="Session 1 — agent calls yantrikdb_skill_define",
|
||||
subtitle="release.yantrikos_clean (procedure)",
|
||||
new_node=NEW_SKILL[0],
|
||||
stats_line="skills=7, outcomes=0, conflicts=0",
|
||||
)
|
||||
frames.append(f)
|
||||
|
||||
# Stage 4 — session 2: search lights up the relevant node (2s).
|
||||
for _ in range(HOLD_FRAMES + 4):
|
||||
f = render_frame(
|
||||
g_after_define,
|
||||
title="Session 2 — fresh agent, calls yantrikdb_skill_search",
|
||||
subtitle='query="ship to PyPI" top_k=5',
|
||||
highlight=NEW_SKILL[0],
|
||||
show_search=True,
|
||||
stats_line="skills=7, outcomes=0, conflicts=0",
|
||||
)
|
||||
frames.append(f)
|
||||
|
||||
# Stage 5 — outcome recorded, success burst (2s).
|
||||
for _ in range(HOLD_FRAMES + 6):
|
||||
f = render_frame(
|
||||
g_after_define,
|
||||
title="Outcome recorded — yantrikdb_skill_outcome",
|
||||
subtitle="release.yantrikos_clean succeeded=true",
|
||||
outcome_node=NEW_SKILL[0],
|
||||
stats_line="skills=7, outcomes=1, conflicts=0\n"
|
||||
"release.yantrikos_clean: successes=1, failures=0",
|
||||
)
|
||||
frames.append(f)
|
||||
|
||||
# Stage 6 — final card (3s).
|
||||
for _ in range(HOLD_FRAMES * 2):
|
||||
f = render_frame(
|
||||
g_after_define,
|
||||
title="Skill lifecycle closed",
|
||||
subtitle="authored · retrieved · outcome recorded",
|
||||
outcome_node=NEW_SKILL[0],
|
||||
stats_line="next session: this skill ranks higher · outcome history persists",
|
||||
)
|
||||
frames.append(f)
|
||||
|
||||
# Assemble.
|
||||
print(f" → rendering {len(frames)} frames @ {FPS}fps "
|
||||
f"({len(frames) / FPS:.1f}s total) …")
|
||||
imageio.mimsave(OUTPUT, frames, fps=FPS, loop=0)
|
||||
print(f" ✓ wrote {OUTPUT.name} ({OUTPUT.stat().st_size / 1024 / 1024:.1f} MB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
"""Seed data for the LLM-driven demo — representative skills shaped like
|
||||
real production entries from a long-running yantrikdb skill_substrate.
|
||||
|
||||
These are anonymized/genericized versions of the kinds of patterns an
|
||||
agent actually crystallizes over many sessions: incident lessons,
|
||||
operational procedures, research protocols, debugging references.
|
||||
|
||||
Loaded into the ephemeral demo substrate before the user prompt in
|
||||
session 1, so the demo's "skills before -> skills after" delta reads
|
||||
as "agent adds to a lived-in substrate," not "agent populates a toy."
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
SEED_SKILLS: list[dict[str, Any]] = [
|
||||
{
|
||||
"skill_id": "research.preregistration.protocol",
|
||||
"skill_type": "procedure",
|
||||
"applies_to": ["research", "methodology", "review"],
|
||||
"body": (
|
||||
"For any research attempt in a single session: "
|
||||
"(1) pre-register the hypothesis and falsification criteria "
|
||||
"in writing BEFORE looking at data; "
|
||||
"(2) define the minimum test that could falsify the claim; "
|
||||
"(3) declare what data you will collect and how it will be analyzed; "
|
||||
"(4) commit the pre-registration to the substrate before running anything. "
|
||||
"If you can't pre-register, the result is exploratory, not confirmatory."
|
||||
),
|
||||
"triggers": ["new experiment", "test hypothesis", "research session"],
|
||||
},
|
||||
{
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"skill_type": "lesson",
|
||||
"applies_to": ["deployment", "incident", "review"],
|
||||
"body": (
|
||||
"When extending an ALLOWED_KINDS list (or any similar allow-list) that's "
|
||||
"checked in BOTH a polling watcher AND a downstream ingest service, the "
|
||||
"deploy ORDER matters: ingest must accept the new kind FIRST, then the "
|
||||
"watcher starts emitting it. If you deploy the watcher first, the ingest "
|
||||
"rejects the new kind during the deploy gap and you lose those events "
|
||||
"silently. Always: downstream first, upstream second."
|
||||
),
|
||||
"triggers": ["extend allowed kinds", "add new event type", "deploy ordering"],
|
||||
},
|
||||
{
|
||||
"skill_id": "incident.service.silent_deadlock_check",
|
||||
"skill_type": "reference",
|
||||
"applies_to": ["incident", "debugging", "operations"],
|
||||
"body": (
|
||||
"Symptom shape — a long-running service has /v1/health returning ok "
|
||||
"but /v1/<operation> hanging indefinitely on otherwise-valid requests. "
|
||||
"Likely causes ranked: (1) blocked on a mutex held by a panicked thread "
|
||||
"(check thread state with py-spy or gdb); (2) connection pool exhausted "
|
||||
"(check pool stats endpoint or DB connection count); (3) deadlock on a "
|
||||
"shared lock between read and write paths. Health endpoint alone is "
|
||||
"insufficient — always probe an operational endpoint in production."
|
||||
),
|
||||
"triggers": ["service hung", "endpoint not responding", "silent deadlock"],
|
||||
},
|
||||
{
|
||||
"skill_id": "workflow.upstream_block.escalation",
|
||||
"skill_type": "procedure",
|
||||
"applies_to": ["workflow", "incident", "coordination"],
|
||||
"body": (
|
||||
"When a downstream feature hits an upstream bug or limitation: "
|
||||
"(1) file an issue on the upstream repo with a minimal reproducer; "
|
||||
"(2) tag the downstream issue as blocked-on-upstream with a link; "
|
||||
"(3) propose a temporary workaround in the downstream that doesn't "
|
||||
"create technical debt; (4) set a calendar reminder to revisit if "
|
||||
"upstream is unresponsive for >7 days. Don't fork upstream silently."
|
||||
),
|
||||
"triggers": ["upstream bug", "blocked feature", "external dependency"],
|
||||
},
|
||||
{
|
||||
"skill_id": "review.user_visible_change.no_marketing",
|
||||
"skill_type": "rule",
|
||||
"applies_to": ["review", "writing", "product"],
|
||||
"body": (
|
||||
"For any change touching user-visible product (web UI, landing page, "
|
||||
"README hero, dashboard, demo), do NOT lead with marketing voice. "
|
||||
"Lead with the concrete user-facing change, the user problem it "
|
||||
"addresses, and the measured/observable outcome. 'Improves X' is "
|
||||
"marketing voice; 'Reduces median latency from 240ms to 90ms on the "
|
||||
"/recall endpoint at p50' is product voice. Substance first."
|
||||
),
|
||||
"triggers": ["product change", "ui update", "demo polish", "marketing copy"],
|
||||
},
|
||||
{
|
||||
"skill_id": "workflow.session_handoff.context_distillation",
|
||||
"skill_type": "procedure",
|
||||
"applies_to": ["workflow", "meta", "memory"],
|
||||
"body": (
|
||||
"Before ending a session that did substantive work, distill: "
|
||||
"(1) what was decided (the conclusion, not the deliberation); "
|
||||
"(2) what was shipped (commit hashes, PR numbers, artifacts); "
|
||||
"(3) what remains blocked and why; "
|
||||
"(4) one sentence the next session needs to know to pick up. "
|
||||
"Crystallize this into the substrate under workflow.* — future "
|
||||
"sessions search for 'where did I leave off' and find it."
|
||||
),
|
||||
"triggers": ["session ending", "handoff", "context distillation"],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def load_seed_skills(provider) -> int:
|
||||
"""Load the seed skills into the provider's substrate. Returns count loaded."""
|
||||
count = 0
|
||||
for entry in SEED_SKILLS:
|
||||
# The plugin's tool dispatcher accepts JSON-serializable args
|
||||
# exactly as Hermes would route an LLM tool call.
|
||||
result = provider.handle_tool_call("yantrikdb_skill_define", entry)
|
||||
if '"stored": true' in result:
|
||||
count += 1
|
||||
return count
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
LLM-driven skill-lifecycle — agent triages an incident using the substrate
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
model: gpt-4o-mini
|
||||
substrate: ephemeral SQLite, pre-seeded with 6 skills from past sessions
|
||||
tools: 11 (the plugin's full surface)
|
||||
|
||||
▸ seeding 6 skills:
|
||||
• research.preregistration.protocol (procedure)
|
||||
• deploy.allowed_kinds.extension_order (lesson)
|
||||
• incident.service.silent_deadlock_check (reference)
|
||||
• workflow.upstream_block.escalation (procedure)
|
||||
• review.user_visible_change.no_marketing (rule)
|
||||
• workflow.session_handoff.context_distillation (procedure)
|
||||
▸ 6 skills loaded — substrate is now lived-in
|
||||
|
||||
───────────────────────────────────────────────────────────────────
|
||||
Session 1 — agent triages a real incident using the substrate
|
||||
───────────────────────────────────────────────────────────────────
|
||||
▸ skill_substrate (before triage): skills=6, outcomes=0, conflicts=0
|
||||
· The agent gets a concrete incident report. Before responding, it uses
|
||||
· yantrikdb_skill_search to gather context from past sessions — the substrate as
|
||||
· working memory under pressure.
|
||||
|
||||
· USER → agent:
|
||||
Our staging service stopped responding to /v1/* endpoints around 03:00 UTC
|
||||
after a deploy that extended ALLOWED_KINDS to include a new event type.
|
||||
/v1/health is still returning 200 but every operational endpoint hangs. We
|
||||
deployed both the polling watcher and the ingest service this morning.
|
||||
What's going on, and what should we check first?
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_search(
|
||||
query="staging service stopped responding after deploy"
|
||||
top_k=3
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"count": 3,
|
||||
"skills": [
|
||||
{
|
||||
"rid": "019e47e4-e141-7459-8e65-848d2fdde181",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"skill_type": "lesson",
|
||||
"applies_to": [
|
||||
"deployment",
|
||||
"incident",
|
||||
"review"
|
||||
],
|
||||
"body": "When extending an ALLOWED_KINDS list (or any similar allow-list) that's checked in BOTH a polling watcher AND a downstream ingest service, the deploy ORDER matters: ingest must accept the new kind FIRST, then the watch …
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_search(
|
||||
query="ALLOWED_KINDS deployment issue"
|
||||
top_k=3
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"count": 3,
|
||||
"skills": [
|
||||
{
|
||||
"rid": "019e47e4-e141-7459-8e65-848d2fdde181",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"skill_type": "lesson",
|
||||
"applies_to": [
|
||||
"deployment",
|
||||
"incident",
|
||||
"review"
|
||||
],
|
||||
"body": "When extending an ALLOWED_KINDS list (or any similar allow-list) that's checked in BOTH a polling watcher AND a downstream ingest service, the deploy ORDER matters: ingest must accept the new kind FIRST, then the watch …
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_search(
|
||||
query="polling watcher ingest service deploy issue"
|
||||
top_k=3
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"count": 3,
|
||||
"skills": [
|
||||
{
|
||||
"rid": "019e47e4-e141-7459-8e65-848d2fdde181",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"skill_type": "lesson",
|
||||
"applies_to": [
|
||||
"deployment",
|
||||
"incident",
|
||||
"review"
|
||||
],
|
||||
"body": "When extending an ALLOWED_KINDS list (or any similar allow-list) that's checked in BOTH a polling watcher AND a downstream ingest service, the deploy ORDER matters: ingest must accept the new kind FIRST, then the watch …
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_outcome(
|
||||
skill_id="deploy.allowed_kinds.extension_order"
|
||||
succeeded=true
|
||||
note="Clarified the importance of deployment order for ALLOWED_KINDS."
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"rid": "019e47e5-6750-73a6-baf5-f1790819bd60",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"recorded": true
|
||||
}
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_outcome(
|
||||
skill_id="incident.service.silent_deadlock_check"
|
||||
succeeded=true
|
||||
note="Provided insights into potential causes for hanging endpoints."
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"rid": "019e47e5-6feb-792a-814c-6ebb232adf96",
|
||||
"skill_id": "incident.service.silent_deadlock_check",
|
||||
"recorded": true
|
||||
}
|
||||
|
||||
· gpt-4o-mini replies:
|
||||
The outcomes of the skills have been successfully logged. If you need
|
||||
further assistance or have additional questions, feel free to ask!
|
||||
▸ skill_substrate (after session 1): skills=6, outcomes=2, conflicts=0
|
||||
|
||||
──────────────────────────────────────────────────────────────────────
|
||||
[ session 1 ended — agent state torn down — substrate persists ]
|
||||
──────────────────────────────────────────────────────────────────────
|
||||
|
||||
────────────────────────────────────────────────────────────────
|
||||
Session 2 — fresh agent, similar-shape incident days later
|
||||
────────────────────────────────────────────────────────────────
|
||||
▸ skill_substrate (session 2 begins — substrate carries session 1's outcomes): skills=6, outcomes=2, conflicts=0
|
||||
· Different agent instance. Zero chat context. Similar-shape incident. The model
|
||||
· searches the substrate first, applies what fits, and — because this incident
|
||||
· shape has now recurred — crystallizes the pattern as a concrete lesson so the
|
||||
· next session doesn't have to re-derive it.
|
||||
|
||||
· USER → agent:
|
||||
We just deployed a new event type to our pipeline. The polling watcher
|
||||
started emitting the new kind at 14:30, ingest service deploy completed at
|
||||
14:35. Now the ingest endpoint hangs on requests touching that event type.
|
||||
Where do I look first?
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_search(
|
||||
query="ingest service hangs after deploying new event type"
|
||||
top_k=5
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"count": 5,
|
||||
"skills": [
|
||||
{
|
||||
"rid": "019e47e4-e141-7459-8e65-848d2fdde181",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"skill_type": "lesson",
|
||||
"applies_to": [
|
||||
"deployment",
|
||||
"incident",
|
||||
"review"
|
||||
],
|
||||
"body": "When extending an ALLOWED_KINDS list (or any similar allow-list) that's checked in BOTH a polling watcher AND a downstream ingest service, the deploy ORDER matters: ingest must accept the new kind FIRST, then the watch …
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_outcome(
|
||||
skill_id="deploy.allowed_kinds.extension_order"
|
||||
succeeded=true
|
||||
note="The deployment order was likely incorrect
|
||||
with the watcher deployed before the ingest service accepted the new event type."
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"rid": "019e47e5-be90-7684-a81e-ea679e336e54",
|
||||
"skill_id": "deploy.allowed_kinds.extension_order",
|
||||
"recorded": true
|
||||
}
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_outcome(
|
||||
skill_id="incident.service.silent_deadlock_check"
|
||||
succeeded=false
|
||||
note="Need to investigate the service state further
|
||||
as the health check is returning ok but operational requests are hanging."
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"rid": "019e47e5-c72c-769b-8a78-d99655190d19",
|
||||
"skill_id": "incident.service.silent_deadlock_check",
|
||||
"recorded": true
|
||||
}
|
||||
|
||||
⚙ gpt-4o-mini → yantrikdb_skill_define(
|
||||
skill_id="incident.ingest.allowed_kinds_deploy_race"
|
||||
body="When deploying a new event type
|
||||
ensure that the ingest service is deployed and fully accepting the new kind before the polling watcher ...
|
||||
skill_type="lesson"
|
||||
applies_to=["deployment"
|
||||
"incident"
|
||||
"ingest"
|
||||
"watcher"]
|
||||
triggers=["deploy new event type"
|
||||
"ingest service hangs"
|
||||
"polling watcher emits new kind"]
|
||||
version="1.0.0"
|
||||
)
|
||||
← plugin:
|
||||
{
|
||||
"rid": "019e47e5-e069-74a4-a653-edcc5d15edac",
|
||||
"skill_id": "incident.ingest.allowed_kinds_deploy_race",
|
||||
"stored": true
|
||||
}
|
||||
|
||||
· gpt-4o-mini replies:
|
||||
I have defined a specific lesson regarding the deployment order of services
|
||||
when introducing new event types. This lesson emphasizes the importance of
|
||||
deploying the ingest service before the polling watcher to avoid hangs and
|
||||
silent failures. ### Lesson Defined: - **Skill ID**:
|
||||
`incident.ingest.allowed_kinds_deploy_race` - **Key Insight**: Always deploy
|
||||
downstream services (like the ingest service) first, followed by upstream
|
||||
services (like the polling watcher) when introducing new event types. This
|
||||
should help prevent similar incidents in the future. If you need further
|
||||
assistance or have additional questions, feel free to ask!
|
||||
▸ skill_substrate (after session 2 — outcomes accrue, new lesson lands): skills=7, outcomes=4, conflicts=0
|
||||
|
||||
────────────────────────────────────────────────────────────────
|
||||
Substrate as working memory — composed, applied, refined
|
||||
────────────────────────────────────────────────────────────────
|
||||
▸ session 1: agent searched the substrate, composed a multi-source
|
||||
diagnosis from 6 prior-session skills, recorded
|
||||
2 outcome(s) against the skills it leaned on
|
||||
▸ session 2: fresh agent + similar incident → found relevant skills →
|
||||
applied them → recorded 2 outcome(s) → crystallized
|
||||
a new lesson when the pattern recurred
|
||||
▸ what this is: the substrate doing real work across real tasks,
|
||||
not store-and-retrieve API theatre
|
||||
▸ docs: https://yantrikdb.com/guides/autonomous-skills/
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
────────────────────────────────────────────────────────────
|
||||
Step 1 — fresh substrate, 0 skills
|
||||
────────────────────────────────────────────────────────────
|
||||
YANTRIKDB_DB_PATH = C:\Users\sync\AppData\Local\Temp\yantrikdb_hermes_demo_msmxkwkh\memory.db
|
||||
YANTRIKDB_NAMESPACE = demo
|
||||
YANTRIKDB_SKILLS_ENABLED = true
|
||||
|
||||
$ provider.handle_tool_call('yantrikdb_stats', {'namespace': 'demo'})
|
||||
→ {"active_memories": 0, "consolidated_memories": 0, "tombstoned_memories": 0, "edges": 0, "entities": 0, "operations": 0, "open_conflicts": 0 …
|
||||
|
||||
─────────────────────────────────────────────────────────────────
|
||||
Step 2 — Session 1: agent observes a pattern, defines a skill
|
||||
─────────────────────────────────────────────────────────────────
|
||||
|
||||
The agent has just shipped a clean release. It noticed the same
|
||||
sequence worked three times: feature branch → CI → squash-merge
|
||||
→ tag → GH release → PyPI verify. It chooses to crystallize.
|
||||
|
||||
$ provider.handle_tool_call('yantrikdb_skill_define', { … })
|
||||
→ {"rid": "019e4783-7496-747a-a2e2-8710f727cf90", "skill_id": "workflow.release.yantrikos_repo", "stored": true}
|
||||
|
||||
$ provider.handle_tool_call('yantrikdb_stats', {'namespace': 'skill_substrate'})
|
||||
→ {"active_memories": 0, "consolidated_memories": 0, "tombstoned_memories": 0, "edges": 0, "entities": 0, "operations": 2, "open_conflicts": 0 …
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
Step 3 — simulated session restart (fresh agent state)
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
Tearing down the agent's in-memory state. The substrate persists.
|
||||
|
||||
$ provider = YantrikDBMemoryProvider() # new instance
|
||||
→ provider ready, substrate has the skill from session 1
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
Step 4 — Session 2: fresh agent searches before acting
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
The agent gets a new request: 'ship v0.4.13 of the plugin.'
|
||||
Before doing anything, it searches the skill substrate.
|
||||
|
||||
$ provider.handle_tool_call('yantrikdb_skill_search', {'query': 'how to ship a release', 'top_k': 3})
|
||||
→ {"count": 1, "skills": [{"rid": "019e4783-7496-747a-a2e2-8710f727cf90", "skill_id": "workflow.release.yantrikos_repo", "skill_type": "procedure", "applies_to": ["release", "workflow", "yantrikos"], "body": "For every release on a yantrikos repo with branch protection: (1) feature …
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
Step 5 — agent follows the skill, reports outcome
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
The agent reads the skill body, ships the release following the
|
||||
6-step procedure, succeeds, and records the outcome.
|
||||
|
||||
$ provider.handle_tool_call('yantrikdb_skill_outcome', { … })
|
||||
→ {"rid": "019e4783-b436-7a18-9b8f-daa6ad818010", "skill_id": "workflow.release.yantrikos_repo", "recorded": true}
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
Done — the autonomy loop closed
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
• Substrate now holds 1 skill (workflow.release.yantrikos_repo)
|
||||
• Outcome ledger has 1 success row, agent's access_count = 1
|
||||
• Next session's agent will see this skill ranked higher
|
||||
|
||||
The plugin's code path you just saw is the same one Hermes invokes
|
||||
when its agent's LLM emits a tool call. The LLM is omitted here for
|
||||
determinism; everything else is the live plugin + live engine.
|
||||
|
||||
Plugin: yantrikdb-hermes-plugin v0.4.12
|
||||
Substrate: C:\Users\sync\AppData\Local\Temp\yantrikdb_hermes_demo_msmxkwkh\memory.db
|
||||
|
||||
(Ephemeral demo home left at C:\Users\sync\AppData\Local\Temp\yantrikdb_hermes_demo_msmxkwkh for inspection)
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
"""Workspace-level conftest.
|
||||
|
||||
Pytest discovers tests under ``tests/`` but the workspace root is itself a
|
||||
Python package (it contains ``__init__.py`` because the root IS the plugin
|
||||
under test). When pytest imports the root package during collection it
|
||||
hits ``from agent.memory_provider import MemoryProvider`` — which only
|
||||
exists inside Hermes.
|
||||
|
||||
Installing the Hermes stubs here, at pytest's earliest import point,
|
||||
lets the root ``__init__.py`` import cleanly. The real test-time fixtures
|
||||
and plugin loader live in ``tests/conftest.py``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import types
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
def _install_hermes_stubs() -> None:
|
||||
if "agent.memory_provider" in sys.modules:
|
||||
return
|
||||
|
||||
sys.modules["agent"] = types.ModuleType("agent")
|
||||
|
||||
mp_mod = types.ModuleType("agent.memory_provider")
|
||||
|
||||
class MemoryProvider(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def is_available(self) -> bool: ...
|
||||
|
||||
@abstractmethod
|
||||
def initialize(self, session_id: str, **kwargs: Any) -> None: ...
|
||||
|
||||
def system_prompt_block(self) -> str:
|
||||
return ""
|
||||
|
||||
def prefetch(self, query: str, *, session_id: str = "") -> str:
|
||||
return ""
|
||||
|
||||
def queue_prefetch(self, query: str, *, session_id: str = "") -> None:
|
||||
return None
|
||||
|
||||
def sync_turn(
|
||||
self, user_content: str, assistant_content: str, *, session_id: str = "",
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def get_tool_schemas(self) -> List[Dict[str, Any]]: ...
|
||||
|
||||
def handle_tool_call(
|
||||
self, tool_name: str, args: Dict[str, Any], **kwargs: Any,
|
||||
) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def shutdown(self) -> None:
|
||||
return None
|
||||
|
||||
def on_turn_start(self, turn_number: int, message: str, **kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
|
||||
return None
|
||||
|
||||
def on_pre_compress(self, messages: List[Dict[str, Any]]) -> str:
|
||||
return ""
|
||||
|
||||
def on_delegation(
|
||||
self, task: str, result: str, *, child_session_id: str = "", **kwargs: Any,
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
def get_config_schema(self) -> List[Dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def save_config(self, values: Dict[str, Any], hermes_home: str) -> None:
|
||||
return None
|
||||
|
||||
def on_memory_write(self, action: str, target: str, content: str) -> None:
|
||||
return None
|
||||
|
||||
mp_mod.MemoryProvider = MemoryProvider
|
||||
sys.modules["agent.memory_provider"] = mp_mod
|
||||
|
||||
sys.modules["tools"] = types.ModuleType("tools")
|
||||
registry_mod = types.ModuleType("tools.registry")
|
||||
|
||||
def tool_error(message: str) -> str:
|
||||
return json.dumps({"error": message})
|
||||
|
||||
registry_mod.tool_error = tool_error
|
||||
sys.modules["tools.registry"] = registry_mod
|
||||
|
||||
|
||||
_install_hermes_stubs()
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
name: yantrikdb
|
||||
version: 0.4.12
|
||||
# Note: plugin.yaml lives at both the repo root AND yantrikdb/ subfolder. The
|
||||
# root copy is the manifest Hermes' user-installed plugin loader reads when a
|
||||
# user does `hermes plugins install yantrikos/yantrikdb-hermes-plugin`; the
|
||||
# subfolder copy is the manifest the bundled-discovery path uses after
|
||||
# `yantrikdb-hermes install <hermes>`. Both should declare the same version.
|
||||
description: "YantrikDB — self-maintaining memory for Hermes with canonicalization, contradiction tracking, recency-aware ranking, explainable recall, and pluggable embedders (bundled potion-2M default; first-class loaders for the model2vec family and the HF sentence-transformers ecosystem; custom Python embedder class as escape hatch). As of v0.2.0 the default backend is in-process (`pip install` and go); HTTP-to-server is optional for HA cluster setups."
|
||||
pip_dependencies:
|
||||
- yantrikdb>=0.7.6
|
||||
- requests>=2.31
|
||||
hooks:
|
||||
- on_session_end
|
||||
- on_pre_compress
|
||||
- on_memory_write
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=68", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "yantrikdb-hermes-plugin"
|
||||
version = "0.4.15"
|
||||
description = "Self-maintaining memory plugin for Hermes Agent — embedded YantrikDB engine, ~10 MB, no server, no token, no GPU. Canonicalizes duplicates, surfaces contradictions, ranks with recency awareness, and explains recall."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
{ name = "Pranab Sarkar", email = "developer@pranab.co.in" },
|
||||
]
|
||||
keywords = [
|
||||
"hermes-agent",
|
||||
"memory",
|
||||
"ai-memory",
|
||||
"llm-agent",
|
||||
"yantrikdb",
|
||||
"knowledge-graph",
|
||||
"rag",
|
||||
"plugin",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||
]
|
||||
dependencies = [
|
||||
"yantrikdb>=0.7.6",
|
||||
"requests>=2.31",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0",
|
||||
"ruff>=0.5",
|
||||
"mypy>=1.10",
|
||||
"types-requests",
|
||||
]
|
||||
# v0.4.2 builtin embedder loaders. Each extra pulls in only what its
|
||||
# loader actually needs; the default install stays slim. Pick one based
|
||||
# on the embedder family you want to use (model2vec for the lightweight
|
||||
# potion family including potion-multilingual-128M; sentence-transformers
|
||||
# for the broader HF ecosystem). Both can be installed together if you
|
||||
# want to A/B different embedders without uninstalling.
|
||||
model2vec = [
|
||||
"model2vec>=0.3",
|
||||
]
|
||||
sentence-transformers = [
|
||||
"sentence-transformers>=2.7",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/yantrikos/yantrikdb-hermes-plugin"
|
||||
Repository = "https://github.com/yantrikos/yantrikdb-hermes-plugin"
|
||||
Changelog = "https://github.com/yantrikos/yantrikdb-hermes-plugin/blob/main/yantrikdb/CHANGELOG.md"
|
||||
Issues = "https://github.com/yantrikos/yantrikdb-hermes-plugin/issues"
|
||||
"Hermes Agent" = "https://github.com/NousResearch/hermes-agent"
|
||||
"YantrikDB Server" = "https://github.com/yantrikos/yantrikdb-server"
|
||||
|
||||
[project.scripts]
|
||||
# CLI entry point — users run this AFTER `pip install yantrikdb-hermes-plugin`
|
||||
# to copy the plugin source into their hermes-agent's plugins/memory/.
|
||||
# Hermes plugins load from the filesystem; pip alone can't drop the source
|
||||
# in the right place, so this CLI is the bridge.
|
||||
yantrikdb-hermes = "yantrikdb_hermes_plugin.cli:main"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Packaging — install the plugin source under a Pythonic name that does NOT
|
||||
# conflict with the `yantrikdb` engine package on PyPI. The CLI copies the
|
||||
# files into <hermes_root>/plugins/memory/yantrikdb/ where Hermes loads
|
||||
# them under the canonical plugin name `yantrikdb`.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["yantrikdb_hermes_plugin"]
|
||||
package-dir = {"yantrikdb_hermes_plugin" = "yantrikdb"}
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
yantrikdb_hermes_plugin = ["*.yaml", "*.md"]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tooling
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
extend-exclude = ["reference/"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"UP", # pyupgrade
|
||||
"B", # flake8-bugbear
|
||||
"SIM", # flake8-simplify
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line length (handled by formatter)
|
||||
"B008", # Do not perform function call in argument defaults (FastAPI-style is fine)
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/*" = ["B", "SIM"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
ignore_missing_imports = true
|
||||
warn_unused_ignores = false
|
||||
warn_return_any = false
|
||||
strict_optional = true
|
||||
check_untyped_defs = true
|
||||
disallow_untyped_defs = false
|
||||
# v0.4.5: the repo root has an __init__.py (entry point for `hermes plugins
|
||||
# install`). The repo dir name `yantrikdb-hermes-plugin` isn't a valid Python
|
||||
# identifier, so mypy errors when it walks up from yantrikdb/ to find a
|
||||
# parent package. explicit_package_bases tells mypy: trust me, yantrikdb/ IS
|
||||
# the package root — don't look higher.
|
||||
explicit_package_bases = true
|
||||
namespace_packages = true
|
||||
exclude = ["reference/", "tests/", "^__init__\\.py$"]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# bump-my-version — atomic version bumps across all three surfaces
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# The plugin's version lives in three places that all have to agree or
|
||||
# Hermes-side display drifts (see CHANGELOG v0.4.14 for the post-mortem on
|
||||
# the v0.4.13 ship that missed plugin.yaml). bump-my-version edits all of
|
||||
# them in one command:
|
||||
#
|
||||
# pipx run bump-my-version bump patch # 0.4.14 -> 0.4.15
|
||||
# pipx run bump-my-version bump minor # 0.4.14 -> 0.5.0
|
||||
# pipx run bump-my-version bump major # 0.4.14 -> 1.0.0
|
||||
#
|
||||
# CHANGELOG.md still needs a hand-written entry for the new version — the
|
||||
# tool can't generate meaningful release notes. After bumping, add the
|
||||
# matching `## [X.Y.Z] — YYYY-MM-DD — Summary` header. The CI `version-sync`
|
||||
# job blocks merge if the three sources drift.
|
||||
[tool.bumpversion]
|
||||
current_version = "0.4.15"
|
||||
commit = false # let the human author the commit message + body
|
||||
tag = false # tag via `gh release create`, not on bump
|
||||
allow_dirty = true
|
||||
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
||||
serialize = ["{major}.{minor}.{patch}"]
|
||||
|
||||
# The pyproject.toml file rule uses a regex anchored to start-of-line
|
||||
# so it only matches `version = "X.Y.Z"` (the [project] field) and NOT
|
||||
# `current_version = "X.Y.Z"` (this same [tool.bumpversion] section
|
||||
# which bumpversion already auto-maintains). Without the anchor, a
|
||||
# bump matches both lines and shifts the project version twice — the
|
||||
# 0.4.14 → 0.4.16 quirk caught during the v0.4.15 ship.
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "pyproject.toml"
|
||||
regex = true
|
||||
search = '^version = "{current_version}"'
|
||||
replace = 'version = "{new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "yantrikdb/plugin.yaml"
|
||||
search = "version: {current_version}"
|
||||
replace = "version: {new_version}"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts = --import-mode=importlib
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# Memory provider comparison harness
|
||||
|
||||
A reproducible side-by-side test harness for the 9 Hermes memory providers (this plugin + the 8 that ship with Hermes). Each provider is installed against a real Hermes instance, exercised with the same canonical inputs, and its `recall()` response shape is captured as a structured fixture so the comparison table in the parent `README.md` is backed by code anyone can re-run, not derived from `plugin.yaml` marketing copy.
|
||||
|
||||
## Why this exists
|
||||
|
||||
The README originally had a "vs other providers" comparison table built from each provider's `plugin.yaml` description. That's not enough — a provider could implement canonicalization, contradiction tracking, or explainable recall without advertising it. Claiming "YantrikDB has X and the others don't" based only on what they say about themselves is strawmanning, not comparing.
|
||||
|
||||
This harness replaces that comparison with verified, reproducible findings. The table in the main README is generated from `findings.yaml` files produced by running each provider against the same fixtures.
|
||||
|
||||
## What gets exercised
|
||||
|
||||
For each provider, the harness drives the **MemoryProvider ABC contract** (the same surface Hermes uses):
|
||||
|
||||
1. **`initialize(session_id)`** — does the provider load cleanly with minimal/default config?
|
||||
2. **`handle_tool_call("<provider>_remember", {...})`** — store a canonical fact.
|
||||
3. **`handle_tool_call("<provider>_recall", {...})`** — retrieve it back; capture the raw response.
|
||||
4. **Duplicate write** — store the same fact a second time; observe whether the provider canonicalizes or pushes a new record.
|
||||
5. **Contradiction write** — store a fact that contradicts an earlier one; observe whether the provider surfaces a conflict, silently overwrites, or stores both.
|
||||
6. **Tool schema introspection** — call `get_tool_schemas()` and check for skill-related tools (`*_skill_define`, `*_skill_search`, `*_skill_outcome` or equivalents).
|
||||
7. **Response field introspection** — does the recall response contain a `why_retrieved` / `reasoning` / `explanation` / `metadata.reason` field that a downstream model could read to know *why* a memory ranked?
|
||||
|
||||
Each step's outcome is recorded in the provider's `findings.yaml`. No subjective judgement — observable behaviour only.
|
||||
|
||||
## Findings schema
|
||||
|
||||
Each `providers/<name>/findings.yaml` is a flat structured record:
|
||||
|
||||
```yaml
|
||||
provider: hindsight
|
||||
version_under_test: "1.2.3" # what we actually pip-installed / cloned
|
||||
verified_at: 2026-05-13 # UTC date the harness was last run against this provider
|
||||
verified_against: "Hermes 0.9.0 in LXC 129 / 192.168.4.x"
|
||||
|
||||
backend: # observable, not declared
|
||||
hosting: cloud | self-hosted | embedded
|
||||
requires_account: true | false
|
||||
requires_separate_server: true | false
|
||||
pip_footprint_mb: 12 # measured via `du -sm`
|
||||
|
||||
contract:
|
||||
initialize_ok: true | false
|
||||
remember_ok: true | false
|
||||
recall_ok: true | false
|
||||
recall_returned_results: 3 # actual count for the canonical query
|
||||
notes: ""
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: true | false # is there a top-level field on each result that names *why* it ranked?
|
||||
why_retrieved_field_name: "reasoning" # the actual key in their response (empty when false)
|
||||
per_result_score: true | false
|
||||
per_result_metadata: true | false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: true | false | unknown # did writing the same fact twice merge or duplicate?
|
||||
contradiction_surfaced: true | false | unknown # does writing a conflicting fact produce a conflict record?
|
||||
contradiction_api: "" # tool name if surfaced; empty otherwise
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: true | false
|
||||
skill_tool_names: [] # the actual *_skill_* tool names exposed
|
||||
|
||||
evidence:
|
||||
transcript_file: providers/hindsight/transcript.md # human-readable session log
|
||||
raw_responses: providers/hindsight/raw/ # captured JSON of every response
|
||||
|
||||
couldnt_verify:
|
||||
reason: "" # only populated when we explicitly skipped (e.g., cloud account unavailable)
|
||||
what_we_know_anyway: "" # plugin.yaml description + repo URL
|
||||
```
|
||||
|
||||
When a step can't be exercised (e.g., a cloud provider requires an account we don't have), `couldnt_verify.reason` is populated explicitly and the relevant `contract.*` / `response_shape.*` / `maintenance.*` fields are set to `unknown` (not `false`). Honesty over coverage.
|
||||
|
||||
## Running the harness
|
||||
|
||||
```bash
|
||||
# Against a single provider (smoke):
|
||||
python -m tests.comparison.harness --provider holographic
|
||||
|
||||
# Against all of them:
|
||||
python -m tests.comparison.harness --all
|
||||
|
||||
# Regenerate the markdown comparison table from findings:
|
||||
python -m tests.comparison.compare > /tmp/comparison.md
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
The harness runs against **LXC 129 (yantrik-memory-test)** which already has Hermes 0.9.0 + yantrikdb 0.7.6 installed (per `VERIFICATION.md`). Each provider is installed into a fresh Python venv to avoid cross-provider dep contamination, and each provider's findings are captured before tearing it down and moving to the next.
|
||||
|
||||
The harness can also run locally without LXC by stubbing Hermes (the same stub conftest the unit tests use). Local runs are useful for development; the LXC run is the one whose findings get published.
|
||||
|
||||
## Providers covered (planned)
|
||||
|
||||
| Provider | Status | Notes |
|
||||
|---|---|---|
|
||||
| yantrikdb (this) | TODO | Will use the existing embedded backend; baseline for the others. |
|
||||
| hindsight | TODO | Self-hosted server; need to start its dependency. |
|
||||
| holographic | TODO | Embedded SQLite — simplest to verify first. |
|
||||
| honcho | TODO | Self-hosted server; honcho-server install required. |
|
||||
| mem0 | TODO | Has both cloud + self-host modes; test self-host. |
|
||||
| openviking | TODO | Local context DB; verify install path. |
|
||||
| byterover | TODO (likely "couldn't verify") | Cloud-only; requires brv CLI auth. |
|
||||
| retaindb | TODO (likely "couldn't verify") | Cloud-only API; requires account. |
|
||||
| supermemory | TODO (likely "couldn't verify") | Cloud-only; requires account. |
|
||||
|
||||
## What this harness explicitly is NOT
|
||||
|
||||
- **Not a quality benchmark.** R@k / NDCG / latency numbers belong in a separate evaluation; this harness answers "what behaviours does the provider expose" not "how well does it expose them".
|
||||
- **Not a value judgement.** Different providers pick different design points. A `false` cell means the provider doesn't expose that surface — not that the provider is worse.
|
||||
- **Not exhaustive.** The seven probes above were picked because they correspond to behaviours users on Reddit asked about. Other behaviours (graph entity recall, summarization, namespace scoping, etc.) can be added as separate steps if they become relevant.
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
"""Aggregate per-provider ``findings_scale.yaml`` files into a markdown table.
|
||||
|
||||
Reads every ``findings_scale_lxc/<provider>/findings_scale.yaml`` and emits a
|
||||
single comparison table to stdout. The table is the README's source of truth:
|
||||
each cell is backed by the corresponding YAML; the YAML is backed by a real
|
||||
session captured in ``transcript.md`` + ``raw/`` next to it.
|
||||
|
||||
Run::
|
||||
|
||||
python -m tests.comparison.compare > /tmp/comparison.md
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
FINDINGS_DIR = Path(__file__).parent / "findings_scale_lxc"
|
||||
|
||||
# Order rows so this plugin's row is first (parity with the README layout),
|
||||
# then alphabetical for the others — no preferential treatment within the
|
||||
# "competitors" block.
|
||||
ROW_ORDER = [
|
||||
"yantrikdb",
|
||||
"byterover", "hindsight", "holographic", "honcho", "mem0",
|
||||
"openviking", "retaindb", "supermemory",
|
||||
]
|
||||
|
||||
|
||||
def _parse_yaml(path: Path) -> dict:
|
||||
"""Minimal YAML parser for our flat schema (no PyYAML dep)."""
|
||||
out: dict = {}
|
||||
section: str | None = None
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
if not line.strip():
|
||||
section = None
|
||||
continue
|
||||
if line.startswith(" "):
|
||||
key, _, val = line.strip().partition(":")
|
||||
val = val.strip().strip('"')
|
||||
if section:
|
||||
out.setdefault(section, {})[key] = val
|
||||
elif line and not line.startswith("#"):
|
||||
key, _, val = line.partition(":")
|
||||
val = val.strip().strip('"')
|
||||
if val == "":
|
||||
section = key.strip()
|
||||
else:
|
||||
out[key.strip()] = val
|
||||
section = None
|
||||
return out
|
||||
|
||||
|
||||
def _bool(v: str) -> bool:
|
||||
return str(v).lower() == "true"
|
||||
|
||||
|
||||
def _format_row(provider: str, data: dict) -> str:
|
||||
is_self = provider == "yantrikdb"
|
||||
name = f"**{provider}** (this)" if is_self else f"[{provider}](https://github.com/NousResearch/hermes-agent/tree/main/plugins/memory/{provider})"
|
||||
backend = data.get("backend", {})
|
||||
hosting = backend.get("hosting", "unknown")
|
||||
scale = data.get("scale", {})
|
||||
pk = data.get("precision_at_k", {})
|
||||
shape = data.get("response_shape", {})
|
||||
maint = data.get("maintenance", {})
|
||||
couldnt = data.get("couldnt_verify", {})
|
||||
|
||||
# Distinguish "totally couldn't verify" (no data captured) from "partial
|
||||
# verify with notes" (real data + a notes line about what didn't finish).
|
||||
queries_completed = int(scale.get("queries_completed", "0") or 0)
|
||||
partial_note = ""
|
||||
if couldnt.get("reason") and queries_completed == 0:
|
||||
verified_at_scale = f"couldn't verify ({hosting})"
|
||||
writes = "—"
|
||||
recall_p50 = "—"
|
||||
pk_str = "—"
|
||||
why_str = "—"
|
||||
maint_str = "—"
|
||||
else:
|
||||
if couldnt.get("reason"):
|
||||
partial_note = f" *(partial: {couldnt['reason'][:80]})*"
|
||||
writes_n = scale.get("corpus_size_written", "0")
|
||||
writes_attempted = scale.get("corpus_size_attempted", "1000")
|
||||
write_p50 = scale.get("write_p50_ms", "0")
|
||||
write_p99 = scale.get("write_p99_ms", "0")
|
||||
recall_p50_v = scale.get("recall_p50_ms", "0")
|
||||
recall_p99 = scale.get("recall_p99_ms", "0")
|
||||
hits = pk.get("hits", "0")
|
||||
total = pk.get("total_queries", "0")
|
||||
pk_val = pk.get("value", "0.0")
|
||||
why = _bool(shape.get("why_retrieved_field", "false"))
|
||||
why_name = shape.get("why_retrieved_field_name", "")
|
||||
contra = maint.get("contradiction_surfaced", "unknown")
|
||||
contra_api = maint.get("contradiction_api", "")
|
||||
dup_canon = maint.get("duplicate_canonicalized", "unknown")
|
||||
|
||||
verified_at_scale = f"yes ({writes_n}/{writes_attempted} writes){partial_note}"
|
||||
writes = f"{writes_n}/{writes_attempted}; p50 {write_p50}ms / p99 {write_p99}ms"
|
||||
recall_p50 = f"p50 {recall_p50_v}ms / p99 {recall_p99}ms"
|
||||
pk_str = f"**{pk_val}** ({hits}/{total})"
|
||||
why_str = f"yes — `{why_name}`" if why else "no"
|
||||
maint_str = ""
|
||||
if contra == "true" and contra_api:
|
||||
maint_str += f"contradiction API: `{contra_api}`; "
|
||||
if dup_canon == "false":
|
||||
maint_str += "duplicates kept separate (no synchronous canon)"
|
||||
elif dup_canon == "true":
|
||||
maint_str += "duplicates canonicalized synchronously"
|
||||
elif dup_canon == "possibly":
|
||||
maint_str += "duplicates partially canonicalized"
|
||||
else:
|
||||
maint_str += "—"
|
||||
|
||||
return f"| {name} | {hosting} | {verified_at_scale} | {writes} | {recall_p50} | {pk_str} | {why_str} | {maint_str} |"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
rows: list[str] = []
|
||||
rows.append("| Provider | Hosting | Verified at 1000 scale | Writes (succeeded/attempted; latency) | Recall latency | Precision@5 | `why_retrieved` field | Maintenance behaviour observed |")
|
||||
rows.append("|---|---|---|---|---|---|---|---|")
|
||||
|
||||
found = []
|
||||
for provider in ROW_ORDER:
|
||||
path = FINDINGS_DIR / provider / "findings_scale.yaml"
|
||||
if not path.exists():
|
||||
continue
|
||||
data = _parse_yaml(path)
|
||||
rows.append(_format_row(provider, data))
|
||||
found.append(provider)
|
||||
|
||||
print("\n".join(rows))
|
||||
print("")
|
||||
print("**Methodology** — Each provider was instantiated against the same Hermes 0.9.0 install (LXC 129 / commit `4610551`), driven through a deterministic 1000-fact corpus (`tests/comparison/fixtures/corpus_1k.json` — 600 realistic facts + 300 noise + 50 planted duplicates + 50 planted contradictions, seed=20260512) and a 20-query set with planted target fact-ids. Precision@5 = (queries whose planted target appeared in the top-5 result set) / (queries completed). Writes are timed individually with backpressure-retry on transient queue-full errors. Per-provider call-shape mappings (`PROVIDER_CALL_SHAPES` in `probe.py`) are needed because providers expose genuinely different APIs (e.g. holographic's action-dispatched `fact_store`, hindsight's `*_retain`/`*_recall` pair). Cloud-only providers without configured accounts emit honest `couldn't_verify` rows; their full `plugin.yaml` description is preserved in their findings file.")
|
||||
print("")
|
||||
print("**Reproduce** — `python -m tests.comparison.runner_scale --all` (LXC) or `python -m tests.comparison.providers.<name>.adapter` (local). Findings + transcripts + raw responses live under [tests/comparison/findings_scale_lxc/](findings_scale_lxc/).")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: byterover
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: cloud
|
||||
requires_account: "true"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: ByteRover — persistent knowledge tree with tiered retrieval via the brv CLI.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: hindsight
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: cloud
|
||||
requires_account: "true"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 1000
|
||||
corpus_size_written: 1000
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.27
|
||||
write_p99_ms: 0.31
|
||||
queries_attempted: 20
|
||||
queries_completed: 20
|
||||
recall_p50_ms: 0.28
|
||||
recall_p99_ms: 0.3
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 20
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0.0
|
||||
contradiction_surfaced: "false"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: /root/bench-harness/findings_scale/hindsight/transcript.md
|
||||
raw_responses_dir: /root/bench-harness/findings_scale/hindsight/raw
|
||||
|
||||
couldnt_verify:
|
||||
reason: ""
|
||||
what_we_know_anyway: ""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"error\": \"Hindsight client unavailable: No module named 'hindsight'\"}"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# hindsight — 1000-memory scale probe
|
||||
started: 2026-05-12 18:06:17 UTC
|
||||
tools: ['hindsight_retain', 'hindsight_recall', 'hindsight_reflect']
|
||||
writing 1000 facts via hindsight_retain...
|
||||
writes: 1000/1000 ok (failures=0); p50=0.27ms p99=0.31ms
|
||||
running 20 queries via hindsight_recall...
|
||||
recalls: 20/20 ok; p50=0.28ms p99=0.30ms; precision@K=0/20
|
||||
duplicate-canonicalization: avg results per Q-dup-* query = 0.0 → unknown
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: holographic
|
||||
version_under_test: 0.1.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: embedded
|
||||
requires_account: "false"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 1000
|
||||
corpus_size_written: 1000
|
||||
write_failures: 0
|
||||
write_p50_ms: 23.43
|
||||
write_p99_ms: 68.52
|
||||
queries_attempted: 20
|
||||
queries_completed: 20
|
||||
recall_p50_ms: 0.06
|
||||
recall_p99_ms: 0.23
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 20
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0.0
|
||||
contradiction_surfaced: "false"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: /root/bench-harness/findings_scale/holographic/transcript.md
|
||||
raw_responses_dir: /root/bench-harness/findings_scale/holographic/raw
|
||||
|
||||
couldnt_verify:
|
||||
reason: ""
|
||||
what_we_know_anyway: ""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"results\": [], \"count\": 0}"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# holographic — 1000-memory scale probe
|
||||
started: 2026-05-12 18:05:49 UTC
|
||||
tools: ['fact_store', 'fact_feedback']
|
||||
writing 1000 facts via fact_store...
|
||||
writes: 1000/1000 ok (failures=0); p50=23.43ms p99=68.52ms
|
||||
running 20 queries via fact_store...
|
||||
recalls: 20/20 ok; p50=0.06ms p99=0.23ms; precision@K=0/20
|
||||
duplicate-canonicalization: avg results per Q-dup-* query = 0.0 → unknown
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: honcho
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: self-hosted
|
||||
requires_account: "false"
|
||||
requires_separate_server: "true"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: "Honcho AI-native memory — cross-session user modeling with dialectic Q&A, semantic search, and persistent conclusions."
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: mem0
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: cloud
|
||||
requires_account: "true"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: "Mem0 — server-side LLM fact extraction with semantic search, reranking, and automatic deduplication."
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: openviking
|
||||
version_under_test: 2.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: self-hosted
|
||||
requires_account: "false"
|
||||
requires_separate_server: "true"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: "OpenViking context database — session-managed memory with automatic extraction, tiered retrieval, and filesystem-style knowledge browsing."
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: retaindb
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: cloud
|
||||
requires_account: "true"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: RetainDB — cloud memory API with hybrid search and 7 memory types.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: supermemory
|
||||
version_under_test: 1.0.0
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: cloud
|
||||
requires_account: "true"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 0
|
||||
corpus_size_written: 0
|
||||
write_failures: 0
|
||||
write_p50_ms: 0.0
|
||||
write_p99_ms: 0.0
|
||||
queries_attempted: 0
|
||||
queries_completed: 0
|
||||
recall_p50_ms: 0.0
|
||||
recall_p99_ms: 0.0
|
||||
|
||||
precision_at_k:
|
||||
hits: 0
|
||||
total_queries: 0
|
||||
value: 0.0
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: false
|
||||
why_retrieved_field_name: ""
|
||||
per_result_score: false
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "unknown"
|
||||
duplicate_count_observed: 0
|
||||
contradiction_surfaced: "unknown"
|
||||
contradiction_api: ""
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: ""
|
||||
raw_responses_dir: ""
|
||||
|
||||
couldnt_verify:
|
||||
reason: is_available() returned False on this LXC (config not set up).
|
||||
what_we_know_anyway: "Supermemory semantic long-term memory with profile recall, semantic search, explicit memory tools, and session ingest."
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
provider: yantrikdb
|
||||
version_under_test: 0.4.2
|
||||
verified_at: 2026-05-12
|
||||
verified_against: LXC 129 yantrik-memory-test / Hermes commit 4610551
|
||||
|
||||
backend:
|
||||
hosting: embedded
|
||||
requires_account: "false"
|
||||
requires_separate_server: "false"
|
||||
|
||||
scale:
|
||||
corpus_size_attempted: 1000
|
||||
corpus_size_written: 256
|
||||
write_failures: 100
|
||||
write_p50_ms: 0.48
|
||||
write_p99_ms: 5.13
|
||||
queries_attempted: 20
|
||||
queries_completed: 20
|
||||
recall_p50_ms: 3.78
|
||||
recall_p99_ms: 32.94
|
||||
|
||||
precision_at_k:
|
||||
hits: 16
|
||||
total_queries: 20
|
||||
value: 0.8
|
||||
|
||||
response_shape:
|
||||
why_retrieved_field: true
|
||||
why_retrieved_field_name: why_retrieved
|
||||
per_result_score: true
|
||||
per_result_metadata: false
|
||||
|
||||
maintenance:
|
||||
duplicate_canonicalized: "false"
|
||||
duplicate_count_observed: 5.0
|
||||
contradiction_surfaced: "true"
|
||||
contradiction_api: yantrikdb_conflicts
|
||||
|
||||
skills:
|
||||
skill_tools_in_schema: false
|
||||
skill_tool_names: []
|
||||
|
||||
evidence:
|
||||
transcript_file: /root/bench-harness/findings_scale/yantrikdb/transcript.md
|
||||
raw_responses_dir: /root/bench-harness/findings_scale/yantrikdb/raw
|
||||
|
||||
couldnt_verify:
|
||||
reason: write phase exceeded 600.0s; aborted at fact 357
|
||||
what_we_know_anyway: ""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9b0-7fe2-a2a6-c3cbfd1055ef\", \"text\": \"The user prefers light mode as their color scheme in Warp.\", \"score\": 1.2584064906074155, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1127234, \"why_retrieved\": [\"semantically similar (0.76)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9e1-74d8-920d-c2d1b0d4e8f1\", \"text\": \"The user prefers Fork as their git GUI in Warp.\", \"score\": 1.2241377701347613, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1610582, \"why_retrieved\": [\"semantically similar (0.69)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9f0-7fbf-a429-ed170490fdfe\", \"text\": \"The user prefers never as their auto-format in Warp.\", \"score\": 1.253660170237387, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1764405, \"why_retrieved\": [\"semantically similar (0.75)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f997-7957-976a-f9843fbce1ce\", \"text\": \"The user prefers 12pt as their font size in Warp.\", \"score\": 1.2203420150899689, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0879989, \"why_retrieved\": [\"semantically similar (0.64)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f985-7bad-878f-147bc9205809\", \"text\": \"The user prefers 2 spaces as their tab width in Warp.\", \"score\": 1.219342015089969, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0699337, \"why_retrieved\": [\"semantically similar (0.68)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9d3-73ff-a894-77e9d6148711\", \"text\": \"The user prefers on save as their auto-format in iTerm2.\", \"score\": 1.2622570175027577, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1477413, \"why_retrieved\": [\"semantically similar (0.77)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9aa-75da-9fee-f51ce3ecc29b\", \"text\": \"The user prefers tokyo night as their color scheme in iTerm2.\", \"score\": 1.243419941213546, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.106122, \"why_retrieved\": [\"semantically similar (0.71)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f998-7d45-aeea-0b7509ace485\", \"text\": \"The user prefers CRLF as their line endings in iTerm2.\", \"score\": 1.1841598850121147, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.088971, \"why_retrieved\": [\"semantically similar (0.60)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f98b-7962-ac4d-d1b2417d9717\", \"text\": \"The user prefers 4 spaces as their tab width in iTerm2.\", \"score\": 1.243419941213546, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.075824, \"why_retrieved\": [\"semantically similar (0.70)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f982-75e2-b3bb-9cedf2ecc667\", \"text\": \"The user prefers 13pt as their font size in iTerm2.\", \"score\": 1.244122557595044, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0660052, \"why_retrieved\": [\"semantically similar (0.73)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f974-7f8e-b194-cc0cd3c66291\", \"text\": \"The user prefers never as their auto-format in fish shell.\", \"score\": 1.2981621490994755, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.052032, \"why_retrieved\": [\"semantically similar (0.83)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f99c-7ee1-9ee5-22af45dce5a7\", \"text\": \"The user prefers JetBrains Mono as their font in fish shell.\", \"score\": 1.2505964287259725, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0929987, \"why_retrieved\": [\"semantically similar (0.68)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f9de-74a9-a40e-2d0fa3cdea33\", \"text\": \"The user prefers 4 spaces as their tab width in fish shell.\", \"score\": 1.2505964287259725, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1585286, \"why_retrieved\": [\"semantically similar (0.69)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f9ab-7eec-b19b-5c9972aae26b\", \"text\": \"The user prefers Hack as their font in fish shell.\", \"score\": 1.2657324241138128, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1075046, \"why_retrieved\": [\"semantically similar (0.78)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f98d-7270-a3af-4678b3e0894f\", \"text\": \"The user prefers 14pt as their font size in fish shell.\", \"score\": 1.2505964287259725, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.077137, \"why_retrieved\": [\"semantically similar (0.74)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9b0-7fe2-a2a6-c3cbfd1055ef\", \"text\": \"The user prefers light mode as their color scheme in Warp.\", \"score\": 1.2585296432927253, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1127234, \"why_retrieved\": [\"semantically similar (0.76)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9e1-74d8-920d-c2d1b0d4e8f1\", \"text\": \"The user prefers Fork as their git GUI in Warp.\", \"score\": 1.2242607853889969, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1610582, \"why_retrieved\": [\"semantically similar (0.69)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9f0-7fbf-a429-ed170490fdfe\", \"text\": \"The user prefers never as their auto-format in Warp.\", \"score\": 1.2537832984158972, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1764405, \"why_retrieved\": [\"semantically similar (0.75)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f997-7957-976a-f9843fbce1ce\", \"text\": \"The user prefers 12pt as their font size in Warp.\", \"score\": 1.2204650204382248, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0879989, \"why_retrieved\": [\"semantically similar (0.64)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f985-7bad-878f-147bc9205809\", \"text\": \"The user prefers 2 spaces as their tab width in Warp.\", \"score\": 1.219465020438225, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0699337, \"why_retrieved\": [\"semantically similar (0.68)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f99f-742f-99fc-29eb3cdc8796\", \"text\": \"The user prefers never as their auto-format in kitty.\", \"score\": 1.2706056135441164, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0952797, \"why_retrieved\": [\"semantically similar (0.79)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9d2-715b-b855-64cc60372fbf\", \"text\": \"The user prefers nord as their color scheme in kitty.\", \"score\": 1.2644820359047568, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.14604, \"why_retrieved\": [\"semantically similar (0.78)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9e1-74d8-920d-c2d4b3380a18\", \"text\": \"The user prefers tig as their git GUI in kitty.\", \"score\": 1.2156274885809477, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.161539, \"why_retrieved\": [\"semantically similar (0.67)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9ae-77cd-8634-8bd78adb2a13\", \"text\": \"The user prefers LF as their line endings in kitty.\", \"score\": 1.2445076967054645, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.110443, \"why_retrieved\": [\"semantically similar (0.69)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f9b2-76bb-8789-2aa21d8c314d\", \"text\": \"The user prefers 2 spaces as their tab width in kitty.\", \"score\": 1.2445076967054645, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.114996, \"why_retrieved\": [\"semantically similar (0.72)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9b2-76bb-8789-2a9f73b1e849\", \"text\": \"The user prefers nord as their color scheme in Hyper.\", \"score\": 1.259531917008004, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1144958, \"why_retrieved\": [\"semantically similar (0.77)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f982-75e2-b3bb-9cedf2ecc667\", \"text\": \"The user prefers 13pt as their font size in iTerm2.\", \"score\": 1.1303281084116132, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0660052, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f995-7103-9c26-20ef0b2e8130\", \"text\": \"The user prefers light mode as their color scheme in Terminal.app.\", \"score\": 1.1804970116209634, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0857205, \"why_retrieved\": [\"semantically similar (0.51)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f979-7cdc-8c5c-46e657dd6c62\", \"text\": \"The user prefers Fira Code as their font in Hyper.\", \"score\": 1.1804970116209634, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0577452, \"why_retrieved\": [\"semantically similar (0.50)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f980-7523-b3de-7d9e80653332\", \"text\": \"The user prefers nord as their color scheme in Emacs.\", \"score\": 1.224160291165821, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0645776, \"why_retrieved\": [\"semantically similar (0.69)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f98a-731e-95cd-c4a467f9744c\", \"text\": \"The user prefers 16pt as their font size in Vim.\", \"score\": 1.210869916475301, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0749261, \"why_retrieved\": [\"semantically similar (0.66)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f98e-771b-af1f-50c2d31a5852\", \"text\": \"The user prefers manually only as their auto-format in PyCharm.\", \"score\": 1.093098706467881, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0782218, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f975-71a2-8452-7065c2509f66\", \"text\": \"The user prefers 16pt as their font size in alacritty.\", \"score\": 1.209270411016291, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0530856, \"why_retrieved\": [\"semantically similar (0.65)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f99c-7ee1-9ee5-22ac0ca357ff\", \"text\": \"The user prefers 4 spaces as their tab width in Hyper.\", \"score\": 1.0966714624543565, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0925598, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f997-7957-976a-f97e3cda0c41\", \"text\": \"The user prefers 14pt as their font size in Cursor.\", \"score\": 1.1793240774267548, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0871115, \"why_retrieved\": [\"semantically similar (0.59)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9cd-72de-ba91-08e5c6c93f98\", \"text\": \"The user prefers manually only as their auto-format in Hyper.\", \"score\": 1.2816776821254907, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1412783, \"why_retrieved\": [\"semantically similar (0.81)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f970-7017-8f91-f8d7f357fe1f\", \"text\": \"The user prefers Source Code Pro as their font in iTerm2.\", \"score\": 1.1911321897586706, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.048805, \"why_retrieved\": [\"semantically similar (0.61)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9a9-7922-a965-0e4cd649e57f\", \"text\": \"The user prefers manually only as their auto-format in Terminal.app.\", \"score\": 1.2490749353533321, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1050863, \"why_retrieved\": [\"semantically similar (0.73)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f9ac-709d-bf08-0ead8a5a6833\", \"text\": \"The user prefers manually only as their auto-format in Cursor.\", \"score\": 1.2490749353533321, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1084075, \"why_retrieved\": [\"semantically similar (0.74)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f98e-771b-af1f-50c2d31a5852\", \"text\": \"The user prefers manually only as their auto-format in PyCharm.\", \"score\": 1.2184075180271425, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0782218, \"why_retrieved\": [\"semantically similar (0.67)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-fa19-7ccc-8f29-09bf0b03de15\", \"text\": \"Project compass targets 1,000 monthly active users.\", \"score\": 1.2563764555183992, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2178957, \"why_retrieved\": [\"semantically similar (0.76)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f970-7017-8f91-f8d7f357fe1f\", \"text\": \"The user prefers Source Code Pro as their font in iTerm2.\", \"score\": 1.058042614124962, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.048805, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa17-77f9-975b-8408a2fb7af7\", \"text\": \"Project kestrel targets 100,000 monthly active users.\", \"score\": 1.156791388573775, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2154737, \"why_retrieved\": [\"semantically similar (0.55)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f99c-7ee1-9ee5-22ac0ca357ff\", \"text\": \"The user prefers 4 spaces as their tab width in Hyper.\", \"score\": 1.0202333037854556, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0925598, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa18-7486-bddc-1cda76e163a8\", \"text\": \"Project iris targets 1,000,000 monthly active users.\", \"score\": 1.1237589077354186, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2169213, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9fb-785b-8f80-42e5989cd1bc\", \"text\": \"Project echo targets 1,000 monthly active users.\", \"score\": 1.1345888546298868, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1870995, \"why_retrieved\": [\"semantically similar (0.51)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9a0-7956-9131-5b283e0546f2\", \"text\": \"The user prefers command line only as their git GUI in Zed.\", \"score\": 1.106713624521368, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.096163, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa08-70e6-8bc3-92ee23c7fd71\", \"text\": \"Project atlas launches in September 2026.\", \"score\": 1.112589662748087, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2006934, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f981-753a-8743-0cab74f46569\", \"text\": \"The user prefers manually only as their auto-format in fish shell.\", \"score\": 1.107713624521368, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.065572, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f995-7103-9c26-20ef0b2e8130\", \"text\": \"The user prefers light mode as their color scheme in Terminal.app.\", \"score\": 1.0661554320609734, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0857205, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9fc-79c6-8dec-79b88870fcdb\", \"text\": \"Project ridge launches in March 2026.\", \"score\": 1.1373249264398595, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.188145, \"why_retrieved\": [\"semantically similar (0.51)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa01-752f-a603-c2d8413a53e6\", \"text\": \"Project ridge supports 42 locales.\", \"score\": 1.1236327724319228, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.193592, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa0c-7fc3-913a-a68d1b014d91\", \"text\": \"Project nexus uses Python for data pipelines.\", \"score\": 0.9290378515682368, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2047148, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-fa04-7ee7-a48f-7a32bdcd15ef\", \"text\": \"Project summit uses Kubernetes for orchestration.\", \"score\": 0.9194284898323382, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1964068, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-fa07-7b8e-a419-2b998a19f282\", \"text\": \"Project ridge launches in September 2026.\", \"score\": 1.1042259697560466, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1999235, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9e9-7a8e-b72d-409d118d83f4\", \"text\": \"The user prefers tabs as their tab width in PyCharm.\", \"score\": 0.8396773983042854, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1699755, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f975-71a2-8452-7065c2509f66\", \"text\": \"The user prefers 16pt as their font size in alacritty.\", \"score\": 0.8337588435009218, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0530856, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-fa19-7ccc-8f29-09bf0b03de15\", \"text\": \"Project compass targets 1,000 monthly active users.\", \"score\": 0.6530206644530013, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2178957, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9fc-79c6-8dec-79bb772c7fec\", \"text\": \"Project tundra uses Python for data pipelines.\", \"score\": 0.6082310362473293, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1887586, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9eb-706f-b9ad-3daf02ee313f\", \"text\": \"The user prefers lazygit as their git GUI in iTerm2.\", \"score\": 0.656019508110759, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1710012, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9fd-7939-8b53-9531129a5490\", \"text\": \"Project monolith uses FastAPI for the API layer.\", \"score\": 0.7181547714717466, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1892211, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f998-7d45-aeea-0b7509ace485\", \"text\": \"The user prefers CRLF as their line endings in iTerm2.\", \"score\": 0.6911664327658864, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.088971, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9e4-7be4-8241-0f1a244ac4f7\", \"text\": \"The user prefers on save as their auto-format in IntelliJ.\", \"score\": 0.7145593209183766, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1644547, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-fa04-7ee7-a48f-7a32bdcd15ef\", \"text\": \"Project summit uses Kubernetes for orchestration.\", \"score\": 0.6197354595811072, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1964068, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f981-753a-8743-0cab74f46569\", \"text\": \"The user prefers manually only as their auto-format in fish shell.\", \"score\": 0.6991923958686753, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.065572, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9f1-7b13-a178-340c0aa33e04\", \"text\": \"The user prefers manually only as their auto-format in Vim.\", \"score\": 1.3595475212347627, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.177413, \"why_retrieved\": [\"semantically similar (0.91)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f98a-731e-95cd-c4a467f9744c\", \"text\": \"The user prefers 16pt as their font size in Vim.\", \"score\": 1.2172476151923588, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0749261, \"why_retrieved\": [\"semantically similar (0.67)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f997-7957-976a-f981cd780270\", \"text\": \"The user prefers never as their auto-format in Vim.\", \"score\": 1.336797161099122, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0875282, \"why_retrieved\": [\"semantically similar (0.88)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f98b-7962-ac4d-d1b2417d9717\", \"text\": \"The user prefers 4 spaces as their tab width in iTerm2.\", \"score\": 1.1915833723810336, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.075824, \"why_retrieved\": [\"semantically similar (0.62)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9f4-7c46-8433-634a37140399\", \"text\": \"The user prefers never as their auto-format in VS Code.\", \"score\": 1.2815569941296587, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1800125, \"why_retrieved\": [\"semantically similar (0.74)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9f7-7a30-9d74-e8b1e9498d72\", \"text\": \"The user prefers tabs as their tab width in VS Code.\", \"score\": 1.368517472271879, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1833193, \"why_retrieved\": [\"semantically similar (0.93)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9ef-7b28-b266-2132530b49ea\", \"text\": \"The user prefers on save as their auto-format in VS Code.\", \"score\": 1.2637292333477141, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1758034, \"why_retrieved\": [\"semantically similar (0.78)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9e7-73cd-b7bb-66847a782930\", \"text\": \"The user prefers 4 spaces as their tab width in VS Code.\", \"score\": 1.3652013483828191, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.167537, \"why_retrieved\": [\"semantically similar (0.92)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f982-75e2-b3bb-9cedf2ecc667\", \"text\": \"The user prefers 13pt as their font size in iTerm2.\", \"score\": 1.1761151469355742, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0660052, \"why_retrieved\": [\"semantically similar (0.58)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f993-7050-a970-b50cd65f12f8\", \"text\": \"The user prefers Source Code Pro as their font in Cursor.\", \"score\": 1.2028916142957813, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.083619, \"why_retrieved\": [\"semantically similar (0.64)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9fb-785b-8f80-42e5989cd1bc\", \"text\": \"Project echo targets 1,000 monthly active users.\", \"score\": 1.1861385366715937, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1870995, \"why_retrieved\": [\"semantically similar (0.60)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-fa0d-73bf-9c53-60e2f48d6459\", \"text\": \"Project echo uses Tailwind for styling.\", \"score\": 1.1249080153322506, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.2056744, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f981-753a-8743-0cab74f46569\", \"text\": \"The user prefers manually only as their auto-format in fish shell.\", \"score\": 1.0245874443298921, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.065572, \"why_retrieved\": [\"semantically similar (0.56)\", \"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9fd-7939-8b53-9531129a5490\", \"text\": \"Project monolith uses FastAPI for the API layer.\", \"score\": 0.9480048077942211, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1892211, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9d2-715b-b855-64cf3a7b6c24\", \"text\": \"The user prefers JetBrains Mono as their font in bash.\", \"score\": 0.8916576863160792, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1466038, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f987-7ab5-9e08-7691d2b47e43\", \"text\": \"The user prefers 2 spaces as their tab width in iTerm2.\", \"score\": 1.3594940405166585, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0713844, \"why_retrieved\": [\"semantically similar (0.91)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9ac-709d-bf08-0eb095d59f88\", \"text\": \"The user prefers Source Code Pro as their font in iTerm2.\", \"score\": 1.2080261511830268, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.108843, \"why_retrieved\": [\"semantically similar (0.65)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9f7-7a30-9d74-e8b1e9498d72\", \"text\": \"The user prefers tabs as their tab width in VS Code.\", \"score\": 1.2960921440471826, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1833193, \"why_retrieved\": [\"semantically similar (0.82)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\", \"keyword_reserved\"]}, {\"rid\": \"019e1d5e-f98e-771b-af1f-50c2d31a5852\", \"text\": \"The user prefers manually only as their auto-format in PyCharm.\", \"score\": 1.1658861520829984, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0782218, \"why_retrieved\": [\"semantically similar (0.56)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9d3-73ff-a894-77e9d6148711\", \"text\": \"The user prefers on save as their auto-format in iTerm2.\", \"score\": 1.2490400855581185, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1477413, \"why_retrieved\": [\"semantically similar (0.74)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f996-70d9-bad6-52fe626cd449\", \"text\": \"The user prefers command line only as their git GUI in PyCharm.\", \"score\": 0.8254855829589701, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0866811, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9ca-7258-8536-3bb702e1b607\", \"text\": \"The user prefers solarized dark as their color scheme in IntelliJ.\", \"score\": 0.6839583569247246, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.13868, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9b4-715e-b36a-867d6a1a03c3\", \"text\": \"The user prefers tabs as their tab width in Zed.\", \"score\": 0.6829560538998092, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1167953, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9fc-79c6-8dec-79bb772c7fec\", \"text\": \"Project tundra uses Python for data pipelines.\", \"score\": 0.6464809995959645, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1887586, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f977-790e-891c-6173737ee63c\", \"text\": \"The user prefers Hack as their font in Cursor.\", \"score\": 0.6714880705829673, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.055366, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9eb-706f-b9ad-3daf02ee313f\", \"text\": \"The user prefers lazygit as their git GUI in iTerm2.\", \"score\": 0.7191800797216334, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1710012, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f982-75e2-b3bb-9cedf2ecc667\", \"text\": \"The user prefers 13pt as their font size in iTerm2.\", \"score\": 0.7099726622270709, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0660052, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f998-7d45-aeea-0b7509ace485\", \"text\": \"The user prefers CRLF as their line endings in iTerm2.\", \"score\": 0.6155082271659432, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.088971, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9d3-73ff-a894-77e9d6148711\", \"text\": \"The user prefers on save as their auto-format in iTerm2.\", \"score\": 0.7191069423963772, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1477413, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}, {\"rid\": \"019e1d5e-f9f5-7276-81be-a2c32f98418d\", \"text\": \"The user prefers gruvbox as their color scheme in PyCharm.\", \"score\": 0.6044459466722939, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1819878, \"why_retrieved\": [\"recent\", \"important (decay=0.60)\"]}]}"
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{\"count\": 5, \"results\": [{\"rid\": \"019e1d5e-f9ca-7258-8536-3bb464894142\", \"text\": \"The user prefers Fork as their git GUI in VS Code.\", \"score\": 1.345605459394392, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1380942, \"why_retrieved\": [\"semantically similar (0.90)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9e7-73cd-b7bb-66847a782930\", \"text\": \"The user prefers 4 spaces as their tab width in VS Code.\", \"score\": 1.197073740847096, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.167537, \"why_retrieved\": [\"semantically similar (0.63)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f9a9-7922-a965-0e4cd649e57f\", \"text\": \"The user prefers manually only as their auto-format in Terminal.app.\", \"score\": 1.1859196683339654, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.1050863, \"why_retrieved\": [\"semantically similar (0.60)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f970-7017-8f91-f8d7f357fe1f\", \"text\": \"The user prefers Source Code Pro as their font in iTerm2.\", \"score\": 1.1944179686305543, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.048805, \"why_retrieved\": [\"semantically similar (0.62)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}, {\"rid\": \"019e1d5e-f996-70d9-bad6-52fe626cd449\", \"text\": \"The user prefers command line only as their git GUI in PyCharm.\", \"score\": 1.2547647987864707, \"importance\": 0.6, \"domain\": \"general\", \"created_at\": 1778609224.0866811, \"why_retrieved\": [\"semantically similar (0.75)\", \"recent\", \"important (decay=0.60)\", \"keyword_match\"]}]}"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# yantrikdb — 1000-memory scale probe
|
||||
started: 2026-05-12 18:07:03 UTC
|
||||
tools: ['yantrikdb_remember', 'yantrikdb_recall', 'yantrikdb_forget', 'yantrikdb_think', 'yantrikdb_conflicts', 'yantrikdb_resolve_conflict', 'yantrikdb_relate', 'yantrikdb_stats']
|
||||
writing 1000 facts via yantrikdb_remember...
|
||||
write retries exhausted for fact #257 after 60 attempts
|
||||
write retries exhausted for fact #258 after 60 attempts
|
||||
write retries exhausted for fact #259 after 60 attempts
|
||||
WRITE TIMEOUT at fact #357 after 600.0s
|
||||
backpressure retries: 6000
|
||||
writes: 256/1000 ok (failures=100); p50=0.48ms p99=5.13ms
|
||||
running 20 queries via yantrikdb_recall...
|
||||
recalls: 20/20 ok; p50=3.78ms p99=32.94ms; precision@K=16/20
|
||||
shape (first non-empty result): why_retrieved=True('why_retrieved') score=True metadata=False
|
||||
duplicate-canonicalization: avg results per Q-dup-* query = 5.0 → false
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue