Commit graph

2630 commits

Author SHA1 Message Date
Tejas Chopra
e9cae0131b fix: expose compression latency bottlenecks
Add Codex WS unit-level timing and bounded parallel compression, clarify context-tool session savings, and avoid costly diff/log fallbacks to Kompress.
2026-05-12 13:34:08 -07:00
chopratejas
0f6df1fef0 docs(readme): redesign with lean-ctx-style crispness
- ASCII block logo replaces plain # heading
- Power-stats line + nav links above the fold
- Time-boxed section headings (30s / 60s)
- What-it-does bullets pruned to one clause each
- Agent table notes trimmed to ≤5 words with ● markers
- Pipeline internals + provider slices moved to collapsed <details>
- New When-to-use / When-to-skip section
- GIFs centered via HTML with captions
- Integrations and What's-inside remain collapsed <details>
2026-05-11 22:33:03 -07:00
chopratejas
3432ee3a96 docs: add README redesign spec (lean-ctx parity + Headroom-first blend) 2026-05-11 22:22:34 -07:00
Tejas Chopra
71348f407f
Merge pull request #458 from chopratejas/fix-litellm-azure-dashboard-compression-issues-454-455
fix(proxy,dashboard): correct savings reporting
2026-05-11 18:44:23 -07:00
Tejas Chopra
6643266ec0 fix(proxy,dashboard): correct savings reporting for backend-routed traffic and drop broken Compression Quality widget
Fixes #454, #455.

Streaming record_request paths were calling metrics without
attempted_input_tokens, so attempted_input_tokens_total stayed at 0 for
backend-routed traffic (litellm-azure, bedrock, anthropic streaming).
active_savings_percent then divided by zero and the dashboard headline
showed 0% even while compression was working. The three streaming sites
now pass the pre-compression request size as the attempted denominator,
matching the non-streaming sibling in openai.py.

The dashboard headline also falls back to proxy_savings_percent when
attempted is missing so historical 0% values self-heal.

The "Compression Quality" widget computed totalWaste / saved as a
percentage and could exceed 100. The metric is conceptually broken (the
two values measure different things across different surfaces — a perfect
semantic compressor surfaces zero waste signals and scores "low quality"
by this formula), not merely unbounded, so capping it just hides the
underlying confusion. Dropped the widget and the matching Quality column
on Recent Requests; removed the dead confidence getters.

For #454's diagnostic gap, added two visibility levers:
- --compress-user-messages CLI flag (+ HEADROOM_COMPRESS_USER_MESSAGES
  env) flips the router's skip_user_messages default off for workloads
  where the bulk of input lives in user messages (OpenAI/Azure chat with
  pasted code/RAG context).
- /stats now includes router.route_counts aggregating the router's
  protection categories (user_msg, system_msg, recent_code,
  excluded_tool, …) so operators can see why compression is low without
  local patching.

