Skip to content

Commit 47958a0

Browse files
committed
feat: add CLI-arg proxy toggles to settings panel
Add the remaining env-backed `headroom proxy` flags to the GUI so they persist via settings.json: optimization on/off (HEADROOM_OPTIMIZE), semantic cache on/off (HEADROOM_CACHE_ENABLED), rate limiting on/off (HEADROOM_RATE_LIMIT_ENABLED), tool-result interception (HEADROOM_INTERCEPT_ENABLED), and the embedding-server sidecar +socket. - Wire the Click path to honor HEADROOM_OPTIMIZE/CACHE_ENABLED/ RATE_LIMIT_ENABLED (previously only the argparse entrypoint read them); when unset _get_env_bool returns the True default, so behavior is unchanged. - Fix HEADROOM_INTERCEPT_ENABLED readers (server + pipeline) to bool-parse instead of truthy os.environ.get, so "0"/"false" disable rather than wrongly enable; place the knob on the Compression page (startup-read, not a live Output Shaping knob). - Widen runtime_env.set_overrides to Mapping[str, object] to clear a pre-existing mypy invariance error. Registry 89 -> 95 fields. Docs updated; INTERCEPT_ENABLED moved out of the live-knobs section (it is startup-read).
1 parent c2ab6ac commit 47958a0

7 files changed

Lines changed: 130 additions & 10 deletions

File tree

docs/environment-variables.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,20 @@ tables in this section, the panel now also surfaces (all restart-required): the
4848
Kompress engine backend, cross-turn dedup and tool-search toggles (§6); the CCR
4949
storage backend, Redis URL and CCR TTL (§7); stateless/offline mode, strict-TLS,
5050
and CORS/WebSocket origins (§5); the Vertex/Bedrock/Gemini/Cloud Code base URLs
51-
(above); and the Qdrant URL/host/port/API-key (§9).
51+
(above); the Qdrant URL/host/port/API-key (§9); the proxy mode and the core
52+
CLI toggles — optimization, semantic cache, and rate-limiting on/off, plus
53+
tool-result interception (§2 Compression); and the embedding-server sidecar (§9).
5254
`manifest_managed` fields are read-only on supervised Docker/service installs.
5355

5456
### Compression
5557

5658
| Variable | Type | Default | Description |
5759
|---|---|---|---|
5860
| `HEADROOM_MODE` | enum | `token` | Proxy posture: `token` (compress; history may be rewritten for max savings) or `cache` (freeze prior turns for provider prefix-cache stability). |
61+
| `HEADROOM_OPTIMIZE` | bool | `true` | Master optimization switch. `false` = passthrough (no compression); mirrors `--no-optimize`. |
62+
| `HEADROOM_CACHE_ENABLED` | bool | `true` | Semantic response cache. `false` mirrors `--no-cache`. |
63+
| `HEADROOM_RATE_LIMIT_ENABLED` | bool | `true` | Enforce RPM/TPM limits. `false` mirrors `--no-rate-limit`. |
64+
| `HEADROOM_INTERCEPT_ENABLED` | bool | `false` | Enable ast-grep tool_result interceptors (Read outliner); mirrors `--intercept-tool-results`. |
5965
| `HEADROOM_SAVINGS_PROFILE` | enum | `coding` | Named compression posture: `agent-90`, `balanced`, `coding`, `general`. |
6066
| `HEADROOM_TARGET_RATIO` | float 0-1 | adaptive | Kompress keep-ratio (lower = more aggressive). |
6167
| `HEADROOM_DISABLE_KOMPRESS` | bool | `false` | Disable Kompress ML compression (structural compression stays on). |
@@ -164,7 +170,6 @@ restart) through the same override store.
164170
| `HEADROOM_VERBOSITY_AUTOTUNE` | bool | Use the AIMD verbosity controller state. |
165171
| `HEADROOM_OUTPUT_HOLDOUT` | float | Fraction of conversations held out for A/B measurement. |
166172
| `HEADROOM_INTERCEPT_READ_MIN_CHARS` | int | Min tool-output chars before the ast-grep read rewrite. |
167-
| `HEADROOM_INTERCEPT_ENABLED` | bool | Enable ast-grep-based tool-result interception/rewrite. |
168173

169174
---
170175

