188 lines
6.9 KiB
Python
188 lines
6.9 KiB
Python
#!/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)
|