Analysis of the issue reporter's attached proxy_savings logs showed
day-on-day savings ranging 0.6%–76% based on workload shape (pasted user
content vs tool-output rounds), not a version regression — the dashboard
0% headline made workload variance look like a regression.
2026-05-11 18:07:30 -07:00
Tejas Chopra
f34748df7a
Merge pull request #457 from JerrettDavis/main
ci: correct live checks when credentials are missing
2026-05-11 16:09:48 -07:00
Tejas Chopra
5f96b836d2
Merge pull request #451 from cowwoc/feature/issue-265-lean-ctx
[codex] Add lean-ctx context tool support
2026-05-11 16:09:38 -07:00
Tejas Chopra
5cc3fd4852
Merge pull request #450 from chopratejas/fix/windows-ort-docker-signing
fix: Windows ORT builds and Docker signing retries
2026-05-11 16:09:29 -07:00
Gili Tzabari
4b061792b2 feat: add lean-ctx context tool support 2026-05-11 17:54:17 -04:00
JerrettDavis
7057227c84 test: fix websocket lifecycle import order 2026-05-11 14:51:18 -05:00
Tejas Chopra
d49eddacc9
Merge pull request #453 from cowwoc/fix/codex-ws-dashboard-performance
[codex] Populate Codex WS dashboard performance metrics
2026-05-11 12:12:36 -07:00
JD Davis
e6a9604217
Merge branch 'chopratejas:main' into main 2026-05-11 13:59:07 -05:00
Gili Tzabari
28a0e19deb Merge origin/main into fix/codex-ws-dashboard-performance 2026-05-11 14:45:30 -04:00
JerrettDavis
2fd48260f6 ci: skip live evals without credentials 2026-05-11 13:35:24 -05:00
Tejas Chopra
9a156105fa
Merge pull request #452 from cowwoc/fix/codex-ws-compression-executor
[codex] Bound Codex Responses compression work
2026-05-11 11:25:27 -07:00
Gili Tzabari
4279a7f353 fix: populate Codex WS dashboard performance metrics 2026-05-11 03:26:59 -04:00
Gili Tzabari
160989c43e fix(proxy): bound Codex Responses compression work 2026-05-11 03:02:58 -04:00
Tejas Chopra
c83687798b Fix Windows ORT builds and Docker signing retries 2026-05-10 20:59:28 -07:00
Tejas Chopra
d90d2caed3
Merge pull request #449 from chopratejas/codex-responses-compression
fix: Compress Codex Responses payloads
2026-05-10 20:33:40 -07:00
Tejas Chopra
ead07cc1b0 Format OpenAI Responses tests 2026-05-10 20:19:53 -07:00
Tejas Chopra
4fbfc02a39 Reduce Codex compression proxy logging 2026-05-10 17:37:42 -07:00
Tejas Chopra
478a75f510 Compress Codex Responses payloads 2026-05-10 17:27:47 -07:00
Tejas Chopra
73b0e56e97
Merge pull request #446 from chopratejas/codex-ws-cancel-logging
fix: Codex ws cancel logging
2026-05-10 13:21:09 -07:00
Tejas Chopra
62a1f23b88 fix: log Codex ws cancellations safely 2026-05-10 09:06:07 -07:00
Tejas Chopra
44231f68cd
Merge pull request #447 from chopratejas/serena-mcp-wrap-default
fix: register serena mcp during wrap
2026-05-09 23:20:10 -07:00
Tejas Chopra
a7160f7eab fix: register serena mcp during wrap 2026-05-09 23:05:59 -07:00
Tejas Chopra
9577ef811b
Merge pull request #444 from chopratejas/fix/learn-cross-platform-paths
fix: harden learn path handling across platforms
2026-05-09 22:34:26 -07:00
Tejas Chopra
22e750c78f Fix learn CLI worker expectation in CI 2026-05-09 22:11:42 -07:00
Tejas Chopra
2732ae7d82
Merge pull request #445 from chopratejas/fix/version-aware-proxy-upgrade
fix: make proxy upgrades version-aware
2026-05-09 18:26:16 -07:00
Tejas Chopra
155c069176 fix: stabilize learn write-failure worker test
Pin os.cpu_count in the write-failure regression test so the expected default worker cap is independent of the GitHub runner CPU count.
2026-05-09 16:00:10 -07:00
Tejas Chopra
ea1f608e79 fix: make proxy upgrades version-aware
Derive source-tree versions from release history so headroom --version no longer reports stale project metadata.

Restart stale idle proxies after an upgrade, but leave active sessions running to avoid interrupting ongoing conversations.

Remove _version.py from version-sync ownership and make version verification catch package/plugin manifest drift.
2026-05-09 15:58:27 -07:00
Tejas Chopra
5ceca13c65 fix: harden learn path handling across platforms 2026-05-09 15:45:26 -07:00
Tejas Chopra
4a88c698ad
Merge pull request #437 from chopratejas/fix/codex-wrap-proxy-mcp-sync
Sync Codex MCP proxy config during wrap
2026-05-09 15:19:29 -07:00
Tejas Chopra
17ffae0cd8 fix: clear CI mypy + rust test failures introduced in eaf5980
compression_units.py:
- Replace dict-unpacking pattern with dataclasses.replace() so mypy can
  type-check fields. The `**base` form forced mypy to infer
  `dict[str, object]`, which doesn't satisfy the per-field types of
  UnitCompressionResult (46 arg-type errors).
- Use `isinstance(candidates, Iterable)` for the transform-iteration
  guard. The previous `iter()` call had a `# type: ignore[arg-type]`
  that was misclassified — mypy actually emits `call-overload` here.

live_zone_thresholds.rs:
- Update the JsonArray threshold assertion from 1024 to 512 to match
  the new constant. eaf5980 lowered THRESHOLD_JSON_ARRAY from 1024 → 512
  in live_zone.rs but missed this integration test.
2026-05-09 15:05:29 -07:00
Tejas Chopra
c7af8307d1 Merge branch 'main' into fix/codex-wrap-proxy-mcp-sync
Resolve conflict in tests/test_proxy_streaming_request_logger.py by
keeping both new tests:
- test_finalize_openai_responses_stream_uses_provider_usage_for_dashboard
  (this branch — OpenAI Responses stream dashboard usage)
- test_finalize_stream_response_recovers_usage_from_truncated_buffer
  (main — SSE buffer recovery on truncated streams)

