项目文件夹

文件
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

761 行
35 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# Journey 5 — Advanced & Integrations
> You've mastered daily use and want more: compress the LLM API stream itself,
> pull in GitHub/GitLab/Jira context, share context across repos or agents, and
> govern rules across your team. This journey covers the power-user surface.
Source files referenced here:
- `rust/src/cli/dispatch/network.rs``serve`, `proxy`, `daemon`, `provider`, `team`
- `rust/src/cli/profile_cmd.rs` — context `profile`
- `rust/src/cli/plugin_cmd.rs`, `rules_cmd.rs`, `pack_cmd.rs`
- `rust/src/tools/registered/ctx_provider.rs`, `ctx_pack.rs`, `ctx_multi_repo.rs`,
`ctx_agent.rs`, `ctx_handoff.rs`
- `rust/src/core/gateway/` (`client.rs`, `catalog.rs`, `router.rs`, `config.rs`),
`rust/src/tools/ctx_tools.rs` — the MCP Tool-Catalog Gateway
---
## 1. The proxy — compress the LLM stream itself
**What it does:** Everything so far compresses *before* your AI calls a tool. The
proxy goes one level deeper: it sits between your AI client and the LLM API and
compresses `tool_results` in-flight, before they reach the model.
```bash
lean-ctx proxy enable # set up env + autostart (writes RC + LaunchAgent)
lean-ctx proxy status
lean-ctx proxy start # start now
lean-ctx proxy stop
lean-ctx proxy disable # remove env + autostart
lean-ctx proxy cleanup # clear proxy state
```
**Golden output — `lean-ctx proxy status`** tells you, at a glance, whether the
proxy is configured, on which port, and whether the process is currently up:
```text
lean-ctx proxy:
Config: enabled
Port: 4444
Process: not running
```
`Config: enabled` with `Process: not running` means it is wired up but not
started — run `lean-ctx proxy start` (or rely on the LaunchAgent/systemd unit).
**Under the hood:** runs on `LEAN_CTX_PROXY_PORT` (default 4444), auth via
`session_token`. `proxy enable` writes `*_BASE_URL` exports into your shell RC,
`~/.claude/settings.json` (`ANTHROPIC_BASE_URL`), and Codex `config.toml`
(`OPENAI_BASE_URL`), and installs `com.leanctx.proxy.plist` (macOS) or a systemd
user unit (Linux). Upstreams are configurable in `[proxy]`.
**Plays nice with provider prompt caching.** Anthropic's `cache_control` and
OpenAI's automatic prompt caching bill cached prefix tokens at a fraction of
the base rate — but only for *byte-identical* prefixes. The proxy therefore
mutates history exclusively in cache-stable ways: tool-result compression is
content-deterministic (the same result compresses identically on every turn),
and old tool results are summarized only at **frozen compaction boundaries**
that advance in large deterministic strides instead of a per-turn rolling
window. Between boundary jumps your request prefix stays byte-identical, so
cache reads keep hitting; a jump costs one re-write and then caching resumes
on the smaller history. Tune via `[proxy].history_mode` (or
`LEAN_CTX_PROXY_HISTORY_MODE`):
| Mode | Behaviour | Use when |
|------|-----------|----------|
| `cache-aware` *(default)* | Prune at frozen 16-message strides, ≥8 recent messages always intact | You use prompt caching (Claude Code, Cursor, most clients) |
| `rolling` | Legacy: summarize everything older than the last 6 messages, every turn | Maximum raw-token reduction, no prompt caching in play |
| `off` | Never prune history (compression still applies) | Debugging, or the client manages history itself |
> **Heads-up (community-reported):** `proxy enable` modifies your shell RC. If a
> base URL "defaults to the wrong provider," check the exported `*_BASE_URL`
> values in your RC and `lean-ctx proxy status`. The unmodified RC is preserved
> as a `*.lean-ctx.bak` backup.
> **Claude Pro/Max subscriptions need an API key for the proxy.** The proxy
> forwards your credential upstream but never *injects* one. A Claude Pro/Max
> subscription authenticates via OAuth directly against `api.anthropic.com`, and
> that token is rejected by any custom `ANTHROPIC_BASE_URL` — routing it through
> the proxy produces a login loop / 401. Therefore `proxy enable` **skips the
> Claude redirect when no `ANTHROPIC_API_KEY` is detected** (env or
> `~/.claude/settings.json`) and leaves Claude Code talking to Anthropic directly.
> `lean-ctx doctor` flags the conflict if a stale redirect remains.
>
> - **On a subscription?** Keep the proxy disabled for Claude and get savings from
> the lean-ctx MCP tools instead (`ctx_read` / `ctx_search` / `ctx_shell`).
> Other providers (OpenAI/Codex, Gemini, Ollama) are still routed through the
> proxy.
> - **Pay-as-you-go?** Export `ANTHROPIC_API_KEY=<your-key>`, then run
> `lean-ctx proxy enable` (or `--force` to override detection). Claude traffic is
> then compressed by the proxy.
### Codex in front of the proxy (native WebSocket + HTTP/SSE)
The proxy serves the OpenAI Responses API on both `/v1/responses` and the bare
`/responses` path over **two transports**: native **WebSocket**
(`ws://127.0.0.1:4444/responses`) — Codex's default — and **HTTP/SSE** for clients
that prefer it ([#440](https://github.com/yvgude/lean-ctx/issues/440)). Point Codex
at the proxy and it connects over WebSockets out of the box; the proxy bridges the
WS frames to the upstream and compresses them like any other request:
```toml
# ~/.codex/config.toml — point Codex at the proxy (WebSockets work as-is)
[model_providers.lean-ctx]
name = "lean-ctx"
base_url = "http://127.0.0.1:4444/v1"
```
> Prefer HTTP/SSE instead? Set `supports_websockets = false` in the provider block
> to force Codex onto the `/v1/responses` HTTP transport.
**Non-loopback HTTP upstreams (e.g. `codex-lb`).** By default an upstream must be
HTTPS unless it is loopback (`127.0.0.1` / `localhost` / `[::1]`). To put the proxy
in front of a *trusted local-network* plaintext service such as
`http://host.docker.internal:2455`, opt in deliberately — otherwise the upstream is
rejected:
```bash
# env (any value) — wins over config.toml
export LEAN_CTX_ALLOW_INSECURE_HTTP_UPSTREAM=1
export LEAN_CTX_OPENAI_UPSTREAM="http://host.docker.internal:2455"
```
```toml
# or persist it in config.toml
[proxy]
openai_upstream = "http://host.docker.internal:2455"
allow_insecure_http_upstream = true
```
> ⚠ This downgrades the upstream hop to plaintext HTTP. Use it **only** on a trusted
> local network (loopback, a container host, a private LAN service you control) —
> never for traffic that crosses an untrusted network. The proxy prints a warning at
> startup whenever a non-loopback HTTP upstream is active.
**Custom HTTPS upstream hosts (e.g. a corporate gateway).** By default the upstream
host must be one of the provider defaults (`api.anthropic.com`, `api.openai.com`,
`chatgpt.com`, `generativelanguage.googleapis.com`). To route through a custom HTTPS
host you control — such as `https://gw.corp.example/anthropic` — opt in deliberately
([#590](https://github.com/yvgude/lean-ctx/issues/590)):
```bash
# env (any value) — works for a foreground `lean-ctx proxy start`
export LEAN_CTX_ALLOW_CUSTOM_UPSTREAM=1
```
```toml
# persist it in config.toml — REQUIRED for the service-managed proxy
[proxy]
anthropic_upstream = "https://gw.corp.example/anthropic"
allow_custom_upstream = true
```
> The env var only reaches a proxy you start **in the foreground** (`proxy start`),
> because it inherits your shell. A proxy started by `lean-ctx proxy enable` /
> `restart` runs as a LaunchAgent / systemd service that never sees your shell env,
> so it would otherwise fall back to the provider default. `enable`/`restart`
> therefore **auto-persist** `allow_custom_upstream = true` when you run them with
> `LEAN_CTX_ALLOW_CUSTOM_UPSTREAM` set and a custom upstream configured — or set the
> flag yourself with `lean-ctx config set proxy.allow_custom_upstream true`.
**Universal provider registry — `[[proxy.providers]]`.** Beyond the four built-in
provider routes, any OpenAI/Anthropic/Gemini-*compatible* endpoint (Azure AI
Foundry, OpenRouter, Groq, vLLM, Ollama, a corporate gateway…) can be declared as
data — no code change. Each entry is served under `/providers/{id}/...` with full
compression, introspection and usage metering for its wire shape:
```toml
[[proxy.providers]]
id = "foundry" # route: /providers/foundry/...
shape = "openai" # anthropic | openai | gemini
base_url = "https://acme.services.ai.azure.com"
api_key_env = "FOUNDRY_API_KEY" # optional: gateway-held key
[[proxy.providers]]
id = "openrouter"
shape = "openai"
base_url = "https://openrouter.ai/api"
[[proxy.providers]]
id = "local"
shape = "openai"
base_url = "http://host.docker.internal:11434" # gateway container → host Ollama
local = true # bill at the shadow rate
```
- **Shape ≠ identity.** The proxy speaks three wire dialects; any number of
provider identities map onto them. A declared HTTPS entry is itself the
custom-host opt-in (no separate `allow_custom_upstream` needed); plaintext HTTP
still requires loopback or `allow_insecure_http_upstream`.
- **`api_key_env` set** → the gateway holds the upstream credential: every caller
credential header is stripped and replaced (callers authenticate with the
lean-ctx Bearer token and never see the provider key). Unset → the caller's own
credentials are forwarded verbatim, exactly like the built-ins.
- **`local`** marks the endpoint as local inference for metering: usage is booked
at the transparent `local_shadow_rate` instead of cloud list prices. Unset, it
is derived from the URL (loopback hosts count as local) — declare it explicitly
when the endpoint is local but not loopback, e.g. the containerized gateway
reaching the host's Ollama via `host.docker.internal`, or an in-cluster vLLM
service. `local = false` likewise pins a loopback-tunneled cloud endpoint to
list-price billing.
- Invalid entries are logged and skipped; the registry is hot-reloaded from
`config.toml` like every upstream. Active entries appear on `/status` under
`providers`.
**Gateway mode — serving a whole org from one host** (`proxy_bind_host`). By
default the proxy binds `127.0.0.1` (nothing changes for local installs). Binding
a non-loopback address turns on gateway hardening **by construction**:
```toml
proxy_bind_host = "0.0.0.0" # env: LEAN_CTX_PROXY_BIND_HOST
proxy_allowed_hosts = ["ai-gateway.example.com"] # Host-header allowlist (DNS rebinding)
proxy_max_rps = 100 # optional; gateway default: 50 rps
```
- The provider-API-key auth fallback is **hard-disabled** (its justification is
strictly "loopback only") — every caller must send the lean-ctx Bearer
token regardless of `proxy_require_token`.
- The Host allowlist extends the loopback-only guard; loopback names always pass.
- A token-bucket rate limit activates (default 50 rps, burst 100; `proxy_max_rps`
overrides, `0` disables). `/health` is exempt for orchestrator liveness probes.
- An unparseable bind value falls back to `127.0.0.1` — a typo can only ever
narrow exposure, never open the listener.
**Per-person gateway keys — metering identity** (`gateway-keys.toml`). An org
gateway can issue one bearer key per person instead of sharing the proxy token.
The file lives at `<config_dir>/gateway-keys.toml` (override:
`LEAN_CTX_GATEWAY_KEYS`), holds **only SHA-256 hashes** of the keys, and is
loaded at proxy startup (rotation = restart, the standard secret-mount flow; a
malformed file fails the start loudly):
```toml
[[keys]]
sha256_hex = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
person = "alice@example.com"
team = "platform" # optional
default_project = "billing" # optional
```
- A request whose Bearer token hash matches an entry
authenticates **and** tags the turn's measured usage with
`person`/`team`/`project` — the basis for per-person/per-project metering.
- The `x-leanctx-project: <name>` request header overrides the key's
`default_project` per request (also works without a key, for solo setups). It
is an internal gateway header and is never forwarded upstream.
- Compute a key's hash with `shasum -a 256` (or `sha256sum`):
`printf '%s' "gk-alice-secret" | shasum -a 256`.
**Active routing — `[proxy.routing]`.** The gateway can rewrite the requested
model in-flight: exact **aliases** (stable org names, transparent swaps) and
intent-based **tier downgrades** (the last user message is classified
`fast|standard|premium`; the tier picks a target). Targets are `"model"` (same
upstream) or `"provider:model"` (re-target to a `[[proxy.providers]]` entry or a
built-in `anthropic|openai|gemini` — same wire shape only; cross-shape
translation is not in M1):
```toml
[proxy.routing]
enabled = true
[proxy.routing.aliases]
"acme/fast" = "foundry:Phi-4-mini-instruct" # stable org-level model name
"claude-opus-4-5" = "claude-sonnet-4-5" # transparent downgrade, same upstream
[proxy.routing.tiers]
fast = "foundry:Phi-4-mini-instruct" # explore/debug-style requests
standard = "" # "" / absent = keep requested model
premium = "" # premium work is never auto-downgraded
```
- **Fail-open by construction:** any miss (rule/classification/unknown provider/
shape mismatch) forwards the request unchanged — a routing bug can cost
savings, never availability. Aliases win over tiers.
- Routed usage events carry `routed_from` (the originally requested model) and
the serving provider, so the savings ledger can prove what the router did.
- Gemini (model in URL path) and the ChatGPT/Codex OAuth route stay passthrough
in M1.
**Loopback-open mode** (`proxy_loopback_open`). When enabled, the proxy skips
ALL authentication on loopback-bound listeners. MCP clients, browser dashboards,
and CLI tools work without setting up tokens. Ignored on non-loopback binds
(gateway mode always requires auth):
```toml
proxy_loopback_open = true # env: LEAN_CTX_PROXY_LOOPBACK_OPEN
```
Retrieve the current proxy token for manual use:
```bash
lean-ctx proxy token # prints token to stdout
lean-ctx proxy token --quiet # no trailing newline (for scripts)
```
### Agent CLI aliases (`skip_agent_aliases`)
`lean-ctx onboard` / `setup` installs shell aliases (`claude`, `codex`, `gemini`,
`codebuddy`) that set `LEAN_CTX_AGENT=1` and `BASH_ENV` so compression activates
automatically in agent sessions. If these aliases conflict with external launchers,
GUI wrappers, or WSL agent detection, disable them:
```toml
skip_agent_aliases = true
```
Or at install time:
```bash
lean-ctx onboard --no-agent-aliases
lean-ctx setup --no-agent-aliases
```
When toggled on, existing alias blocks are removed from `~/.zshrc` and `~/.bashrc`
on the next `setup` / `onboard` run.
This does **not** affect the shell compression hook (`_lc()`) — use
`shell_hook_disabled` to disable that. The `shell_activation` setting controls
*when* aliases activate, `skip_agent_aliases` controls *whether* they are installed.
**Counterfactual baseline — `[proxy.baseline]`.** The parameters that make
avoided-cost claims auditable. Frozen per deployment (contract annex), not
tunable at runtime by the vendor:
```toml
[proxy.baseline]
reference_model = "claude-opus-4.5" # what the org would have used without lean-ctx
local_shadow_rate_per_mtok = 0.25 # USD/MTok booked for local/loopback inference
```
- Every usage event stores `reference_cost_usd` = the request's **uncompressed**
input tokens priced at the reference model's input rate — the counterfactual
the ledger settles against. Unset `reference_model` = no counterfactual is
claimed.
- `is_local` traffic books the **shadow rate** as its actual cost (default 0.25
USD/MTok, never 0): local compute is free of provider fees, not of hardware
and power — keeping "savings vs. local" honest instead of infinite.
**Provider-verified savings — `proxy.counterfactual_metering` (#701).** The
baseline above still *estimates* the uncompressed side (bytes/4). Opt in to
replace the estimate with a receipt: for every request the proxy actually
rewrote, it fires Anthropic's **free** `count_tokens` endpoint with the
original, uncompressed body — concurrently with the real forward, never
delaying or mutating it — and pairs the provider-counted answer with the same
response's billed usage. `/status` then carries a `verified_savings` block
(`counterfactual_input_tokens`, `billed_input_tokens`, signed
`verified_saved_tokens`) and `lean-ctx proxy status` prints a `Verified:` line
next to the estimate. Same-request pairing means no traffic-mix confounds; a
net-negative result (stub overhead exceeding the squeeze) is reported honestly,
never clamped. Anthropic only — OpenAI/Gemini have no free counting endpoint.
```bash
lean-ctx config set proxy.counterfactual_metering true
```
**Self-hosted org gateway — `lean-ctx gateway serve`** (build with
`--features gateway-server`). One process bundling the hardened proxy, the
Postgres usage store and an admin listener:
```bash
DATABASE_URL="postgres://gateway@db/leanctx" \
LEAN_CTX_GATEWAY_ADMIN_TOKEN="$(openssl rand -hex 24)" \
lean-ctx gateway serve --port=8484 --admin-port=8485
```
- **Proxy** (`--port`): the exposed surface — all `proxy_bind_host` /
allowlist / Bearer / rate-limit rules above apply unchanged.
- **Usage store** (`DATABASE_URL`): every measured turn becomes a
`usage_events` row (person/team/project × provider/model × tokens/cost +
baseline fields). Schema is applied idempotently at start. **Fail-open:** an
unreachable Postgres degrades metering (events dropped and counted), never
live LLM traffic; without `DATABASE_URL` the store is simply off.
`?sslmode=require` in the URL activates TLS (rustls, webpki roots —
certificate *and* hostname always verified); required for managed Postgres
(Azure/AWS/GCP). Plain TCP stays available for in-cluster databases.
- **Admin listener** (`--admin-port`, default proxy port + 1): binds
`127.0.0.1` by default — widening is an explicit decision via
`[gateway_server].admin_bind_host` (or `LEAN_CTX_GATEWAY_ADMIN_BIND_HOST`);
invalid values fall back to loopback. Keep it cluster-internal (no ingress).
Requires `LEAN_CTX_GATEWAY_ADMIN_TOKEN` (env-only, like all tokens); without
it only the proxy runs. Every response carries hardened headers (CSP
`default-src 'self'`, `frame-ancestors 'none'`, `nosniff`, `no-referrer`;
`Cache-Control: no-store` on APIs); failed auth is throttled per source IP
(10/min → 429) and audit-logged (IP + path, SIEM-collectable).
- `GET /` — the **Gateway Console**: an embedded admin dashboard (login with
the admin token; kept in `sessionStorage` only). Org overview, spend/savings
trend, sortable breakdowns by person/project/model/provider with one-click
CSV export, provider credential status, drop counter, seat projection,
live "last updated" indicator. No CDN, no build step — served from the
binary.
- `GET /api/admin/usage?from=<ISO>&to=<ISO>` — person × project × model ×
provider breakdown with cost/savings sums, totals and the seat projection
(window defaults to the last 30 days).
- `GET /api/admin/timeseries?from=<ISO>&to=<ISO>` — per-UTC-day
requests/cost/saved/reference series (gapless; empty days are explicit
zeros) for trend charts.
- `GET /api/admin/status` — live health/config card: version, uptime, store
connectivity (probed per request), drop counter, provider registry with
credential presence, routing/baseline posture.
- `GET /metrics` — Prometheus text: per-model requests/tokens/cost, verified
ledger savings (total + per mechanism), dropped-event counter.
- `GET /healthz` — unauthenticated liveness.
```toml
[gateway_server]
seats = 800 # projection divisor ("if all seats saved like active users")
org_label = "Acme AI Gateway" # display name on cockpit + reports
# Admin listener bind (default 127.0.0.1 — secure by default). Containers set
# "0.0.0.0" so the pod/compose port mapping reaches it; exposure then stays
# governed by the mapping/Service, not the bind.
admin_bind_host = "127.0.0.1"
# admin_url: set on *client* machines to show the org-wide breakdown in their
# cockpit (ROI view) via GET /api/usage-breakdown; without it the cockpit shows
# the local snapshot of this machine only.
admin_url = "https://gateway.internal:8485"
```
**Gateway lifecycle CLI** (all under `lean-ctx gateway …`, `gateway-server`
builds):
```bash
lean-ctx gateway init pilot --org="Acme AG" --seats=800 \
--reference-model=claude-opus-4.5 --person=alice@acme.com # plug-and-play instance
cd pilot && docker compose up -d # gateway + Postgres 17
lean-ctx gateway doctor --dir . # go-live preflight (exit≠0 on FAIL)
lean-ctx gateway keys add --person=bob@acme.com --team=core # key shown once, hash stored
lean-ctx gateway keys list && lean-ctx gateway keys revoke --person=bob@acme.com
lean-ctx gateway report --out=q3.html # printable value report (usage_events)
```
- `init` generates `config.toml`, `.env` (0600; proxy/admin tokens + Postgres
password + `DATABASE_URL`), `docker-compose.yml` (healthchecks, restart
policies, admin port bound to `127.0.0.1`), `gateway-keys.toml`, `.gitignore`
and a README — and never overwrites an existing instance.
- `doctor` checks config posture (open bind without required tokens = FAIL),
security posture (admin exposure, upstream-TLS and Postgres-TLS stance),
key-set validity, token presence/strength, Postgres connectivity
(`SELECT 1`), provider `api_key_env` presence and live ports — each line with
a concrete fix command.
- **Upstream resilience:** the proxy retries exactly once (150–350 ms jittered
backoff) on connect errors and on 429/502/503 — statuses where the upstream
provably did not process the request. 500/504 and mid-stream failures are
never retried. On a failed retry the *original* upstream response is passed
through. On SIGTERM the gateway finishes in-flight requests and drains the
usage-event queue (bounded, 5 s) before exit.
**Live upstream — `config.toml` is the source of truth for a running proxy**
([#449](https://github.com/yvgude/lean-ctx/issues/449)). A long-lived proxy
(LaunchAgent / systemd / IDE-spawned) re-reads its upstreams from `config.toml`
every ~2s, so a change takes effect **without a restart**:
```bash
lean-ctx config set proxy.openai_upstream https://api.openai.com # live in ≤2s
lean-ctx proxy status # shows the active upstreams
```
- **`LEAN_CTX_*_UPSTREAM` env vars are a *start-time* override only.** An
environment variable cannot reach a process that is already running, so for a
service-managed proxy use `config.toml` (or `lean-ctx proxy restart`, which
re-reads `config.toml` and drops any start-time env override). This is the
common trap with MCP hosts: **Codex (and other MCP clients) launch the lean-ctx
MCP server with a stripped, allowlisted environment** that omits
`LEAN_CTX_*_UPSTREAM`, so the proxy that server spawns never sees it — even
though `lean-ctx` *invoked directly as a CLI* does. Put the upstream in
`config.toml` and it applies to every proxy regardless of how it was started.
- An **invalid** value (typo, unreachable scheme) keeps the last good upstream —
a live proxy is never silently rerouted to the provider default.
- `lean-ctx doctor` warns when the running proxy's live upstream **drifts** from
what `config.toml` resolves to (typically an env override masking a later edit)
and points you at `lean-ctx proxy restart`.
- Tune the reload cadence with `LEAN_CTX_PROXY_RELOAD_SECS` (default `2`).
---
## 2. HTTP MCP & multi-repo — `lean-ctx serve`
For clients that speak Streamable HTTP instead of stdio, or to serve several
repos at once:
```bash
lean-ctx serve --daemon # background HTTP MCP server
lean-ctx serve --root ~/work/api:api \
--root ~/work/web:web # multi-repo, with aliases
lean-ctx serve --status
lean-ctx serve --stop
```
Multi-repo search fuses results across roots with Reciprocal Rank Fusion
(`--rrf-k`). The MCP equivalent is `ctx_multi_repo` (`add_root`, `list_roots`,
`search`, `save_config`).
The **daemon** (`lean-ctx daemon`) is the local IPC service (Unix socket in
`~/.local/share/lean-ctx/`); most users never touch it directly.
---
## 3. External context providers — `ctx_provider`
**What it does:** Brings issues, PRs/MRs, pipelines, tickets, and DB schema into
context so `ctx_semantic_search` and `ctx_knowledge` can find them.
Supported: GitHub, GitLab, Jira, Postgres, and arbitrary MCP bridges.
```text
ctx_provider action=list
ctx_provider action=gitlab_issues state=opened labels=bug
ctx_provider action=gitlab_mrs
ctx_provider action=query provider=jira resource=PROJ-123
```
**Auth:** via env tokens — `GITHUB_TOKEN`/`GH_TOKEN`, `GITLAB_TOKEN`/`CI_JOB_TOKEN`,
`JIRA_URL`+`JIRA_EMAIL`+`JIRA_TOKEN`, `DATABASE_URL`. Jira also supports OAuth via
`lean-ctx provider auth jira`. Configure under `[providers]` in `config.toml`.
**The pipeline:** provider data flows through the same consolidation path as
everything else — `execute()``consolidate()` → BM25 chunks + graph edges +
knowledge facts. That's why a GitHub issue can show up as a cross-source hint
when you read a related file.
---
## 4. Context profiles — `lean-ctx profile`
> Not to be confused with **tool profiles** (`lean-ctx tools`, Journey 2). Tool
> profiles pick *which MCP tools* exist. **Context profiles** tune *compression
> and read-mode behavior*.
```bash
lean-ctx profile list
lean-ctx profile show [name]
lean-ctx profile active
lean-ctx profile diff A B
lean-ctx profile set <name>
```
Set the active profile with `LEAN_CTX_PROFILE`; project overrides live in
`<repo>/.lean-ctx/profiles/`.
---
## 5. Packaging & sharing context — `lean-ctx pack` / `ctx_pack`
**Context packages** bundle curated context (and PR-specific "PR packs") so it
can be installed elsewhere or shared with teammates.
```bash
lean-ctx pack pr # build a PR pack for the current diff
lean-ctx pack create --name my-context
lean-ctx pack list
lean-ctx pack install <name>
lean-ctx pack export / import
```
Packages live under `packages/` with a `package-index.json`. `ctx_pack` exposes
the same actions to your AI.
---
## 6. Multi-agent coordination — `ctx_agent`, `ctx_handoff`, `ctx_share`
For workflows where several AI agents collaborate:
| Tool | Purpose |
|------|---------|
| `ctx_agent` | Register agents, post/read messages, `handoff`, `sync`, shared diaries |
| `ctx_handoff` | Deterministic handoff bundles (Context Ledger Protocol) |
| `ctx_share` | Push/pull cached file contexts between agents |
| `ctx_task` | A2A task orchestration (create/update/cancel) |
State lives under `agents/` (registry, diaries, shared knowledge) with per-agent
identity keys in `keys/`. Handoff bundles are written to `handoffs/`.
---
## 7. Governing rules — `lean-ctx rules` / `ctx_rules`
Keeps the lean-ctx rule blocks in sync across every agent's rule file
(`.cursor/rules`, `AGENTS.md`, `CLAUDE.md`, …).
```bash
lean-ctx rules status # what's installed where
lean-ctx rules sync # re-sync all agents
lean-ctx rules diff # show drift
lean-ctx rules lint # validate
```
Scope via `rules_scope` (`both`/`global`/`project`). Promote high-confidence
knowledge into rules with `lean-ctx export-rules`.
---
## 8. Plugins — `lean-ctx plugin`
```bash
lean-ctx plugin list
lean-ctx plugin enable <name>
lean-ctx plugin info <name>
lean-ctx plugin init # scaffold a new plugin
lean-ctx plugin hooks # show hook points
```
Plugins live under `<config-dir>/lean-ctx/plugins/`. `ctx_plugins` exposes
list/enable/disable/info/hooks to your AI.
---
## 9. Client integration internals — `instructions` & `hook`
These are the low-level building blocks `setup`/`init` (Journey 1) wire up for
you. You rarely call them by hand, but they're documented for anyone integrating
a new client or debugging an integration:
```bash
lean-ctx instructions --client cursor # compile guidance for one client
lean-ctx instructions --client claude --profile standard --crp tdd
lean-ctx instructions --client codex --json --include-rules
lean-ctx instructions --list-clients # which client IDs are supported
```
`instructions` renders the system-prompt/tool-instruction block a given client
should receive — useful when adding support for an editor `setup` doesn't know
yet, or to inspect exactly what guidance lean-ctx injects. `--client <id>` selects
the target (see `--list-clients`); `--profile` and `--crp off|compact|tdd` tune
the tool surface and output style; `--unified` emits one combined block; `--json`
adds metadata and, with `--include-rules`, the rules-file contents. Output is
**deterministic** for the same inputs, which is what lets the docs-drift CI gate
diff it reliably.
```bash
lean-ctx hook <rewrite|redirect|observe|copilot|codex-pretooluse|codex-session-start|rewrite-inline>
```
`hook` exposes the agent hook entry points that editors call automatically
(Cursor/Claude/Copilot/Codex). They are invoked by the editor's hook mechanism,
not typed manually — listed here so the integration surface is fully accounted
for.
**Portable hook binary — `hook_binary` / `LEAN_CTX_HOOK_BINARY` (#708).**
Generated hook commands normally bake the machine-absolute binary path
(agent hosts run hooks under a minimal shell without your `PATH`, #367). If
you sync agent settings such as `~/.claude/settings.json` across machines
with different usernames, that absolute path is wrong everywhere else — and
each machine's `init`/`doctor --fix` rewrites it, turning your settings sync
into permanent ping-pong. Set a verbatim, env-based expression instead:
```bash
lean-ctx config set hook_binary '$HOME/.local/bin/lean-ctx'
# or per-invocation: LEAN_CTX_HOOK_BINARY='$HOME/.local/bin/lean-ctx' lean-ctx init
```
Every *shell-executed* hook command then emits the expression verbatim — the
hook host's shell expands `$HOME` at run time — and `doctor` accepts it as
current. MCP server registrations and launchd/systemd autostart units are
deliberately unaffected: nothing expands shell variables there, so they keep
the real absolute path.
---
## 10. MCP Tool-Catalog Gateway — `ctx_tools` (downstream MCP servers)
**The problem it solves:** every MCP server you connect injects its *entire* tool
catalog into the system prompt — on every request. Ten servers can mean dozens of
tool schemas the model must read and disambiguate before it does anything. More
tools measurably *lowers* tool-selection accuracy and raises cost. lean-ctx only
ever shrank its **own** surface; the gateway extends that to *external* catalogs.
**What it does:** lean-ctx becomes an **MCP gateway** in front of any number of
downstream MCP servers. Instead of registering all their tools, it exposes one
meta-tool, `ctx_tools`:
| Action | What it does |
|--------|--------------|
| `find` | Rank the aggregated downstream catalog against your query (BM25, the same engine as `ctx_search`) and return the top-N as compact **ChoiceCards** |
| `call` | Proxy a `server::tool` call to its owning server and return the result |
| `list` | Show configured servers + how many tools each contributes |
| `refresh` | Drop the catalog cache and re-aggregate |
Net effect: **unlimited downstream tools at roughly constant context cost** — the
model only ever sees the handful that matter for the task in front of it.
**How to use it (config is global-only, off by default):**
```toml
# ~/.lean-ctx/config.toml
[gateway]
enabled = true
top_n = 5 # tools returned per `find`
cache_ttl_secs = 300 # catalog cache lifetime
call_timeout_secs = 30
[[gateway.servers]]
name = "fs" # becomes the namespace: fs::read_file
transport = "stdio" # spawn a local server as a child process
command = "mcp-server-filesystem"
args = ["/path/to/project"]
[[gateway.servers]]
name = "linear"
transport = "http" # connect to a remote server
url = "https://mcp.linear.app/mcp"
headers = { Authorization = "Bearer ${LINEAR_TOKEN}" }
```
Then, from the agent:
```jsonc
// 1) Discover — "what can touch issues?"
ctx_tools {"action":"find","query":"create an issue with a title and assignee"}
// 2) Invoke the chosen handle
ctx_tools {"action":"call","tool":"linear::create_issue",
"arguments":{"title":"Fix login","assignee":"me"}}
```
**Golden output — `ctx_tools find`** returns a ranked, citation-style shortlist
plus the size of the full catalog it is shielding you from:
```text
gateway: 3 tool(s) for "create an issue" (catalog: 47 tool(s) across 4 server(s))
1. linear::create_issue — Create a Linear issue
params: title*, assignee, team
2. linear::update_issue — Update fields on an existing issue
params: id*, title, state
3. github::create_issue — Open a GitHub issue
params: repo*, title*, body
Invoke one with:
ctx_tools {"action":"call","tool":"<server::tool>","arguments":{ ... }}
```
**What happens under the hood:**
- `rust/src/core/gateway/client.rs` — a real MCP client built on the official
`rmcp` SDK. `stdio` spawns the server as a child process; `http` uses the
streamable-HTTP transport with custom headers. Every connect/list/call is
bounded by `call_timeout_secs`; sessions are opened per operation and shut down
cleanly (no stale child processes).
- `rust/src/core/gateway/catalog.rs` — aggregates each enabled server's tools
into a namespaced `server::tool` catalog behind an in-process **TTL cache**.
Per-server fetch errors are *surfaced*, never hidden, so a misconfigured server
is visible to the agent.
- `rust/src/core/gateway/router.rs` — builds an **ephemeral BM25 index** over the
catalog per query and returns the top-N. Deterministic for a fixed catalog.
- `rust/src/tools/ctx_tools.rs` — gates on config, routes the action, and proxies
the call; downstream results flow back through the same ephemeral firewall and
sensitivity floor as native tools.
**Security:** `[gateway]` is **global-only** — it is never merged from a
project-local `.lean-ctx.toml`, so cloning an untrusted repo can never point the
gateway at an arbitrary command or endpoint. It is a complete no-op until you set
`enabled = true`.
---
## UX notes captured during this walkthrough
- The proxy is the most powerful and the most invasive feature (it edits RC files
and redirects API base URLs). The community-reported "defaults to wrong
provider" issue is called out inline with the recovery path (check `*_BASE_URL`,
`proxy status`, `.bak` backup).
- "profile" is overloaded: tool profile (Journey 2) vs. context profile (here).
Both journeys cross-reference each other to defuse the confusion.
--- lean-ctx: ctx_compose bundles search+read+symbols in one call ---
--- lean-ctx: ctx_compose bundles search+read+symbols in one call ---