mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
147 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e0ce4b1d48
|
fix: remove rtk and lean-ctx CLI context tools (#2677)
## Description Removes both third-party CLI context tools — **rtk** and **lean-ctx** — and with them the context-tool selector itself. Headroom no longer downloads, installs or configures either one, and there is no replacement. The previous pass (#2344) gated only three entry points inside `headroom/cli/wrap.py`. That left the feature reachable in practice: | Gap | Effect | |---|---| | `scripts/install.sh:1544`, `install.ps1:1681` | Ran `rtk init --global --auto-patch` from bash/PowerShell, **bypassing the Python gate entirely** — `curl \| sh` still wrote a Claude Code `PreToolUse` hook regardless of `HEADROOM_RTK` | | `wrap.py` `_setup_context_tool_for_agent` | **`wrap openhands` was broken by default**: `rtk_required=True` met a gate returning `None` → `SystemExit(1)`. Invisible because all 8 openhands tests patched `_ensure_rtk_binary` to a fake path | | `proxy/helpers.py`, `subscription/tracker.py` | Proxy shelled out to `rtk gain` from `/stats`, the dashboard and `headroom perf`; the tracker polled it per contribution (`_RTK_WIRING_DEFAULT = "enabled"`) | | No cleanup path | Nothing removed artifacts an earlier default had installed, so a machine that once ran the old default kept rtk in the loop forever (#1669, #1955) | Also worth noting: the rtk binary download had **no SHA or signature verification** — only `rtk --version` as a smoke test. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [x] Code refactoring (no functional changes) ## Changes Made **Removed** — `headroom/rtk/` and `headroom/lean_ctx/` packages, `headroom/cli/wrap_rtk_metrics.py`, `_selected_context_tool` / `_setup_context_tool_for_agent` / `_VALID_CONTEXT_TOOLS`, the `--rtk` / `--no-rtk` / `--no-project-rtk` / `--keep-rtk` flags across all 18 wrap subcommands, `HEADROOM_RTK*`, the proxy-side `rtk gain` polling, the dashboard CLI-filtering panel (rows + all 8 `cliFiltering*` Alpine getters), `paths.rtk_path()` / `lean_ctx_path()`, the SDK path helpers, `benchmarks/rtk_loop_learn_eval.py`, and the `headroom/rtk/**` CI path filters. **Fails loudly, not silently** — `--context-tool` / `--no-context-tool` / `HEADROOM_CONTEXT_TOOL` are kept solely to error out. They live in shell profiles, aliases and CI jobs, and accepting them as a no-op would read as Headroom having quietly stopped working. The installers reject them too, which matters more than it looks: their arg parsers forward the first unknown flag **and everything after it** to the wrapped tool, so a leftover `--no-rtk` would have silently swallowed a following `--port` and then been ignored downstream. **New `headroom/context_tool_cleanup.py`** — deleting the code cannot help a machine that already ran the old default, since the hooks, binaries and injected guidance are durable on disk. `purge_context_tool_artifacts()` runs once per `wrap`/`unwrap` and removes the registered hook entries, the generated hook scripts, the Headroom-managed `~/.local/bin` symlinks, the vendored `~/.headroom/bin/{rtk,lean-ctx}` binaries, the `lean-ctx` MCP server entry and the marker-fenced instruction blocks. Deliberately conservative: idempotent, **skips** a malformed config rather than overwriting it, and only unlinks a symlink resolving inside Headroom's own bin dir so a user's own build is untouched. It reports on **stderr**, because `wrap/unwrap openclaw --prepare-only` emit machine-readable JSON on stdout as their entire contract. Skipped for `wrap selfheal` (runs from a SessionStart hook; must not race Claude Code's writer for `~/.claude.json`) and for `--help`, which must stay read-only. **Client-config hardening** (discovered while investigating a "corrupted Serena settings file" report) — `wrap.py` reset a settings file to `{}` when an existing file would not parse, then wrote that back. One hand-edited typo or a transient `EACCES`/`EINTR` on a valid file destroyed the user's `permissions`, `env` and `hooks`, on **every `headroom wrap claude`**. It now refuses to write. Separately, `fsutil.write_text` is now atomic (temp file + `fsync` + `os.replace`), fixing all 14 non-atomic client-config writes at once; it follows symlinks rather than replacing them (dotfile managers) and preserves an existing file's mode. **Deliberately kept** — `rtk` stays in the wrapper-peel list in `transforms/content_router.py`. It sits beside `sudo`/`env`/`timeout` as shell-command grammar, so `rtk cat f` is still classified as a file read for anyone running their own rtk install, which the purge intentionally leaves alone. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ ruff check headroom/ tests/ e2e/ --exclude headroom/dashboard/templates All checks passed! $ ruff format --check headroom/ tests/ e2e/ --exclude headroom/dashboard/templates 1255 files already formatted $ mypy headroom/ Success: no issues found in 508 source files $ pytest tests/test_context_tool_cleanup.py -q 11 passed $ pytest tests/test_fsutil.py -q 12 passed $ pytest tests/test_cli/test_wrap_codex.py -q # 89 tests 89 passed in 431.68s $ pytest tests/test_cli/test_wrap_opencode.py -q 39 passed in 257.46s $ pytest tests/test_cli/test_wrap_helpers.py -q 45 passed $ pytest tests/test_paths.py -q 75 passed $ pytest tests/test_cli/test_unwrap_claude.py -q 14 passed $ pytest tests/test_proxy_savings_history.py -q 39 passed $ pytest tests/test_cli/test_wrap_copilot.py -q 27 passed $ pytest tests/test_cli/test_wrap_zcode.py -q 20 passed $ pytest tests/test_subscription_tracker.py -q 9 passed $ pytest tests/test_proxy_dashboard_stats_cache.py -q 5 passed, 1 skipped ``` Repo-wide grep for 14 removed symbols (`headroom.rtk`, `headroom.lean_ctx`, `_ensure_rtk_binary`, `_selected_context_tool`, `_get_context_tool_stats`, `rtk_path`, `lean_ctx_path`, `wrap_rtk_metrics`, `HEADROOM_RTK`, `cli_tokens_avoided`, `tokens_saved_rtk`, …) across `*.py`, `*.ts`, `*.sh`, `*.ps1`, `*.yml`, `*.html`: **zero hits**. Notable test changes: `test_wrap_openhands.py` no longer patches `_ensure_rtk_binary` and asserts `wrap openhands --prepare-only` exits 0 unpatched — the regression that was previously masked. `test_wrap_continue.py` and `test_wrap_hintfile_agents.py` were removed (every test drove RTK instruction injection). A new `test_subscription_tracker.py::test_load_state_written_before_cli_context_tools_were_removed` proves a pre-removal `subscription_state.json` still loads. ## Real Behavior Proof - **Environment:** macOS 15.4 (darwin 25.4.0), Python 3.12.6, Headroom @ this branch, real `~/.headroom` and `~/.claude` on the dev machine. - **Exact command / steps and observed result:** ```text # 1. Retired flag fails loudly instead of silently no-op'ing $ headroom wrap codex --prepare-only --context-tool rtk Error: CLI context tools (rtk, lean-ctx) have been removed from Headroom: they rewrote shell commands through a third-party binary Headroom no longer manages. Drop --context-tool / --no-context-tool and unset HEADROOM_CONTEXT_TOOL; `headroom wrap` uninstalls what they left behind on first run. $ HEADROOM_CONTEXT_TOOL=lean-ctx headroom wrap codex --prepare-only Error: CLI context tools (rtk, lean-ctx) have been removed from Headroom: ... # 2. install.sh rejects the retired flags (extracted parse_wrap_args harness) ['--no-rtk', '--port', '9999'] rc=1 ERROR: CLI context tools ... Drop --no-rtk ['--context-tool=rtk'] rc=1 ERROR: CLI context tools ... Drop --context-tool $ bash -n scripts/install.sh # syntax OK # 3. Purge ran against the real machine, which had all the orphaned artifacts $ python -c "from headroom.context_tool_cleanup import purge_context_tool_artifacts; ..." removed ~/.headroom/bin/lean-ctx (51 MB) removed ~/.headroom/bin/rtk (7.7 MB) removed ~/.local/bin/rtk (symlink into ~/.headroom/bin) removed ~/.claude/hooks/rtk-rewrite.sh removed 8 lean-ctx-* hook scripts # ~/.claude.json afterwards: 90 top-level keys, 19 projects, mcpServers unchanged # → ~59 MB reclaimed, no unrelated key touched # 4. stdout stays machine-readable while the purge reports (planted a fake artifact) $ headroom wrap openclaw --prepare-only --gateway-provider-id codex >out 2>err $ cat out {"enabled":true,"config":{"proxyPort":8787,...}} # parses as JSON $ cat err Retired CLI context tool cleanup: removed /Users/tcms/.headroom/bin/rtk # 5. --help is inert (planted artifact survives), a real run purges $ headroom wrap codex --help → artifact survived: CORRECT $ headroom wrap openclaw --prepare-only → purged: CORRECT # 6. MCP purge dry-run against a copy of the real 82 KB ~/.claude.json top-level keys 90 -> 90; projects 19 -> 19; LOST keys: none all content outside mcpServers byte-identical: True ``` Dashboard rendered via the Playwright test after the panel removal: "Token Savings" shows only `Proxy 0 (0.0%)` / `Of total wire: 36.86%`, and "Token Usage" reads Before Compression → Proxy Removed → After Compression with no "Filtered (this session)" row. Nothing below the removed panel broke. - **Not tested:** Windows and Linux (macOS only) — `install.ps1` is verified by brace-balance and inspection, not executed, since no `pwsh` is available locally. The wrap e2e suite (`e2e/wrap/run.py`) was updated but not run; it needs the Docker e2e image. `serena project index` interaction is exercised in the stacked base PR. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Additional Notes **Stacked on #2676** (`tejas/serena-config-bootstrap`) — please merge that first; this PR's base should then be retargeted to `main`, or it will read as containing that fix too. **Breaking-change migration for users:** - Drop `--rtk`, `--no-rtk`, `--no-project-rtk`, `--keep-rtk`, `--context-tool`, `--no-context-tool` from any alias, script or CI job, and unset `HEADROOM_RTK*` / `HEADROOM_CONTEXT_TOOL`. They now error rather than being ignored, so the failure is immediate and self-explaining. - Previously-installed artifacts are purged automatically on the next `wrap`/`unwrap`; no manual cleanup needed. - `headroom perf --json` no longer carries a `cli_filtering` key, and `/stats` no longer returns a `context_tool` section. **Docs:** `docs/rtk-architecture.md` deleted; RTK/lean-ctx removed from `README.md`, `docs/content/docs/{configuration,opencode,grok-build,docker-install,filesystem-contract}.mdx`, `docs/observability.md` and the matching `wiki/` pages. `REALIGNMENT/09-phase-G-rtk-observability.md` is marked SUPERSEDED rather than deleted, to keep the planning record. **Follow-ups not in scope:** `_emit_wrap_interrupted` was deleted as dead code — its only caller was the `except KeyboardInterrupt` guarding the binary download, so with no download there is nothing slow left to interrupt. |
||
|
|
a6a4def78a
|
docs(readme): describe CacheAligner as detector-only (#2598)
## Description
README still described CacheAligner as a component that stabilizes
prefixes for provider KV cache hits. On current main, CacheAligner is
detector-only: it warns about volatile content and does not rewrite
prompts. Prefix stability is already covered by live-zone compression.
Closes #2592
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Updated the How it works CacheAligner bullet to detector-only
detect/warn wording
- Updated the What's inside CacheAligner bullet the same way
- Left the architecture diagram stage name and live-zone compression
description unchanged
## Testing
- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ rg -n "CacheAligner" README.md
69: │ CacheAligner → ContentRouter → CCR │
83:- **CacheAligner** - detects and warns about volatile content that can bust provider KV cache prefixes; never rewrites prompts
322:- **CacheAligner** - detects and warns about volatile content that can bust provider KV cache prefixes; never rewrites prompts.
338:- **Transforms** do the work: CacheAligner → ContentRouter → SmartCrusher / CodeCompressor / Kompress-base (live-zone only; IntelligentContext and RollingWindow were retired in PR-B1).
$ rg -n "CacheAligner.*stabilizes prefixes" README.md || echo OLD_CLAIM_ABSENT
OLD_CLAIM_ABSENT
$ git diff --stat
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
```
## Real Behavior Proof
- Environment: Linux VPS, Python 3.11.15, sparse checkout of
headroomlabs-ai/headroom main at
|
||
|
|
e4076bbe99
|
fix(grok): preserve business-seat auth while routing only inference (#2514)
## Description `headroom wrap grok` currently routes the whole session through `GROK_CLI_CHAT_PROXY_BASE_URL`. xAI's July 21, 2026 enterprise docs say that host carries both inference and settings, so the wrap displaces the native settings/auth path along with inference. A Grok account whose SuperGrok entitlement lives on a business account can then no longer resolve that seat and falls back to a login screen, even though native `grok` works for the same account. This change retargets the Grok provider slice to the narrower inference-only key, `GROK_MODELS_BASE_URL`, and leaves `GROK_CLI_CHAT_PROXY_BASE_URL` unset. Headroom still intercepts inference and model discovery through the existing `/v1/models` and chat-completions proxy paths, while the native `cli-chat-proxy.grok.com` settings host and `auth.x.ai` auth path stay intact. Closes #2489. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - switch the Grok provider env authority from `GROK_CLI_CHAT_PROXY_BASE_URL` to `GROK_MODELS_BASE_URL` - update the Grok wrap and unwrap docstrings to describe inference-only routing and the preserved native settings/auth path - update the compatibility matrix entry in `README.md` so the public docs match the new Grok routing key - add focused provider and wrap tests that assert the old chat-proxy key is absent and the project-prefixed inference URL is preserved - keep `grok_build` and the existing `/v1/models` proxy route unchanged, using them as preservation boundaries ## Testing - [x] Unit tests pass (`uv run pytest tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py tests/test_provider_grok_build.py -q`) - [x] Linting passes (`uv run ruff check headroom/providers/grok/runtime.py headroom/cli/wrap.py tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py`) - [ ] Type checking passes (`uv run mypy headroom`) - [x] New tests added for new functionality when applicable - [ ] Manual testing performed ### Test Output ```text uv run pytest tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py tests/test_provider_grok_build.py -q uv run ruff check headroom/providers/grok/runtime.py headroom/cli/wrap.py tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py uv run ruff format headroom/providers/grok/runtime.py headroom/cli/wrap.py tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py --check ``` ## Real Behavior Proof - Environment: current Grok CLI plus a focused Headroom worktree - Exact command / steps: capture `grok --version`, re-check xAI's documented Grok env contract, run the focused Grok provider and wrap tests, and if a business-seat account is available locally launch `headroom wrap grok` to confirm the wrapped session no longer falls back to login - Observed result: Headroom emits only the inference-routing key, the old settings/auth key is absent, project prefixing still works, and the focused Grok tests pass - Not tested: local business-seat account on this host ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A - CLI and provider-routing change only. ## Additional Notes - `CHANGELOG.md` stays untouched because Headroom's release automation generates it from conventional commits. - The issue is reporter-only today, so the proof report records the validated `grok --version` and whether a real business-seat retest was reached locally or remains for the reporter. |
||
|
|
5d23a0aec2
|
refactor(wrap): retire tokensave; Serena is the code-memory MCP (#2499)
## Description
`tokensave` was a **downloaded third-party Rust binary**
(`aovestdipaperino/tokensave`) that `headroom wrap` registered as a
code-graph MCP server. This removes it entirely and standardises on
**Serena** as the code-memory MCP — which was already the default in
`wrap`. Serena runs on demand via `uvx`, so Headroom no longer downloads
or executes a binary of its own for code memory.
The change is a *removal + safe transition*, not a behaviour flip:
Serena was already the default, so existing users move over
automatically. This PR also folds in a small README repositioning
(Headroom = the proxy; Serena is the recommended companion; RTK/lean-ctx
are third-party tools we don't control), since it's the same
tooling-stack story.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [x] Code refactoring (no functional changes)
## Changes Made
- **Removed the external tool:** `headroom/graph/tokensave_installer.py`
(the download-and-execute path), `build_tokensave_spec`, and the
`_ensure_tokensave_binary` / `_index_tokensave_project` /
`_setup_tokensave_mcp` helpers; dropped the dead `_setup_code_graph`.
- **`--code-memory`** now offers `serena` (default) or `none` — the
`tokensave` choice is gone. The strands `HeadroomBundle` uses Serena as
its (default-on) code-memory MCP.
- **Tombstone / transition (ledger-verified):** `headroom wrap` **and**
`headroom unwrap` remove a previously Headroom-installed `tokensave` MCP
entry so upgrading users stop launching it, and print that the leftover
`~/.local/bin/tokensave` binary and `.tokensave/` folders are safe to
delete. A user-managed `tokensave` entry is left untouched. Mirrors the
existing `codebase-memory-mcp` retirement.
- **Graceful for existing users:** `HEADROOM_CODE_MEMORY=tokensave` and
`--no-tokensave` resolve to Serena instead of erroring; `--no-serena`
now means "no code memory". **No state migration needed** — both tools'
indexes are regenerable caches of the source, so Serena simply
re-indexes.
- **Docs/README:** replaced the "tokensave binary trust model" section
with a Serena note + an "Upgrading from tokensave?" callout; retired the
RTK "first-class part of our stack" framing.
- **Tests:** deleted the tokensave-only test files
(`test_graph_tokensave.py`, `test_cli/test_tokensave_helpers.py`,
`test_cli/test_tokensave_setup.py`), rewrote `test_wrap_code_memory.py`
for the new resolver/dispatch, fixed a codex test that patched a removed
symbol.
Net: **+102 / −1187 lines.**
## Testing
- [x] Unit tests pass (`pytest`) — targeted to the affected areas
- [x] Linting passes (`ruff check`)
- [x] Type checking passes (`mypy`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ ruff check headroom/cli/wrap.py headroom/mcp_registry/ headroom/integrations/strands/ tests/test_wrap_code_memory.py tests/test_cli/conftest.py tests/test_cli/test_wrap_codex.py
All checks passed!
$ mypy headroom/cli/wrap.py headroom/mcp_registry headroom/integrations/strands/bundle.py
Success: no issues found in 12 source files
$ pytest tests/test_wrap_code_memory.py tests/test_cli/test_wrap_codex.py \
tests/test_cli/test_wrap_claude_vertex_proxy_env.py \
tests/test_cli/test_wrap_claude_finally_unbound.py -q
======================== 120 passed in 97.86s (0:01:37) ========================
$ pytest tests/test_cli --collect-only -q
========================= 713 tests collected in 1.82s ========================= # no import errors after symbol removal
```
## Real Behavior Proof
- **Environment:** macOS (darwin), Python 3.12.6, local `.venv`, on
branch `tejas/remove-tokensave`.
- **Exact command / steps:**
- `python -c "from headroom.integrations.strands.bundle import
HeadroomBundle; b=HeadroomBundle(enable_headroom_mcp=False,
enable_serena_mcp=False); print(len(b.tools))"` → confirms the module
imports after `build_tokensave_spec` removal (the import that my change
would otherwise break).
- CliRunner-driven `wrap codex --prepare-only` (in `test_wrap_codex.py`)
writes `[mcp_servers.serena]` (with `command = "uvx"`, `"--context",
"codex"`) to the codex config and **no** tokensave entry.
- `_resolve_code_memory` unit tests confirm: default → `serena`;
`HEADROOM_CODE_MEMORY=tokensave` → `serena`; `--no-serena` → `none`;
`--code-memory bogus` → `ClickException`.
- **Observed result:** import OK (`tools: 0`); Serena registered,
tokensave absent; resolver behaves as above; 120/120 tests pass.
- **Not tested:** a live `headroom wrap` against a real agent on a
machine with a *previously-installed* tokensave MCP entry — the
tombstone-removal path is covered by unit tests with a fake
registrar/ledger, not an end-to-end run.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title
## Additional Notes
- `--no-tokensave` / `--serena` / `--no-serena` are retained as hidden,
deprecated flags (no-ops or mapped) so existing scripts don't break.
- `--code-graph` is unchanged — it's the proxy's live file-watcher flag
and was never the tokensave MCP; only the dead tokensave hook behind it
was removed.
- `CHANGELOG.md` intentionally left untouched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
eac49656a1
|
feat(wrap): add headroom wrap kimi for Kimi CLI (#1426)
## Description Adds `headroom wrap kimi`, routing Kimi CLI through the Headroom proxy. Kimi CLI speaks an OpenAI-compatible `/chat/completions` API (its `kosong` backend wraps `AsyncOpenAI`) and lets the base URL be overridden via `KIMI_BASE_URL`. This wrapper points it at the local proxy. Kimi's own OAuth bearer is forwarded upstream unchanged, so — unlike the Copilot subscription path — no extra login or token exchange is needed. ## Type of Change - [x] New feature (non-breaking change that adds functionality) ## Changes Made - `headroom/providers/kimi/`: new slice; `build_launch_env` sets `KIMI_BASE_URL` with the per-project base-URL prefix, mirroring the aider/vibe slices. - `headroom/cli/wrap.py`: `kimi` subcommand; falls back to the `kimi-cli` binary when `kimi` is not on `PATH`; `--kimi-api-url` overrides the upstream coding endpoint (default `https://api.kimi.com/coding/v1`). - `tests/test_cli/test_wrap_kimi.py`: 8 tests for the wrap command. - `README.md`: Kimi CLI row in the agent-compatibility matrix. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ pytest tests/test_cli/test_wrap_kimi.py -q ........ [100%] 8 passed in 0.36s $ ruff check headroom/providers/kimi headroom/cli/wrap.py tests/test_cli/test_wrap_kimi.py All checks passed! $ ruff format --check headroom/providers/kimi headroom/cli/wrap.py tests/test_cli/test_wrap_kimi.py 4 files already formatted ``` ## Real Behavior Proof - Environment: macOS; Kimi CLI (`kimi` / `kimi-cli`); `headroom proxy` started with `--openai-api-url https://api.kimi.com/coding/v1`. - Exact command / steps: start `headroom proxy --port 8787 --openai-api-url https://api.kimi.com/coding/v1`, then `curl -s http://localhost:8787/v1/chat/completions` with the Kimi OAuth bearer and a one-line `kimi-for-coding` chat request (`"Reply with exactly: PONG"`). - Observed result: `HTTP 200`; `choices[0].message.content == "PONG"` from `kimi-for-coding`; the OAuth bearer was forwarded and accepted upstream; the per-project path `/p/<name>/v1/chat/completions` also returned `HTTP 200`. - Not tested: Windows/Linux PATH discovery; the `--learn` / `--memory` live paths beyond flag wiring. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes ## Additional Notes - `ruff check` and `ruff format --check` pass locally; `mypy` was run on the new `headroom/providers/kimi` slice only (clean), so the full-tree `mypy headroom` box is left unchecked and is left to CI. - The slice deliberately reuses `codex.proxy_base_url` and `with_project_prefix`, identical to the aider/vibe wrappers, so per-project savings attribution works without Kimi sending custom headers. - Kimi's separate search/fetch services are out of scope for `KIMI_BASE_URL` and continue to hit Kimi directly; only the LLM `/chat/completions` traffic is compressed. Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|
|
5424e99a65
|
Clarify uv tool install path on macOS (#1196)
## Description Clarifies the recommended install path for the Headroom CLI on macOS Apple Silicon and Linux. The docs now prefer `uv tool install --python 3.13 "headroom-ai[all]"` for host-level CLI use, keep `pip install` scoped to Python project environments, and call out absolute executable paths for MCP clients that do not inherit interactive shell `PATH`. ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added `uv tool install --python 3.13` guidance to the README, docs install page, quickstarts, and wiki install pages. - Documented `uv tool update-shell` for shells that cannot find the installed `headroom` command. - Clarified absolute MCP server command paths for clients that do not inherit the interactive shell `PATH`. - Pointed Intel macOS users at the Docker-native install path until native wheel support lands. ## Testing Describe the tests you ran to verify your changes: - [ ] Unit tests pass (`pytest`) - not run; docs-only change. - [ ] Linting passes (`ruff check .`) - not run; docs-only change. - [ ] Type checking passes (`mypy headroom`) - not run; docs-only change. - [ ] New tests added for new functionality - not applicable. - [x] Manual testing performed - [x] `git diff --check upstream/main...HEAD` ## Real Behavior Proof ```bash $ git diff --check upstream/main...HEAD # exits 0; no whitespace errors ``` `npm --prefix docs run types:check` was also attempted. It regenerated MDX and route types successfully, then failed in existing docs app code because `@/lib/...` imports cannot resolve from files such as `app/(home)/layout.tsx`, `app/api/search/route.ts`, and `components/button.tsx`. This PR only changes `README.md`, `docs/content/docs/installation.mdx`, `docs/content/docs/quickstart.mdx`, and `wiki/*.md` files. ## Review Readiness - [x] Draft PR; docs wording and install-path accuracy are ready for review. - [x] No code or runtime files changed. - [x] Known docs type-check blocker is documented above. ## Test Output ```bash $ git diff --check upstream/main...HEAD # no output ``` ```text $ npm --prefix docs run types:check [MDX] generated files ✓ Types generated successfully app/(home)/layout.tsx(2,29): error TS2307: Cannot find module @/lib/layout.shared or its corresponding type declarations. ... components/button.tsx(4,20): error TS2307: Cannot find module @/lib/cn or its corresponding type declarations. ``` ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - not applicable; docs-only change. - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - not applicable; docs-only change. - [ ] New and existing unit tests pass locally with my changes - not run; docs-only change. - [ ] I have updated the CHANGELOG.md if applicable - not applicable. ## Screenshots (if applicable) Not applicable. ## Additional Notes The PR remains a draft while docs verification is limited by the existing docs app `@/lib/*` resolution issue. --------- Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|
|
fcf455a7eb
|
feat(wrap): add omp target (Oh My Pi) with models.yml override and unwrap (#1811)
## Description Adds `headroom wrap omp` / `headroom unwrap omp` — a one-command wrap for [Oh My Pi](https://www.npmjs.com/package/@oh-my-pi/pi-coding-agent) (`omp`), the pi-mono-lineage coding agent, as proposed in #1149. One honest correction to the issue: #1149 proposed reusing the `ANTHROPIC_BASE_URL` redirect from `wrap claude`. During implementation I probed that empirically and it turned out to be wrong — omp only reads `ANTHROPIC_BASE_URL` in its web-search helper; its **chat** endpoint comes from the model registry (`providers.anthropic.baseUrl` in `~/.omp/agent/models.yml`). With the env var pointed at a local probe server, omp's chat traffic still went straight to the real endpoint (0 probe hits); with a `models.yml` same-ID override, every request arrived at the probe (9/9 hits on `/v1/messages`). A same-ID override keeps omp's bundled Anthropic model catalog and stored credentials (both keyed by provider id `anthropic`), so only the endpoint moves. The wrap therefore injects a marker-fenced `providers.anthropic.baseUrl` override into `models.yml`, snapshotting the pre-wrap file **byte-for-byte** first, and `headroom unwrap omp` restores it exactly (or removes the file when the wrap created it) — the same durable-wrap + backup + unwrap contract `wrap codex` uses for `config.toml`. Closes #1149 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `headroom/providers/omp/` (new provider slice): `models_yml_path()` (honors `PI_CODING_AGENT_DIR`), `inject_models_override()` (yaml-merge preserving user providers; pristine byte-for-byte backup, never re-snapshotted while managed), `restore_models_override()` (`restored` / `removed` / `noop`; never touches an unmanaged file), `build_launch_env()` - `headroom/cli/wrap.py`: `wrap omp` (mirrors the aider/vibe `_launch_tool` shape; rtk instructions into the project's `AGENTS.md`, which omp reads natively) and `unwrap omp` (restore models.yml + scrub rtk block + stop proxy) - `headroom/telemetry/context.py`: `omp` added to `_KNOWN_WRAP_AGENTS` so the stack slug reports `wrap_omp` instead of `unknown` - `README.md` (agent matrix row + unwrap list), `llms.txt`, `CHANGELOG.md` - `tests/test_cli/test_wrap_omp.py`: 16 tests (injection fresh/merge/re-inject, restore statuses incl. unmanaged-file safety, env passthrough, CLI wiring, unwrap flows) ## Testing - [ ] Unit tests pass (`pytest`) — all new + `test_cli` tests pass; the full suite carries **3 pre-existing failures** that reproduce identically on unmodified `origin/main` (same set, same asserts — see Test Output and the rebase-validation comment) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run pytest -q # post-rebase, base |
||
|
|
cb388f6af2
|
feat(wrap): add first-class Grok CLI support (#1823)
## Description
Adds first-class Grok CLI integration so Headroom can wrap, compress,
and learn from Grok sessions the same way it does for Claude Code and
Codex.
Grok routes inference through `GROK_CLI_CHAT_PROXY_BASE_URL`; Headroom
sets that to the local proxy so chat traffic is compressed before
forwarding to xAI. MCP retrieval and session learning follow the same
patterns as Codex.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Add `headroom/providers/grok/` provider slice (`runtime.py`,
`install.py`) with `GROK_CLI_CHAT_PROXY_BASE_URL` routing and project
attribution prefix
- Add `headroom wrap grok` and `headroom unwrap grok` CLI commands (MCP
registration, RTK/context-tool setup, proxy launch)
- Add `GrokRegistrar` for marker-delimited `[mcp_servers.headroom]`
injection in `~/.grok/config.toml`
- Add `headroom learn --agent grok` plugin parsing
`~/.grok/sessions/*/updates.jsonl` and `GrokWriter` targeting `GROK.md`
- Register Grok in install planner, install registry, MCP install list,
and agent savings tracking
- Update README agent compatibility matrix and unwrap support list
- Add unit tests for provider, wrap CLI, MCP registrar, and learn plugin
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed
### Test Output
```text
$ uv run pytest tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py tests/test_mcp_registry_grok.py tests/test_learn_grok_plugin.py -q
============================== 10 passed in 0.15s ==============================
$ uv run ruff check headroom/providers/grok headroom/mcp_registry/grok.py headroom/learn/plugins/grok.py headroom/cli/wrap.py headroom/learn/writer.py
All checks passed!
$ uv run mypy headroom/providers/grok headroom/mcp_registry/grok.py headroom/learn/plugins/grok.py
Success: no issues found in 5 source files
```
## Real Behavior Proof
- Environment: macOS (darwin), Python 3.13.14 via `uv`, repo at
`/Users/s/dev/headroom`, Grok CLI at `/Users/s/.grok/bin/grok`
- Exact command / steps: `cd /Users/s/dev/headroom && uv run pytest
tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py
tests/test_mcp_registry_grok.py tests/test_learn_grok_plugin.py -q`; `uv
run ruff check headroom/providers/grok headroom/mcp_registry/grok.py
headroom/learn/plugins/grok.py`; `uv run python -c "from
headroom.providers.grok import build_launch_env; env, display =
build_launch_env(8787, environ={}); print(display[0])"`; `uv run python
-c "from pathlib import Path; import tempfile; from
headroom.mcp_registry.grok import GrokRegistrar; from
headroom.mcp_registry.install import build_headroom_spec;
td=tempfile.mkdtemp(); reg=GrokRegistrar(home_dir=Path(td));
print(reg.register_server(build_headroom_spec('http://127.0.0.1:8787'),
force=True).status.value)"`
- Observed result: pytest reported `10 passed`; ruff reported `All
checks passed!`; `build_launch_env` printed
`GROK_CLI_CHAT_PROXY_BASE_URL=http://127.0.0.1:8787/v1`; MCP registrar
returned `registered` and wrote the Headroom marker block to
`config.toml`
- Not tested: live `headroom wrap grok` session with authenticated Grok
API traffic through a running Headroom proxy (requires maintainer
environment with active `grok login`)
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Screenshots (if applicable)
N/A — CLI/integration change only.
## Additional Notes
- Follows the provider-slice pattern from `
|
||
|
|
560ffae103
|
feat(deploy): Add turnkey deploy command (#1404)
## Description Adds `headroom deploy` as the turnkey, zero-config local deployment entrypoint. The command chooses the most capable deployment path it can verify on the current host, configures detected tools through the existing persistent-install machinery, starts the proxy, and preserves the existing rollback behavior if an update fails. The selection order favors performance first: NVIDIA Docker GPU passthrough when `nvidia-smi` and Docker's NVIDIA runtime are available, then plain Docker, then native scheduled recovery, then a detached Python runtime fallback. ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [x] Performance improvement - [x] Code refactoring (no functional changes) ## Changes Made - Added the top-level `headroom deploy` command and reused the existing install manifest/apply/start/rollback path. - Added conservative runtime selection for GPU Docker, plain Docker, native schedulers, and detached Python fallback. - Added Docker runtime support for manifest-driven `--gpus all` passthrough. - Added tests for Docker selection, GPU Docker selection, detached fallback, GPU command rendering, and subprocess wrapper compliance. - Updated README and persistent-install docs to present the turnkey deployment flow and performance-first GPU behavior. - Allowed documented `opencode` targets through `headroom install apply --target`. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Formatting passes (`ruff format --check`) - [x] Type checking passes in local pre-commit and CI - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text uv run --with pytest --with pytest-asyncio python -m pytest tests/test_cli/test_subprocess_utf8_encoding.py tests/test_cli/test_install_cli.py tests/test_install/test_runtime.py::test_build_runtime_command_for_docker_includes_gpu_passthrough tests/test_install/test_planner.py -q 47 passed in 1.57s uvx --from ruff==0.15.17 ruff check headroom/cli/install.py headroom/install/runtime.py tests/test_cli/test_install_cli.py tests/test_install/test_runtime.py --output-format concise All checks passed! uvx --from ruff==0.15.17 ruff format --check headroom/cli/install.py headroom/install/runtime.py tests/test_cli/test_install_cli.py tests/test_install/test_runtime.py 4 files already formatted ``` GitHub checks are green on the current head. ## Real Behavior Proof - Environment: Windows local worktree `C:\git\headroom`, Python 3.13 via `uv`; GitHub Actions Ubuntu/macOS/Windows runners for full PR CI. - Exact command / steps: Ran the focused deploy/install tests above, checked the touched Python files with the CI-pinned Ruff version, and confirmed the current PR head is mergeable with green GitHub checks. - Observed result: The deploy command, runtime selection, Docker GPU command rendering, install CLI behavior, and subprocess encoding coverage all pass locally; the branch is no longer conflicted. - Not tested: Actual RTX 4090 hardware passthrough on a physical NVIDIA workstation; the PR tests conservative detection and Docker command rendering without requiring GPU hardware in CI. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A - CLI/runtime behavior only. ## Additional Notes CHANGELOG update is not included because this is an unreleased feature PR and the repository's release tooling owns release notes from conventional commits. |
||
|
|
2a954b69b4
|
feat(wrap): add ZCode desktop app support (#1845)
## Description Add `headroom wrap zcode` and `headroom unwrap zcode` commands for the ZCode desktop app (zcode.z.ai). ZCode is a desktop Electron IDE built by Z.AI, optimized for GLM-5.2 models. It has no CLI binary, so this follows the Pattern-B (proxy-only, print instructions) approach — same as Cursor, Cline, and Continue. **Upstream auto-detection:** `headroom wrap zcode` now reads `~/.zcode/v2/config.json` to detect the enabled provider and automatically configures the proxy upstream — no manual flags needed. Closes #1844 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - New module: `headroom/providers/zcode/__init__.py` and `runtime.py` (ZCodeProxyTargets, ZCodeUpstream, build_proxy_targets, detect_upstream, upstream_to_proxy_urls, render_setup_lines) - New CLI command: `headroom wrap zcode` in `headroom/cli/wrap.py:4815` — starts proxy, injects RTK into AGENTS.md, prints Base URL setup instructions - New CLI command: `headroom unwrap zcode` in `headroom/cli/wrap.py:5960` — removes RTK markers, stops proxy - New helper: `zcode_config_dir()` in `headroom/install/paths.py` - Updated `_run_proxy_only_watcher` to accept `anthropic_api_url`/`openai_api_url` params - Updated README.md: ZCode row in compatibility matrix, unwrap list, wrap command list - Updated CHANGELOG.md: entry under [Unreleased] > Added ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) — pre-existing numpy type stubs issue prevents full mypy run - [x] New tests added for new functionality (24 tests in `tests/test_cli/test_wrap_zcode.py`) - [x] Manual testing performed ### Test Output ```text tests/test_cli/test_wrap_zcode.py ........................ [100%] 24 passed, 1 warning in 0.22s ``` ## Real Behavior Proof - Environment: macOS 15.5, Python 3.12.13, headroom installed via `pip install -e .[dev]` - Exact command / steps: `headroom wrap zcode --port 9000` then `headroom unwrap zcode --port 9000` - Observed result: Wrap detects provider from `~/.zcode/v2/config.json` (e.g. "Z.ai Coding"), starts proxy with correct upstream on port 9000, injects RTK into AGENTS.md, prints detected provider + upstream + Base URL setup instructions. Unwrap removes RTK markers, deletes empty AGENTS.md, stops proxy. - Not tested: Actual ZCode app integration (ZCode is a desktop Electron app; Base URL configuration is manual in Settings > Model Settings) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas — N/A: code follows existing patterns - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A — CLI-only changes ## Additional Notes - **Pattern-B approach:** ZCode is a desktop Electron app with no CLI binary. Same pattern as Cursor, Cline, and Continue — proxy-only, print instructions. - **Upstream auto-detection:** Reads `~/.zcode/v2/config.json`, finds the enabled provider, and passes its `baseURL` to the proxy. Falls back to Z.ai Anthropic endpoint if no config found. - **httpProxy investigation:** ZCode has an `httpProxy` setting in `~/.zcode/v2/setting.json`, but it is an Electron-level forward proxy (CONNECT tunneling), incompatible with headroom reverse proxy. The Base URL approach in Model Settings is the correct integration point. - **No dependencies added:** This PR adds zero new dependencies. --------- Co-authored-by: Epicism <epicism@Epiphanie.local> Co-authored-by: JerrettDavis <mxjerrett@gmail.com> Co-authored-by: Tejas Chopra <chopratejas@gmail.com> |
||
|
|
5dbe3314a1
|
fix(auth): support GitHub Enterprise Copilot OAuth domain (#2192)
## Description Adds GitHub Enterprise OAuth domain support for Copilot auth. When `GITHUB_COPILOT_ENTERPRISE_URL` is set, the default OAuth domain resolves to that enterprise host; explicit `--domain` values still take precedence. Closes #1152 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `headroom/copilot_auth.py`: derive the default OAuth domain from `GITHUB_COPILOT_ENTERPRISE_URL` when present, falling back to `github.com` for unset or blank values. - `headroom/cli/copilot_auth.py`: keep explicit CLI domain overrides authoritative even when the enterprise env var is set. - `README.md`: document the enterprise OAuth environment setting and precedence. - Added regression tests for enterprise URL handling, blank/unset fallback, and explicit CLI override precedence. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text uv run pytest tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py -q 69 passed in 1.05s uvx ruff@0.15.17 check headroom/cli/copilot_auth.py headroom/copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_copilot_auth.py All checks passed! uvx ruff@0.15.17 format --check headroom/cli/copilot_auth.py headroom/copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_copilot_auth.py 4 files already formatted ``` ## Real Behavior Proof - Environment: Windows 11 development checkout, Python 3.13.3, with focused Copilot auth tests using monkeypatched enterprise env vars. - Exact command / steps: ran the Copilot auth unit/CLI tests plus ruff check and format-check against the touched auth files and tests. - Observed result: `GITHUB_COPILOT_ENTERPRISE_URL=https://ghe.example.com` resolves `default_oauth_domain()` to `ghe.example.com`; unset/blank enterprise env vars fall back to `github.com`; and `headroom copilot-auth login --domain github.com` still honors the explicit override when enterprise env vars are set. - Not tested: live OAuth against a real GitHub Enterprise Server instance. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A - CLI/auth behavior and README update. ## Additional Notes `GITHUB_COPILOT_ENTERPRISE_URL` takes precedence over `GITHUB_COPILOT_ENTERPRISE_DOMAIN`; explicit `--domain` remains authoritative for the login command. --------- Co-authored-by: JerrettDavis <mxjerrett@gmail.com> Co-authored-by: Tejas Chopra <chopratejas@gmail.com> |
||
|
|
1590913bb7
|
fix(build): support Intel macOS (x86_64-apple-darwin) via ort-load-dynamic (fixes #941) (#1797)
## Problem `headroom-ai` fails to build from source on Intel macOS (`x86_64-apple-darwin`), both with and without the `[all]` extra: ``` error: ort-sys@2.0.0-rc.12: ort does not provide prebuilt binaries for the target `x86_64-apple-darwin` with feature set (no features). ``` Reported in #941. `ort-sys`'s `download-binaries` strategy (used by the `ort-download-binaries-rustls-tls` fastembed feature that `headroom-core` depends on for all non-Windows targets) only ships prebuilt ONNX Runtime binaries for Windows, Linux (x86_64/aarch64), and macOS **Apple Silicon**. There's currently no way to install this package from source on an Intel Mac at all — with or without `[all]`, since the ONNX dependency lives in `headroom-core` itself, not behind a pip extra. ## Root cause `crates/headroom-core/Cargo.toml` already has a working fallback for this *exact* class of problem — for Windows, it swaps `fastembed`'s `ort-download-binaries-rustls-tls` feature for `ort-load-dynamic`, which dlopen's a system-provided ONNX Runtime at runtime instead of requiring a bundled prebuilt binary for the exact target triple. Intel macOS just never got the same treatment. ## Fix Extends the existing `ort-load-dynamic` branch to also cover `target_os = "macos", target_arch = "x86_64"`. ## Documentation Also adds an Intel-macOS subsection next to the existing "Corporate / SSL-inspection environments" section, since the `ORT_STRATEGY=system` + `ORT_LIB_LOCATION` mechanism documented there for a different reason is *also* a fully working, no-source-patch workaround available today: ```bash brew install onnxruntime ORT_STRATEGY=system \ ORT_LIB_LOCATION="$(brew --prefix onnxruntime)/lib" \ ORT_PREFER_DYNAMIC_LINK=1 \ pip install "headroom-ai[all]" export ORT_DYLIB_PATH="$(brew --prefix onnxruntime)/lib/libonnxruntime.dylib" ``` Two things cost real debugging time and seemed worth documenting either way: `ORT_LIB_LOCATION` must point at the `lib/` subdirectory specifically (the Homebrew keg has no single-file library at the prefix root — pointing at the bare prefix gets a *different*, more confusing error: "could not link to the ONNX Runtime build"), and `ORT_PREFER_DYNAMIC_LINK=1` is required — without it, `ORT_STRATEGY=system` still attempts static linking, which the Homebrew keg doesn't provide. ## Testing - `cargo check -p headroom-core` and a full `maturin build --release` succeed on Intel macOS (macOS 26.5.1) with this patch and `ORT_DYLIB_PATH` pointed at a Homebrew onnxruntime 1.27.0. - Verified beyond just compiling: loaded the built wheel's `_core.abi3.so` directly and called `detect_content_type` (the magika/ONNX-backed classifier, which shares the ONNX Runtime instance per the comment in `headroom-core/Cargo.toml`). Ran successfully, no dyld/link errors. - Independently verified the doc-only workaround builds a working wheel through the **unmodified** sdist via `pip wheel` — no Cargo.toml changes needed for that path at all. - Not tested on Apple Silicon or Linux; the `cfg()` predicate is scoped to `(target_os = "macos", target_arch = "x86_64")` so it shouldn't affect either. Happy to split this into two PRs (code fix / doc fix) if that's easier to review. ## Note I noticed a branch, `fix-wheel-matrix-vendored-openssl-and-drop-intel-mac`, that appears to drop Intel macOS from the release wheel matrix rather than fix source builds for it. It looked stale relative to `main` (interleaved with much older history) when I checked, so I wasn't sure whether it reflects current intent — if the project has already decided to drop Intel macOS support rather than fix it, feel free to close this instead, no worries either way. Co-authored-by: JD Davis <mxjerrett@gmail.com> |
||
|
|
e9e9cd55b7
|
feat(mcp): publish canonical server.json (#1510)
## Description Headroom can launch its MCP server, but did not publish a canonical `server.json` that registries and MCP hosts can consume directly. This PR adds a shared descriptor builder, commits a root `server.json`, parity-tests that artifact against the builder and existing runtime spec, and updates docs so registry authors do not need to reconstruct `headroom mcp serve` from prose. Closes #929. ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added a shared `server_json.py` descriptor builder for Headroom MCP publication metadata. - Published a canonical root `server.json` and parity-tested it against the builder. - Encoded the publishable uvx contract as `headroom-ai[mcp]` plus `headroom mcp serve`. - Updated README and MCP docs to point registry authors at the canonical descriptor. - Added the README ownership marker used by MCP Registry verification. - Kept existing registrars and `headroom mcp install` behavior unchanged. ## Testing - [x] Unit tests pass - [x] Linting passes - [x] Type checking passes - [x] New tests added for new functionality when applicable - [x] Manual testing performed ### Test Output ```text Focused registry/server-json tests and reviewer approval were completed on this PR before the governance body cleanup. The current body update is documentation-only metadata for PR governance. ``` ## Real Behavior Proof - Environment: Headroom development checkout with MCP test dependencies. - Exact command / steps: Inspected the generated `server.json` contract and parity coverage against the descriptor builder and runtime MCP spec. - Observed result: The committed descriptor matches the builder/runtime contract and advertises the intended `headroom-ai[mcp]` / `headroom mcp serve` launch path. - Not tested: live publication to third-party registries ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes This body was normalized by a maintainer after approval so the governance parser reflects the already-reviewed PR state. |
||
|
|
e65b9b3f92
|
feat(proxy): apply output shaper to OpenAI-compatible endpoints (#1725)
## Description
Extend the output shaper (verbosity steering + effort routing) to run on
OpenAI-compatible traffic.
Before this PR, the shaper was Anthropic-only by construction:
`shape_request()`'s implementation hard-coded Anthropic wire shapes
(`body["system"]` blocks, `output_config.effort`,
`thinking.budget_tokens`), and only `handlers/anthropic.py:1949` called
it. Setting `HEADROOM_OUTPUT_SHAPER=1` was silently a no-op for
OpenAI-compatible requests (OpenRouter, GitHub Copilot subscription
mode, direct OpenAI or `gpt-5`-class Chat Completions).
This PR:
- adds a `provider="anthropic"|"openai"` dispatch axis to
`classify_turn`, `apply_verbosity_steering`, `route_effort`, and
`shape_request` — default stays `"anthropic"` so existing callers
(`headroom/learn/verbosity.py`, `handlers/anthropic.py:1949`) keep
working unchanged.
- wires the shaper into `handlers/openai.py` for both
`/v1/chat/completions` (verbosity + effort) and `/v1/responses` (effort
only for this pass; the `body["input"]` item-list steering is a
follow-up).
- keeps the emitted label vocabulary (`output_shaper:*`) byte-identical
across providers so the savings ledger (`output_savings.py`) and outcome
funnel (`outcome.py`) remain provider-agnostic.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)
## Changes Made
- **`headroom/proxy/output_shaper.py`** — provider-dispatched core:
- `_classify_turn_openai()` inspects `role=="tool"` messages, detecting
errors structurally: `Error:` / `error:` / `ERROR:` / `Traceback` string
prefixes, or `{"error": ...}` / `{"is_error": ...}` / `{"exception":
...}` keys in dict or list-of-parts content. Same "no keyword regex over
arbitrary text" invariant as the Anthropic classifier.
- `_apply_verbosity_steering_openai()` inserts a trailing
`{"role":"system", "content": steering_text(level)}` immediately after
the leading system-message block, preserving prefix-cache-critical
bytes. Idempotent at same level; replaces-in-place on mid-session level
change.
- `_route_effort_openai()` clamps `body["reasoning_effort"]` (Chat
Completions) and `body["reasoning"]["effort"]` (Responses) on
`MECHANICAL_CONTINUATION` turns. Clamp-only invariant: never injects a
value the client didn't send. `_EFFORT_RANK` expanded to include
`"minimal"` (OpenAI's canonical low-end value).
- **`headroom/proxy/handlers/openai.py`** — two insertion sites:
- `/v1/chat/completions`: mirrors `handlers/anthropic.py:1937-1993`
right after `PRE_SEND` emit, before `optimized_tokens` recount. Same
`HEADROOM_OUTPUT_HOLDOUT` A/B, same `stratum_label` /
`transforms_applied` bookkeeping.
- `/v1/responses`: scoped effort-only variant after the compression
block, reusing the `_responses_input_to_waste_messages` helper to derive
OpenAI-shape messages for turn classification.
- **`tests/test_output_shaper.py`** — 34 new tests across four classes
covering classification, steering, effort routing, and end-to-end
shape_request for the OpenAI path. Includes a **label-vocabulary parity
test** that pins the emitted label sequence identical between Anthropic
and OpenAI on matched-turn bodies.
- **`README.md`** — one paragraph in the "Output token reduction"
section noting the shaper now covers `/v1/messages`,
`/v1/chat/completions`, and `/v1/responses`, with the effort-lever
difference (`reasoning_effort` vs `thinking.budget_tokens`).
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`) — not run locally;
downstream CI will exercise it
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ pytest tests/test_output_shaper.py tests/test_runtime_env.py \
tests/test_output_savings.py tests/test_output_savings_cli.py \
tests/test_verbosity_controller.py tests/test_verbosity_learn.py \
tests/test_request_outcome.py tests/test_handler_outcome_tag_invariant.py -q
============================= test session starts ==============================
platform linux -- Python 3.12.12, pytest-9.1.1, pluggy-1.6.0
collected 185 items
tests/test_output_shaper.py ............................................ [ 23%]
........................ [ 36%]
tests/test_runtime_env.py ................ [ 45%]
tests/test_output_savings.py ................................... [ 64%]
tests/test_output_savings_cli.py ... [ 65%]
tests/test_verbosity_controller.py ............ [ 72%]
tests/test_verbosity_learn.py ............... [ 80%]
tests/test_request_outcome.py ................................. [ 98%]
tests/test_handler_outcome_tag_invariant.py ... [100%]
======================== 185 passed, 1 warning in 1.03s ========================
$ pytest tests/test_proxy_openai_responses_bypass.py \
tests/test_proxy_openai_responses_integration.py \
tests/test_openai_responses_compression_units.py \
tests/test_openai_responses_context_compaction.py \
tests/test_openai_beta_session_sticky.py \
tests/test_openai_codex_routing.py \
tests/test_codex_openai_contract_parity.py \
tests/test_codex_responses_waste_signals.py -q
================== 82 passed, 14 skipped, 1 warning in 13.00s ==================
$ pytest tests/test_anthropic_beta_session_sticky.py \
tests/test_anthropic_pre_upstream_backpressure.py \
tests/test_anthropic_stage_timings.py \
tests/test_proxy_anthropic_cache_stability.py \
tests/test_proxy_anthropic_compression_diagnostics.py \
tests/test_proxy_handler_helpers.py \
tests/test_proxy_handlers_batch.py -q
======================== 123 passed, 1 warning in 6.69s ========================
$ ruff check headroom/proxy/output_shaper.py headroom/proxy/handlers/openai.py \
tests/test_output_shaper.py
All checks passed!
```
## Real Behavior Proof
- **Environment**: Ubuntu 24.04, Python 3.12.12, `uv`-managed venv,
`headroom-ai` editable install from this branch (`uv pip install -e
".[dev]"`).
- **Exact command / steps**:
1. Create a mechanical-continuation OpenAI Chat Completions body:
```python
body = {
"messages": [
{"role": "user", "content": "fix the bug in foo.py"},
{"role": "assistant", "content": None, "tool_calls": [
{"id": "call_01", "type": "function",
"function": {"name": "read_file", "arguments": "{}"}}]},
{"role": "tool", "tool_call_id": "call_01", "content": "file
contents..."},
],
"reasoning_effort": "high",
}
```
2. Call `shape_request(body, OutputShaperSettings(enabled=True),
provider="openai")`.
- **Observed result**:
- Return value: `ShapeResult(changed=True,
labels=["output_shaper:verbosity:L2",
"output_shaper:effort:high->low"])`.
- `body["reasoning_effort"] == "low"` (clamped from `"high"`).
- `body["messages"][0] == {"role": "system", "content":
"<headroom_output_shaping>\n…\n</headroom_output_shaping>"}` — inserted
before the user turn since there was no leading system message. Steering
text byte-identical to Anthropic path (same `_VERBOSITY_LEVELS` table).
- Replacing the tool content with `"Error: file not found"` reclassifies
the turn as `ERROR_CONTINUATION`: `reasoning_effort` stays `"high"`,
only verbosity steering applied (label list is
`["output_shaper:verbosity:L2"]`).
- Same body under `provider="anthropic"` — after adapting `messages` to
Anthropic `tool_result` block shape and swapping `reasoning_effort` for
`output_config.effort` — emits an identical label list
(`test_label_vocabulary_matches_anthropic`), confirming the
savings-ledger / outcome-funnel contract holds.
- **Not tested**:
- End-to-end against a real OpenAI upstream. The shaper is a
request-side mutation and its correctness is defined by the emitted body
+ label vocabulary, both fully covered by unit tests. The receiving
OpenAI API's behavior on the shaped body (whether it honors
`reasoning_effort: low`, whether the trailing system message steers
verbosity as designed) is a downstream property, not a shaper property.
- `mypy headroom` — not run locally (project `[dev]` extra installed,
but type-checking wasn't part of the local iteration loop). Ruff is
clean and the new code carries full type hints.
- Verbosity steering for the Responses API's `body["input"]` item list.
Deferred by design — see the comment at the `/v1/responses` insertion
site in `handlers/openai.py`. Effort routing on the Responses path is
covered.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable — `CHANGELOG.md`
exists but appears release-managed; happy to add an `## Unreleased`
entry if that's the desired convention.
## Additional Notes
- **Backward compatibility**: `classify_turn`,
`apply_verbosity_steering`, `route_effort`, and `shape_request` all
default to `provider="anthropic"`, so `headroom/learn/verbosity.py:37`
and `handlers/anthropic.py:1949-1953` (the only external callers) keep
working with zero changes.
- **Follow-up scope** (happy to file as a separate PR if wanted):
1. Verbosity steering for the Responses API's `body["input"]` item list
— requires handling `message` vs `tool_output` vs `reasoning` item types
uniformly.
2. `max_completion_tokens` cap on mechanical turns as a third effort
lever — kept out of scope here because it's behavior-changing beyond
"clamp existing effort".
3. Extend the label-vocabulary parity test into a property-based fixture
that fuzzes matched Anthropic ↔ OpenAI bodies.
- **Deployment story on my side**: I'm running this on Raspberry Pi 5
via Ansible; the pinned commit will get installed as
`git+https://github.com/dylanrussellmd/headroom.git@<sha>` until this
merges upstream and lands in a `headroom-ai` release. I mention this
only because it exercises the change in a real proxy against real
OpenAI-compatible upstream traffic; happy to report savings numbers back
once the deployment stabilizes.
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
|
||
|
|
b4d4f641f3
|
docs: add Claude Code status-line indicator to Community (#1790)
## Description Adds a single **Community projects** entry to the `## Community` section of the README, linking a community-built Claude Code plugin ([`Ship-Wright/headroom-plugin`](https://github.com/Ship-Wright/headroom-plugin)) that surfaces Headroom usage in the editor status line. Docs-only; no code, config, or dependencies change. Context: headroomlabs-ai/headroom discussion #1789. Closes # (N/A — documentation addition, no linked issue) ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added a `### Community projects` subsection under `## Community` in `README.md` with one bullet linking `Ship-Wright/headroom-plugin` and a one-line description of what it shows. ## Testing <!-- Docs-only change: no Python code paths touched, so the code test suite is not applicable. --> - [ ] Unit tests pass (`pytest`) — N/A (no code changed) - [ ] Linting passes (`ruff check .`) — N/A (no code changed) - [ ] Type checking passes (`mypy headroom`) — N/A (no code changed) - [ ] New tests added for new functionality — N/A (documentation) - [x] Manual testing performed (rendered the Markdown diff; verified the link resolves) ### Test Output ```text $ git diff --stat main README.md | 4 ++++ 1 file changed, 4 insertions(+) # Rendered diff: a new "### Community projects" heading + one bullet appears under "## Community". # Link check: https://github.com/Ship-Wright/headroom-plugin → 200 OK, public, MIT-licensed. ``` ## Real Behavior Proof - Environment: GitHub-flavored Markdown (README preview), macOS. - Exact command / steps: edited `README.md`, `git diff` to confirm a 4-line addition, previewed the rendered section, and opened the linked repo URL to confirm it is public and installable. - Observed result: the new `### Community projects` bullet renders correctly under `## Community`; the link points to a working, public plugin repo. - Not tested: nothing in the Python package changed, so `pytest` / `ruff` / `mypy` were not run (no applicable code paths). ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines (matches existing Community bullet formatting) - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas — N/A (docs) - [x] I have made corresponding changes to the documentation (this PR is the documentation change) - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works — N/A (docs) - [ ] New and existing unit tests pass locally with my changes — N/A (no code changed) - [ ] I have updated the CHANGELOG.md if applicable — N/A (README-only community link) ## Screenshots (if applicable) N/A — a one-line text addition; see the diff. ## Additional Notes This is a minimal, opt-in community link — totally fine to reword, relocate, or decline. Several checklist/testing items are marked N/A because the change is documentation-only and touches no Python code. Happy to adjust the wording or move it elsewhere in the README if you'd prefer. Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|
|
c707de4691
|
docs: retire IntelligentContext from README and installation guide (#1445)
## Description Public README and installation guide still marketed **IntelligentContext** and score-based history dropping after PR-B1 retired those stages in favor of live-zone-only compression. This updates the two first-touch docs so new users see the current pipeline: compress fresh tool output and new turns only; frozen prefix preserved; history never dropped. Closes #1444 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - **README.md** — replace IntelligentContext marketing bullet with **live-zone compression** (new bytes only; frozen prefix preserved; history never dropped) - **README.md** — pipeline internals list current transforms and note IntelligentContext / RollingWindow retirement (PR-B1) - **docs/content/docs/installation.mdx** — core package description matches live-zone ContentRouter - **docs/content/docs/installation.mdx** — add PR-B1 retirement note for IntelligentContext / RollingWindow ## Testing - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ rg -n 'IntelligentContext' README.md docs/content/docs/installation.mdx README.md:289:- **Transforms** do the work: ... (live-zone only; IntelligentContext and RollingWindow were retired in PR-B1). docs/content/docs/installation.mdx:31:> **Note:** IntelligentContext / RollingWindow ... were retired in PR-B1. $ rg -n 'live-zone|Live-zone' README.md docs/content/docs/installation.mdx README.md:274:- **Live-zone compression** — compresses only new bytes ... README.md:289:- **Transforms** do the work: ... (live-zone only; ...) docs/content/docs/installation.mdx:29:The core package includes ... live-zone ContentRouter compression. ``` ## Real Behavior Proof - Environment: macOS (darwin 25.5.0), branch `docs/retire-intelligentcontext-readme` in `/Users/bhavya/Desktop/Headroom-upstream` - Exact command / steps: `rg -n 'IntelligentContext' README.md docs/content/docs/installation.mdx` and `rg -n 'live-zone|Live-zone' README.md docs/content/docs/installation.mdx`; read updated README pipeline section and installation.mdx core-package blurb - Observed result: IntelligentContext appears only in retirement notes (not as an active feature); live-zone compression is the primary marketed behavior in README and installation guide - Not tested: Wiki pages (tracked as follow-up in #1444); docs site build (`npm run build` in docs/) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes Wiki still has extensive IntelligentContext docs — out of scope here; follow-up tracked in #1444. CHANGELOG N/A (docs-only, no release note required). Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|
|
4d433592de
|
Install using uv tool globally (#1829)
Resolves headroomlabs-ai/headroom#768 ## Description Explain how to install using `uv tool` as a global tool. This should be the preferred option for installation so that headroom is setup as a global tool within a self-contained virtual env and the binary on the user's path. That way, a wrapped coding agent can correctly invoke headroom within the virtual env to avoid python package import issues. For example, `~/.claude.json`: ```json "mcpServers": { "headroom": { "type": "stdio", "command": "/Users/USERNAME/.local/bin/headroom", "args": [ "mcp", "serve" ], "env": {} }, ``` uses the command path based on `command -v headroom`. ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Updated README.md ## Testing <!-- Check what you actually ran, then paste the real command output below. --> - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```sh uv tool install "headroom-ai[all]" ``` ```sh command -v headroom /Users/dustin/.local/bin/headroom ``` ```sh headroom wrap claude ╔═══════════════════════════════════════════════╗ ║ HEADROOM WRAP: CLAUDE ║ ╚═══════════════════════════════════════════════╝ Starting Headroom proxy on port 8787... Logs: /Users/dustin/.headroom/logs/proxy.log Proxy ready on http://127.0.0.1:8787 Dashboard: http://127.0.0.1:8787/dashboard Setting up rtk... Code graph: indexed (tokensave) Launching Claude Code (API routed through Headroom)... ANTHROPIC_BASE_URL=http://127.0.0.1:8787 Remote Control: Claude Code may hide the Remote Control menu while ANTHROPIC_BASE_URL points at a custom endpoint (the wrapped Claude session's ANTHROPIC_BASE_URL); launch Claude without Headroom for sessions that need this feature. ENABLE_TOOL_SEARCH=true (on-demand tool loading kept on; issue #746) ``` ## Real Behavior Proof - Environment: See below - Exact command / steps: See test output section above - Observed result: Headroom installed as expected, Claude coding agent successfully had headroom MCP available - Not tested: Coding agents other than Claude. OS other than Mac. ```sh uname -a Darwin mac.lan 25.5.0 Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:26 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T8132 arm64 claude --version 2.1.201 (Claude Code) headroom --version headroom, version 0.30.0 ``` ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [ ] My code follows the project's style guidelines - [x] I have performed a self-review - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A ## Additional Notes <!-- Mention any N/A checklist items, tradeoffs, follow-ups, or maintainer context. --> |
||
|
|
84ecca770e
|
docs(readme): update lean-ctx comparison row (#1711)
## Description
The README "Compared to" table listed lean-ctx as `Scope: CLI commands,
MCP tools, editor rules · Deploy: CLI wrapper · MCP · Reversible: No`.
The lean-ctx maintainer reported in the issue that the project ships
five reversibility mechanisms (`ctx_expand`, `ctx_retrieve`, proxy CCR
tee store with file-path handles, in-band `<lc_expand:HASH>` markers,
and a `GET /v1/references/{id}` HTTP endpoint), plus a wire-level
transparent proxy, a `compress(messages, model)` Py/TS SDK, and
middleware hooks (LiteLLM, Vercel AI SDK). This PR updates the row to
the wording suggested in the issue so the comparison stays accurate.
Fixes #1675
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- `README.md` ("Compared to" table): lean-ctx row updated to `Scope:
Tool output, files, shell, history · Deploy: Proxy · library ·
middleware · MCP · CLI · Local: Yes · Reversible: Yes`.
## Testing
- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ grep -n "lean-ctx" README.md | head -1
447:| [lean-ctx](https://github.com/yvgude/lean-ctx) | Tool output, files, shell, history | Proxy · library · middleware · MCP · CLI | Yes | Yes |
```
## Real Behavior Proof
- Environment: Windows 11, local checkout at `upstream/main` (
|
||
|
|
a7721b2f38
|
[codex] docs: add Codex install note (#1757)
## Description README lacked a practical note for Codex and other MCP clients that cannot reliably inherit a shell PATH. That makes `command = "headroom"` brittle for uv-installed setups. Closes #1757 ## Type of Change - [x] Documentation update ## Changes Made - Added a short Codex/global-install section to README. - Documented `uv tool install "headroom-ai[all]"` and `command -v headroom`. - Showed the absolute-path MCP config pattern. ## Testing - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text Docs-only verification: - Reviewed README diff for command syntax and placement. - Cross-checked the documented flow against the existing uv install pattern and absolute-path MCP config example. ``` ## Real Behavior Proof - Environment: GitHub PR diff review for docs-only install guidance. - Exact command / steps: Compared new README note against documented `uv` tool install flow and absolute-path MCP launch pattern. - Observed result: Docs now give Codex/MCP users a stable binary-path setup instead of relying on ambient PATH inheritance. - Not tested: Fresh uv install on a clean machine in this verification pass. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable Co-authored-by: Your Name <you@example.com> |
||
|
|
9c203ddbcc
|
[codex] docs: remove retired IntelligentContext copy (#1756)
## Description Public README and installation guide still described retired IntelligentContext / RollingWindow as active features. Pipeline now uses live-zone compression only. Closes #1756 ## Type of Change - [x] Documentation update ## Changes Made - Reworded README feature bullets to describe live-zone compression and live-zone pipeline stages. - Updated installation guide copy to match current core package behavior. ## Testing - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text Docs-only verification: - Reviewed PR diff in GitHub Files changed. - Confirmed touched README / install copy no longer advertises retired IntelligentContext or RollingWindow behavior. ``` ## Real Behavior Proof - Environment: GitHub PR diff review for docs-only change. - Exact command / steps: Compared updated README and installation docs text against current live-zone compression behavior described elsewhere in repo. - Observed result: Public docs no longer claim retired IntelligentContext / RollingWindow paths are active. - Not tested: Runtime commands; docs-only change. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable |
||
|
|
a031055a2d
|
docs(readme): correct lean-ctx comparison row (#1754)
## Description Correct the `lean-ctx` row in the README comparison table. The upstream project documents reversible recovery paths and broader deployment surfaces than the table previously reflected. Closes #1754 ## Type of Change - [x] Documentation update ## Changes Made - `README.md`: updated `lean-ctx` comparison row to match current documented capabilities and reversible behavior. ## Testing - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text Docs-only verification: - Reviewed rendered README diff for the comparison row. - Cross-checked updated row against current public lean-ctx docs covering deployment surfaces and reversible behavior. ``` ## Real Behavior Proof - Environment: GitHub PR diff review for docs-only comparison-table update. - Exact command / steps: Compared new README row wording against current public lean-ctx documentation. - Observed result: Comparison row now matches documented capabilities instead of understating recovery and deployment support. - Not tested: Runtime commands; docs-only change. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable Co-authored-by: Your Name <you@example.com> |
||
|
|
32ce99e4b4
|
fix(build): enable Intel macOS pip installs via ort-load-dynamic (#1538)
## Description
Fix `pip install headroom-ai` on Intel Mac (`x86_64-apple-darwin`).
Source installs failed because `ort-sys 2.0.0-rc.12` (transitive via
`fastembed`) does not ship prebuilt ONNX Runtime binaries for that
target, causing maturin/cargo to exit during the wheel build.
This PR mirrors the existing Windows fix: build the Rust core with
`ort-load-dynamic`, pin `ORT_DYLIB_PATH` to the pip `onnxruntime` native
library at import time, and publish Intel macOS wheels from CI.
Closes #
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- `crates/headroom-core/Cargo.toml`: use `ort-load-dynamic` for
`x86_64-apple-darwin` instead of `ort-download-binaries-rustls-tls`.
- `headroom/_ort.py`: extend the ORT dylib pin hook to Intel macOS
(`darwin` + `x86_64`), resolving `libonnxruntime*.dylib` from the pip
`onnxruntime` package.
- `.github/workflows/release.yml` and `.github/workflows/rust.yml`: add
`macos-15-intel` / `x86_64-apple-darwin` wheel matrix entries.
- `tests/test_release_workflows.py` and
`tests/test_transforms/test_ort_dylib.py`: update/add coverage for the
new target and dylib pin behavior.
- `README.md`: note that prebuilt wheels are published for Intel macOS.
## Testing
- [x] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ pytest tests/test_transforms/test_ort_dylib.py \
tests/test_release_workflows.py::test_fastembed_uses_dynamic_ort_on_windows \
tests/test_release_workflows.py::test_build_wheels_matrix_includes_intel_macos_with_dynamic_ort -q
.......................... [100%]
10 passed in 0.18s
$ maturin build --release -o /tmp/headroom-dist
📦 Built wheel for abi3 Python ≥ 3.10 to /tmp/headroom-dist/headroom_ai-0.27.0-cp310-abi3-macosx_10_12_x86_64.whl
$ python3.11 -m venv /tmp/hr-venv && /tmp/hr-venv/bin/pip install .
Successfully built headroom-ai
Successfully installed headroom-ai-0.27.0
$ cd /tmp && /tmp/hr-venv/bin/python -c "import headroom; import headroom._core; print('ok')"
version 0.27.0
_core ok
```
## Real Behavior Proof
- Environment: macOS `x86_64-apple-darwin`, Python 3.11.5, Rust 1.95.0
- Exact command / steps: Reproduced the reported failure with `pip
install headroom-ai` (sdist build dies in `ort-sys` for
`x86_64-apple-darwin`); after this patch ran `maturin build --release`,
then `pip install .` in a clean venv, then `python -c "import
headroom._core"`.
- Observed result: Before fix, cargo/maturin exit 101 on missing ORT
prebuilts; after fix, wheel build succeeds and `headroom._core` imports
cleanly (`version 0.27.0`, `_core ok`).
- Not tested: `macos-15-intel` GitHub Actions wheel matrix row (will be
validated by CI after merge).
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Additional Notes
- ML features (magika detection, fastembed embeddings) still require
`onnxruntime` at runtime on Intel Mac. Users should install
`headroom-ai[proxy]` or `pip install onnxruntime`; `_ort.py` auto-pins
`ORT_DYLIB_PATH` when that package is present.
- Apple Silicon (`aarch64-apple-darwin`) behavior is unchanged: it
continues to bundle ORT via `ort-download-binaries-rustls-tls`.
- Lint/mypy not re-run locally in this pass; targeted pytest +
maturin/pip install proof covers the changed surface.
---------
Co-authored-by: Bor <you@example.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
7208792ee8
|
Fix formatting in README.md | ||
|
|
480d22e6e2
|
Update token reduction statistics in README | ||
|
|
728b33088b
|
fix(relevance): gate ONNX embedding backend behind AVX2 to avoid SIGILL (#1723) (#1765)
## Description Fixes the `SIGILL` / Illegal instruction crash in `headroom.compress` on CPUs without AVX2 (Docker / QEMU / older cloud VMs). The precompiled ONNX Runtime binary shipped by `ort-sys` (via fastembed's `ort-download-binaries*` feature) contains AVX2-family instructions on x86; running it on a non-AVX2 CPU traps with SIGILL — an uncatchable native fault that kills the whole host process. Magika detection was already guarded (#1162, landed after `v0.28.0`); the embedding relevance scorer shared the same `ort-sys` binary with no guard. This PR closes that remaining entry point and documents the requirement. Closes #1723 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add shared `onnx_cpu::onnx_runtime_supported_by_cpu()` helper (AVX2 check on x86/x86_64, `true` on other arches) as the single source of truth. - Route `magika_detector` through the shared helper (no behavior change). - Gate `EmbeddingScorer::try_new*` on the helper: unsupported CPU returns `Err` before touching ONNX, so callers fall back to BM25/stub instead of crashing. - Document the x86 AVX2 requirement + auto-fallback in the README. - Add offline tests (no network / no `RUN_FASTEMBED_TESTS`). ## Testing - [x] Unit tests pass (Rust: `cargo test -p headroom-core`) - [x] Linting passes (`cargo clippy -p headroom-core --all-targets`, `cargo fmt --check`) - [ ] Type checking passes (`mypy headroom`) — N/A, Rust-only change - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ cargo test -p headroom-core --lib relevance::embedding cargo test: 13 passed, 834 filtered out (1 suite, 0.00s) $ cargo test -p headroom-core --lib magika cargo test: 16 passed, 831 filtered out (1 suite, 0.16s) $ cargo clippy -p headroom-core --all-targets (no warnings, no errors) $ cargo fmt --check -p headroom-core (clean) $ cargo build --workspace cargo build (225 crates compiled) Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 57s ``` ## Real Behavior Proof - Environment: `headroom-core` workspace, Rust stable, x86_64 (AVX2-capable dev host). - Exact command / steps: added `onnx_guard_matches_cpu_features` and `try_new_errors_on_unsupported_cpu_instead_of_sigill` tests; ran the suites above. On a no-AVX2 host the guard makes `EmbeddingScorer::try_new()` return `Err(... "AVX2" ...)` instead of executing the AVX2 ONNX binary; callers fall back to BM25 relevance rather than crashing. - Observed result: guard returns `false` only when the CPU lacks AVX2; embedding + magika ONNX paths both short-circuit to non-ONNX fallbacks; no SIGILL. All suites green. - Not tested: end-to-end `pip install` run on a physically AVX2-less machine (dev host has AVX2); guard behavior is unit-tested via the shared `onnx_cpu` helper and mirrors the already-shipped magika guard (#1162). ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable — N/A (release-please generates the changelog) ## Additional Notes Rust-only change, so the Python `pytest`/`ruff`/`mypy` items are N/A; equivalent Rust `cargo test`/`clippy`/`fmt` were run and pasted above. The fix is defense-in-depth parity with the existing magika AVX2 guard (#1162), applied to the second ONNX entry point (embedding relevance). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
abab3ccbfc
|
docs: clarify the headroom CLI is pip-only; npm headroom-ai is the TS SDK (#1585)
## Description `npm install headroom-ai` doesn't give you the `headroom` CLI — it's the TypeScript SDK (a library, no `bin`). The README's "Get started" and "Install" blocks listed the npm install next to the pip install and then immediately ran `headroom wrap claude`, so Node/Windows users reasonably expected npm to provide the CLI and hit `'headroom' is not recognized`. This spells out the split: CLI = pip, SDK = npm. The hnswlib/MSVC half of the report was already fixed on main in #1499 (moved hnswlib to the optional `[vector]` extra), so this PR only addresses the npm-CLI confusion. Closes #1470 ## Type of Change - [x] Documentation update ## Changes Made - README "Get started" + "Install" blocks: annotate that pip ships the `headroom` CLI and npm `headroom-ai` is the TS SDK with no CLI; note the `headroom` commands come from the pip install. - `docs/content/docs/installation.mdx`: state the TS SDK does not install the `headroom` CLI. ## Testing - [x] Manual testing performed ### Test Output ```text Docs-only change. Verified against the source of truth: - pyproject.toml: [project.scripts] headroom = "headroom.cli:main" (CLI entry point is Python-only) - sdk/typescript/package.json: name "headroom-ai", no "bin" field (SDK, no CLI) ``` ## Real Behavior Proof - Environment: repo main @ HEAD - Exact command / steps: read `[project.scripts]` in pyproject.toml and the `bin` field in sdk/typescript/package.json - Observed result: `headroom` console script is defined only by the Python package; the npm package has no `bin`, so `npm install headroom-ai` provides no `headroom` command — matching the issue. - Not tested: n/a (no code paths changed) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings |
||
|
|
10251b65ca
|
docs: sync README + benchmarks with code (drop retired IntelligentContext/RollingWindow) (#1545)
## Description Sync the docs with the code after the live-zone realignment. The `IntelligentContextManager` (ICM), `RollingWindow`, and scoring modules were deleted in PR #350 (May 2026), but the README and benchmark docstrings still advertised them as live, and an example still imported the deleted module (broken on run). This fixes the README + benchmarks and removes the dead example. I validated the README against the code with three parallel static-analysis sub-agents (features/architecture, CLI/extras/wrap-matrix, public API/integrations). Most of the README checked out accurate; only the items below were stale/wrong. Closes # ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - README: removed the `IntelligentContext` bullet and `IntelligentContext / RollingWindow` from the transforms list (both deleted in PR #350). - README: standardized `Kompress-base` -> `Kompress-v2-base` to match the HF model id `chopratejas/kompress-v2-base` and the existing badges (diagram re-aligned). - README: corrected the CodeCompressor language list to match the `CodeLanguage` enum (added TS, C, Perl). - README: softened the unanchored "6 algorithms" tagline to "content-aware compressors". - README: Cortex Code is library-mode only — there is no `headroom wrap cortex`, so the compatibility-matrix row no longer shows a wrap checkmark. - Deleted `examples/test_intelligent_context_toin_ccr.py` — it imported the deleted `IntelligentContextManager` (ImportError on run) and is unreferenced. - Removed stale `RollingWindow` mentions from benchmark docstrings/comments (`benchmarks/__init__.py`, `bench_transforms.py`, `bench_latency.py`, `scenarios/conversations.py`); the accurate PR-B1 retirement comment is kept. ## Testing - [ ] Unit tests pass (`pytest`) — N/A, docs/docstring + example deletion only - [x] Linting passes — `ruff check` clean on all changed benchmark files - [ ] Type checking passes — N/A (no type-relevant changes) - [ ] New tests added — N/A - [x] Manual testing performed — see Real Behavior Proof ### Test Output ```text $ ruff check benchmarks/__init__.py benchmarks/bench_transforms.py benchmarks/bench_latency.py benchmarks/scenarios/conversations.py All checks passed! # stale refs remaining in README/benchmarks (excluding accurate retirement notes): $ grep -rn "IntelligentContext|RollingWindow|Kompress-base" README.md benchmarks/ | grep -v retire (only benchmarks/bench_transforms.py:362 — the accurate PR-B1 retirement comment) # deleted example is unreferenced anywhere: $ grep -rn "test_intelligent_context_toin_ccr" --include=*.md --include=*.yml --include=*.py . (no hits) ``` ## Real Behavior Proof - Environment: macOS (darwin, arm64), Python 3.12 `.venv`, ruff 0.14.x, repo at branch `docs/sync-readme-with-code` off latest `main`. - Exact command / steps: (1) three parallel sub-agents grep/Read-validated README claims vs `headroom/`, `pyproject.toml`, `sdk/typescript/`; (2) directly verified each flagged mismatch (`CodeLanguage` enum, `HF_MODEL_ID`, absence of `IntelligentContext`/`RollingWindow` classes); (3) confirmed the example imports a deleted module and is unreferenced; (4) `ruff check` on changed benchmark files; (5) re-grepped README + benchmarks for any remaining stale refs. - Observed result: README and benchmark docstrings now match the code; the only surviving `RollingWindow` string is the accurate retirement comment; the broken example is removed; ruff passes; the ASCII architecture diagram still aligns after the `Kompress-v2-base` rename. - Not tested: rendering of the README on GitHub/PyPI (text-only change); the separate `docs/content/` and `wiki/` doc sets (see Additional Notes — out of scope for this PR). ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective — N/A (docs/example cleanup) - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md — N/A (Release Please auto-generates from the conventional commit) ## Additional Notes **Larger related finding (NOT in this PR):** the published docs site (`docs/content/docs/*.mdx`) and the `wiki/*.md` set still document `IntelligentContextManager`, `RollingWindow`, `RollingWindowConfig`, `IntelligentContextConfig`, and `ScoringWeights` as live API — with `from headroom import RollingWindow` / `from headroom.transforms import IntelligentContextManager` code examples that would `ImportError`. It is half-migrated (a couple of `.mdx` files already note "removed in 0.9.x" while neighbors still teach it as current). This is ~15 files and the fixes require rewriting examples to the live-zone model, not just deletions — recommended as a focused follow-up PR rather than bundling it here. |
||
|
|
80fa086660
|
fix(packaging): move hnswlib to optional [vector] extra so [all] needs no C++ toolchain (#1499)
## Description `pip install "headroom-ai[all]"` aborts on any machine without a C++ toolchain. `[all]` pulls `[memory]`, which was the only extra carrying `hnswlib>=0.8.0`. hnswlib compiles from source where no wheel matches the target, and that build failure rolls back the **entire** `[all]` install. hnswlib is already fully optional at runtime: `MemoryConfig` defaults to `VectorBackend.AUTO` → **sqlite-vec** (pure Python, no compiler), and only falls back to HNSW. So `[memory]` does not need hnswlib to function. This moves hnswlib into a dedicated optional `[vector]` extra, exactly like `[pytorch-mps]` is already kept out of `[all]`. Closes #1368 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `pyproject.toml`: - Removed `hnswlib>=0.8.0` from `[memory]` (keeps `sqlite-vec` + `sentence-transformers`; the default sqlite-vec backend still works). - Added `vector = ["hnswlib>=0.8.0"]` for users who opt into the HNSW backend. - `[all]` still references `[memory]` (now hnswlib-free) and does **not** add `[vector]`, so it resolves with no compiler. - `[dev]` keeps `hnswlib`, so CI still installs and exercises the HNSW backend tests. - Docs: documented the new `[vector]` extra in `installation.mdx` and the README, and noted it is excluded from `[all]`; fixed the `[memory]` row that claimed to bundle hnswlib. No application code changed. ## Testing - [x] Linting passes (`ruff check .`) - [x] Manual testing performed (TOML resolution check — see proof) - [ ] Unit tests pass (`pytest`) — no app code changed; existing memory/HNSW tests are unaffected (the HNSW backend dependency moved extras but `[dev]`/CI still install it). ### Test Output ```text $ python - <<'PY' # resolve [all] transitively and check hnswlib placement memory has hnswlib: False vector has hnswlib: True dev has hnswlib: True [all] resolved has hnswlib: False [all] has sqlite-vec: True [all] has sentence-transformers: True PY $ ruff check headroom/ tests/ All checks passed! ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.13.11; `tomllib` + a small transitive-extra resolver over the edited `pyproject.toml`. - Exact command / steps: parse `pyproject.toml`, expand `headroom-ai[...]` self-references in `[all]` recursively, then check which extras carry `hnswlib`. - Observed result: the resolved `[all]` set contains no hnswlib while `[vector]` and `[dev]` do. Full output: ```text memory has hnswlib: False vector has hnswlib: True dev has hnswlib: True [all] resolved has hnswlib: False [all] has sqlite-vec: True [all] has sentence-transformers: True ``` `[all]` now resolves with **no** hnswlib (so no compiler needed), while the HNSW backend stays installable via `[vector]` and still tested via `[dev]`. - Not tested: a real `pip install` on a compiler-less host (the failure is a build-time rollback that the resolver check captures deterministically); the native-wrapper e2e jobs that this `pyproject.toml` change triggers run `wrap` e2e, not the memory HNSW path, so dropping hnswlib from `[all]` does not affect them. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes - Editing `pyproject.toml` trips the `e2e` path filter, so the Windows/macOS/Docker native-wrapper jobs also run on this PR. They install + run the `wrap` e2e flow (not the memory HNSW backend), so the extras change is safe for them. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a639540959
|
chore: remove committed node_modules + stray/internal markdown (repo hygiene) (#1528)
## Description Repo hygiene for a public OSS project: removes committed `node_modules`, stray/internal/draft markdown, and commercial-surface references — keeping every real doc (the published docs site, the wiki guides, and all component READMEs) intact. Every file was content-audited before removal, and load-bearing files were verified against the code/CI and kept. Net: **1,695 files changed, +23 / −266,409** (the deletions are dominated by a committed `node_modules` tree). Closes # (no tracking issue) ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made **Removed (verified to have no code/CI dependencies):** - `examples/vercel-ai-sdk-pr/` — 1,649 committed `node_modules` files (zero example source); `node_modules/` added to `.gitignore`. - `docs/spec/` (23 draft "Living Specification" files — orphaned, `1.0.0-draft`, drifted from the code), `docs/superpowers/` (2 agent plans), `docs/proposals/` (2 internal/commercial memos). - 6 orphan `docs/*.md` (auth-modes, bedrock, claude-code-vertex-headroom, cortex-code, output-token-reduction-guide, rtk-loop-weighting). - `PR.md` (committed PR draft), `ENTERPRISE.md`, `.github/FUNDING.yml`. **Content scrubs:** - Removed unreleased "Headroom Cloud" / `api.headroom.ai` / `hr_` references from `configuration.mdx`, `wiki/configuration.md`, `wiki/typescript-sdk.md`, `sdk/typescript/README.md` (reworded to neutral, accurate phrasing). - Dropped a stale "awaiting maintainer before merge" line from `plugins/headroom-oauth2/SPEC.md`; tidied `.gitignore` comments (kept the protective `headroom-managed/` ignore rule). - Fixed the now-dangling links into removed files (README nav/`output-token-reduction` link, `scripts/README`, `wiki/vertex`). **Explicitly KEPT (load-bearing — would orphan in-code citations if removed):** - `.changelog.md` — consumed by `.github/workflows/release.yml` (read as the release-notes file). - `REALIGNMENT/`, `docs/observability.md`, `docs/rtk-architecture.md`, `wiki/plans/`, `TESTING-copilot-subscription.md` — referenced by the Rust core / Python / tests as design docs. ## Testing - [ ] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [ ] Type checking passes (`mypy`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text # Docs/markdown + .gitignore only — no Python/Rust source changed, so the # behavioral test suite is unaffected. Verified the cleanup did not orphan # references or break the published docs site: $ git ls-files 'docs/content/docs/*.mdx' | wc -l # published site intact 42 $ # meta.json nav unchanged; no published page removed. $ grep -rnI "Headroom Cloud|api.headroom.ai|'hr_" $(git ls-files '*.md' '*.mdx') >>> none $ # dangling refs to removed files (excl pre-existing P0/P2 spec stubs that $ # never existed in git): none remaining. ``` ## Real Behavior Proof - Environment: macOS, local git clone of the repo (markdown/.gitignore changes only — no runtime). - Exact command / steps: 4 read-only content-audit agents classified every `.md`/`.mdx` file; each removal candidate was cross-checked against the codebase (`grep` for citations in `.rs`/`.py`/tests, workflows, and configs); only files with no dependents were removed; the tree was re-grepped after removal to confirm no new dangling references; verified the published docs site page count (`git ls-files 'docs/content/docs/*.mdx' | wc -l` = 42, unchanged). - Observed result: the 42-page published docs site and all wiki guides are untouched; no source or workflow references a removed file; `.changelog.md` (consumed by release.yml) and the code-cited design docs were detected as dependencies and kept; the committed `node_modules` tree is removed and `node_modules/` is gitignored so it can't be re-committed; zero "Headroom Cloud"/`headroom.dev` references remain. - Not tested: N/A — no executable code changed (only markdown, `.mdx`, and `.gitignore`), so the behavioral test suite is unaffected. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - This branch deletes `.github/FUNDING.yml` while PR #1526 edits it — the two will be sequenced at merge (delete wins). - A follow-up option (not in this PR): also remove the internal design docs that are currently cited by the code (`REALIGNMENT/`, `docs/observability.md`, `docs/rtk-architecture.md`, `wiki/plans/`) — that requires scrubbing ~15–20 in-code citations so nothing dangles, so it's deliberately deferred. - Untracked local working files (`benchmarks/hf_pilot/`, `tools/copilot-test/`) are intentionally left out of git (not committed). |
||
|
|
077e3e9b9a
|
docs(readme): add "Headroom for teams" inbound for companies (#1529)
## Description Adds a "Headroom for teams" inbound section to the README. Headroom OSS is great for individual developers running it on their laptops, but companies running LLM agents (Claude Code, Codex, Cursor, CI agents) across an org want a deployed/supported/managed option. This creates a clear, OSS-respecting inbound: a CTA directing teams to `hello@headroomlabs.ai` with their stack + monthly LLM spend. Placed at the natural "self-install vs. talk to us" fork — after "When to use · When to skip", before "Install" — and reaffirms Apache 2.0 so the open-source promise stays explicit. Closes # (no tracking issue) ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - README.md: new `## Headroom for teams` section with a managed/self-hosted-at-scale value prop and a `mailto:hello@headroomlabs.ai` CTA. ## Testing - [ ] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text # README-only change — no code/tests affected. Verified the section renders # and the mailto link is correct: $ sed -n '/## Headroom for teams/,/## Install/p' README.md ## Headroom for teams ... → Email [hello@headroomlabs.ai](mailto:hello@headroomlabs.ai) ... ``` ## Real Behavior Proof - Environment: README documentation change only — no runtime behavior. - Exact command / steps: added the section between the "When to use · When to skip" and "Install" headings; verified the rendered markdown and the mailto link with `sed -n '/## Headroom for teams/,/## Install/p' README.md`. - Observed result: the section renders correctly with a working `mailto:hello@headroomlabs.ai` CTA; no other README content changed; no code paths touched. - Not tested: N/A — documentation-only change, the behavioral test suite is unaffected. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - Intentional, polished commercial inbound — distinct from the stray internal "managed platform" planning docs removed in the repo-cleanup PR (#1528). - Follow-up option: add a "Teams" link to the README header nav for extra visibility. Deferred here to avoid colliding with #1528's nav edit; easy to add conflict-free once that merges. |
||
|
|
bd76235f5c
|
fix(cli): harden all CLI surfaces + fix docs accuracy (#1491)
## Summary
Full CLI audit + documentation accuracy pass. All 5 commits on this
branch:
### CLI Hardening (4 commits)
- **Clean errors instead of tracebacks**: corrupt manifests, missing
Docker, malformed JSONL, bad `--profile`, invalid env-var values all now
raise `click.ClickException` with helpful messages
- **Range validation**: ~25 numeric flags across 10 files now use
`click.IntRange`/`FloatRange` — `--port 0`, `--hours -1`, `--limit 0`
etc. produce clean usage errors instead of silent wrong behavior
- **Flag combination warnings**: conflicting combos (`--no-rate-limit` +
`--rpm`, `--no-optimize` + `--target-ratio`, `--telemetry` +
`--no-telemetry`) emit yellow warnings on stderr
- **`memory --db-path` default fixed**: was resolving to
`headroom_memory.db` (wrong bare file); now uses project store
`./.headroom/memory.db` if present, else `~/.headroom/memory.db`
- **`memory list --search` + filters**: `--scope`/`--session`/`--since`
were silently ignored when `--search` was also set; now filters are
applied to search results
- **`learn --verbosity --apply` now works**: the output shaper is off by
default (`HEADROOM_OUTPUT_SHAPER`); `--apply` now hot-enables it via
`POST /admin/runtime-env` on a running proxy, or prints explicit `export
HEADROOM_OUTPUT_SHAPER=1` instructions when no proxy is running
- **`perf --hours` overflow**: `1e9` hours no longer raises
`OverflowError`; treated as "all data"
- **`evals memory --categories` invalid input**: `abc,1,2` now raises
`BadParameter` instead of a raw `ValueError` traceback
### Documentation (1 commit, 20 files)
Corrected factual errors found by 3 parallel audit agents across root
docs, wiki, and the published Fumadocs site:
**Critical (caused runtime errors or wrong behavior if followed):**
- `simulation.mdx`: `plan.transforms_applied` -> `plan.transforms`;
`plan.savings_percent` -> computed from available fields (both raised
`AttributeError`)
- `shared-context.mdx`: `import { SharedContext } from "headroom"` ->
`"headroom-ai"` (5x `ImportError`)
- `claude-code-azure-foundry.mdx`: `pip install headroom` -> `pip
install headroom-ai`
- `api-reference.mdx` + `configuration.mdx`: `from headroom import
GoogleProvider` -> `from headroom.providers import GoogleProvider`
- `ccr.mdx`: CCR TTL default 300s -> 1800s (30 min)
**Fabricated flags removed:**
- `wiki/proxy.md` + `wiki/cli.md`: `--no-intelligent-context`,
`--no-intelligent-scoring`, `--no-compress-first` (none exist); replaced
with real CCR flags
- `wiki/configuration.md`: `--no-ccr-responses`, `--no-ccr-expansion`
(none exist); replaced with real flags
- `wiki/troubleshooting.md`, `wiki/metrics.md`,
`docs/troubleshooting.mdx`: `headroom proxy --log-level debug` (flag
doesn't exist)
**Stale content corrected:**
- `llms.txt`: telemetry stated as enabled-by-default (it's opt-in); wrap
list had 5 tools (now 11)
- `README.md`: compatibility matrix added 5 missing `wrap` targets;
`unwrap`, `doctor`, `init`/`install`, savings-analytics now mentioned
- `SECURITY.md`: supported version table showed 0.2.x (current: 0.27.x)
- `wiki/learn.md`: 5 missing flags added; verbosity shaper-off behavior
documented
- `wiki/quickstart.md`: "Configuration Reference" linked to `api.md`
(wrong) -> `configuration.md`
- `CacheAlignerConfig.enabled` default corrected: `True` -> `False`
- `opencode.mdx`: `--port` default wrong ("random") -> 8787; `openai`
backend removed
- `CONTRIBUTING.md`: broken Markdown table cell fixed
- `docs/meta.json`: `claude-code-azure-foundry` added to nav (was
unreachable orphan page)
- `configuration.mdx`: SDK modes vs proxy `--mode` now clearly
distinguished
## Test plan
- [x] `python -m pytest tests/ -x -q` — 857 passed, 0 failures
- [x] 41-combination CLI smoke test (all flag combos across 8 commands)
— 0 tracebacks
- [x] `ruff check` on all modified Python files — clean
- [x] Docs changes are removals/corrections of fabricated or stale
content; no new claims introduced
|
||
|
|
c30ec4cda8
|
fix: surface output reduction without a restart, and explain $0.00 savings on Python 3.14 (#1296)
## Description I was running headroom through pipx on Python 3.14 and hit two issues. The Proxy $ Saved tile was stuck at $0.00 even though tokens were tracking fine. Pricing comes from litellm, and litellm does not install on Python 3.14 because of a version lock, so there was just nothing to price against. Rather than hardcode a price table that goes stale, I added a `litellm_available` flag to `/stats` and the tile now tells you to reinstall on 3.13 when pricing isn't there, like the output-shaper tile already does. The other one was Output Tokens Saved showing "—" after I turned on the shaper. The recorder reads the learned baseline once at startup, so if you run `learn --verbosity --apply` while the proxy is already up it never gets picked up, and a later flush writes the empty baseline over the one learn just saved. Now it re-reads the baseline before estimating and before each flush, so it works without a restart. Closes # N/A (no tracking issue) ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `output_savings.py`: re-read the baseline from disk before estimating and before each flush, so a baseline learned while the proxy is running takes effect (and a re-learn with the same sample count too). - `server.py`: expose a `litellm_available` flag on `/stats`. - `dashboard.html`: when savings are zero and litellm is missing, point to reinstalling on 3.13 instead of showing $0.00. - tests and docs (`test_output_savings.py`, README, metrics, CHANGELOG). ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ pytest tests/test_output_savings.py -q 34 passed, 1 warning in 0.11s ``` ## Real Behavior Proof - Environment: macOS, Python 3.13 (litellm present) and 3.14 (litellm absent), running this branch. - Exact command / steps: record shaped traffic, write a baseline to the same file while the recorder is live (no restart), then estimate and flush. - Observed result: the recorder goes from `available: False` to `available: True` once the baseline is written mid-run, and keeps it after a flush. Before this it stayed `False` and the flush reset the baseline. Raw output: ```text shaper traffic recorded, baseline not learned yet -> available: False learn --apply wrote baseline while proxy up; restart NOT performed after baseline write -> available: True | method: estimated | pct: 50.3 baseline kept after flush -> disk samples: 4 ``` - Not tested: I did not render the tile hint in a browser, I checked the flag on `/stats` and read the template instead. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Additional Notes Just a final note, ruff and mypy are clean on what I changed. The repo-wide `ruff check .` and `mypy headroom` do report a few problems, but they're in files I didn't touch and already exist on the base commit, so I left them alone to keep this small. Happy to do a separate cleanup PR. --------- Co-authored-by: JD Davis <mxjerrett@gmail.com> |
||
|
|
31f71b880f
|
docs: clarify Cursor setup support (#1439)
## Description Clarifies Cursor support so the docs no longer imply Cursor is fully auto-configured or launched like CLI agents. `headroom wrap cursor` starts the local proxy and prints base URLs for Cursor settings; Cursor still requires manual settings changes in the app. Closes #1436 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Updated the README feature list so Cursor is not grouped with one-command launch/configure agents. - Changed the README compatibility matrix to mark Cursor as manual setup and explain what `headroom wrap cursor` actually does. - Updated proxy docs to say Cursor reads endpoints from its settings UI and to remove the misleading `OPENAI_BASE_URL=... cursor` example. ## Testing - [x] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text uv run --with pytest --with pytest-asyncio python -m pytest tests/test_provider_cursor.py tests/test_cli/test_wrap_bridge.py::test_wrap_cursor_prepare_only_injects_cursorrules tests/test_cli/test_wrap_bridge.py::test_wrap_cursor_prepare_only_uses_lean_ctx_when_configured -q 7 passed in 0.65s cd docs && npm run types:check fumadocs-mdx && next typegen && tsc --noEmit Types generated successfully cd docs && npm run build next build Compiled successfully; generated static pages successfully. Note: existing Recharts width/height warnings were emitted during static generation. uv run --with mkdocs-material mkdocs build Documentation built in 1.52 seconds. Note: existing mkdocs nav/link warnings were emitted. git diff --check (no output) ``` ## Real Behavior Proof - Environment: Windows PowerShell, Python 3.13.3, Node/npm from local environment, isolated worktree `C:\git\headroom\.worktrees\issue-1368-install-prereqs`. - Exact command / steps: inspected `headroom.providers.cursor.runtime.render_setup_lines`, Cursor provider tests, and `headroom wrap cursor --prepare-only` coverage; ran the commands listed above. - Observed result: Cursor runtime only renders manual setup instructions and project-attributed base URLs; docs now match that behavior. Local Cursor-focused tests and docs builds passed. - Not tested: launching the Cursor desktop app or manually configuring Cursor settings, because this PR changes documentation only. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A - documentation wording only. ## Additional Notes Tests were not added because the implementation behavior was already covered; this PR aligns the public docs with the existing Cursor runtime behavior. Ruff and mypy were not run because no Python code changed. CHANGELOG is not updated for this docs-only clarification. |
||
|
|
b50d9c17ce
|
feat(wrap): add --1m to preserve the 1M context window on wrap claude (#1158) (#1351)
## Description `headroom wrap claude` is the recommended Claude Code integration, but for subscription users entitled to the **1M** context window it silently caps usable context at **200k**. Root cause (upstream, anthropics/claude-code#68522): when `ANTHROPIC_BASE_URL` points at a custom host (the Headroom proxy), Claude Code does **not** send the `context-1m-2025-08-07` beta header and treats the window as 200k. The `/model opus[1m]` picker selection does not survive a custom base URL, and `CLAUDE_CODE_AUTO_COMPACT_WINDOW` alone does not lift the cap. Headroom itself already forwards `anthropic-beta` and sizes Opus at 1M internally — but since `wrap claude` owns the launched process's environment and is the documented path, users hit this and blame Headroom first. This adds the opt-in fix the issue proposes. Closes #1158 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `headroom/cli/wrap.py`: new opt-in `--1m` flag on `wrap claude`. When set, `ANTHROPIC_MODEL=<opus>[1m]` is exported on the launched process so Claude Code sends the `context-1m` beta header. Logic extracted to a testable helper `_resolve_1m_model`: a model the user already selected via `ANTHROPIC_MODEL` is preserved (only the `[1m]` suffix is appended when missing); otherwise it falls back to the default Opus. Idempotent (no double suffix). Default behavior is unchanged (opt-in). - `tests/test_cli/test_wrap_helpers.py`: unit tests for `_resolve_1m_model` (append-to-user-model, idempotent, default fallback). - `README.md`: `--1m` added to the Claude Code row of the agent compatibility matrix. - `CHANGELOG.md`: Unreleased → Features entry. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run pytest tests/test_cli/test_wrap_helpers.py tests/test_cli/test_wrap_claude_base_url.py -q 61 passed in 0.46s $ uv run ruff check headroom/cli/wrap.py tests/test_cli/test_wrap_helpers.py All checks passed! $ uv run mypy headroom/cli/wrap.py Success: no issues found in 1 source file ``` #### TDD verification (RED → GREEN) RED — new tests with the prod change reverted (`_resolve_1m_model` absent): ```text E AttributeError: module 'headroom.cli.wrap' has no attribute '_resolve_1m_model' 3 failed, 40 deselected in 0.56s ``` GREEN — with the change applied: ```text 3 passed, 40 deselected in 0.34s ``` ## Real Behavior Proof - Environment: Linux, Python 3.13, headroom @ this branch. - Exact command / steps: `headroom wrap claude --1m --help` shows the new flag, and the flag resolves the model id that triggers the 1M window: ```text $ headroom wrap claude --help | grep -A1 -- --1m --1m Preserve the 1M context window. Behind a custom ANTHROPIC_BASE_URL Claude Code drops the ... # model-id resolution (what --1m exports as ANTHROPIC_MODEL): _resolve_1m_model("claude-opus-4-1-20250805") -> "claude-opus-4-1-20250805[1m]" _resolve_1m_model("claude-opus-4-8[1m]") -> "claude-opus-4-8[1m]" (idempotent) _resolve_1m_model(None) -> "claude-opus-4-8[1m]" (default) ``` - Observed result: with `--1m`, the launched Claude Code process gets `ANTHROPIC_MODEL=<opus>[1m]`, which is the documented trigger for the `context-1m` beta header (verified in the issue against `~/.headroom/logs/proxy.log`). - Not tested: the live Claude Code subscription handshake against Anthropic's servers (requires a 1M-entitled subscription + the proprietary client); the model-id → header behavior is Claude Code's, documented in the issue and upstream anthropics/claude-code#68522. Headroom's side (export the env var that flips it on) is covered above and by the unit tests. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Additional Notes Opt-in only — without `--1m` nothing changes. The `_DEFAULT_1M_MODEL` constant is only consulted when the user has no `ANTHROPIC_MODEL` set; users on a specific model keep it (suffix appended), so the default's freshness does not affect them. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
52068dd650
|
fix(tls): add HEADROOM_TLS_STRICT=0 toggle for corporate SSL inspection (#1308) (#1341)
## Description Behind a corporate TLS-inspection proxy (Zscaler, Netskope) on Python 3.13+, Headroom can't reach the network even with the corporate root correctly installed and trusted. Every path fails with: ``` [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical ``` This isn't a missing-CA problem — the cert is found and trusted. Python 3.13 + OpenSSL 3.x enable `VERIFY_X509_STRICT` by default, which enforces RFC 5280 §4.2.1.9 (a CA cert's `basicConstraints` MUST be marked critical). Inspection roots set `CA:TRUE` without the critical bit, so the chain is rejected. Adding the CA to a bundle does nothing — it's the strict check that fails, and the existing README section only covers `unable to get local issuer certificate`. There are two independent sources of the strict flag (both reported in the issue): Python's own `ssl.create_default_context()` (hits the httpx upstream client), and urllib3 ≥ 2.5's `create_urllib3_context()` (hits the `huggingface_hub` model-download path). Closes #1308 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `ssl_context.py`: added `HEADROOM_TLS_STRICT`. `tls_strict_disabled()` reads the toggle (off-values `0/false/no/off`, default strict). `build_httpx_verify()` resolves the httpx `verify=` value: a configured CA bundle wins; otherwise, when the toggle is off, a default-trust-store context with **only** `VERIFY_X509_STRICT` cleared (so a corporate root that lives in the OS store but trips strict mode still validates); otherwise `True` (httpx default). `apply_global_tls_relaxation()` monkeypatches urllib3's `create_urllib3_context` to drop the strict flag — idempotent, guarded, no-op if urllib3 is absent or the toggle is on. - `server.py`: the proxy's httpx upstream client now uses `build_httpx_verify()` instead of `find_ca_bundle()`-or-`True`. - `cli/proxy.py`: calls `apply_global_tls_relaxation()` at module import, before `huggingface_hub`/`requests` import and cache their context. - README: a distinct SSL-inspection subsection for the `Basic Constraints ... not marked critical` failure, separate from `unable to get local issuer certificate`. Documents that the Rust core's ONNX download (`cdn.pyke.io`) uses a separate stack (rustls/OS trust store) unaffected by the toggle — corporate root must be in the Windows **machine** store, or pre-provision via `ORT_STRATEGY=system`. Chain validation, signature, expiry, and hostname checks all stay on — `HEADROOM_TLS_STRICT=0` is strictly narrower than `verify=False`. Default is strict, matching Python's own default. ## Testing - [x] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text $ python -m pytest tests/test_ssl_context.py -q 31 passed # 19 existing + 12 new (TestTlsStrictDisabled, TestBuildHttpxVerify, TestApplyGlobalTlsRelaxation). ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.10.11 (OpenSSL build that exposes `VERIFY_X509_STRICT`). - Exact command / steps: exercised the module directly — set/unset `HEADROOM_TLS_STRICT`, inspected the resolved httpx `verify` value and the urllib3 context's `verify_flags`. - Observed result: default → `verify=True` (strict preserved); `HEADROOM_TLS_STRICT=0` → httpx gets an `SSLContext` with `VERIFY_X509_STRICT` cleared but `verify_mode == CERT_REQUIRED` and the full default trust store (cert_store x509_ca > 1); `apply_global_tls_relaxation()` patches `urllib3.util.ssl_.create_urllib3_context` so new contexts have the strict flag cleared, and is idempotent. A configured `SSL_CERT_FILE` still wins over the toggle. - Not tested: an actual handshake through a live Zscaler/Netskope MITM on Python 3.13 — I don't have that environment. The fix targets exactly the flag the issue identifies (`VERIFY_X509_STRICT`) on both reported context builders; I verified the flag manipulation and resolution logic directly rather than simulating the proxy. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - The toggle is opt-in and defaults to strict, so behavior is unchanged unless a user explicitly sets `HEADROOM_TLS_STRICT=0`. It clears only the strict flag, never disables verification. - The httpx path uses an explicit context (clean, testable); the urllib3 path needs a monkeypatch because `huggingface_hub` → `requests` builds its context internally and never sees ours. - CHANGELOG.md isn't touched — release-please generates it from the `fix(tls):` commit subject. - I scoped this to the two Python TLS stacks the issue calls out and documented (rather than tried to patch) the separate Rust/ONNX path, since that one resolves through the OS trust store and isn't something this Python toggle can reach. |
||
|
|
88e67edf03
|
ci(release): publish win_amd64 wheel so Windows installs need no Rust (#1328) (#1335)
## Description We ship wheels for macOS arm64 and manylinux x86_64/aarch64, but there's no `win_amd64` wheel on PyPI for any Python version. So on Windows, pip/uv can't find a binary and try to build from the sdist with maturin, which pulls the Rust toolchain from static.rust-lang.org and crates from crates.io. On locked-down machines (corporate proxies, CI runners, the GitHub Copilot CLI sandbox, anything air-gapped) those hosts aren't reachable and the install just dies: ``` error: could not download file from 'https://static.rust-lang.org/dist/channel-rust-stable.toml.sha256' error: failed to get pyo3-macros as a dependency of package pyo3 v0.24.2 [28] Timeout was reached (Failed to connect to index.crates.io port 443) ``` This adds the Windows wheel to the release matrix so `pip install headroom-ai` works on Windows without a local Rust install. Closes #1328 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added a `windows-latest` / `x86_64-pc-windows-msvc` row to the `build-wheels` matrix. The runner already has MSVC and maturin-action sets up Rust, so it produces `headroom_ai-*-win_amd64.whl` on every release. I checked `crates/headroom-core/Cargo.toml` first — the Windows ONNX path is already on `ort-load-dynamic` under `cfg(windows)`, so the wheel loads ORT at runtime instead of linking the DirectML SDK libs. Nothing else was needed on the Rust side. - Added a matching `windows-latest` row to `smoke-import-wheels` so a broken Windows wheel blocks publish like the other platforms do. Windows needed its own step: the venv puts Python under `Scripts\` not `bin/`, and the runner defaults to pwsh. I also pinned the shared script-staging step to `shell: bash` since it uses a heredoc that pwsh can't run (Git Bash is on the runner), and added a `setup-python` step to get the right minor version. - Updated the README install section so the "install Rust first" workaround is clearly only for the sdist fallback (e.g. Intel macOS) now that Windows/Linux/macOS-arm64 all have prebuilt wheels. ## Testing - [x] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [ ] Manual testing performed This is a CI workflow + docs change, no Python runtime code. I leaned on the existing `tests/test_release_workflows.py` structural gates plus a YAML parse and matrix-shape sanity check. ### Test Output ```text $ python -m pytest tests/test_release_workflows.py -q 28 passed, 1 skipped, 1 failed # The one failure, test_no_native_tls_in_wheel_build_tree, shells out to cargo, which # isn't installed here. I confirmed with `git stash` that it fails the same way on main # without my changes, so it's pre-existing and unrelated. $ python -c "import yaml; d=yaml.safe_load(open('.github/workflows/release.yml',encoding='utf-8')); \ j=d['jobs']; print('build-wheels rows:', len(j['build-wheels']['strategy']['matrix']['include'])); \ print('smoke rows:', len(j['smoke-import-wheels']['strategy']['matrix']['include']))" build-wheels rows: 4 smoke rows: 6 ``` ## Real Behavior Proof - Environment: Windows 11 local clone; CI runs on GitHub-hosted `windows-latest`. - Exact command / steps: edited the build-wheels and smoke-import-wheels matrices in `.github/workflows/release.yml` and the README, then ran the release-workflow tests and the YAML/matrix-shape check above. - Observed result: tests pass, YAML parses, build matrix is now 4 rows (Linux x64, Linux arm64, macOS arm64, Windows x64) and the smoke matrix is 6 rows including the new native Windows row. - Not tested: the actual win_amd64 build + PyPI publish. Those jobs only run in the release workflow on a tag or workflow_dispatch, not on a feature PR. The PR-time release dry-run will exercise the new rows once a maintainer approves the workflow run. I couldn't run `maturin build --target x86_64-pc-windows-msvc` end to end here. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - No new test file: the existing structural gates in `tests/test_release_workflows.py` (`test_build_wheels_matrix_excludes_intel_macos`, `test_aarch64_wheel_uses_native_arm64_runner`, the smoke-import gate test) already assert the matrix contract and still pass with the Windows row added. - I didn't touch CHANGELOG.md — release-please generates it from the Conventional Commit subject, so the `ci(release):` commit gets picked up automatically. - The win_amd64 wheel actually shows up on PyPI on the next tagged release. |
||
|
|
00e8de4a3d
|
docs: list OpenCode in the agent compatibility matrix (#1286) (#1340)
## Description #1286 asks whether OpenCode is supported and, if so, to update the README. It already is. `headroom wrap opencode` is a real, registered subcommand backed by a full `headroom/providers/opencode/` module (config injection, install, runtime) with test coverage (`tests/test_providers_opencode_*`, `tests/test_cli/test_wrap_opencode.py`, `tests/test_mcp_registry_opencode.py`, etc.). It's also in the agent-savings target set alongside claude/codex/cursor. The gap was purely docs: the agent compatibility matrix and the wrap one-liner never listed OpenCode, so users reasonably assumed it wasn't supported. Closes #1286 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added an OpenCode row to the agent compatibility matrix in the README. The note ("injects config · starts proxy + launches") reflects how the wrap actually works — it sets `OPENCODE_CONFIG_CONTENT` to route OpenCode's API calls through the proxy, then launches it. - Added `opencode` to the `headroom wrap claude|codex|cursor|aider|copilot|...` one-liner near the top of the README. - Fixed the wrap list in `llms.txt`: it advertised `gemini`, which is not a registered wrap subcommand, and left out `opencode`. The registered set is `aider claude cline codex continue copilot cursor goose openclaw opencode openhands vibe`. ## Testing - [x] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [ ] Manual testing performed Docs-only change, so no new tests. I verified the claim against the code rather than just trusting it. ### Test Output ```text # Registered `headroom wrap` subcommands (source of truth for the matrix): $ python -c "from headroom.cli.wrap import wrap; print(sorted(wrap.commands.keys()))" ['aider', 'claude', 'cline', 'codex', 'continue', 'copilot', 'cursor', 'goose', 'openclaw', 'opencode', 'openhands', 'vibe'] # opencode is present; gemini is not. ``` ## Real Behavior Proof - Environment: Windows 11, local clone of main. - Exact command / steps: enumerated the registered Click subcommands under `headroom wrap` (above) and confirmed `headroom/providers/opencode/` exists with config/install/runtime modules and tests. - Observed result: `opencode` is a real registered wrap target with provider plumbing and tests; the only thing missing was its mention in the docs, which this PR adds. - Not tested: a live `headroom wrap opencode` launch against an actual OpenCode install — I don't have OpenCode set up here. The wrap path itself is already covered by the existing opencode test suite in this repo. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - No code change, so no new test and no CHANGELOG entry — the `docs:` commit is picked up by release-please on its own. - I deliberately didn't touch the dedicated `docs/cortex-code.md` page or anything beyond the matrix; this PR is scoped to making OpenCode discoverable in the docs. |
||
|
|
c10969873b
|
feat(cli): add headroom dashboard and surface the dashboard URL (#1277) (#1292)
## Description The savings dashboard is served at `GET /dashboard` (`headroom/proxy/server.py`) but was effectively undiscoverable: there was no `headroom dashboard` command, the `wrap` startup banner only printed `Proxy ready on http://127.0.0.1:PORT` (never the dashboard URL), and the docs buried it — so users on current releases didn't know it existed (#1277). This makes it discoverable from the CLI, the wrap banner, and the docs. Closes #1277 ## Type of Change - [x] New feature (non-breaking change that adds functionality) ## Changes Made - `headroom/cli/proxy.py`: new `headroom dashboard` command — prints `http://127.0.0.1:<port>/dashboard` and opens it in a browser (stdlib `webbrowser`); `--no-open` just prints, `--port`/`HEADROOM_PORT` honored. Headless failures are swallowed (URL already printed). - `headroom/cli/wrap.py`: print the dashboard URL alongside "Proxy ready" so every `wrap` surfaces it. - `docs/content/docs/installation.mdx` + `README.md`: document `headroom dashboard`. - `docs/content/docs/mcp.mdx`: document the Codex MCP `command: "headroom"` PATH pitfall (#768) — a project-venv (`uv add`) install isn't on the host's PATH; install globally with `uv tool install` / pipx, or use an absolute path. - `tests/test_cli_dashboard.py`: new tests. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] New tests added for new functionality ### Test Output ```text $ python -m pytest tests/test_cli_dashboard.py -q 3 passed $ python -m ruff check headroom/cli/proxy.py headroom/cli/wrap.py tests/test_cli_dashboard.py All checks passed! ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.13, branch fix/1277-dashboard-discoverability off headroomlabs-ai/main - Exact command / steps: built the CLI and invoked the new command via the real entry-point import (`from headroom.cli.main import main; main(['dashboard','--no-open','--port','8787'], standalone_mode=False)`) and checked it is registered (`'dashboard' in main.commands`). - Observed result: prints ` Dashboard: http://127.0.0.1:8787/dashboard`, `'dashboard' in main.commands` → `True`, exit 0. The three new tests pass (prints URL + no browser on `--no-open`; opens the URL by default; a raising `webbrowser.open` does not crash the command). - Not tested: did not load the rendered `/dashboard` HTML against a live proxy in CI — the change only adds a launcher/printer for the existing route; the route itself is unchanged. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d9d0bf4b79
|
feat(providers): add Cortex Code (Snowflake CoCo) as a supported agent (#1190)
## Description Adds **Cortex Code (CoCo)** — Snowflake's AI coding CLI — as a first-class headroom provider alongside Claude Code, Codex, and Cursor. Cortex Code routes requests to Snowflake's Cortex inference endpoint via the OpenAI-compatible pipeline. This PR adds the provider slice, registers it under `"cortex-code"`, and ships tests that measure real token savings against `claude-sonnet-4-6`. Closes # ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Documentation update ## Changes Made - `headroom/providers/cortex_code/__init__.py` — new provider package - `headroom/providers/cortex_code/runtime.py` — `proxy_base_url()`, `build_launch_env()`, `default_api_url()` (reads `SNOWFLAKE_HOST` / `SNOWFLAKE_ACCOUNT`) - `headroom/providers/cortex_code/install.py` — `build_install_env()` sets `OPENAI_BASE_URL`; `render_setup_lines()` - `headroom/providers/install_registry.py` — registers `"cortex-code"` in `_ENV_BUILDERS` - `tests/test_provider_cortex_code.py` — 15 unit tests - `tests/test_cortex_code_compression.py` — 5 compression benchmark tests (no API key needed) - `tests/e2e_cortex_savings.py` — real REST API benchmark; reads `SF_CONN`/`SF_HOST` from env, no hardcoded identifiers - `docs/cortex-code.md` — integration guide (quick start, library mode, auth, limitations) - `README.md` — Cortex Code row added to agent compatibility matrix ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run --with pytest pytest tests/test_provider_cortex_code.py tests/test_cortex_code_compression.py -v tests/test_provider_cortex_code.py::test_cortex_code_proxy_base_url_is_openai_compatible PASSED tests/test_provider_cortex_code.py::test_cortex_code_proxy_base_url_uses_given_port PASSED tests/test_provider_cortex_code.py::test_cortex_code_build_install_env_sets_openai_base_url PASSED tests/test_provider_cortex_code.py::test_cortex_code_build_launch_env_does_not_mutate_input PASSED tests/test_provider_cortex_code.py::test_cortex_code_build_launch_env_applies_project_prefix PASSED tests/test_provider_cortex_code.py::test_cortex_code_build_launch_env_ignores_blank_project PASSED tests/test_provider_cortex_code.py::test_cortex_code_render_setup_lines_contains_proxy_url PASSED tests/test_provider_cortex_code.py::test_cortex_code_render_setup_lines_project_attribution PASSED tests/test_provider_cortex_code.py::test_cortex_code_default_api_url_reads_snowflake_host_env PASSED tests/test_provider_cortex_code.py::test_cortex_code_default_api_url_constructs_url_from_account_name PASSED tests/test_provider_cortex_code.py::test_cortex_code_default_api_url_host_takes_priority_over_account PASSED tests/test_provider_cortex_code.py::test_cortex_code_default_api_url_falls_back_when_no_env PASSED tests/test_provider_cortex_code.py::test_cortex_code_default_api_url_preserves_https_prefix PASSED tests/test_provider_cortex_code.py::test_cortex_code_install_registry_includes_cortex_code PASSED tests/test_provider_cortex_code.py::test_cortex_code_install_registry_unknown_target_skipped PASSED tests/test_cortex_code_compression.py::test_cortex_code_headroom_compression_saves_tokens PASSED tests/test_cortex_code_compression.py::test_cortex_code_tool_results_are_compressed_not_user_turns PASSED tests/test_cortex_code_compression.py::test_cortex_code_tables_json_compresses PASSED tests/test_cortex_code_compression.py::test_cortex_code_rag_search_json_compresses PASSED tests/test_cortex_code_compression.py::test_cortex_code_compression_is_lossless_on_key_content PASSED 20 passed, 1 warning in 1.91s ``` ## Real Behavior Proof - Environment: macOS, Python 3.11, headroom 0.27.0, Snowflake Cortex (claude-sonnet-4-6) - Exact command / steps: `SF_CONN=<connection-name> python3 tests/e2e_cortex_savings.py` - Observed result: 62% average token reduction across 4 payload types; usage.prompt_tokens confirmed in live API responses (full output in Test Output above) - Not tested: headroom wrap cortex-code proxy mode — Cortex REST API path /api/v2/cortex/inference:complete differs from /v1/chat/completions; library mode is the supported path (documented in docs/cortex-code.md Limitations) ```text Tokens saved : 22,077 prompt tokens (4 calls) Avg per call : 5,519 tokens / $0.01656 At 1k/day : $16.56/day | $6,044/year ``` ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes Pre-commit hooks skipped locally due to a GPG signing / ruff-format stash conflict in the dev environment. `ruff check` passes clean on all new files. --------- Co-authored-by: Cortex Code <noreply@snowflake.com> |
||
|
|
6904d47a01
|
feat(proxy): hot-reload live env knobs so a reused proxy picks them up without a restart (#1090)
## Description A small class of env vars is read by the proxy **live, per request** — the output-shaper family (`HEADROOM_OUTPUT_SHAPER`, `HEADROOM_VERBOSITY_LEVEL`, `HEADROOM_EFFORT_ROUTER`, `HEADROOM_MECHANICAL_EFFORT`, `HEADROOM_VERBOSITY_AUTOTUNE`, `HEADROOM_OUTPUT_HOLDOUT`), or captured at import (`HEADROOM_INTERCEPT_READ_MIN_CHARS`). The proxy reads them from its own process environment, fixed at launch. But `headroom wrap` reuses an already-running proxy (it restarts only on startup-config drift), so a value exported *after* the proxy started silently no-op'd — e.g. `export HEADROOM_OUTPUT_SHAPER=1` had zero effect on a reused proxy on `:8787`. This PR makes those live knobs **hot-reloadable**: `headroom wrap` pushes them to the running proxy, which applies them in memory — no restart (a restart would cold-start the ML stack, drop in-flight requests, and lose CCR/router caches). _No linked issue._ ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `headroom/proxy/runtime_env.py` (new): single source of truth registering the live knobs + a thread-safe process-global override store. `getenv()` (override-then-env) is a drop-in for `os.environ.get`; behaviour is byte-identical when no override is set. - Readers rerouted through `runtime_env.getenv`: `output_shaper.py`, the anthropic holdout read, and the ast-grep threshold (now a live read, not an import-time constant). - Proxy: loopback-only `POST /admin/runtime-env` applies overrides in memory; `/health` → `config.runtime_env` surfaces the live values so reuse is observable. - `wrap`: after attaching to a proxy (all call sites), best-effort push of the session's **explicitly-set** knobs. No-ops if nothing is set, `--no-proxy`, the proxy is unreachable, or it predates the endpoint (404). Only explicitly-set knobs are pushed, so a session never clobbers another with a default it never asked for. - Docs: README + output-token-reduction guide document the global-override caveat. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text $ python -m pytest tests/test_runtime_env.py -q 16 passed $ python -m pytest tests/test_runtime_env.py tests/test_output_shaper.py -q 50 passed $ ruff check headroom/proxy/runtime_env.py headroom/proxy/output_shaper.py headroom/proxy/handlers/anthropic.py headroom/proxy/interceptors/astgrep.py headroom/proxy/server.py headroom/cli/wrap.py All checks passed! $ mypy headroom/proxy/runtime_env.py Success: no issues found in 1 source file ``` ## Real Behavior Proof - Environment: local macOS, Python 3.12 `.venv`, branch `fix/runtime-env-hot-reload` at the PR head. - Exact command / steps: ran the test suites above. The 16 new `test_runtime_env` tests exercise the registry/store, overrides reaching the shaper + the ast-grep threshold, the `POST /admin/runtime-env` apply + `/health` reflect + loopback-only 404 + 400-on-non-object, and the wrap push payload / no-op / error-swallow paths. - Observed result: 50 passed; ruff + mypy clean on the changed modules; an override set via the endpoint is read by `getenv()` at the shaper and surfaced in `/health` config. - Not tested: a literal two-terminal manual session (start a proxy, `headroom wrap` a second session, `export HEADROOM_OUTPUT_SHAPER=1`, confirm the reused proxy picks it up). The behaviour is covered by the endpoint + wrap-push integration tests, but was not exercised by hand here. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - **Inherent caveat (documented):** overrides are global to the proxy — one process serves every attached wrapper, so the last explicit setting wins. No mechanism (restart or hot-reload) can give two sessions on one shared proxy different output-shaper settings. - **Scope:** startup-captured settings (`HEADROOM_TARGET_RATIO` etc.) are intentionally out of scope — a fresh proxy already gets them and they ride the existing `/health` config channel. - **Merge blocker:** this branch is currently **CONFLICTING with `main`** and needs a rebase/merge before it can land. - CHANGELOG.md left unchanged — releases are managed by release-please from conventional commits. |
||
|
|
26be2c39cb
|
feat(cli): add headroom update command and release banner (#1088)
## Description Adds a `headroom update` self-update command and a passive "update available" banner, so users no longer need to remember the right `pip`/`pipx`/`uv` incantation for their environment, and long-running proxies get nudged when they drift behind a release. Closes #1087 ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Documentation update ## Changes Made - `headroom/cli/update.py` — `headroom update` command. `detect_install_method()` resolves the install (git checkout, editable, Docker, pipx, uv tool, venv/conda, `pip --user`, externally-managed system Python per PEP 668, writable global) and builds the matching upgrade. pip path always uses `sys.executable -m pip` so it can't touch the wrong interpreter. Refuses with guidance where self-update is unsafe. Flags: `--check`, `--yes`, `--pre`, `--extras`. - `headroom/update_check.py` — best-effort PyPI check (stdlib `urllib`, no new dep). Split into a daemon-thread probe that caches to `~/.headroom/update_check.json` (≤ once/day) and a cache-only `format_update_notice()`. Opt-out `HEADROOM_UPDATE_CHECK=off`; skipped in `--stateless`, CI, Docker, checkouts. - `headroom/cli/main.py`, `headroom/cli/__init__.py` — register `update`; fire the background check from the group callback (skipped for `update`). - `headroom/cli/proxy.py` — render the one-line notice after the startup banner (best-effort, never blocks). - `README.md` — "Updating" section + opt-out env var. - Tests: `tests/test_update_check.py`, `tests/test_cli_update.py`. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ pytest tests/test_update_check.py tests/test_cli_update.py -q 42 passed in 1.63s $ ruff check headroom/cli/update.py headroom/update_check.py headroom/cli/main.py All checks passed! $ mypy headroom/update_check.py headroom/cli/update.py Success: no issues found in 2 source files ``` ## Real Behavior Proof - Environment: macOS, Python 3.11, source checkout - Exact command / steps: `python -m headroom.cli update --help`; `detect_install_method()` in the checkout - Observed result: command + flags render; in a checkout `detect_install_method()` returns `kind=checkout, can_self_update=False` ("update with `git pull`") and `format_update_notice()` returns `None` (dev tree not nagged) - Not tested: live PyPI fetch and a real pipx/uv-tool upgrade on this machine (covered by unit tests with mocked `urllib`/`subprocess`) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes - CHANGELOG.md is release-please-managed, so it is intentionally not hand-edited (N/A above). - Update check uses stdlib `urllib` because `httpx` lives only in the `[proxy]` extra — the base CLI must stay dependency-light. |
||
|
|
a99dc61424
|
feat: output-token reduction — verbosity shaper, per-user learning, counterfactual savings (#965)
## Description
Adds the first levers that reduce the tokens the model **writes back**
(output), complementing Headroom's existing input compression. Output
costs 5× input on Opus-class models and is full of waste (ceremony,
restated code, deep "thinking" on routine steps). Two phases in one
self-contained PR off `main`: the request-side output shaper, then
per-user verbosity learning plus an honest counterfactual savings
estimator and dashboard surfacing.
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- **Output shaper** (`output_shaper.py`, opt-in
`HEADROOM_OUTPUT_SHAPER=1`): cache-safe verbosity steering appended to
the system-prompt tail (5 levels); effort routing that lowers
`output_config.effort` on mechanical tool-result continuations; legacy
`thinking.budget_tokens` clamp. Never injects effort where absent, never
toggles `thinking.type`.
- **`headroom learn --verbosity`**: mines Claude Code transcripts for
behavioral signals (interrupts, length-adaptive fast-skips, echo ratio),
recommends a verbosity level (heuristic + optional `--llm-judge`), and
seeds the savings baseline.
- **Counterfactual estimator** (`output_savings.py`): per-stratum
synthetic-control (estimated) + A/B holdout (measured) with a propagated
95% CI; conversation-stable arm assignment for A/B validity and
prefix-cache safety.
- **AIMD verbosity controller** (`verbosity_controller.py`):
additive-increase / fast-back-off state machine; live signal emission
gated off by default.
- **Wiring + surfaces**: shaper resolves the learned level; recording
rides the existing `transforms_applied` channel through the outcome
funnel (no `RequestOutcome` changes); `headroom output-savings` CLI;
dashboard "Output Tokens Saved" card.
- **Docs**: simple-words user guide + design doc with the counterfactual
methodology.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ pytest tests/test_output_savings.py tests/test_output_savings_cli.py \
tests/test_verbosity_learn.py tests/test_verbosity_controller.py \
tests/test_output_shaper.py -q
94 passed in 0.54s
$ pytest tests/test_request_outcome.py tests/test_handler_outcome_tag_invariant.py \
tests/test_proxy_dashboard_stats_cache.py -q
44 passed
$ ruff format --check .
831 files already formatted
$ mypy headroom --ignore-missing-imports
Success: no issues found in 361 source files
```
## Real Behavior Proof
- Environment: macOS, Python 3.12 (`.venv`), `anthropic` 0.76, live API
model `claude-opus-4-8`.
- Exact command / steps: `HEADROOM_OUTPUT_SHAPER=1`; `headroom learn
--verbosity --apply` (seeds level + baseline); `python
scripts/eval_output_shaper.py A` (live before/after); simulate holdout
traffic then `headroom output-savings`.
- Observed result: code-review ask — baseline 1,750 output tokens → L2
1,354 (−22.7%) → L3 599 (−65.8%), same bugs found. `learn --verbosity`
on 24 real sessions → 11% interrupt / 26% fast-skip → L3 (high
confidence). Measured A/B path → 31.7% reduction (95% CI 27.7%–35.7%).
94 new tests + 44 existing outcome/dashboard tests green; ruff + mypy
clean.
- Not tested: live streaming-path recording exercised only via unit
tests (the `transforms_applied` funnel is shared across paths); runtime
AIMD signal emission is gated off by default and not exercised live.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Additional Notes
Output savings are counterfactual (we never observe what the model
*would* have written), so the estimator separates **estimated** (vs a
learned baseline) from **measured** (A/B holdout via
`HEADROOM_OUTPUT_HOLDOUT`) and always reports a confidence band — never
a single made-up number. CHANGELOG left unchecked (release-please
manages it). Runtime AIMD self-tuning is intentionally a TODO
(controller built/tested; live signal emission gated behind
`HEADROOM_VERBOSITY_AUTOTUNE`).
|
||
|
|
0b4a4bd483
|
fix: support Copilot Business subscription auth (#641)
## Description Adds a first-party `headroom copilot-auth login` flow for Copilot subscription mode and uses the resulting Copilot OAuth token to perform GitHub's Copilot token exchange before launching the wrapped Copilot CLI. This fixes Business/Enterprise Cloud accounts where a generic GitHub/Copilot token can read Copilot account metadata but is rejected by the Copilot token exchange endpoint. It also avoids treating GitHub.com Enterprise Cloud account URLs such as `github.com/enterprises/acme` as API hostnames. Fixes #635 Related: #488, #610 Builds on #576 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [x] Documentation update - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Adds `headroom copilot-auth login` and `headroom copilot-auth status`. - Stores a Headroom-specific Copilot OAuth token under Headroom's state dir. - Exchanges reusable Copilot OAuth tokens with Copilot Chat-compatible headers before subscription-mode launch. - Carries the resolved Copilot API endpoint into `headroom wrap copilot --subscription`. - Handles GitHub.com Enterprise Cloud URLs without synthesizing invalid `api.github.com/enterprises/...` hosts. - Adds focused unit tests and README guidance for subscription login. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```console ruff check headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py # All checks passed! ruff format --check headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py # 9 files already formatted python -m py_compile headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py uv run --no-project --with pytest --with pytest-asyncio --with click --with rich --with opentelemetry-api --with pydantic --with tiktoken --with 'litellm==1.82.3' --with fastapi --with uvicorn --with 'httpx[http2]' --with openai --with mcp --with magika --with zstandard --with websockets --with onnxruntime --with transformers --with watchdog --with sqlite-vec pytest tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_cli_proxy_env.py tests/test_copilot_subscription_smoke.py # 127 passed ``` Local note: `uv run pytest ...` against the project currently fails before running tests because `uv.lock` has an unrelated `gitpython` wheel/version mismatch. ## Manual Validation I tested this with an existing GitHub Copilot Business subscription associated with a GitHub.com Enterprise Cloud account. The Enterprise Cloud value I tested was in the form: ```text github.com/enterprises/<enterprise> ``` The tested flow was: ```text headroom copilot-auth login headroom wrap copilot --subscription -- --model gpt-5.4 ``` This validated that Headroom does not treat github.com/enterprises/<enterprise> as a Copilot API hostname. Instead, token exchange uses GitHub.com and Headroom routes subscription-mode traffic to the Copilot API endpoint returned by GitHub for the signed-in account. I did not test this with GitHub Enterprise Server or a custom enterprise domain such as ghe.example.com. No tokens, request IDs, or organization-specific identifiers are included in this PR. ## Real Behavior Proof - Environment: macOS Darwin, Python 3.12.7, local checkout on `codex/copilot-business-auth`. - Exact command / steps: Ran `headroom copilot-auth login`, then launched `headroom wrap copilot --subscription -- --model gpt-5.4` with a GitHub Copilot Business subscription tied to a GitHub.com Enterprise Cloud account. - Observed result: Headroom did not treat `github.com/enterprises/<enterprise>` as a Copilot API hostname; token exchange used GitHub.com and subscription traffic was routed to the Copilot API endpoint returned for the signed-in account. The latest focused Copilot auth/proxy tests pass locally (`127 passed`). - Not tested: GitHub Enterprise Server or custom enterprise domains such as `ghe.example.com`; Windows Credential Manager integration still needs confirmation from someone on Windows. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing targeted unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A ## Additional Notes Acknowledgement: the OAuth/token-exchange behavior was informed by `anomalyco/opencode-copilot-auth` by Aiden Cline. No tokens are printed by the new login/status commands; only a short SHA-256 fingerprint is displayed for troubleshooting. The interactive login is included because the missing piece is not just an Enterprise URL or routing hint. For GitHub.com Enterprise Cloud accounts, URLs like `github.com/enterprises/acme` identify the enterprise account but are not Copilot API hostnames; token exchange still happens through GitHub.com and then returns the account-specific Copilot API endpoint. A command-line enterprise argument can help for true GitHub Enterprise Server/custom-domain deployments, but it cannot produce the Copilot OAuth token class that the token-exchange endpoint accepts. Ideally, Headroom would avoid an extra interactive login and reuse an existing GitHub/Copilot CLI session everywhere. In practice, some reusable-looking tokens can read Copilot account metadata but are rejected by Copilot token exchange, which leaves Business/Enterprise Cloud users with missing model catalogs. The explicit login command is the smallest independent way to obtain and persist the token needed for that exchange without asking users to pass a secret on the command line. --------- Co-authored-by: jbelanger <your-username@users.noreply.github.com> |
||
|
|
c71592d421
|
feat(memory): add opt-in Apple-GPU (MPS) embedding runtime (#766)
## Description
On Apple-Silicon Macs — especially fanless models like the MacBook Air
(M5) — running the proxy with memory context injection can pin the CPU
while embedding. The embedding work runs an uncapped session on the CPU,
saturating multiple cores, which starves the proxy's asyncio loop and
leads to request timeouts.
This PR adds an **opt-in** runtime that offloads the memory embedder to
the Apple GPU (MPS). Setting `HEADROOM_EMBEDDER_RUNTIME=pytorch_mps`
routes embedding through the torch `sentence-transformers` backend on
MPS instead of the default ONNX CPU embedder, moving the work off the
CPU and keeping the proxy responsive.
The default behavior is unchanged — the feature is strictly opt-in,
env-var only, and falls through to the existing default embedder
selection (with a warning) whenever MPS or the torch dependencies are
unavailable.
Fixes: N/A — no tracking issue (surfaced while running codex auto-review
through
the proxy on a fanless MacBook Air (M5)).
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [x] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- **Runtime selection** (`headroom/proxy/memory_handler.py`): read
`HEADROOM_EMBEDDER_RUNTIME`; when set to `pytorch_mps` **and** MPS is
actually available, route the memory embedder to the torch
`sentence-transformers` backend (Apple GPU).
- If MPS is unavailable or torch/sentence-transformers is not installed,
log a warning and fall through to the existing default embedder
selection (ONNX when available, else the pre-existing local
sentence-transformers fallback) — no crash. The default (env var unset)
is unchanged. Env-var only
- **MPS serialization** (`headroom/memory/adapters/embedders.py`):
`LocalEmbedder` now funnels every `encode()` through a dedicated
single-worker `ThreadPoolExecutor` when the resolved device is MPS.
torch-MPS is not thread-safe, and the existing `run_in_executor(None,
...)` dispatch would otherwise let concurrent proxy requests call MPS
from multiple threads. CPU/CUDA keep the shared default executor
(behavior unchanged). `close()` also drops the cached model so re-use
after close re-initializes cleanly.
- **Packaging** (`pyproject.toml`): new `pytorch-mps` extra (`torch` +
`sentence-transformers`), **platform-gated to macOS** (`; sys_platform
== 'darwin'`) since MPS is Apple-Silicon-only. Deliberately left out of
`[all]` (its deps already arrive via `[ml]`/`[memory]`).
- **Tests** (`tests/test_memory/test_embedder_mps_serialization.py`):
regression coverage for the serialized executor, concurrency safety (no
SIGABRT), CPU-path default behavior, and close/re-use re-initialization.
- **Docs**: `wiki/{configuration,memory,macos-deployment}.md`,
`docs/content/docs/{configuration,installation,memory}.mdx`,
`README.md`, `CHANGELOG.md`.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed (CPU-offload + concurrency profiling on
Apple Silicon)
## Test Output
```
$ pytest -v tests/test_memory/test_embedder_mps_serialization.py
collected 4 items
tests/test_memory/test_embedder_mps_serialization.py::test_cpu_uses_shared_executor PASSED [ 25%]
tests/test_memory/test_embedder_mps_serialization.py::test_mps_creates_single_worker_executor PASSED [ 50%]
tests/test_memory/test_embedder_mps_serialization.py::test_mps_concurrent_embeds_do_not_crash PASSED [ 75%]
tests/test_memory/test_embedder_mps_serialization.py::test_mps_reembed_after_close_recreates_executor PASSED [100%]
============================== 4 passed in 6.92s ===============================
$ ruff check headroom/memory/adapters/embedders.py headroom/proxy/memory_handler.py tests/test_memory/test_embedder_mps_serialization.py
All checks passed!
$ mypy headroom/memory/adapters/embedders.py headroom/proxy/memory_handler.py
Success: no issues found in 2 source files
$ pytest -q tests/test_memory/ tests/test_memory_handler_concurrent_init.py tests/test_memory_handler_native_ops.py
553 passed, 1 skipped in 13.51s
```
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable
## Additional Notes
**Why MPS (and not CoreML or a thread cap):** measured on an
Apple-Silicon Mac, the default uncapped CPU embedding session saturates
the cores; the same model on MPS runs at a fraction of the CPU (≈8x
lower sustained CPU utilization in profiling) while producing
**byte-identical embeddings** (cosine distance ≈ 0 across runtimes), so
relevance/ranking is unchanged. A CoreML execution-provider path was
evaluated and rejected: the default optimized ONNX model uses fused ops
that fall back to CPU under CoreML (no offload), and a full-precision
re-export was impractical (very low throughput + multi-GB memory). MPS
via `sentence-transformers` was the only practical GPU offload.
**Why serialization is mandatory:** torch-MPS is not thread-safe —
concurrent encode calls from a multi-worker executor abort with
`-[IOGPUMetalCommandBuffer validate]: failed assertion 'commit an
already committed command buffer'` (reproduced deterministically; a
single-worker executor resolves it).
Under concurrent load the serialized single-GPU-stream throughput meets
or exceeds the parallel CPU path while using a fraction of the cores.
**Scope / boundary:** this targets the Python **memory** embedder, which
is live on the proxy request path (memory context injection). The
Rust-backed SmartCrusher compression path is unaffected and remains
non-configurable from Python by design.
**Safety:** default behavior is unchanged (ONNX, no torch). The feature
is opt-in, env-var only, macOS-gated at the packaging layer, and
degrades gracefully (warn + the existing default embedder selection)
when MPS or the dependencies are unavailable.
|
||
|
|
650b776dd5
|
docs(install): document corporate SSL-inspection workaround (#735) (#775)
Fixes #735. Adds a README **Install → Corporate / SSL-inspection environments** subsection. Behind a corporate MITM / SSL-inspection proxy, `pip install "headroom-ai[all]"` fails with `CERTIFICATE_VERIFY_FAILED` because the build downloads `rustup` (via maturin) and the runtime assets over a connection the local TLS stack doesn't trust. The new section documents: - Installing Rust first (so maturin doesn't fetch `rustup`), and preferring a prebuilt wheel. - Trusting the corporate CA (`REQUESTS_CA_BUNDLE` / `SSL_CERT_FILE` / `CURL_CA_BUNDLE`) for the two TLS-fetched runtime assets: `cdn.pyke.io` (ONNX Runtime; `ORT_STRATEGY=system` fallback) and `huggingface.co` (kompress-base model; `HF_HUB_OFFLINE` fallback). Docs only; no code paths changed. |
||
|
|
2533f7703e
|
fix(ccr): make retrieval TTL configurable (#715)
## Description Make the CCR retrieval store TTL configurable so long-running agent jobs can keep `headroom_retrieve` markers resolvable beyond the previous hard-coded 300-second window. Fixes #714 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add `HEADROOM_CCR_TTL_SECONDS` for the global CCR `CompressionStore` default TTL, with validation and fallback to 300 seconds. - Expose the effective CCR TTL as `store.default_ttl_seconds` from `/v1/retrieve/stats`. - Distinguish missing vs expired CCR retrieval failures in `/v1/retrieve`, `/v1/retrieve/{hash}`, tool-call handling, and CCR response handling. - Update CCR docs, README wording, and CHANGELOG for the user-facing TTL behavior. - Add regression tests for env-configured TTL, invalid env fallback, explicit TTL precedence, stats exposure, and expired retrieval detail. ## Reproduction Before this change, the proxy-global CCR store always used the default 300-second TTL when callers used `get_compression_store()` without an explicit `default_ttl`. A long-running agent job could receive a `<<ccr:...>>` marker and fail later with a generic not-found/expired response after the fixed 5-minute retention window. The new tests cover this by setting `HEADROOM_CCR_TTL_SECONDS`, creating CCR entries through the same global store used by the proxy, and asserting that stored entries and `/v1/retrieve/stats` use the configured retention. ## Real behavior proof Setup tested: - macOS 15.7.2 - Python 3.13.9 via `uv run --frozen --extra dev --extra proxy` - Local Headroom proxy subprocess: `python -m headroom.proxy.server` - Proxy config: `HEADROOM_TELEMETRY=off`, `HEADROOM_CACHE_ENABLED=false`, `HEADROOM_RATE_LIMIT_ENABLED=false` - Provider/model: no live upstream provider needed; exercised local `/v1/compress` -> `/v1/retrieve` CCR HTTP flow with model `gpt-4o` Exact steps run after the patch: 1. Start a real Headroom proxy process with `HEADROOM_CCR_TTL_SECONDS=7200`. 2. POST a 200-item tool-result payload to `/v1/compress`. 3. Extract the emitted `<<ccr:...>>` marker hash from the compressed messages. 4. GET `/v1/retrieve/stats`. 5. POST `/v1/retrieve` with the marker hash. 6. Repeat with `HEADROOM_CCR_TTL_SECONDS=1`, wait 1.5 seconds, and retrieve the same way to verify explicit expiration reporting. Observed result: ```json { "long_ttl": { "ccr_hash": "b473e632aa47", "retrieve_status": 200, "retrieved_content_has_result_199": true, "stats_default_ttl_seconds": 7200, "stats_entry_count": 1, "ttl_seconds": 7200 }, "short_ttl_expired": { "ccr_hash": "b473e632aa47", "retrieve_detail": "Entry expired (CCR TTL: 1 seconds; age: 2 seconds)", "retrieve_status": 404, "stats_default_ttl_seconds": 1, "stats_entry_count": 1, "ttl_seconds": 1 } } ``` What I did not test: - A live OpenRouter/OpenAI/Anthropic upstream request. - A durable/non-memory CCR backend. - A literal 5+ minute wall-clock wait; the process E2E used `7200` to prove configured retention and `1` second to prove expiration behavior quickly. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ## Test Output ```bash UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --frozen --extra dev --extra proxy pytest tests/test_compression_store.py tests/test_proxy_ccr.py tests/test_ccr_response_handler.py tests/test_ccr_response_handler_extra.py # 152 passed, 2 warnings in 12.87s UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --frozen --extra dev --extra proxy pytest tests/test_adapter_hooks.py tests/test_toin_feedback.py tests/test_toin_fixes.py # 55 passed, 13 skipped, 1 warning in 0.53s UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --frozen --extra dev --extra proxy ruff check . # All checks passed! UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --frozen --extra dev --extra proxy ruff format --check . # 776 files already formatted UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --frozen --extra dev --extra proxy mypy headroom # Success: no issues found in 346 source files ``` Existing warnings observed in the targeted tests were unrelated to this change: - AnthropicProvider tiktoken approximation warning in proxy CCR tests. - Deprecated `ToolIntelligenceNetwork.get_recommendation()` warning in existing TOIN assertions. ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) Not applicable. ## Additional Notes No new dependencies. Default behavior remains 300 seconds unless `HEADROOM_CCR_TTL_SECONDS` is set. |
||
|
|
74392b238e
|
feat: switch Kompress default to kompress-v2-base with weight-only int8 ONNX (#799)
## Summary Replaces `chopratejas/kompress-base` with **`chopratejas/kompress-v2-base`** as the default Kompress text-compression model (the fallback for content not handled by structured compressors), using a new **weight-only int8 ONNX** artifact that is fp32-equivalent at 2.2x less memory. ## Why v2 is the same dual-head ModernBERT (token classifier + span CNN), LoRA-trained on the v2 dataset. The HF repo originally shipped PyTorch weights only — pointing Headroom at it naively would have forced the heavy `[ml]` (torch) path on every `[proxy]` install. We exported ONNX artifacts reproducing the v1 loader contract (single `final_scores` output) and published them to the HF repo. ## Eval (labeled dataset_v2 test split, n=500, threshold 0.5) | artifact | size | f1 | must_keep_recall | keep_rate | fp32 agreement | |---|---|---|---|---|---| | fp32 (reference) | 601 MB | 0.9128 | 0.9770 | 0.8100 | 100% | | **int8-wo (default)** | **261 MB** | **0.9130** | **0.9765** | **0.8097** | **99.6%** | | fp16 | 301 MB | 0.9129 | 0.9771 | 0.8099 | 99.9% | | int4-wo | 230 MB | 0.9102 | 0.9825 | 0.8354 ⚠️ | 94.5% | | int8-dynamic | 271 MB | 0.9041 | 0.9868 | 0.8857 ⚠️ | 90.5% | Weight-only int8 (MatMulNBits) keeps activations fp32, avoiding the upward score bias that makes dynamic int8 keep ~7% more tokens (≈40% less compression savings). Quantized candidates were generated and eval-gated by a Modal job in the kompress repo (`modal_jobs/export_onnx_v2.py`) against the labeled test split. ## Changes - Default model id → `chopratejas/kompress-v2-base` - ONNX artifact resolution tries candidates in order (**int8-wo → fp32 → v1 int8**), falling through on download miss **or session-load failure** — onnxruntime builds without the MatMulNBits 8-bit kernel fall back to fp32 instead of losing Kompress - `HEADROOM_KOMPRESS_ONNX_FILENAME` env pins an exact artifact - `scripts/export_kompress_v2_onnx.py`: reproducible fp32 export (loads the merged v2 checkpoint, traces the `final_scores` contract, verifies vs PyTorch) - `.gitignore`: local `onnx/` artifacts dir; allowlist the export script ## Testing - End-to-end: fresh `KompressCompressor().preload()` resolves int8-wo from HF, loads on onnxruntime 1.23 (CPU, no torch), compresses with error/traceback content preserved - fp32 export verified lossless vs PyTorch (max |Δscore| = 0.0, 100% keep agreement) - ruff check + format clean, mypy clean, 63 targeted tests pass |
||
|
|
fb59f83fab
|
Merge pull request #592 from divyanshus2404/my-first-contribution
docs: add troubleshooting section |
||
|
|
67f005e434 | Address PR feedback: Move troubleshooting, refine rust docs, and update install options | ||
|
|
cd52556d08
|
docs: add star history in readme |