@@ -286,6 +291,7 @@ selection* for the `HEADROOM_KOMPRESS_BACKEND` value table (`auto`, `onnx`,
286291
|---|---|
287292
| `HEADROOM_EMBEDDER_RUNTIME` | Set `pytorch_mps` to run the memory embedder on Apple GPU (requires `[pytorch-mps]` extra, MPS-availability-gated). |
288293
| `HEADROOM_EMBEDDING_SERVER_SOCKET` | Unix socket path for the out-of-process embedding server. |
294+
| `HEADROOM_EMBEDDING_SERVER` | Run a shared out-of-process embedding server sidecar across workers (saves ~600 MB RSS). GUI-editable (§2 Memory). |
289295
| `HEADROOM_EMBED_CONCURRENCY` | Max concurrent embedding calls. |
290296
| `HEADROOM_EMBED_NUM_THREADS` | Thread count for the embedding backend. |
291297
| `HEADROOM_QDRANT_URL` | Full Qdrant URL (e.g. hosted Qdrant Cloud). |

headroom/cli/proxy.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,9 +1168,13 @@ def proxy(
11681168
cloudcode_api_url=provider_api_overrides.cloudcode,
11691169
vertex_api_url=provider_api_overrides.vertex,
11701170
mode=effective_mode,
1171-
optimize=not no_optimize,
1172-
cache_enabled=not no_cache,
1173-
rate_limit_enabled=not no_rate_limit,
1171+
# CLI flag disables; else honor the env toggle (settings.json path).
1172+
# When the env var is unset _get_env_bool returns the True default, so
1173+
# behavior is identical to the historic `not no_<flag>`.
1174+
optimize=not no_optimize and _get_env_bool("HEADROOM_OPTIMIZE", True),
1175+
cache_enabled=not no_cache and _get_env_bool("HEADROOM_CACHE_ENABLED", True),
1176+
rate_limit_enabled=not no_rate_limit
1177+
and _get_env_bool("HEADROOM_RATE_LIMIT_ENABLED", True),
11741178
rate_limit_requests_per_minute=rpm if rpm is not None else 60,
11751179
rate_limit_tokens_per_minute=tpm if tpm is not None else 100_000,
11761180
compress_user_messages=_get_env_bool("HEADROOM_COMPRESS_USER_MESSAGES", False),

headroom/proxy/runtime_env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def getenv(name: str, default: str | None = None) -> str | None:
103103
return os.environ.get(name, default)
104104

105105

106-
def set_overrides(values: dict[str, object]) -> dict[str, str]:
106+
def set_overrides(values: Mapping[str, object]) -> dict[str, str]:
107107
"""Apply hot-reload overrides for known knobs. Returns what was applied.
108108
109109
Unknown keys and non-string values are ignored (the endpoint is loopback-only

headroom/proxy/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,12 @@ def _router_config_for(kompress_disabled: bool) -> ContentRouterConfig:
846846
self._code_aware_status = "lazy" if config.code_aware_enabled else "disabled"
847847

848848
_intercept_prefix: list = []
849-
if os.environ.get("HEADROOM_INTERCEPT_ENABLED"):
849+
if os.environ.get("HEADROOM_INTERCEPT_ENABLED", "").strip().lower() in (
850+
"1",
851+
"true",
852+
"yes",
853+
"on",
854+
):
850855
from headroom.proxy.interceptors import ToolResultInterceptorTransform
851856

852857
_intercept_prefix = [ToolResultInterceptorTransform()]

headroom/settings_store.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,75 @@ def live(self) -> bool:
987987
help="API key for hosted Qdrant (e.g. Qdrant Cloud).",
988988
tier="advanced",
989989
),
990+
# --- CLI-arg toggles (env-backed; restart-required) ---------------------
991+
# These mirror `headroom proxy` flags that resolve from an env var, so the
992+
# settings.json -> setdefault path reaches them.
993+
# Compression page
994+
SettingField(
995+
"HEADROOM_OPTIMIZE",
996+
"optimize",
997+
"Optimization enabled",
998+
"Compression",
999+
"bool",
1000+
default=True,
1001+
help="Master switch. Off = passthrough mode (no compression/optimization); mirrors --no-optimize.",
1002+
tier="basic",
1003+
),
1004+
# Compression page (tool_result interception is a startup-read transform, so
1005+
# it must NOT sit on the Output Shaping page, whose fields are auto-live).
1006+
SettingField(
1007+
"HEADROOM_INTERCEPT_ENABLED",
1008+
"intercept_enabled",
1009+
"Tool-result interception",
1010+
"Compression",
1011+
"bool",
1012+
default=False,
1013+
help="Enable ast-grep tool_result interceptors (Read outliner, etc.); mirrors --intercept-tool-results.",
1014+
tier="advanced",
1015+
),
1016+
# CCR & Caching page
1017+
SettingField(
1018+
"HEADROOM_CACHE_ENABLED",
1019+
"cache_enabled",
1020+
"Semantic cache enabled",
1021+
"CCR",
1022+
"bool",
1023+
default=True,
1024+
help="Semantic response cache. Off mirrors --no-cache.",
1025+
tier="basic",
1026+
),
1027+
# Limits & Budget page
1028+
SettingField(
1029+
"HEADROOM_RATE_LIMIT_ENABLED",
1030+
"rate_limit_enabled",
1031+
"Rate limiting enabled",
1032+
"Limits",
1033+
"bool",
1034+
default=True,
1035+
help="Enforce RPM/TPM limits. Off mirrors --no-rate-limit.",
1036+
tier="basic",
1037+
),
1038+
# Memory page (out-of-process embedding server sidecar)
1039+
SettingField(
1040+
"HEADROOM_EMBEDDING_SERVER",
1041+
"embedding_server",
1042+
"Embedding server sidecar",
1043+
"Memory",
1044+
"bool",
1045+
default=False,
1046+
help="Run a shared out-of-process embedder across workers (saves ~600 MB RSS); mirrors --embedding-server.",
1047+
tier="advanced",
1048+
),
1049+
SettingField(
1050+
"HEADROOM_EMBEDDING_SERVER_SOCKET",
1051+
"embedding_server_socket",
1052+
"Embedding server socket",
1053+
"Memory",
1054+
"str",
1055+
default=None,
1056+
help="Unix socket path for the embedding sidecar. Default: /tmp/headroom-embed-<port>.sock.",
1057+
tier="advanced",
1058+
),
9901059
# --- Output Shaping (live: applied via runtime_env, no restart) ----------
9911060
# These mirror headroom/proxy/runtime_env.py RUNTIME_ENV_KNOBS. A save
9921061
# persists to settings.json AND hot-reloads through set_overrides(), so it

headroom/transforms/pipeline.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,13 @@ def _build_default_transforms(self) -> list[Transform]:
144144
# users try it and compare before we make it the default.
145145
import os as _os
146146

147-
if getattr(self.config, "intercept_tool_results", False) or _os.environ.get(
148-
"HEADROOM_INTERCEPT_ENABLED"
149-
):
147+
_intercept_env = _os.environ.get("HEADROOM_INTERCEPT_ENABLED", "").strip().lower() in (
148+
"1",
149+
"true",
150+
"yes",
151+
"on",
152+
)
153+
if getattr(self.config, "intercept_tool_results", False) or _intercept_env:
150154
from headroom.proxy.interceptors import ToolResultInterceptorTransform
151155

152156
transforms.append(ToolResultInterceptorTransform())

tests/test_settings_pages.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def test_added_curated_knobs_on_expected_pages(self):
8080
"qdrant_host": "Memory",
8181
"qdrant_port": "Memory",
8282
"qdrant_api_key": "Memory",
83+
"optimize": "Compression",
84+
"intercept_enabled": "Compression",
85+
"cache_enabled": "CCR & Caching",
86+
"rate_limit_enabled": "Limits & Budget",
87+
"embedding_server": "Memory",
88+
"embedding_server_socket": "Memory",
8389
}
8490
for key, page in expected.items():
8591
field = settings_store._BY_KEY[key]
@@ -204,3 +210,29 @@ def test_mode_enum_roundtrip_and_apply(self, workspace):
204210
def test_mode_rejects_unknown_value(self, workspace):
205211
with pytest.raises(settings_store.SettingsValidationError):
206212
settings_store.save({"mode": "turbo"})
213+
214+
215+
class TestCliArgToggles:
216+
def test_positive_toggles_default_enabled(self):
217+
for key in ("optimize", "cache_enabled", "rate_limit_enabled"):
218+
assert settings_store._BY_KEY[key].default is True, key
219+
220+
def test_disable_toggle_serializes_zero(self, workspace):
221+
settings_store.save({"optimize": False, "cache_enabled": False})
222+
settings_store.apply_to_environ(settings_store.load())
223+
assert os.environ["HEADROOM_OPTIMIZE"] == "0"
224+
assert os.environ["HEADROOM_CACHE_ENABLED"] == "0"
225+
226+
def test_intercept_and_embedding_server_are_bools(self):
227+
assert settings_store._BY_KEY["intercept_enabled"].type == "bool"
228+
assert settings_store._BY_KEY["intercept_enabled"].default is False
229+
assert settings_store._BY_KEY["embedding_server"].default is False
230+
231+
def test_intercept_enabled_applies_as_one(self, workspace):
232+
settings_store.save({"intercept_enabled": True})
233+
settings_store.apply_to_environ(settings_store.load())
234+
assert os.environ["HEADROOM_INTERCEPT_ENABLED"] == "1"
235+
236+
def test_embedding_server_socket_str_roundtrip(self, workspace):
237+
settings_store.save({"embedding_server_socket": "/tmp/e.sock"})
238+
assert settings_store.load()["embedding_server_socket"] == "/tmp/e.sock"

0 commit comments

Comments
 (0)