Commit graph

147 commits

Author SHA1 Message Date
Tejas Chopra
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.
2026-07-30 22:59:41 -07:00
AxelRay
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 f74d874, branch
fix/readme-cache-aligner-detector-only-2592
- Exact command / steps: `rg -n "CacheAligner" README.md`; `rg -n
"CacheAligner.*stabilizes prefixes" README.md || echo OLD_CLAIM_ABSENT`;
`git diff --stat`
- Observed result: both README CacheAligner bullets use detector-only
wording; old "stabilizes prefixes" claim for CacheAligner is absent;
diff is README.md only (+2/-2)
- Not tested: docs-site marketing.tsx and wiki/index.md still carry
older CacheAligner marketing copy (out of scope for this README issue);
no runtime proxy/pytest path (docs-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
- [ ] 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
- [x] I did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)

## Screenshots (if applicable)

N/A

## Additional Notes

- Scope is README only for #2592. Marketing site / wiki wording can be a
follow-up if maintainers want the same detector-only language there.

Co-authored-by: axelray-dev <axelray-dev@users.noreply.github.com>
2026-07-27 06:42:17 -07:00
Rod Boev
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.
2026-07-23 15:43:03 -07:00
Tejas Chopra
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)
2026-07-22 20:59:24 -07:00
kaz
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>
2026-07-15 21:40:58 +00:00
Dávid Balatoni
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>
2026-07-15 21:06:30 +00:00
LunarECL
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 4f22cbb0
3 failed, 7723 passed, 515 skipped in 262.64s
  FAILED tests/test_cli/test_wrap_claude_base_url.py::test_wrap_marker_is_stale_when_pid_reused
  FAILED tests/test_rtk_session_savings.py::test_rtk_reader_returns_none_on_nonzero_exit
  FAILED tests/test_rtk_session_savings.py::test_lean_ctx_reader_returns_none_on_failure_and_logs
  → all three reproduce identically on unmodified origin/main (4f22cbb0), run the
    same way (same worktree + venv, sources switched): 3 failed, 7707 passed —
    this branch = baseline + the 16 new tests, nothing else changes.
    (The pre-rebase run against e8151f05 showed the same shape: one order-dependent
    flake that also reproduced on its baseline; these are env/order-dependent.)

$ uv run pytest tests/test_cli/ -q     # post-rebase
542 passed + 1 of the pre-existing failures above   # includes the 16 new test_wrap_omp.py tests

$ uv run ruff check . ; echo ruff-check-exit:$?
All checks passed!
ruff-check-exit:0

$ uv run ruff format --check .         # post-rebase
1 pre-existing violation: headroom/proxy/handlers/anthropic.py — flagged identically
on unmodified origin/main (not touched by this PR); every file this PR touches is clean

$ uv run mypy headroom               # post-rebase; output redirected to file; exit captured
Success: no issues found in 409 source files
mypy-exit:0
```

## Real Behavior Proof

- Environment: macOS 15 (arm64, M1 Pro), Python 3.12.13 (uv venv,
editable install incl. Rust `_core`), headroom @ this branch, base
extras only (no `[ml]`), Anthropic account signed into omp. Initial
proof ran on base e8151f05 with omp 16.3.6 (`@oh-my-pi/pi-coding-agent`
via bun); re-validated after the rebase onto 4f22cbb0 with omp 16.3.11 —
fresh numbers in the rebase-validation comment.

- Exact command / steps: four scenarios, run in this order —
1. Mechanism probe (why models.yml, not env): local HTTP probe server on
`127.0.0.1:18999`; ran `omp -p "say ok" --model claude-fable-5
--no-session --no-tools` once with
`ANTHROPIC_BASE_URL=http://127.0.0.1:18999`, once with
`~/.omp/agent/models.yml` containing `providers.anthropic.baseUrl:
http://127.0.0.1:18999`.
2. One-command path: `headroom wrap omp --no-rtk --port 8790 -- -p "Read
CHANGELOG.md and count how many '### Fixed' headings it contains. Answer
with just the number." --model claude-fable-5 --no-session --max-time
180`
3. Routing stats: separate proxy on :8788, wrap with `--no-proxy`, then
`GET /stats`.
4. Restore: `headroom unwrap omp`, plus an isolated
`PI_CODING_AGENT_DIR=/tmp/omp-agent-test` run with a pre-existing user
`models.yml`, then `cmp` against the original.

