diff --git a/headroom/cli/proxy.py b/headroom/cli/proxy.py index 88e4c0c26..229693374 100644 --- a/headroom/cli/proxy.py +++ b/headroom/cli/proxy.py @@ -417,6 +417,18 @@ def dashboard(port: int, no_open: bool) -> None: "Env: HEADROOM_ANTHROPIC_PRE_UPSTREAM_MEMORY_CONTEXT_TIMEOUT_SECONDS." ), ) +@click.option( + "--compression-max-workers", + type=int, + default=None, + envvar="HEADROOM_COMPRESSION_MAX_WORKERS", + help=( + "Bound the dedicated compression threadpool (CPU-bound Kompress work). " + "Default (unset): min(32, (cpu_count or 1) * 4). Lower it to reduce CPU " + "oversubscription under concurrent sessions; a value < 1 is clamped to 1. " + "Env: HEADROOM_COMPRESSION_MAX_WORKERS." + ), +) @click.option( "--log-file", default=None, @@ -843,6 +855,7 @@ def proxy( anthropic_pre_upstream_concurrency: int | None, anthropic_pre_upstream_acquire_timeout_seconds: float | None, anthropic_pre_upstream_memory_context_timeout_seconds: float | None, + compression_max_workers: int | None, log_file: str | None, log_messages: bool, codex_wire_debug: bool, @@ -1167,6 +1180,7 @@ def proxy( # Precedence: CLI > env > auto-compute (click's ``envvar`` # handles the env-var fallback). anthropic_pre_upstream_concurrency=anthropic_pre_upstream_concurrency, + compression_max_workers=compression_max_workers, anthropic_pre_upstream_acquire_timeout_seconds=( anthropic_pre_upstream_acquire_timeout_seconds if anthropic_pre_upstream_acquire_timeout_seconds is not None diff --git a/tests/test_cli_proxy_improvements.py b/tests/test_cli_proxy_improvements.py index ae1e1b6e1..3230fc2ec 100644 --- a/tests/test_cli_proxy_improvements.py +++ b/tests/test_cli_proxy_improvements.py @@ -448,3 +448,34 @@ class TestHelpTextCompleteness: result = runner.invoke(main, ["proxy", "--mode", "bogus_mode_xyz"]) assert result.exit_code != 0 assert "invalid" in result.output.lower() or "choice" in result.output.lower() + + +class TestCompressionMaxWorkers: + """--compression-max-workers / HEADROOM_COMPRESSION_MAX_WORKERS must reach ProxyConfig. + + Regression: the field was documented in ProxyConfig and consumed by the + server, but the CLI never defined the option or passed it through, so it + was permanently None (always resolving to the min(32, cpu*4) default). + """ + + def test_flag_reaches_config(self, runner: CliRunner, mock_run_server: dict) -> None: + result = runner.invoke( + main, ["proxy", "--compression-max-workers", "3"], catch_exceptions=False + ) + assert result.exit_code == 0, result.output + assert mock_run_server["config"].compression_max_workers == 3 + + def test_env_reaches_config(self, runner: CliRunner, mock_run_server: dict) -> None: + result = runner.invoke( + main, + ["proxy"], + env={"HEADROOM_COMPRESSION_MAX_WORKERS": "5"}, + catch_exceptions=False, + ) + assert result.exit_code == 0, result.output + assert mock_run_server["config"].compression_max_workers == 5 + + def test_default_is_none(self, runner: CliRunner, mock_run_server: dict) -> None: + result = runner.invoke(main, ["proxy"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert mock_run_server["config"].compression_max_workers is None