streaming.py auto-merged cleanly (additive only).
2026-05-09 14:17:40 -07:00
Tejas Chopra
359e8c9a5b fix: align docs and prune dead compatibility surfaces 2026-05-09 14:07:59 -07:00
Tejas Chopra
eaf5980b4a fix: stabilize codex compression, stats, and proxy lifecycle 2026-05-09 13:47:53 -07:00
Tejas Chopra
ac1d11c9a2 fix: sync Codex MCP proxy config during wrap 2026-05-08 19:14:07 -07:00
Tejas Chopra
b166bd3d6a
Merge pull request #436 from chopratejas/fix/sse-usage-recovery-on-truncated-stream
fix(proxy): recover SSE usage from truncated final event
2026-05-08 18:40:19 -07:00
Tejas Chopra
dcc004234c
Merge pull request #435 from chopratejas/fix/init-e2e-claude-local-mcp-add-expectation
test(init-e2e): expect mcp add in seq_claude_local install sequence
2026-05-08 18:03:39 -07:00
Tejas Chopra
8714c64a20 test(init-e2e): expect mcp add in seq_claude_local install sequence
d9d8972 wired auto-MCP registration into ``init`` so ``[Retrieve
more: hash=…]`` markers stay live for users who never ran
``headroom mcp install`` separately, but the ``seq_claude_local``
e2e assertion was still pinned to the pre-MCP two-command sequence
and failed in docker-native-e2e on main.

The ``-e HEADROOM_PROXY_URL=…`` arg is only emitted when the proxy
port differs from the 8787 default; this case sets ``--port 9011``,
so the env arg is included in the expected argv.
2026-05-08 17:38:34 -07:00
Tejas Chopra
d91254a0f2 fix(proxy): recover SSE usage from truncated final event
The per-chunk SSE parser only flushes events terminated by `\n\n`.
When upstream truncates mid-event (client disconnect, network drop,
RemoteProtocolError), the message_start (cache_read /
cache_creation) or message_delta (output_tokens) usage events sit
in the residual sse_buffer and never get parsed — the finalizer
then logs cache_read=cache_write=0, which the freeze heuristic on
the next request reads as "no provider cache, reprocess
everything," producing a different prefix and a real cache miss
on the *next* turn.

Append `\n\n` to the residual buffer at end-of-stream so the
existing parser drains the partial event. Only fills None / 0
slots so a real cache_read=0 from earlier in the stream isn't
clobbered.
2026-05-08 17:23:10 -07:00
Tejas Chopra
d9d8972ac4 fix(mcp): auto-register headroom MCP server in wrap claude/codex and init -g
The proxy compresses tool_result payloads and emits [Retrieve more: hash=…]
markers, but Claude Code / Codex had no headroom_retrieve tool to call on
those markers unless the user separately ran 'headroom mcp install'. The
markers were dead pointers — silent quality loss.

Adds a per-agent MCP registrar abstraction (mcp_registry/) and wires it
into wrap and init so MCP install happens automatically alongside rtk:

  - mcp_registry/base.py — MCPRegistrar ABC, ServerSpec, RegisterResult,
    RegisterStatus enum.
  - mcp_registry/claude.py — Claude Code registrar (claude mcp add CLI
    with .claude.json / mcp.json file fallback).
  - mcp_registry/codex.py — OpenAI Codex registrar (marker-delimited TOML
    block edits to ~/.codex/config.toml; preserves user's other config).
  - mcp_registry/install.py — install_everywhere() orchestrator with
    detect-then-register semantics.
  - mcp_registry/display.py — shared format_result()/format_results() for
    consistent CLI output across wrap, init, and 'headroom mcp install'.

Adding a new agent (Cursor, Continue, Cline, Windsurf, Goose) is now a
single new file plus one entry in get_all_registrars(); call sites and
display logic don't change.

Test seam is constructor injection (home_dir, claude_cli) — zero patches
in 66 new tests across the registry. Removed 13 brittle CLI integration
tests in test_mcp.py that were patching module-level globals; equivalent
coverage now lives at the registrar/orchestrator layer.

wrap codex: snapshot ~/.codex/config.toml at the top of the command so
the existing wrap→unwrap round-trip captures the true pre-wrap state
even though MCP install now writes to the same file mid-flow.

220 tests pass (66 new + 154 existing CLI + integration). ruff and mypy
clean on touched files.
2026-05-08 17:18:32 -07:00
Tejas Chopra
7afb30186a
Merge pull request #433 from chopratejas/rtk-dashboard-compression-savings
Combine rtk and compression savings in dashboard
2026-05-08 15:36:50 -07:00
Tejas Chopra
ecb3e00451
Merge pull request #434 from chopratejas/fix/cache-safe-text-block-compression
fix(content-router,proxy): cache-safe text-block compression and online streaming usage
2026-05-08 15:36:33 -07:00
chopratejas
79baee082f fix(content-router,proxy): cache-safe text-block compression and online streaming usage
PR #431 (merged) added text-block compression to support DeepSeek + Cline,
but the gate ("skip user/system") leaves assistant text blocks compressible
by default. Assistant content is echoed back by the client in subsequent
turns and becomes part of the upstream provider's prefix cache (Anthropic
explicit cache_control, DeepSeek/OpenAI auto-prefix). Compressing it
silently changes the bytes the next turn must match for a cache hit —
turning a 90% read discount into a 25% write penalty on Anthropic, or a
full prefill on DeepSeek/OpenAI when the in-process result cache evicts
or differs across restarts.

