diff --git a/docs/content/docs/ccr.mdx b/docs/content/docs/ccr.mdx index 57e3cb1a5..061e41a9e 100644 --- a/docs/content/docs/ccr.mdx +++ b/docs/content/docs/ccr.mdx @@ -156,11 +156,8 @@ response = client.chat.completions.create( messages=messages, ) -# CCR is enabled by default. To disable: -# headroom proxy --no-ccr-responses - -# To disable proactive expansion: -# headroom proxy --no-ccr-expansion +# CCR is enabled by default in the current proxy path. Use --no-optimize for +# full passthrough behavior. ``` diff --git a/docs/content/docs/code-compression.mdx b/docs/content/docs/code-compression.mdx index 472cdbd52..32b59c89b 100644 --- a/docs/content/docs/code-compression.mdx +++ b/docs/content/docs/code-compression.mdx @@ -89,7 +89,7 @@ config = CodeCompressorConfig( max_body_lines=5, # Lines to keep per function body min_tokens_for_compression=100, # Skip small content language_hint=None, # Auto-detect if None - fallback_to_llmlingua=True, # Use LLMLingua for unknown langs + fallback_to_kompress=True, # Use Kompress for unknown langs ) compressor = CodeAwareCompressor(config) @@ -110,7 +110,7 @@ result = compressor.compress(code) | `max_body_lines` | `5` | Max lines to keep per function body | | `min_tokens_for_compression` | `100` | Skip files smaller than this | | `language_hint` | `None` | Override language detection | -| `fallback_to_llmlingua` | `True` | Use LLMLingua for unsupported languages | +| `fallback_to_kompress` | `True` | Use Kompress for unsupported languages | ## Before and After diff --git a/docs/content/docs/configuration.mdx b/docs/content/docs/configuration.mdx index fa0f92abf..2ee202482 100644 --- a/docs/content/docs/configuration.mdx +++ b/docs/content/docs/configuration.mdx @@ -221,6 +221,7 @@ normalized = weights.normalized() headroom proxy \ --port 8787 \ # Port to listen on --host 0.0.0.0 \ # Host to bind to + --mode token \ # token compression mode; use cache for prefix-cache stability --budget 10.00 \ # Daily budget limit in USD --log-file headroom.jsonl # Log file path ``` @@ -234,18 +235,28 @@ headroom proxy --no-optimize # Disable semantic caching headroom proxy --no-cache -# Enable LLMLingua ML compression -headroom proxy --llmlingua -headroom proxy --llmlingua --llmlingua-device cuda --llmlingua-rate 0.4 +# Preserve provider prefix-cache stability instead of maximizing token removal +headroom proxy --mode cache + +# Enable memory and live learning +headroom proxy --memory +headroom proxy --learn --min-evidence 3 ``` ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| -| `HEADROOM_LOG_LEVEL` | Logging level | `INFO` | -| `HEADROOM_STORE_URL` | Database URL | temp directory | -| `HEADROOM_DEFAULT_MODE` | Default mode | `optimize` | +| `HEADROOM_HOST` | Proxy bind host | `127.0.0.1` | +| `HEADROOM_PORT` | Proxy bind port | `8787` | +| `HEADROOM_MODE` | Proxy optimization mode: `token` or `cache` | `token` | +| `HEADROOM_WORKERS` | Uvicorn worker count | `1` | +| `HEADROOM_LIMIT_CONCURRENCY` | Maximum concurrent connections before 503 | `1000` | +| `HEADROOM_MAX_CONNECTIONS` | Maximum upstream HTTP connections | `500` | +| `HEADROOM_MAX_KEEPALIVE` | Maximum upstream keep-alive connections | `100` | +| `HEADROOM_BUDGET` | Daily budget limit in USD | -- | +| `HEADROOM_TELEMETRY` | Set to `off` to disable anonymous telemetry | enabled | +| `HEADROOM_STATELESS` | Set to `true` to disable filesystem writes | `false` | | `HEADROOM_MODEL_LIMITS` | Custom model config (JSON string or file path) | -- | | `HEADROOM_BASE_URL` | Base URL of the Headroom proxy (TypeScript SDK) | `http://localhost:8787` | | `HEADROOM_API_KEY` | API key for Headroom Cloud authentication | -- | diff --git a/docs/content/docs/image-compression.mdx b/docs/content/docs/image-compression.mdx index 61d8ad067..52a9f9cba 100644 --- a/docs/content/docs/image-compression.mdx +++ b/docs/content/docs/image-compression.mdx @@ -130,11 +130,10 @@ compressor = ImageCompressor( ### Proxy Configuration ```bash -# Enable image compression (default) -headroom proxy --image-optimize - -# Disable image compression -headroom proxy --no-image-optimize +# Image optimization is part of the ContentRouter path when the optional image +# dependencies are installed. There are no public proxy CLI image toggles in +# the current release. +headroom proxy ``` ## Performance diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 0b75e045a..08b82e260 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -173,9 +173,9 @@ These variables configure Headroom at runtime. Set them in your shell, `.env` fi | Variable | Default | Description | |---|---|---| | `HEADROOM_PORT` | `8787` | Port the proxy listens on | -| `HEADROOM_HOST` | `0.0.0.0` | Host the proxy binds to | -| `HEADROOM_MODE` | `optimize` | Default mode: `optimize`, `audit`, or `passthrough` | -| `HEADROOM_LOG_LEVEL` | `INFO` | Logging level | +| `HEADROOM_HOST` | `127.0.0.1` | Host the proxy binds to | +| `HEADROOM_MODE` | `token` | Default optimization mode: `token` or `cache` | +| `HEADROOM_TELEMETRY` | enabled | Set to `off` to disable anonymous telemetry | ### TypeScript SDK diff --git a/docs/content/docs/metrics.mdx b/docs/content/docs/metrics.mdx index 4f741ff23..9d07b0d25 100644 --- a/docs/content/docs/metrics.mdx +++ b/docs/content/docs/metrics.mdx @@ -95,8 +95,7 @@ curl http://localhost:8787/health { "status": "healthy", "version": "0.1.0", - "uptime_seconds": 3600, - "llmlingua_enabled": false + "uptime_seconds": 3600 } ``` diff --git a/docs/content/docs/proxy.mdx b/docs/content/docs/proxy.mdx index cf72774fc..c29d19bdf 100644 --- a/docs/content/docs/proxy.mdx +++ b/docs/content/docs/proxy.mdx @@ -30,48 +30,67 @@ Telemetry is enabled by default. Opt out with `HEADROOM_TELEMETRY=off` or `--no- |--------|---------|-------------| | `--host` | `127.0.0.1` | Host to bind to | | `--port` | `8787` | Port to bind to | +| `--workers` | `1` | Number of Uvicorn worker processes | +| `--limit-concurrency` | `1000` | Maximum concurrent connections before Uvicorn returns 503 | +| `--max-connections` | `500` | Maximum upstream HTTP connections | +| `--max-keepalive` | `100` | Maximum upstream keep-alive connections | +| `--mode` | `token` | Optimization mode: `token` prioritizes compression, `cache` preserves provider prefix-cache stability | | `--no-optimize` | `false` | Disable optimization (passthrough mode) | | `--no-cache` | `false` | Disable semantic caching | | `--no-rate-limit` | `false` | Disable rate limiting | | `--log-file` | None | Path to JSONL log file | +| `--log-messages` | `false` | Store full request/response content for the live feed | | `--budget` | None | Daily budget limit in USD | | `--openai-api-url` | `https://api.openai.com` | Custom OpenAI API URL | +| `--anthropic-api-url` | Anthropic default | Custom Anthropic API URL | +| `--gemini-api-url` | Gemini default | Custom Gemini API URL | +| `--backend` | `anthropic` | Backend: `anthropic`, `bedrock`, `openrouter`, `anyllm`, or `litellm-` | +| `--no-telemetry` | `false` | Disable anonymous telemetry | +| `--stateless` | `false` | Disable filesystem writes and keep runtime state in memory | ### Context management | Option | Default | Description | |--------|---------|-------------| -| `--no-intelligent-context` | `false` | Fall back to RollingWindow (oldest-first drops) | -| `--no-intelligent-scoring` | `false` | Disable multi-factor importance scoring | -| `--no-compress-first` | `false` | Disable trying deeper compression before dropping | +| `--mode token` | `token` | Prioritize token compression. This is the default. | +| `--mode cache` | `token` | Preserve prior turns to maximize provider prefix-cache hit rate. | +| `--intercept-tool-results` | `false` | Opt into tool-result interceptors such as ast-grep Read outlining. | +| `--no-read-lifecycle` | `false` | Disable stale/superseded Read-output compression. | +| `--code-aware` / `--no-code-aware` | disabled | Enable or disable AST-based code compression. Requires `headroom-ai[code]`. | +| `--code-graph` | `false` | Index the current project and watch files via codebase-memory-mcp. | -By default, the proxy uses **IntelligentContextManager** which scores messages by recency, semantic similarity, TOIN-learned patterns, error indicators, and forward references. Dropped messages are stored in CCR for retrieval. +By default, the proxy uses the shared **ContentRouter** pipeline. It routes text, logs, JSON, code, images, and tool outputs through the currently enabled compressors and preserves reversible CCR markers where applicable. ```bash -# Use legacy RollingWindow -headroom proxy --no-intelligent-context +# Maximize compression +headroom proxy --mode token -# Faster but less intelligent scoring -headroom proxy --no-intelligent-scoring +# Preserve provider prefix cache stability +headroom proxy --mode cache ``` -### LLMLingua (ML compression) +### Optional features | Option | Default | Description | |--------|---------|-------------| -| `--llmlingua` | `false` | Enable LLMLingua-2 ML-based compression | -| `--llmlingua-device` | `auto` | Device: `auto`, `cuda`, `cpu`, `mps` | -| `--llmlingua-rate` | `0.3` | Target compression rate (0.3 = keep 30%) | +| `--memory` | `false` | Enable persistent user memory and provider-appropriate memory tools | +| `--memory-db-path` | `{cwd}/.headroom/memory.db` | Override the memory SQLite path | +| `--no-memory-tools` | `false` | Disable automatic memory tool injection | +| `--no-memory-context` | `false` | Disable automatic memory context injection | +| `--memory-top-k` | `10` | Number of memories to inject as context | +| `--learn` | `false` | Enable live traffic learning; implies `--memory` | +| `--no-learn` | `false` | Explicitly disable traffic learning | +| `--min-evidence` | `5` | Minimum observations before a learned pattern is persisted | +| `--codex-wire-debug` | `false` | Write local Codex wire snapshots and matching proxy log traces | ```bash -pip install "headroom-ai[llmlingua]" - -headroom proxy --llmlingua --llmlingua-device cuda -headroom proxy --llmlingua --llmlingua-rate 0.2 +headroom proxy --memory +headroom proxy --learn --min-evidence 3 +headroom proxy --codex-wire-debug ``` - -LLMLingua adds ~2 GB of dependencies (torch, transformers), 10-30s cold start, and ~1 GB RAM. Enable when maximum compression justifies the cost. + +The old LLMLingua proxy toggles are no longer part of the CLI. Headroom's proxy compression path uses ContentRouter plus the current built-in compressors, including Kompress where applicable. ## API endpoints diff --git a/docs/content/docs/text-and-logs.mdx b/docs/content/docs/text-and-logs.mdx index b19536486..fc0c11f44 100644 --- a/docs/content/docs/text-and-logs.mdx +++ b/docs/content/docs/text-and-logs.mdx @@ -11,7 +11,7 @@ Headroom provides specialized compressors for text-based content that isn't JSON | `LogCompressor` | Build/test logs | Errors, stack traces, summaries | 85-95% | | `DiffCompressor` | Unified diffs | Changed lines, context | 60-80% | | `TextCompressor` | General text | Relevant paragraphs, anchors | 60-80% | -| `LLMLinguaCompressor` | Any text (max compression) | Semantic meaning via ML | 80-95% | +| `KompressCompressor` | General text fallback | Learned token scoring via ONNX | 30-50% | ## SearchCompressor @@ -134,21 +134,12 @@ print(result.compressed) - Headers and section markers - Document structure and organization -## LLMLingua (Optional, Maximum Compression) - -For maximum compression on any text, Headroom integrates with Microsoft's LLMLingua-2, a BERT-based token classifier trained via GPT-4 distillation. It achieves up to 20x compression while preserving semantic meaning. +## Kompress ```python -from headroom.transforms import LLMLinguaCompressor, LLMLinguaConfig +from headroom.transforms.kompress_compressor import KompressCompressor -config = LLMLinguaConfig( - device="auto", # auto, cuda, cpu, mps - code_compression_rate=0.4, # Conservative for code - json_compression_rate=0.35, # Moderate for JSON - text_compression_rate=0.25, # Aggressive for text -) - -compressor = LLMLinguaCompressor(config) +compressor = KompressCompressor() result = compressor.compress(long_output) print(f"Before: {result.original_tokens} tokens") @@ -156,22 +147,10 @@ print(f"After: {result.compressed_tokens} tokens") print(f"Saved: {result.savings_percentage:.1f}%") ``` - - LLMLingua adds ~2GB of model weights and 50-200ms latency per request. Install it only when you need maximum compression: `pip install "headroom-ai[llmlingua]"` + + The old LLMLingua transform and helper functions are no longer exported. Use Kompress and ContentRouter for text compression. -### Memory Management - -```python -from headroom.transforms import unload_llmlingua_model, is_llmlingua_model_loaded - -# Check if model is loaded -print(is_llmlingua_model_loaded()) # True - -# Free ~1GB RAM when done -unload_llmlingua_model() -``` - ## Content Type Detection If you're building your own routing logic, you can use the content type detector directly: @@ -200,7 +179,7 @@ The ContentRouter selects the right compressor automatically. Here's when each f | pytest, npm, cargo markers | LogCompressor | Build tool output patterns | | `---/+++` and `@@` markers | DiffCompressor | Unified diff format | | Prose, documentation | TextCompressor | Fallback for non-structured text | -| Any (max compression mode) | LLMLinguaCompressor | Explicitly enabled | +| Long plain text | KompressCompressor | ContentRouter fallback | ## Performance @@ -210,4 +189,4 @@ The ContentRouter selects the right compressor automatically. Here's when each f | LogCompressor | 5,000 lines | 100-200 lines | ~3ms | | DiffCompressor | Large diff | Changed hunks only | ~2ms | | TextCompressor | 10,000 chars | 2,000 chars | ~2ms | -| LLMLinguaCompressor | Any text | 5-20% of original | 50-200ms | +| KompressCompressor | Plain text | 50-70% of original | model-dependent | diff --git a/docs/spec/006-actors.md b/docs/spec/006-actors.md index 1b0d6c421..c9fe2053d 100644 --- a/docs/spec/006-actors.md +++ b/docs/spec/006-actors.md @@ -25,8 +25,7 @@ The developer using an AI coding agent with Headroom. export ANTHROPIC_API_KEY=sk-... # Optional overrides -export HEADROOM_MODE=compress -export HEADROOM_CACHE_ENABLED=true +export HEADROOM_MODE=token ``` --- @@ -140,10 +139,9 @@ The person assessing Headroom for organizational adoption. **Security Configuration:** ```bash # Maximum privacy settings -HEADROOM_CACHE_ENABLED=false -HEADROOM_TELEMETRY_ENABLED=false -HEADROOM_LEARN_ENABLED=false -HEADROOM_DASHBOARD_ENABLED=false +HEADROOM_TELEMETRY=off +HEADROOM_STATELESS=true +headroom proxy --no-cache --no-optimize ``` --- diff --git a/docs/spec/007-behavior.md b/docs/spec/007-behavior.md index f6df2ee6e..b0bcabbb7 100644 --- a/docs/spec/007-behavior.md +++ b/docs/spec/007-behavior.md @@ -4,7 +4,7 @@ ## Proxy Modes -### Passthrough Mode +### Passthrough Headroom forwards requests without modification. @@ -14,7 +14,7 @@ Headroom forwards requests without modification. - No compression applied - Useful for testing or debugging -**Configuration:** `HEADROOM_MODE=audit` +**Configuration:** `headroom proxy --no-optimize` **Request Flow:** ``` @@ -23,7 +23,7 @@ Client → Proxy → Provider API → Response --- -### Optimize Mode +### Token Mode Headroom applies deterministic transforms to requests. @@ -34,7 +34,7 @@ Headroom applies deterministic transforms to requests. - CCR caching enabled - Token budget enforced -**Configuration:** `HEADROOM_MODE=optimize` +**Configuration:** `HEADROOM_MODE=token` or `headroom proxy --mode token` **Request Flow:** ``` @@ -45,17 +45,16 @@ Client → Proxy → [SmartCrusher] → [CacheAligner] --- -### Simulate Mode +### Cache Mode -Headroom returns transform plan without API call. +Headroom preserves prior turns where possible to maximize provider prefix-cache hit rate. **Behavior:** -- Analyzes content for compression opportunity -- Returns TransformResult with planned transforms -- No actual compression or provider call -- Useful for debugging/optimization +- Freezes provider-confirmed cached prefixes +- Compresses the mutable tail of the request +- Trades some token savings for better cache stability -**Configuration:** `HEADROOM_MODE=simulate` +**Configuration:** `HEADROOM_MODE=cache` or `headroom proxy --mode cache` --- @@ -65,9 +64,9 @@ Session modes control how Headroom handles context windows. | Mode | Description | Use Case | |------|-------------|----------| -| `audit` | Observe only, no modifications | Monitoring | -| `optimize` | Apply deterministic transforms | Production | -| `simulate` | Return plan without API call | Debugging | +| `token` | Prioritize token removal | Default proxy mode | +| `cache` | Preserve prior turns for provider prefix-cache stability | Long Claude/Codex sessions | +| passthrough | Disable optimization with `--no-optimize` | Debugging | --- diff --git a/docs/spec/008-capabilities.md b/docs/spec/008-capabilities.md index a5fa25f35..acbc573e8 100644 --- a/docs/spec/008-capabilities.md +++ b/docs/spec/008-capabilities.md @@ -22,15 +22,14 @@ ## Compression Capabilities -### Semantic Cache +### Proxy Cache -**Description:** Caches semantically similar requests using CCR pattern. +**Description:** The proxy has semantic cache support and CCR-backed retrieval, controlled by CLI configuration. **Configuration:** ```bash -HEADROOM_CACHE_ENABLED=true -HEADROOM_CACHE_TTL=3600 -HEADROOM_CACHE_MAX_SIZE=10000 +headroom proxy # cache enabled by default +headroom proxy --no-cache ``` **Behavior:** diff --git a/docs/spec/009-compliance.md b/docs/spec/009-compliance.md index 4db500a6b..d7046c68a 100644 --- a/docs/spec/009-compliance.md +++ b/docs/spec/009-compliance.md @@ -62,17 +62,16 @@ ## Configuration for Maximum Privacy ```bash -HEADROOM_CACHE_ENABLED=false -HEADROOM_TELEMETRY_ENABLED=false -HEADROOM_LEARN_ENABLED=false -HEADROOM_DASHBOARD_ENABLED=false +HEADROOM_TELEMETRY=off +HEADROOM_STATELESS=true +headroom proxy --no-cache --no-optimize ``` This configuration results in: - No prompt data stored - No data exported - No analytics collected -- Headroom acts as pure compression proxy +- Headroom acts as a passthrough proxy --- diff --git a/docs/spec/010-data.md b/docs/spec/010-data.md index 7b58c571a..ca2aa5892 100644 --- a/docs/spec/010-data.md +++ b/docs/spec/010-data.md @@ -101,9 +101,9 @@ CREATE TABLE compression_store ( | Variable | Default | Description | |----------|---------|-------------| -| `HEADROOM_CACHE_ENABLED` | `true` | Enable compression cache | -| `HEADROOM_CACHE_TTL` | `3600` | Cache TTL in seconds | -| `HEADROOM_CACHE_MAX_SIZE` | `10000` | Max cache entries | +| CLI `--no-cache` | unset | Disable semantic cache for the proxy process | +| `HEADROOM_WORKSPACE_DIR` | `~/.headroom` | Workspace root for proxy state, logs, memory, and savings | +| `HEADROOM_STATELESS` | `false` | Disable filesystem writes and keep runtime state in memory | --- diff --git a/docs/spec/011-deployment.md b/docs/spec/011-deployment.md index 6eb697dad..ce4e5cf8b 100644 --- a/docs/spec/011-deployment.md +++ b/docs/spec/011-deployment.md @@ -30,8 +30,7 @@ services: - "8787:8787" environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - HEADROOM_MODE=compress - - HEADROOM_CACHE_ENABLED=true + - HEADROOM_MODE=token volumes: - headroom-data:/root/.headroom @@ -121,14 +120,12 @@ deployment: | Variable | Default | Description | |----------|---------|-------------| -| `HEADROOM_MODE` | `audit` | Proxy mode (audit/optimize/simulate) | +| `HEADROOM_MODE` | `token` | Proxy mode (`token` or `cache`) | | `HEADROOM_PORT` | `8787` | Proxy port | -| `HEADROOM_HOST` | `0.0.0.0` | Proxy host | +| `HEADROOM_HOST` | `127.0.0.1` | Proxy host | | `ANTHROPIC_API_KEY` | - | Anthropic API key | | `OPENAI_API_KEY` | - | OpenAI API key | -| `HEADROOM_CACHE_ENABLED` | `true` | Enable cache | -| `HEADROOM_CACHE_TTL` | `3600` | Cache TTL | -| `HEADROOM_TELEMETRY_ENABLED` | `true` | Enable telemetry | +| `HEADROOM_TELEMETRY` | enabled | Set to `off` to disable telemetry | ### Config File diff --git a/docs/spec/015-interfaces.md b/docs/spec/015-interfaces.md index daf297688..73b6a619c 100644 --- a/docs/spec/015-interfaces.md +++ b/docs/spec/015-interfaces.md @@ -15,10 +15,19 @@ headroom proxy [OPTIONS] **Options:** | Flag | Default | Description | |------|---------|-------------| -| `--host` | `0.0.0.0` | Bind host | +| `--host` | `127.0.0.1` | Bind host | | `--port` | `8787` | Bind port | -| `--llmlingua-device` | `cpu` | LLMLingua device (cpu/cuda) | -| `--config` | - | Config file path | +| `--mode` | `token` | Optimization mode: `token` or `cache` | +| `--workers` | `1` | Uvicorn worker processes | +| `--limit-concurrency` | `1000` | Maximum concurrent connections before 503 | +| `--no-optimize` | `false` | Passthrough mode | +| `--no-cache` | `false` | Disable semantic cache | +| `--no-rate-limit` | `false` | Disable rate limiting | +| `--memory` | `false` | Enable persistent memory | +| `--learn` | `false` | Enable live traffic learning | +| `--backend` | `anthropic` | Backend: anthropic, bedrock, openrouter, anyllm, or litellm-* | +| `--no-telemetry` | `false` | Disable anonymous telemetry | +| `--stateless` | `false` | Disable filesystem writes | --- @@ -55,16 +64,17 @@ headroom install [OPTIONS] ### `headroom mcp` -Start MCP server. +Manage the Headroom MCP server. ```bash -headroom mcp [OPTIONS] +headroom mcp [OPTIONS] COMMAND [ARGS]... ``` -**Options:** -| Flag | Default | Description | -|------|---------|-------------| -| `--port` | `8766` | MCP server port | +**Commands:** +- `install` — Install the MCP server into detected coding agents +- `serve` — Start the stdio MCP server +- `status` — Check configuration status +- `uninstall` — Remove Headroom MCP config --- @@ -128,9 +138,12 @@ headroom learn [OPTIONS] **Options:** | Flag | Default | Description | |------|---------|-------------| -| `--agent` | `auto` | Agent type | -| `--mode` | `auto` | Learn mode | -| `--session` | - | Session ID | +| `--project` | current directory | Project directory to analyze | +| `--all` | `false` | Analyze all discovered projects | +| `--apply` | `false` | Write recommendations instead of dry-run | +| `--agent` | `auto` | Agent to analyze: auto, claude, codex, gemini, or plugin | +| `--model` | auto | LLM model for analysis | +| `--workers` | auto | Parallel workers for session scanning | --- @@ -210,12 +223,16 @@ X-Headroom-Compressed-Tokens: 5325 | Variable | Default | Description | |----------|---------|-------------| -| `HEADROOM_MODE` | `audit` | Operation mode (audit/optimize/simulate) | +| `HEADROOM_MODE` | `token` | Proxy optimization mode (`token` or `cache`) | | `HEADROOM_PORT` | `8787` | Proxy port | -| `HEADROOM_HOST` | `0.0.0.0` | Proxy host | -| `HEADROOM_STORE_URL` | `sqlite:///headroom.db` | Storage URL | -| `HEADROOM_PROXY_URL` | `http://localhost:8787` | Proxy URL | -| `HEADROOM_LOG_LEVEL` | `INFO` | Log level | +| `HEADROOM_HOST` | `127.0.0.1` | Proxy host | +| `HEADROOM_WORKERS` | `1` | Uvicorn worker count | +| `HEADROOM_LIMIT_CONCURRENCY` | `1000` | Maximum concurrent connections before 503 | +| `HEADROOM_MAX_CONNECTIONS` | `500` | Maximum upstream HTTP connections | +| `HEADROOM_MAX_KEEPALIVE` | `100` | Maximum upstream keep-alive connections | +| `HEADROOM_BUDGET` | - | Daily budget limit in USD | +| `HEADROOM_TELEMETRY` | enabled | Set to `off` to disable anonymous telemetry | +| `HEADROOM_STATELESS` | `false` | Disable filesystem writes | ### Provider @@ -230,12 +247,11 @@ X-Headroom-Compressed-Tokens: 5325 | Variable | Default | Description | |----------|---------|-------------| -| `HEADROOM_CACHE_ENABLED` | `true` | Enable cache | -| `HEADROOM_CACHE_TTL` | `3600` | Cache TTL in seconds | -| `HEADROOM_CACHE_MAX_SIZE` | `10000` | Max cache entries | -| `HEADROOM_LEARN_ENABLED` | `false` | Enable learn | -| `HEADROOM_DASHBOARD_ENABLED` | `false` | Enable dashboard | -| `HEADROOM_TELEMETRY_ENABLED` | `true` | Enable telemetry | +| `HEADROOM_TELEMETRY` | enabled | Set to `off` to disable telemetry | +| `HEADROOM_MIN_EVIDENCE` | `5` | Minimum observations before live learning persists a pattern | +| `HEADROOM_PROXY_EXTENSIONS` | - | Comma-separated proxy extensions to enable | +| `HEADROOM_STATELESS` | `false` | Disable filesystem writes | +| `HEADROOM_MODEL_LIMITS` | - | Model limits override as JSON or file path | ### Compression diff --git a/docs/spec/016-observability.md b/docs/spec/016-observability.md index 0070f6d67..d9f797819 100644 --- a/docs/spec/016-observability.md +++ b/docs/spec/016-observability.md @@ -102,7 +102,7 @@ logging: - Top compressed endpoints - Session overview -**Requires:** `HEADROOM_DASHBOARD_ENABLED=true` +**Requires:** the proxy process to be running. The dashboard is served by default at `/dashboard`. --- diff --git a/docs/spec/017-operations.md b/docs/spec/017-operations.md index e30299c04..41f3c7a62 100644 --- a/docs/spec/017-operations.md +++ b/docs/spec/017-operations.md @@ -165,8 +165,8 @@ rate(headroom_cache_hits_total[5m]) / (rate(headroom_cache_hits_total[5m]) + rat | Symptom | Cause | Solution | |---------|-------|----------| -| "Connection refused" | Proxy not running | `headroom proxy start` | -| "Cache miss on every request" | Cache disabled | Set `HEADROOM_CACHE_ENABLED=true` | +| "Connection refused" | Proxy not running | Start it with `headroom proxy` | +| "Cache miss on every request" | Cache disabled | Start without `--no-cache` | | "No savings shown" | Database locked | Check file permissions | | "Provider timeout" | Network issue | Check firewall/proxy | diff --git a/docs/spec/018-policies.md b/docs/spec/018-policies.md index 2851c5fa4..6a989351f 100644 --- a/docs/spec/018-policies.md +++ b/docs/spec/018-policies.md @@ -37,9 +37,8 @@ All settings can be overridden via environment variables: ```bash -HEADROOM_CACHE_ENABLED=false -HEADROOM_MODE=passthrough -HEADROOM_MAX_TOKENS=8192 +HEADROOM_MODE=token +headroom proxy --no-cache ``` ### Config File diff --git a/headroom/cli/mcp.py b/headroom/cli/mcp.py index efb62b563..b721e23c7 100644 --- a/headroom/cli/mcp.py +++ b/headroom/cli/mcp.py @@ -299,7 +299,7 @@ def mcp_status() -> None: @click.option( "--direct", is_flag=True, - help="Use direct CompressionStore access (same process as proxy)", + help="(Deprecated, ignored) Direct CompressionStore access is no longer supported", ) @click.option( "--debug", @@ -343,10 +343,13 @@ def mcp_serve(proxy_url: str | None, direct: bool, debug: bool) -> None: # Use default if not specified effective_proxy_url = proxy_url or DEFAULT_PROXY_URL - server = create_ccr_mcp_server( - proxy_url=effective_proxy_url, - direct_mode=direct, - ) + if direct: + click.echo( + "Warning: --direct is deprecated and ignored; MCP retrieval uses the proxy URL.", + err=True, + ) + + server = create_ccr_mcp_server(proxy_url=effective_proxy_url) async def run() -> None: try: diff --git a/headroom/config.py b/headroom/config.py index 15440451c..7079b0ebd 100644 --- a/headroom/config.py +++ b/headroom/config.py @@ -2,7 +2,7 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import InitVar, dataclass, field from datetime import datetime from enum import Enum from typing import Any, Literal @@ -473,10 +473,10 @@ class HeadroomConfig: # to the top-level config when PR-B1 retired the rolling-window stage. output_buffer_tokens: int = 4000 - # Content Router - intelligent content-type based compression - # Routes content to appropriate compressor (Kompress for text, SmartCrusher for JSON, - # CodeCompressor for code, LogCompressor for logs, etc.) - content_router_enabled: bool = True + # Deprecated compatibility argument. ContentRouter is always present + # in the default pipeline; accepting this avoids breaking old config + # constructors while keeping it out of runtime state. + content_router_enabled: InitVar[bool | None] = None # Tool-result interceptors (ast-grep Read outline, etc.). Opt-in for now. # Env var HEADROOM_INTERCEPT_ENABLED=1 also enables (for CLI `--intercept-tool-results`). diff --git a/headroom/proxy/handlers/openai.py b/headroom/proxy/handlers/openai.py index 7ddd293a8..59abfd229 100644 --- a/headroom/proxy/handlers/openai.py +++ b/headroom/proxy/handlers/openai.py @@ -1599,12 +1599,13 @@ class OpenAIHandlerMixin: metadata={"path": request.url.path, "stream": stream}, ) - # PR-C5: Python no longer compresses /v1/responses — Rust handles - # item-aware compression natively (see crates/headroom-proxy/src/ - # handlers/responses.rs). We synthesise a minimal `messages` list - # purely for downstream memory injection + telemetry; list-typed - # `input` is consulted via `body["input"]` directly via the - # live-zone-tail helpers below. + # /v1/responses uses provider-specific CompressionUnit extraction + # below, then routes mutable text through ContentRouter. The + # standalone Rust proxy has native item-aware handling, but the + # Python CLI runtime does not run that proxy today. We synthesise a + # minimal `messages` list purely for downstream memory injection and + # telemetry; list-typed `input` is consulted directly by the unit + # extraction helpers. input_data = body.get("input", "") instructions = body.get("instructions") @@ -1698,10 +1699,9 @@ class OpenAIHandlerMixin: tokenizer = get_tokenizer(model) original_tokens = tokenizer.count_messages(messages) - # PR-C5: Python compression on /v1/responses is retired — the Rust - # handler at crates/headroom-proxy/src/handlers/responses.rs is the - # canonical compression path. Defaults below feed downstream - # telemetry and memory injection without invoking the pipeline. + # Defaults below feed downstream telemetry and memory injection. + # If optimization remains enabled, the Responses payload is compressed + # later through `_compress_openai_responses_payload`. optimized_messages = messages optimized_tokens = original_tokens tokens_saved = 0 @@ -1864,16 +1864,11 @@ class OpenAIHandlerMixin: else: url = build_copilot_upstream_url(self.OPENAI_API_URL, "/v1/responses") - # Hot-fix (post-PR-C5): re-enable /v1/responses compression via the - # PyO3 inline call to the Rust live-zone dispatcher. PR-C5 retired - # the Python pipeline expecting that the standalone - # `crates/headroom-proxy` Rust binary would sit in front of the - # Python proxy and compress here. That binary is not deployed by - # the CLI today (`headroom proxy`, `headroom wrap codex` both run - # only the Python proxy), so /v1/responses traffic has been - # uncompressed since v0.20.16. This call closes that gap by - # invoking the same dispatcher in-process via `headroom._core`. - # All policy gating already happened upstream (auth_mode classify, + # The standalone Rust proxy has native /v1/responses item handling, + # but the default CLI runtime is this Python proxy. Compress the + # Python runtime path here by extracting mutable Responses text into + # CompressionUnits and routing them through ContentRouter. Policy + # gating already happened upstream (auth_mode classify, # CompressionPolicy resolve at request entry). if self.config.optimize and not _bypass: try: @@ -2207,8 +2202,8 @@ class OpenAIHandlerMixin: 1. Accepts the client WebSocket 2. Receives the first message (``response.create`` request) 3. Opens an upstream WebSocket to OpenAI - 4. Sends the request upstream as-is (PR-C5: Python compression on - /v1/responses retired — Rust handles item-aware compression) + 4. Compresses eligible `response.create` text through the Python + ContentRouter path, then sends the request upstream 5. Relays all subsequent messages bidirectionally """ try: @@ -2504,12 +2499,10 @@ class OpenAIHandlerMixin: # deregister / metrics / stage-timings emission as usual. return - # PR-C5 retired Python compression on this path expecting the - # standalone Rust proxy binary to take over. That binary is not - # deployed by the CLI today (`headroom proxy` runs only Python - # via uvicorn). Hot-fix follow-up to PR #406: the first frame - # is now compressed via the inline PyO3 binding right before - # upstream send (see the compression block further below). + # The standalone Rust proxy has a native Responses path, but the + # CLI runtime runs this Python proxy. Compress eligible + # `response.create` frames through the shared Python + # CompressionUnit + ContentRouter path before upstream send. # Subsequent client→upstream frames are now ALSO compressed # via `_maybe_compress_response_create_frame` in # `_client_to_upstream` so long-lived subscription Codex diff --git a/headroom/proxy/models.py b/headroom/proxy/models.py index 76c2b610c..6289808a6 100644 --- a/headroom/proxy/models.py +++ b/headroom/proxy/models.py @@ -6,7 +6,7 @@ Extracted from server.py to keep the codebase maintainable. from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import InitVar, dataclass, field from datetime import datetime from typing import Any, Literal @@ -139,8 +139,10 @@ class ProxyConfig: # Read lifecycle management read_lifecycle: bool = True - # Smart content routing - smart_routing: bool = True + # Deprecated compatibility argument. ContentRouter is always active in + # the Python proxy; accepting this avoids breaking old config constructors + # while keeping it out of runtime state. + smart_routing: InitVar[bool | None] = None # Caching cache_enabled: bool = True diff --git a/headroom/proxy/server.py b/headroom/proxy/server.py index 361601b62..78fcdd958 100644 --- a/headroom/proxy/server.py +++ b/headroom/proxy/server.py @@ -2985,7 +2985,6 @@ if __name__ == "__main__": if args.log_file else os.environ.get("HEADROOM_LOG_FILE"), log_full_messages=args.log_messages or _get_env_bool("HEADROOM_LOG_MESSAGES", False), - smart_routing=True, code_aware_enabled=code_aware_enabled, # Connection pool settings max_connections=_get_env_int("HEADROOM_MAX_CONNECTIONS", args.max_connections), diff --git a/headroom/telemetry/toin.py b/headroom/telemetry/toin.py index 3b17b5b9d..792cc9fd6 100644 --- a/headroom/telemetry/toin.py +++ b/headroom/telemetry/toin.py @@ -69,7 +69,7 @@ import time import warnings from collections.abc import Callable from dataclasses import dataclass, field -from typing import Any, Final, Literal +from typing import Any, Final from .models import FieldSemantics, ToolSignature @@ -380,45 +380,6 @@ class ToolPattern: return pattern -@dataclass -class _CompressionHint: - """Internal recommendation envelope (PR-B5: private, observation-only). - - Pre-B5 this was the public return type of `get_recommendation()` — - the request-time hint API now retired. The dataclass is retained as - `_CompressionHint` purely for the deprecated stub's signature and - for the publish CLI's internal aggregation; no new code should - construct or consume it. Read recommendations from - `recommendations.toml` produced by `headroom.cli.toin_publish`. - """ - - # Should we compress at all? - skip_compression: bool = False - - # How aggressively to compress - max_items: int = 20 - compression_level: Literal["none", "conservative", "moderate", "aggressive"] = "moderate" - - # Which fields to preserve (never remove) - preserve_fields: list[str] = field(default_factory=list) - - # Which strategy to use - recommended_strategy: str = "default" - - # Why this recommendation - reason: str = "" - confidence: float = 0.0 - - # Source of recommendation - source: Literal["network", "local", "default"] = "default" - based_on_samples: int = 0 - - # === TOIN Evolution: Learned Field Semantics === - # These enable zero-latency signal detection in SmartCrusher. - # field_hash -> FieldSemantics (learned semantic type, important values, etc.) - field_semantics: dict[str, FieldSemantics] = field(default_factory=dict) - - @dataclass class TOINConfig: """Configuration for the Tool Output Intelligence Network.""" @@ -1008,7 +969,7 @@ class ToolIntelligenceNetwork: sites from flooding logs. Returns: - Always `None`. The legacy `_CompressionHint` envelope is no + Always `None`. The legacy compression-hint envelope is no longer constructed at request time. """ cls = type(self) diff --git a/headroom/transforms/pipeline.py b/headroom/transforms/pipeline.py index 7822d24fa..4941f25de 100644 --- a/headroom/transforms/pipeline.py +++ b/headroom/transforms/pipeline.py @@ -98,11 +98,6 @@ class TransformPipeline: # - Logs -> LogCompressor # - Search results -> SearchCompressor # - HTML -> HTMLExtractor - if not self.config.content_router_enabled: - logger.warning( - "HeadroomConfig.content_router_enabled=False is deprecated and ignored; " - "ContentRouter is always present in the default pipeline" - ) transforms.append(ContentRouter()) logger.info("Pipeline using ContentRouter for intelligent content-aware compression") diff --git a/tests/test_canonical_pipeline.py b/tests/test_canonical_pipeline.py index 96d6622d3..5608338e5 100644 --- a/tests/test_canonical_pipeline.py +++ b/tests/test_canonical_pipeline.py @@ -149,7 +149,7 @@ def test_pipeline_extension_manager_uses_canonical_stage_contract(): def test_default_transform_pipeline_always_uses_content_router() -> None: - config = HeadroomConfig(content_router_enabled=False) + config = HeadroomConfig() pipeline = TransformPipeline(config) diff --git a/tests/test_proxy_ccr.py b/tests/test_proxy_ccr.py index d9c0ff7a4..2daf6d71b 100644 --- a/tests/test_proxy_ccr.py +++ b/tests/test_proxy_ccr.py @@ -420,7 +420,6 @@ class TestEndToEndTOINIntegration: reset_compression_store() config = ProxyConfig( optimize=True, # Enable optimization - smart_routing=False, # Use legacy mode for simpler testing cache_enabled=False, rate_limit_enabled=False, cost_tracking_enabled=False, diff --git a/tests/test_proxy_warmup.py b/tests/test_proxy_warmup.py index fa09577cd..d7024644f 100644 --- a/tests/test_proxy_warmup.py +++ b/tests/test_proxy_warmup.py @@ -131,7 +131,6 @@ def _stub_pipelines(monkeypatch): cache_enabled=False, rate_limit_enabled=False, cost_tracking_enabled=False, - smart_routing=False, code_aware_enabled=False, ) proxy = HeadroomProxy(config) @@ -168,7 +167,6 @@ async def test_startup_optimize_false_leaves_slots_null(): cache_enabled=False, rate_limit_enabled=False, cost_tracking_enabled=False, - smart_routing=False, ) proxy = HeadroomProxy(config) diff --git a/tests/test_responses_pyo3_compression.py b/tests/test_responses_pyo3_compression.py index 58d756d95..c1fcf135f 100644 --- a/tests/test_responses_pyo3_compression.py +++ b/tests/test_responses_pyo3_compression.py @@ -1,10 +1,9 @@ -"""Hot-fix tests: PyO3 inline `/v1/responses` compression. +"""Rust binding tests for `/v1/responses` live-zone compression. -Re-enables compression after PR-C5 retired the Python pipeline. The -standalone Rust proxy binary (`crates/headroom-proxy`) was supposed to -handle this, but it's not deployed by the CLI today. This module -exposes a PyO3 binding so the Python proxy can call the live-zone -dispatcher in-process. +The default Python CLI runtime currently compresses Responses payloads +through CompressionUnit extraction plus ContentRouter. This module keeps +the lower-level PyO3 live-zone binding covered so Rust migration work +cannot silently break the exposed bridge. These tests pin: diff --git a/tests/test_responses_ws_pyo3_compression.py b/tests/test_responses_ws_pyo3_compression.py index 1da24eb5d..f029dc360 100644 --- a/tests/test_responses_ws_pyo3_compression.py +++ b/tests/test_responses_ws_pyo3_compression.py @@ -1,11 +1,9 @@ -"""WebSocket /v1/responses compression integration tests. +"""WebSocket-shaped `/v1/responses` Rust binding tests. -PR-C5 retired Python compression on the WS path expecting the standalone -Rust proxy binary to take over (it isn't deployed by the CLI). PR #406 -re-enabled compression on the HTTP path via the inline PyO3 binding. -This module pins the WS-side equivalent: the first frame from the client -must be compressed via the same PyO3 dispatcher before forwarding to -the upstream WebSocket. +The default Python CLI runtime now compresses WS `response.create` frames +through its CompressionUnit + ContentRouter path. These tests keep the +lower-level PyO3 live-zone binding covered on WebSocket-shaped envelopes +so Rust migration work cannot silently break the exposed bridge. The tests exercise the compression *transformation logic* in isolation — they replicate the body-shape handling the WS handler does (envelope diff --git a/tests/test_toin.py b/tests/test_toin.py index 41800c75b..73f1fe06e 100644 --- a/tests/test_toin.py +++ b/tests/test_toin.py @@ -145,24 +145,6 @@ class TestToolPattern: assert pattern.full_retrieval_rate == 0.0 -@pytest.mark.skip( - reason=( - "PR-B5: CompressionHint is now private (_CompressionHint) and " - "the request-time hint API is retired. See " - "tests/test_toin_observation_only.py for the replacement contract." - ) -) -class TestCompressionHint: - """Retired: CompressionHint was the public envelope for the - request-time hint API removed in PR-B5.""" - - def test_default_values(self): - pass - - def test_custom_values(self): - pass - - class TestTOINConfig: """Test TOINConfig data model.""" diff --git a/tests/test_toin_observation_only.py b/tests/test_toin_observation_only.py index 744b18773..13de8ce9e 100644 --- a/tests/test_toin_observation_only.py +++ b/tests/test_toin_observation_only.py @@ -75,8 +75,7 @@ def test_compression_hint_is_not_publicly_exported(): import headroom.telemetry as telemetry_pkg assert not hasattr(telemetry_pkg, "CompressionHint"), ( - "PR-B5: CompressionHint became private (_CompressionHint) and " - "must not be importable from headroom.telemetry." + "PR-B5: CompressionHint was retired and must not be importable from headroom.telemetry." )