Files
yvgude--lean-ctx/docs/reference/20-hermes-context-engine.md
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

204 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Journey 20 — Hermes Context Engine
> You're embedding lean-ctx *inside* an agent framework rather than calling it as
> an MCP server. This journey covers making lean-ctx the **active context engine**
> for [Hermes Agent](https://github.com/hermes-agent): it replaces Hermes' built-in
> `ContextCompressor`, owns the context window, and gives the agent first-class
> recall tools to page durable memory back in losslessly.
Source files referenced here:
- `rust/src/tools/ctx_transcript_compact.rs``compact_messages`, `render_result`,
`serialize_transcript` (the deterministic compaction core)
- `rust/src/tools/registered/ctx_transcript_compact.rs` — the MCP/`/v1` tool wrapper
- `integrations/hermes-lean-ctx/engine.py``LeanCtxEngine` (the `ContextEngine` adapter)
- `integrations/hermes-lean-ctx/compaction.py` — the local Python fallback
- `integrations/hermes-lean-ctx/tools.py`, `schemas.py` — native recall tools
- `integrations/hermes-lean-ctx/transport.py`, `config.py`, `presets.py``/v1`
client, `LEANCTX_*` config, model-window presets
- `integrations/hermes-lean-ctx/plugin.yaml` — the Hermes plugin manifest
---
## 0. The mental model — engine vs. MCP server
Every other journey treats lean-ctx as a tool an agent *calls*. Here it is the
other way around: lean-ctx becomes the component the agent loop *delegates its
context window to*.
- As an **MCP server**, lean-ctx answers `ctx_*` tool calls the model decides to
make. It never sees the full conversation.
- As a **context engine**, the host (Hermes) hands lean-ctx the entire message
array on every turn and asks it to compact it. lean-ctx decides what stays
verbatim, what becomes a recoverable summary, and what gets offloaded into
durable session memory.
Only one context engine can be active at a time, so lean-ctx and Hermes'
built-in `ContextCompressor` (or `hermes-lcm`) are mutually exclusive.
---
## 1. The compaction core — `ctx_transcript_compact`
**What it does:** Compacts an OpenAI-format message array deterministically. It
keeps the system preamble and a *fresh tail* verbatim, replaces older turns with
a recoverable summary, and offloads the raw turns into lean-ctx session memory so
the recall tools (and the autonomy consolidation pipeline) can page them back in.
It is the 77th MCP tool and is exposed on both the MCP surface and the HTTP `/v1`
tools API, so every client — this plugin, the CLI, other editors — gets the same
tested behaviour.
```text
ctx_transcript_compact messages=<OpenAI message array>
fresh_tail_tokens=4000 # recent tokens kept verbatim
protect_min_messages=6 # min recent messages kept verbatim
focus_topic="auth refactor" # optional: bias the summary
```
| Parameter | Default | Meaning |
|---|---|---|
| `messages` (required) | | OpenAI-format message array to compact |
| `fresh_tail_tokens` | `4000` | Recent tokens kept verbatim (the fresh tail) |
| `protect_min_messages` | `6` | Minimum recent messages kept verbatim |
| `focus_topic` | | Optional topic to prioritise in the summary |
**Returns** JSON `{messages, stats}`: the compacted array plus deterministic
stats (`original_tokens`, `compacted_tokens`, `did_compact`, …).
**Two invariants, enforced and tested:**
1. **A `tool_call` and its `tool_result` are never split** across the compaction
boundary. Truncating between them would leave the model with a dangling call.
2. **Output is byte-stable** for the same input — no timestamps, counters or
randomness — so it preserves the provider's prompt-cache prefix (#498).
**Under the hood:** `compact_messages()` (`rust/src/tools/ctx_transcript_compact.rs`)
splits the array into the protected head + tail and the summarizable middle,
renders the summary, and returns a `CompactResult`. The registered wrapper
(`registered/ctx_transcript_compact.rs`) then best-effort offloads the summarized
turns into the bound session via `ctx_session` (as a `finding`), capped at
`OFFLOAD_MAX_CHARS` (8 000). Offload is skipped when no session is bound (e.g. a
one-shot CLI call), so the tool is safe to call anywhere.
---
## 2. The plugin — `integrations/hermes-lean-ctx`
**What it does:** A thin Python `ContextEngine` (`LeanCtxEngine`, `engine.py`) that
Hermes loads via `register_context_engine`. It is an adapter, not a
re-implementation — the heavy lifting stays in the daemon.
```
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 + fresh tail verbatim and
replaces older turns with a recoverable summary. It calls the daemon's
`ctx_transcript_compact`; if the daemon is unreachable it falls back to a pure
Python compaction (`compaction.py`) so the agent loop never breaks.
- **Native recall tools** (`tools.py` / `schemas.py`) inject `ctx_search`,
`ctx_semantic_search`, `ctx_read`, `ctx_expand`, `ctx_knowledge` and
`ctx_summary` into the agent's tool list, so the model can page detail back in
on demand after a compaction.
- **Cross-session persistence** via session lifecycle hooks: `resume` on start,
`ctx_summary` + a deterministic `ctx_handoff` ledger on end.
- **Model-window presets** (`presets.py`) infer the context window from the model
name until the host calls `update_model(context_length=…)`, which always wins.
---
## 3. Setup
```bash
# 1. Install the plugin (symlinks this checkout into ~/.hermes/plugins).
cd integrations/hermes-lean-ctx && ./scripts/install.sh
# 2. Start the lean-ctx HTTP tools API (serves /v1; default port 8080).
# NOTE: the always-on proxy (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"
```
`lean-ctx init --agent hermes` prints this same engine-plugin hint, so the
onboarding path points here automatically.
If the 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=<token> # only if you ran serve with --auth-token
```
---
## 4. Configuration — `LEANCTX_*` env vars
Read by `config.py`; Hermes' explicit `update_model(context_length=…)` always
overrides the inferred window.
| 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` | 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 tool (fallback: local Python) |
| `LEANCTX_WORKSPACE_ID` / `LEANCTX_CHANNEL_ID` | | Optional routing for multi-workspace daemons |
---
## 5. 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 |
| Engine location | in-agent | in-plugin | in the lean-ctx daemon (single source of truth) |
---
## 6. Testing & benchmarks
```bash
# Hermetic unit tests (no daemon required):
cd integrations/hermes-lean-ctx && 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
```
`benchmarks/` (`run.py`) is a real, runnable head-to-head harness — token
savings, `compress()` latency and recoverable-recall against the import-guarded
`ContextCompressor` / `hermes-lcm`. No mock data: unit tests exercise the real
compaction logic and a recording gateway; live tests hit a real daemon.
---
## Where the neighbouring topics live
| Topic | Reference |
|---|---|
| The `/v1` HTTP+SSE contract and SDKs | [Team, Cloud & CI](09-team-cloud-ci.md) |
| `lean-ctx serve` over HTTP, multi-repo | [Advanced & Integrations](05-advanced.md) |
| Sessions, knowledge, handoff ledgers | [Memory & Knowledge](03-memory-and-knowledge.md) |
| Writing your own plugins / WASM | [Advanced & Integrations](05-advanced.md) |