Files
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

268 lines
11 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 14 — Performance Tuning
> lean-ctx is fast by default, but on a huge monorepo, a constrained CI runner,
> or a low-RAM laptop you may want to bound how much disk/RAM it uses or find
> what's slow. This journey covers the memory profile, the index/cache caps, and
> the slow-command log — with the exact knobs and their defaults.
Source files:
- `rust/src/core/config/mod.rs``memory_profile`, `bm25_max_cache_mb`, `graph_index_max_files`
- `rust/src/cli/config_cmd.rs``config show` (effective limits)
- `rust/src/core/bm25_index.rs`, `graph_index.rs` — index caps
- `rust/src/shell/exec.rs``slow_command_threshold_ms`
---
## 0. See your effective limits first
Before tuning, look at what's actually in effect. `lean-ctx config show` resolves
config + env + defaults into one view and tags the **source** of each value:
```text
╭─── Simplified (high-level) ───────────────────────────────╮
│ compression_level = Max ← config
│ max_disk_mb = 0 ← default
│ max_ram_percent = 5 ← default
│ max_staleness_days = 0 ← default
│ memory_profile = Performance ← default
╰────────────────────────────────────────────────────────────╯
╭─── Derived effective limits ────────────────────────────────╮
│ archive_max_disk_mb = 500 MB
│ bm25_max_cache_mb = 512 MB
│ archive_max_age_hours = 48 h
│ graph_index_max_files = 0
╰────────────────────────────────────────────────────────────╯
```
`← config` vs `← default` tells you whether a value is yours or the built-in.
`0` means "unbounded / use the derived default" (see each knob below).
---
## 1. The memory profile — one dial for the footprint
`memory_profile` sets the overall disk/RAM posture; the *derived* limits
(archive size, BM25 cache, staleness) follow from it unless you override them
individually.
```toml
memory_profile = "balanced" # low | balanced | performance
```
```bash
# or per-process, no config edit:
LEAN_CTX_MEMORY_PROFILE="low" lean-ctx serve --daemon
```
Reach for `low` on small CI runners or low-RAM machines; `performance`
trades disk for speed on a workstation. `balanced` is in between.
---
## 2. Bounding the search index (BM25)
The BM25 full-text index is the biggest disk consumer on large repos.
```toml
bm25_max_cache_mb = 512 # cap the BM25 cache (derived from profile if unset)
extra_ignore_patterns = ["vendor/**", "*.min.js"] # never index these
```
```bash
LEAN_CTX_BM25_MAX_CACHE_MB=256 lean-ctx index build
```
When the cap is hit, lean-ctx tells you exactly how to react (raise the cap or
add ignore patterns). If an index is oversized or corrupt, reclaim it with
`lean-ctx cache prune` — the next read rebuilds a clean one.
---
## 3. Bounding the code graph
```toml
graph_index_max_files = 0 # 0 = unlimited; set a cap on giant monorepos
```
On a very large tree, capping `graph_index_max_files` keeps graph builds fast and
bounded; when the limit is reached, lean-ctx prints
`[graph_index: reached configured limit of N files. Set graph_index_max_files = 0 for unlimited.]`
so the truncation is never silent.
To skip indexing entirely (e.g. an ephemeral CI job that only needs reads):
```bash
LEAN_CTX_NO_INDEX=1 lean-ctx <cmd> # or LEAN_CTX_DISABLE_SEARCH_INDEX=1
```
The resident `ctx_search` trigram index verifies freshness against the live
filesystem on **every** lookup via a cheap corpus signature, so an edit — even
through a tool lean-ctx never sees (native editors, `git checkout`) — is
reflected on the next search. On very large *indexed* trees you can coalesce that
per-lookup stat-walk under bursty search load, trading a bounded staleness window
for fewer walks:
```bash
LEAN_CTX_SEARCH_INDEX_COALESCE_MS=1000 lean-ctx <cmd> # default 0 = always verify
```
---
## 4. Disk / RAM / staleness budgets
These cross-cutting budgets apply across caches and indexes; `0` means "use the
profile-derived default":
| Knob | Env override | Meaning |
|------|--------------|---------|
| `max_disk_mb` | `LEAN_CTX_MAX_DISK_MB` | total on-disk budget across caches/indexes |
| `max_ram_percent` | `LEAN_CTX_MAX_RAM_PERCENT` | RAM ceiling as % of system memory (default 5) |
| `max_staleness_days` | `LEAN_CTX_MAX_STALENESS_DAYS` | auto-prune entries older than N days |
`config show` warns if `max_disk_mb` is set lower than
`archive.max_disk_mb + bm25_max_cache_mb`, so your sub-budgets can't quietly
exceed the global cap.
---
## 5. Finding what's slow — `slow-log`
lean-ctx records commands that exceed `slow_command_threshold_ms` (default
`5000`) so you can see where wall-clock time goes:
```toml
slow_command_threshold_ms = 5000
```
```bash
lean-ctx slow-log list # show recorded slow commands
lean-ctx slow-log clear # reset the log
```
Pair this with `lean-ctx gain --deep` (cost + heatmap) and `lean-ctx ghost`
(uncompressed-command waste) from [Journey 11](11-analytics-and-insights.md) to
turn "it feels slow" into a concrete list.
---
## 6. Keeping caches lean
```bash
lean-ctx cache stats # size + hit rate
lean-ctx cache prune # drop oversized/quarantined/orphaned indexes
lean-ctx cache reset --project # wipe just this project's cache
```
A healthy cache has a high hit rate (each hit is a ~13-token re-read).
`cache prune` is the safe periodic maintenance command; it never touches valid,
in-budget entries.
---
## 7. Workload fit — where lean-ctx nets out (and where it doesn't)
lean-ctx saves tokens two ways: **cold-read compression** (a single read sent
smaller) and **cached re-reads** (an unchanged file re-read collapses to a
~13-token back-reference). It also *adds* a fixed per-turn prefix — the MCP tool
schemas, the server instructions, and the rules block. Whether you net out ahead
depends on the harness and the provider:
| Factor | Nets ahead | Can cost tokens |
|--------|-----------|-----------------|
| Context lifetime | one long-lived context (agent loop, interactive session) — re-reads land in the same window, so the ~13-token stub is usable | **phase-isolated** harness (a fresh process/context per phase) — the back-reference can't resolve in a cold context, so there is no re-read dividend |
| Provider pricing | **prompt-cache-priced** (the injected prefix rides the provider cache and is billed once) | **non-caching / request-metered** — the prefix is re-sent and re-billed *every turn* |
On a phase-isolated **and** non-caching workload the cached-re-read lever has no
surface and the injected prefix is pure re-billed overhead. That is an
architecturesurface fit, not a failure mode — but you should tune for it.
### Win vs. break-even at a glance
Three independent levers decide the outcome. The savings stack when they line
up and cancel when they don't:
- **Reach** — how much of the request body lean-ctx can shrink. As a *tool layer*
(`ctx_*` MCP tools) it only compresses its own outputs (~5 % of the window). As
the *wire-layer proxy* (or a true context **engine** like Hermes) it compresses
**every** `tool_result` and prunes history cache-stably (~95 %).
- **Lifetime** — *long-lived* (one agent loop / interactive context, so re-reads
land in the same window and collapse to a ~13-token stub) vs. *phase-isolated*
(a fresh process/context per phase, where the back-reference can't resolve).
- **Pricing** — *prompt-cache-priced* (the injected prefix rides the provider
cache and is billed once) vs. *non-caching / request-metered* (the prefix is
re-sent and re-billed every turn).
| Reach | Lifetime | Pricing | Verdict |
|-------|----------|---------|---------|
| engine / proxy | long-lived | cache-priced | **Clear win** — re-reads collapse, the 95 % surface is compressed, the prefix is cached once |
| engine / proxy | long-lived | non-caching | **Win** — re-read dividend + full-body compression outweigh the re-billed prefix |
| tool-only | long-lived | cache-priced | **Modest win** — cached prefix + warm `ctx_*` re-reads, but only ~5 % reach |
| tool-only | phase-isolated | non-caching | **Break-even** — no warm re-reads, ~5 % reach, prefix re-billed each turn; tune with the row below |
The honest target is therefore: **own the window** (route through `proxy enable`
or the engine), keep **one long-lived context**, and prefer a **cache-priced**
rail. Where you can't, the goal is *break-even, not a loss* — which is exactly
what the next sections tune for.
### The meter's denominator (read this before quoting `gain`)
`lean-ctx gain` measures compression on **lean-ctx-touched traffic** (the reads
and shell output it actually processed) — its denominator is that traffic, **not
your full provider bill**. It does not subtract the per-turn prefix lean-ctx
injects. So on a non-caching rail the dashboard can read net-positive while the
billed input moved net-negative. To keep this honest, `gain` now prints a
**Methodology** line and `gain --json` carries `injected_overhead_tokens_per_turn`:
```text
net bill impact ≈ tokens_saved injected_overhead_tokens_per_turn × turns
```
### Reaching tool output the `ctx_*` tools can't wrap
The `ctx_*` tools only compress their own results — they cannot wrap the output
of *another* MCP server's tools (e.g. a host's `store`/`artifact` tools). The
**proxy** can: it sits at the provider API and compresses **every** `tool_result`
in the request body regardless of which tool produced it.
```bash
lean-ctx proxy enable # redirect the provider base URL through lean-ctx
```
So if the heaviest token sink arrives via another server's MCP tools, route the
provider through the proxy rather than relying on the tool layer. (On a
non-caching provider the proxy shrinks what is *sent*; it cannot un-bill a prefix
the client re-sends each turn.)
### Recommended config for a phase-isolated / non-caching harness
```toml
rules_injection = "off" # host supplies its own steering — write no rules file (#361)
```
```bash
LEAN_CTX_MINIMAL=1 lean-ctx serve --daemon # trim the tool surface to the core set
```
Make every cold read carry its weight by defaulting to a compressing read mode
via a [persona](10-customization-and-governance.md) (`default_read_mode = "map"`
or `"signatures"`), since there are no warm re-reads to harvest. The cold-read
modes are the right lever here.
---
## Tuning checklist
| Constraint | Knob |
|------------|------|
| Low-RAM / small CI runner | `memory_profile = "conservative"` |
| Index eats too much disk | `bm25_max_cache_mb` + `extra_ignore_patterns` |
| Giant monorepo, slow graph | `graph_index_max_files = <N>` |
| No index needed at all | `LEAN_CTX_NO_INDEX=1` |
| Hard disk/RAM ceiling | `LEAN_CTX_MAX_DISK_MB` / `LEAN_CTX_MAX_RAM_PERCENT` |
| "What's slow?" | `slow-log list` + `gain --deep` |
| Reclaim space now | `cache prune` |
| Host supplies its own steering | `rules_injection = "off"` |
| Phase-isolated / non-caching harness | `LEAN_CTX_MINIMAL=1` + persona `default_read_mode = "map"` + `proxy enable` |
| Reach another server's tool output | `lean-ctx proxy enable` |