- Observed result: end-to-end routing through the proxy proven for every
scenario —
- Probe: env-var run → **0 probe hits**, omp answered normally
(bypassed). models.yml run → **9 hits on `/v1/messages?beta=true`** with
real Messages bodies. This is the routing mechanism the wrap uses.
- One-command run: wrap started the proxy ("Proxy ready on
http://127.0.0.1:8790"), wrote the override (`models.yml:
providers.anthropic.baseUrl=http://127.0.0.1:8790/p/headroom-wrap-omp`),
launched omp, and omp answered **"7"** (correct — real `read` tool work
through the proxy). Proxy log for the session (3 requests,
`anthropic_messages` path):
    ```
PERF model=claude-fable-5 msgs=1 tok_before=36 cache_read=0
cache_write=61939 cache_hit_pct=0
PERF model=claude-fable-5 msgs=3 tok_before=796 cache_read=0
cache_write=63308 cache_hit_pct=0
PERF model=claude-fable-5 msgs=5 tok_before=935 cache_read=63308
cache_write=215 cache_hit_pct=100
    ```
    Prompt caching survives the proxy (100% hit on the follow-up turn).
- Routing stats (:8788 session): `requests.total: 2, by_provider:
{"anthropic": 2}, by_model: {"claude-fable-5": 2}`, per-project prefix
`/p/headroom-wrap-omp` attributed.
- Unwrap: `Removed wrap-created models.yml` (file gone); isolated
pre-existing-file run: backup created, user's `my-gw` provider preserved
in the managed file, and after `unwrap omp` the restored file is
**byte-identical** (`cmp` clean).
- Compression: **not observed in this environment** — `tok_saved=0`,
`transforms=router:noop` / `too_small`. Honest reading: omp minimizes
its own tool outputs client-side (a 300-item JSON tool result reached
the proxy at only ~657 tokens) and the `[ml]` text compressor wasn't
installed; small print-mode payloads sit below crush thresholds, and
passthrough-by-default is the documented safety contract. The wrap's
value here is proven at the routing/lifecycle/cache layer; compression
numbers will match whatever the proxy does for a given content mix.

- Not tested: Windows / Linux; lean-ctx mode with omp
(`HEADROOM_CONTEXT_TOOL=lean-ctx` — `lean-ctx init --agent omp` depends
on lean-ctx recognizing the agent; failure degrades with a warning by
design); long interactive (non `-p`) sessions; `--memory` / `--learn` /
`--code-graph` flags combined with omp; OAuth-vs-API-key matrix beyond
my local account.

## 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
- [ ] New and existing unit tests pass locally with my changes — all
except the 3 documented pre-existing failures, which fail identically on
unmodified origin/main
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A — terminal evidence inline above.

## Additional Notes

- The models.yml override is regenerated from the pristine backup on
every wrap, so re-running with a different `--port` updates the endpoint
idempotently and the backup is never clobbered.
- Scope note from #1149 stands: this routes omp's **Anthropic** provider
family. omp's other providers (OpenAI-direct, Gemini, ...) resolve their
endpoints from their own registry entries; users can already point those
at Headroom with their own custom provider in `models.yml`.
- `headroom/providers/omp/` deliberately contains no install-time / MCP
pieces — this is the thin wrap + unwrap slice only.

---------

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-15 19:30:19 +00:00
roman-t3a
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 `b17c6d81` / `93a1f211`
(Codex/Cursor/Aider extraction).
- Routing uses the session env var only (not `config.toml` endpoint
override) so `grok login` session auth continues to work.
- Manual E2E wrap/unwrap with real Grok sessions is left for maintainer
verification.

---------

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-15 18:51:38 +00:00
JD Davis
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.
2026-07-15 18:37:20 +00:00
David Wells
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>
2026-07-14 16:07:39 -04:00
Noam Asor
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>
2026-07-14 16:07:17 -04:00
Gregory R. Warnes
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>
2026-07-14 13:25:37 -04:00
Rod Boev
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.
2026-07-14 13:25:29 -04:00
Dylan Russell, MD
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>
2026-07-14 13:25:21 -04:00
Ship-Wright
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>
2026-07-14 13:25:14 -04:00
Bhavya Chopra
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>
2026-07-08 09:16:11 -05:00
Dustin Burke
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. -->
2026-07-07 23:27:56 -05:00
Parideboy
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` (9fbd47ba).
- Exact command / steps: reviewed the reversibility docs and code
referenced in the issue (lean-ctx
`docs/comparisons/vs-headroom.md#reversibility`,
`rust/src/proxy/ccr.rs`); edited the single table row; rendered the
Markdown table locally to confirm column alignment.
- Observed result: row now matches lean-ctx's documented scope, deploy
surfaces, and reversibility; table renders with the same five columns as
the surrounding rows.
- Not tested: independent runtime verification of every lean-ctx
recovery path (claims are per the lean-ctx maintainer's issue report and
linked docs/source).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: JD Davis <mxjerrett@gmail.com>
2026-07-07 23:06:04 -05:00
KET ⚡
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>
2026-07-07 12:51:22 -05:00
KET ⚡
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
2026-07-07 12:51:02 -05:00
KET ⚡
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>
2026-07-07 12:50:33 -05:00
Rob Francis
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>
2026-07-06 18:33:34 -05:00
Tejas Chopra
7208792ee8
Fix formatting in README.md 2026-07-06 09:07:54 -07:00
Tejas Chopra
480d22e6e2
Update token reduction statistics in README 2026-07-06 09:06:56 -07:00
Parideboy
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>
2026-07-03 12:10:34 -07:00
Manmit Singh
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
2026-06-30 08:47:47 -07:00
Tejas Chopra
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.
2026-06-28 22:36:41 -07:00
Parideboy
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>
2026-06-28 13:16:55 -07:00
Tejas Chopra
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).
2026-06-27 23:32:54 -07:00
Tejas Chopra
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.
2026-06-27 23:28:23 -07:00
Tejas Chopra
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
2026-06-27 14:48:43 -07:00
Lucas Santos
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>
2026-06-26 12:15:42 -05:00
JD Davis
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.
2026-06-25 13:40:02 -05:00
Ben Younes
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>
2026-06-24 10:15:19 -05:00
Lakshya Sharma
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.
2026-06-24 09:51:30 -05:00
Lakshya Sharma
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.
2026-06-24 09:48:37 -05:00
Lakshya Sharma
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.
2026-06-23 14:39:40 -05:00
Parideboy
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>
2026-06-22 19:05:38 -05:00
sfc-gh-nashukla
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>
2026-06-21 22:18:47 -07:00
Tejas Chopra
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.
2026-06-18 09:50:50 -07:00
Focused Instability
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.
2026-06-18 11:22:20 -05:00
Tejas Chopra
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`).
2026-06-16 21:06:43 -07:00
Joel Belanger
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>
2026-06-12 20:46:38 -05:00
Logan Kang
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.
2026-06-11 12:59:20 -05:00
Khalid Shaikh
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.
2026-06-11 11:01:52 -05:00
Hc
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.
2026-06-10 23:20:46 -05:00
Tejas Chopra
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
2026-06-09 23:28:40 -07:00
Devanshi Vyas
fb59f83fab
Merge pull request #592 from divyanshus2404/my-first-contribution
docs: add troubleshooting section
2026-06-06 12:08:07 -07:00
Divyanshu Singh
67f005e434 Address PR feedback: Move troubleshooting, refine rust docs, and update install options 2026-06-07 00:27:09 +05:30
Devanshi Vyas
cd52556d08
docs: add star history in readme 2026-06-05 16:49:46 -07:00