# hermes-lean-ctx **lean-ctx as Hermes' active context engine.** This plugin replaces Hermes Agent's built-in `ContextCompressor` with deterministic, prompt-cache-friendly compaction and injects lean-ctx's code-intelligence + cross-session memory tools natively into the agent's tool list. Instead of being "just another MCP server the agent might call", lean-ctx *owns* the context window: it decides what to keep verbatim, offloads older turns into durable memory, and gives the agent first-class recall tools to page that memory back in losslessly. ## Why lean-ctx over the alternatives | | built-in `ContextCompressor` | hermes-lcm | **hermes-lean-ctx** | |---|---|---|---| | Strategy | summarize + drop | DAG + SQLite + FTS | BM25 + graph + knowledge + semantic + LITM placement | | Recall after compaction | lossy | lossless (grep/expand) | lossless (`ctx_search`/`ctx_semantic_search`/`ctx_expand`/`ctx_read`/`ctx_knowledge`) | | Cross-session memory | no | per-project | yes (sessions, knowledge, handoff ledgers) | | Determinism / prompt-cache | n/a | partial | deterministic, byte-stable output (prompt-cache friendly) | | Engine location | in-agent | in-plugin | in the lean-ctx daemon (Single Source of Truth) | Compaction logic lives in the daemon's `ctx_transcript_compact` tool, so every client (this plugin, the CLI, other editors) gets identical, tested behaviour. ## How it works ``` Hermes agent loop └─ ContextEngine ABC ── LeanCtxEngine (this plugin, thin adapter) └─ leanctx SDK ── HTTP /v1 ── lean-ctx daemon └─ ctx_transcript_compact, ctx_search, ctx_knowledge, … ``` - **`compress(messages)`** keeps the system preamble and a *fresh tail* verbatim, and replaces older turns with a compact, recoverable summary. It calls the daemon's `ctx_transcript_compact` (which also offloads the raw turns into session memory for recall). If the daemon is unreachable it falls back to a pure-Python compaction so the agent loop never breaks. - **`tool_call`/`tool_result` pairs are never split** across the compaction boundary — this invariant is enforced and tested on both the daemon and plugin side. - **Native tools** (`get_tool_schemas`/`handle_tool_call`) expose `ctx_search`, `ctx_semantic_search`, `ctx_read`, `ctx_expand`, `ctx_knowledge`, `ctx_summary` so the agent can page detail back in on demand. - **Cross-session persistence** via session lifecycle hooks: `resume` on start, `ctx_summary` + a deterministic `ctx_handoff` ledger on end. ## Requirements - A running **lean-ctx daemon** exposing the HTTP `/v1` tools API (`lean-ctx serve`). See [Setup](#setup). - The **`leanctx` Python SDK** in Hermes' environment: `pip install lean-ctx-client`. - Optional: `tiktoken` for exact token counts (a char-based estimate is used otherwise). - A Hermes Agent build that supports context-engine plugins (`context.engine` + `register_context_engine`). ## Setup ```bash # 1. Install the plugin (symlinks this checkout into ~/.hermes/plugins). ./scripts/install.sh # 2. Start the lean-ctx HTTP tools API (serves /v1; default port 8080). # NOTE: the always-on proxy (port 4444+) does NOT serve /v1/tools — use serve. lean-ctx serve --host 127.0.0.1 --port 8080 # 3. Install the SDK in Hermes' Python. pip install lean-ctx-client # 4. Activate the engine in ~/.hermes/config.yaml: # context: # engine: "lean-ctx" ``` If your server is not on the default, point the plugin at it: ```bash export LEANCTX_BASE_URL=http://127.0.0.1:8080 export LEANCTX_TOKEN= # only if you ran serve with --auth-token ``` Only one context engine can be active, so lean-ctx and hermes-lcm cannot be enabled at the same time. ## Configuration (`LEANCTX_*` env vars) | Variable | Default | Meaning | |---|---|---| | `LEANCTX_BASE_URL` | `http://127.0.0.1:8080` | lean-ctx `/v1` base URL | | `LEANCTX_HTTP_PORT` | `8080` | Port used when `LEANCTX_BASE_URL` is unset | | `LEANCTX_TOKEN` | – | Bearer token (if `serve --auth-token`) | | `LEANCTX_TIMEOUT` | `30.0` | HTTP timeout (seconds) | | `LEANCTX_CONTEXT_LENGTH` | `200000` | Context window used until the host calls `update_model` | | `LEANCTX_THRESHOLD_FRACTION` | `0.75` | Fraction of the window at which compaction fires | | `LEANCTX_PROTECT_FRACTION` | `0.25` | Recent fraction kept verbatim (the fresh tail) | | `LEANCTX_PROTECT_MIN_MESSAGES` | `6` | Minimum recent messages kept verbatim | | `LEANCTX_PROTECT_MIN_TOKENS` | `2000` | Minimum tail token budget | | `LEANCTX_ENABLE_TOOLS` | `1` | Inject native recall tools into the agent | | `LEANCTX_CORE_COMPACTION` | `1` | Prefer the daemon's `ctx_transcript_compact` (fallback: local) | | `LEANCTX_WORKSPACE_ID` / `LEANCTX_CHANNEL_ID` | – | Optional routing for multi-workspace daemons | Model context windows are inferred from the model name when the host does not provide one (see `presets.py`); Hermes' explicit `update_model(context_length=…)` always wins. ## Testing ```bash # Hermetic unit tests (no daemon required): python -m pytest # Live integration against a real daemon: lean-ctx serve --host 127.0.0.1 --port 8080 --auth-token test-token & LEANCTX_LIVE_URL=http://127.0.0.1:8080 LEANCTX_LIVE_TOKEN=test-token \ python -m pytest tests/test_live_daemon.py -v ``` No mock data is used: unit tests exercise the real compaction logic and a recording gateway; live tests hit a real daemon. ## Benchmarks `benchmarks/` contains a real, runnable head-to-head harness (token savings, `compress()` latency, recoverable-recall). See [benchmarks/README.md](benchmarks/README.md). ## License Apache-2.0 — see the lean-ctx repository.