Re-aligns the design around prefix-cache safety:

  * Block-level cache_control protection (defense in depth). Any block
    carrying cache_control is the client's explicit cache breakpoint;
    never modified, regardless of role or block type. Closes the gap
    that frozen_message_count alone leaves — that count is a coarse
    message-level approximation; this is the per-block guarantee.
    Applies to both tool_result and text paths.

  * compress_assistant_text_blocks defaults to False (off). Assistant
    text blocks are skipped by default, restoring pre-#431 cache safety
    for Anthropic flows. Per-request opt-in via kwargs (or via
    ContentRouterConfig.compress_assistant_text_blocks for deployment-
    wide enable) preserves the Cline + DeepSeek goal — only enable
    when the backend doesn't honor cache_control AND compression is
    deterministic enough that the auto-prefix cache still hits across
    eviction/restart.

  * Unknown roles default-skip too (was: compressed). developer/judge/
    custom roles are safer to leave untouched than to compress
    aggressively without thinking through their cache semantics.

  * Online streaming usage parser. Replaces the per-stream
    list[bytes] buffer with a single last_completion_tokens int updated
    per chunk via a module-level _parse_completion_tokens_from_sse_chunk
    helper. Streaming memory is now O(1) regardless of stream length —
    important for 200K-output reasoning models and DeepSeek V4 Pro's
    384K max output.

  * Renames the unused min_tokens parameter to min_chars (the threshold
    has always been chars, not tokens, in both the tool_result and text
    paths). Now also wired through ContentRouterConfig
    .min_chars_for_block_compression so the threshold is configurable
    per Realignment build constraints.

Tests:
  * 17 new tests in tests/test_transforms_content_router.py covering
    the role matrix (user / system / assistant / tool / unknown),
    cache_control protection on both paths, opt-in semantics, the
    min_chars threshold, and idempotent pinning detection.
  * 9 new tests in tests/test_streaming_usage_parser.py covering the
    online parser's success and edge cases (usage frame, [DONE],
    invalid JSON, multi-frame chunks, zero tokens, non-dict payloads,
    invalid UTF-8).

Trade-off: deployments pointed at non-cache-aware backends (DeepSeek
direct, OpenAI direct) lose blanket assistant-text compression by
default — they opt in via config. Anthropic flows go back to being
prefix-cache-safe out of the box.
2026-05-08 15:20:54 -07:00
Tejas Chopra
d322e6df1f
Merge pull request #431 from chopratejas/fix/deepseek-text-block-compression-and-streaming-metrics
fix(content-router,proxy): compress text blocks + close DeepSeek metrics gaps
2026-05-08 14:48:20 -07:00
Tejas Chopra
6eec38767b Combine rtk and compression savings in dashboard 2026-05-08 14:46:54 -07:00
Tejas Chopra
7c94e72841
Merge pull request #430 from chopratejas/codex-wire-debug-ws-metrics
fix: add Codex wire debug and WS usage metrics
2026-05-08 14:34:54 -07:00
Tejas Chopra
d955abb1d6 fix(content-router,proxy): compress text blocks + close DeepSeek metrics gaps
ContentRouter._process_content_blocks previously only compressed tool_result
blocks. Anthropic-format requests routed through the OpenAI/DeepSeek backend
arrive with text blocks in their content lists; those were passing through
unchanged, so DeepSeek + Cline saw zero compression. Adds text-block
handling with role-based protection (user/system text blocks are skipped so
the user's actual prompt is never silently mutated) and reuses the existing
two-tier compression cache.

Streaming OpenAI-via-backend path now buffers chunks to parse the final
SSE usage frame for completion_tokens, forwards waste_signals through
metrics + RequestLog, and emits a RequestLog entry so the dashboard's
recent-requests feed and "What Headroom Removed" stop being empty for
this code path. Same RequestLog wiring added to the non-streaming
backend path, which previously logged nothing at all.

DeepSeek V4 entries added to the model registry and OpenAIProvider
context limits with values verified against api-docs.deepseek.com
(1M context / 384K max output) and LiteLLM model_cost (deprecated
deepseek-chat / deepseek-reasoner aliases at 131K). LiteLLM lookup
remains the first source; these are the manual fallback that suppresses
the unknown-model warning.
2026-05-08 14:33:50 -07:00