0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
285 lines
12 KiB
Python
285 lines
12 KiB
Python
"""Per-auth-mode compression policy — Phase F PR-F2.1, extended in F2.2 (Python parity).
|
|
|
|
Hand-mirrored port of `headroom_core::compression_policy::CompressionPolicy`
|
|
(Rust). The Rust crate is the source of truth; this module exists so
|
|
the Python proxy's `TransformPipeline` (which still runs `CacheAligner`
|
|
and other detector-only transforms) can read the same per-mode flags
|
|
the Rust dispatcher does.
|
|
|
|
A parity test (`tests/test_compression_policy.py`) instantiates one of
|
|
each variant and asserts the field map matches what the Rust unit
|
|
tests assert. F2.2 should consider exposing the Rust struct via PyO3
|
|
to retire this hand-mirror — that's deliberately out of scope here so
|
|
F2.1/F2.2 can ship.
|
|
|
|
See `crates/headroom-core/src/compression_policy.rs` for the canonical
|
|
docstring (per-mode rationale, why-a-struct, etc.).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
from headroom.proxy.auth_mode import AuthMode
|
|
|
|
# ── F2.2 per-mode default values (CONSERVATIVE pending bake telemetry) ──
|
|
# Mirrors the Rust ``pub(crate) const`` block in
|
|
# ``crates/headroom-core/src/compression_policy.rs``. Centralised here so
|
|
# a follow-up tune lands in one place per language. The Rust tests assert
|
|
# the values directly; the Python parity tests assert the *fields* match
|
|
# (not the values — that would double-pin against drift in a way that
|
|
# masks real divergence).
|
|
#
|
|
# Per the realignment build constraints (project memory
|
|
# ``feedback_realignment_build_constraints.md``): "configurable / no
|
|
# hardcoded values". The configuration *is* the per-mode default — we
|
|
# deliberately do NOT add a separate env var per field. Operators tune
|
|
# by editing these constants and shipping a new build, mirroring the
|
|
# Rust pattern exactly.
|
|
|
|
#: PAYG: aggressive — let volatile content noise up to ~128 tokens slip
|
|
#: before flagging. Higher than Subscription because PAYG users opt in
|
|
#: to aggressive compression.
|
|
_VOLATILE_TOKEN_THRESHOLD_PAYG: int = 128
|
|
|
|
#: Subscription: conservative — flag volatile content earlier (32
|
|
#: tokens) so cache prefixes stay stable.
|
|
_VOLATILE_TOKEN_THRESHOLD_SUBSCRIPTION: int = 32
|
|
|
|
#: PAYG: cap lossy compression at 45% of original tokens. Aggressive
|
|
#: but bounded — F2.1 had no cap (effectively ``1.0``), F2.2 introduces one.
|
|
_MAX_LOSSY_RATIO_PAYG: float = 0.45
|
|
|
|
#: Subscription: conservative cap at 25%. Cache stability over savings.
|
|
_MAX_LOSSY_RATIO_SUBSCRIPTION: float = 0.25
|
|
|
|
#: Anthropic prompt-cache write multiplier: a ``cache_creation`` token
|
|
#: costs 1.25x a plain input token (5-minute TTL tier). Input to the
|
|
#: net-cost mutation formula (#856). Mirrors the Rust ``pub const``.
|
|
CACHE_WRITE_MULTIPLIER: float = 1.25
|
|
|
|
#: Anthropic prompt-cache read multiplier: a ``cache_read`` token costs
|
|
#: 0.1x a plain input token. Input to the net-cost mutation formula
|
|
#: (#856). Mirrors the Rust ``pub const``.
|
|
CACHE_READ_MULTIPLIER: float = 0.1
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CompressionPolicy:
|
|
"""Per-auth-mode policy that downstream compression stages consult.
|
|
|
|
Five fields after F2.2:
|
|
|
|
- ``live_zone_only`` (F2.1) — gate for the Python TransformPipeline
|
|
to skip pre-cache-marker mutation.
|
|
- ``cache_aligner_enabled`` (F2.1) — gate for the Python
|
|
``CacheAligner`` transform.
|
|
- ``volatile_token_threshold`` (F2.2) — per-mode token threshold
|
|
below which content is treated as cache-stable. Plumbed through
|
|
the struct; no current detector consumes it (intentional — the
|
|
detector refactor is a follow-up).
|
|
- ``max_lossy_ratio`` (F2.2) — per-mode upper bound on lossy
|
|
compression aggressiveness (fraction in ``[0.0, 1.0]``). Plumbed
|
|
through the struct; no current compressor consumes it.
|
|
- ``toin_read_only`` (F2.2) — TOIN learning gate. ``True`` =
|
|
serve cached patterns but never write new observations from
|
|
this request (Subscription).
|
|
"""
|
|
|
|
live_zone_only: bool
|
|
"""When True, transforms MUST NOT modify bytes outside the post-
|
|
cache-marker live zone. The Rust live-zone dispatcher is already
|
|
live-zone-only by construction, so this flag is effectively a
|
|
no-op on the Rust path; it exists for the Python TransformPipeline
|
|
where transforms like CacheAligner inspect the cached prefix."""
|
|
|
|
cache_aligner_enabled: bool
|
|
"""When False, the CacheAligner transform's `should_apply` MUST
|
|
return False — that's the load-bearing F2.1 gate for the cache-
|
|
instability complaints in #327 / #388."""
|
|
|
|
volatile_token_threshold: int
|
|
"""F2.2: per-mode token-count threshold below which content is
|
|
treated as cache-stable. Subscription is conservative
|
|
(low → flag aggressively → keep prompts stable); PAYG aggressive
|
|
(high → tolerate more volatile noise). Plumbed but unconsumed in
|
|
F2.2 — the volatile detector in ``cache_aligner.py`` is shape-
|
|
based, not token-count-based; wiring it is a follow-up."""
|
|
|
|
max_lossy_ratio: float
|
|
"""F2.2: per-mode upper bound on lossy compression aggressiveness,
|
|
expressed as the fraction of original tokens that may be dropped
|
|
(``0.0`` = no lossy, ``1.0`` = unlimited). Subscription ``0.25``;
|
|
PAYG ``0.45``. Plumbed but unconsumed in F2.2 — distinct from the
|
|
caller-driven ``target_ratio`` kwarg in the Python ContentRouter."""
|
|
|
|
toin_read_only: bool
|
|
"""F2.2: when True, TOIN serves cached recommendations but
|
|
never writes new pattern observations from this request.
|
|
Subscription True (consistency over learning); PAYG/OAuth False
|
|
(network effect keeps growing). The gate is read by
|
|
``smart_crusher.py`` and ``content_router.py`` at the
|
|
``record_compression`` call sites."""
|
|
|
|
def net_mutation_gain(
|
|
self,
|
|
delta_t: int,
|
|
suffix_tokens: int,
|
|
expected_reads: float,
|
|
p_alive: float,
|
|
) -> float:
|
|
"""Net gain (in plain-input-token cost units) of a mutation that
|
|
removes ``delta_t`` tokens from a message whose cached suffix is
|
|
``suffix_tokens`` long (#856).
|
|
|
|
Mirrors ``CompressionPolicy::net_mutation_gain`` in the Rust
|
|
crate (source of truth — see its docstring for the derivation)::
|
|
|
|
gain = dT * (w + r*(R - 1)) - P_alive * (w - r) * (S + dT)
|
|
|
|
The warm-case penalty covers ``S + dT``: with a live cache the
|
|
``dT`` tokens are already cache-written, so keeping them costs
|
|
only reads — a mutation avoids at most ``dT*r*R``, not a fresh
|
|
write.
|
|
|
|
Inputs are clamped: ``delta_t``/``suffix_tokens`` to ``>= 0``
|
|
(the Rust signature takes ``u32``), ``expected_reads`` to
|
|
``>= 0`` (NaN → 0), ``p_alive`` to ``[0, 1]`` (NaN → 1, the
|
|
conservative full-penalty assumption — same as Rust).
|
|
"""
|
|
w = CACHE_WRITE_MULTIPLIER
|
|
r = CACHE_READ_MULTIPLIER
|
|
dt = max(0, delta_t)
|
|
suffix = max(0, suffix_tokens)
|
|
# Python max()/min() propagate NaN from the first argument, unlike
|
|
# f32::max in the Rust source of truth — guard explicitly.
|
|
reads = 0.0 if math.isnan(expected_reads) else max(expected_reads, 0.0)
|
|
alive = 1.0 if math.isnan(p_alive) else min(max(p_alive, 0.0), 1.0)
|
|
return float(dt) * (w + r * (reads - 1.0)) - alive * (w - r) * float(suffix + dt)
|
|
|
|
def should_mutate_deep(
|
|
self,
|
|
delta_t: int,
|
|
suffix_tokens: int,
|
|
expected_reads: float,
|
|
p_alive: float,
|
|
) -> bool:
|
|
"""Decision form of :meth:`net_mutation_gain`: mutate iff the
|
|
gain is strictly positive."""
|
|
return self.net_mutation_gain(delta_t, suffix_tokens, expected_reads, p_alive) > 0.0
|
|
|
|
def break_even_reads(self, delta_t: int, suffix_tokens: int) -> float:
|
|
"""Remaining-read count at which a warm-cache (``p_alive=1``)
|
|
mutation breaks even::
|
|
|
|
R = ((w - r) / r) * (S/dT) = 11.5 * S/dT (Anthropic 5-min)
|
|
|
|
With the corrected penalty this reproduces the #856 anchors
|
|
exactly: 2K/50K -> 287.5, 50K/10K -> 2.3.
|
|
|
|
Returns 0 when ``delta_t`` is ``<= 0`` (no savings — callers
|
|
gate on ``delta_t > 0``; the Rust signature takes ``u32``).
|
|
Mirrors the Rust method.
|
|
"""
|
|
if delta_t <= 0:
|
|
return 0.0
|
|
w = CACHE_WRITE_MULTIPLIER
|
|
r = CACHE_READ_MULTIPLIER
|
|
return ((w - r) / r) * (float(max(0, suffix_tokens)) / float(delta_t))
|
|
|
|
|
|
def policy_for_mode(mode: AuthMode) -> CompressionPolicy:
|
|
"""Resolve the F2.1+F2.2 policy for an auth mode.
|
|
|
|
PAYG and OAuth are identical (aggressive: live-zone-not-only,
|
|
cache-aligner on, relaxed thresholds, TOIN write-enabled).
|
|
Subscription is the user-visible win: live-zone-only with cache
|
|
aligner disabled, tighter thresholds, TOIN read-only.
|
|
|
|
F2.2-followup may diverge OAuth from PAYG once telemetry is
|
|
collected.
|
|
"""
|
|
if mode == AuthMode.PAYG:
|
|
return CompressionPolicy(
|
|
live_zone_only=False,
|
|
cache_aligner_enabled=True,
|
|
volatile_token_threshold=_VOLATILE_TOKEN_THRESHOLD_PAYG,
|
|
max_lossy_ratio=_MAX_LOSSY_RATIO_PAYG,
|
|
toin_read_only=False,
|
|
)
|
|
if mode == AuthMode.OAUTH:
|
|
# Identical to PAYG in F2.1/F2.2. The parity test in
|
|
# ``tests/test_compression_policy.py`` is the canary that
|
|
# catches a future divergence and forces a deliberate update
|
|
# there + in the Rust crate.
|
|
return CompressionPolicy(
|
|
live_zone_only=False,
|
|
cache_aligner_enabled=True,
|
|
volatile_token_threshold=_VOLATILE_TOKEN_THRESHOLD_PAYG,
|
|
max_lossy_ratio=_MAX_LOSSY_RATIO_PAYG,
|
|
toin_read_only=False,
|
|
)
|
|
if mode == AuthMode.SUBSCRIPTION:
|
|
return CompressionPolicy(
|
|
live_zone_only=True,
|
|
cache_aligner_enabled=False,
|
|
volatile_token_threshold=_VOLATILE_TOKEN_THRESHOLD_SUBSCRIPTION,
|
|
max_lossy_ratio=_MAX_LOSSY_RATIO_SUBSCRIPTION,
|
|
toin_read_only=True,
|
|
)
|
|
raise ValueError(f"Unhandled AuthMode variant: {mode!r}")
|
|
|
|
|
|
def policy_default_payg() -> CompressionPolicy:
|
|
"""The PAYG-equivalent policy used when the
|
|
``HEADROOM_PROXY_AUTH_MODE_POLICY_ENFORCEMENT`` flag is disabled
|
|
(default in F2.1 c1-c4; flipped to enabled in c5/5).
|
|
|
|
Centralised so the proxy handlers do not duplicate the constant,
|
|
and so a future change to PAYG semantics propagates to both the
|
|
enforcement-on and enforcement-off paths.
|
|
"""
|
|
return policy_for_mode(AuthMode.PAYG)
|
|
|
|
|
|
_ENFORCEMENT_ENV = "HEADROOM_PROXY_AUTH_MODE_POLICY_ENFORCEMENT"
|
|
|
|
|
|
def is_enforcement_enabled() -> bool:
|
|
"""Read the enforcement flag from the environment.
|
|
|
|
Same env var the Rust proxy reads (``Config::auth_mode_policy_enforcement``)
|
|
so the two paths stay in lockstep with one operator switch.
|
|
Default (when unset): ``True`` from F2.1 c5/5 onward, matching
|
|
the Rust default after the c5 flip.
|
|
|
|
NOT cached — read every call so an operator can flip the env var
|
|
in a hot-reload scenario. The cost is one ``dict.get`` per call,
|
|
well below noise.
|
|
"""
|
|
val = os.environ.get(_ENFORCEMENT_ENV, "enabled").strip().lower()
|
|
# Same set of off-values the telemetry beacon honours
|
|
# (`headroom/telemetry/beacon.py::_OFF_VALUES`) so operators don't
|
|
# have to remember a different vocabulary per flag.
|
|
return val not in ("disabled", "off", "false", "0", "no")
|
|
|
|
|
|
def resolve_policy(auth_mode: AuthMode | None) -> CompressionPolicy:
|
|
"""Resolve the effective ``CompressionPolicy`` for a request.
|
|
|
|
- If the enforcement flag is off, returns PAYG-equivalent
|
|
regardless of the classified auth mode.
|
|
- If the enforcement flag is on and ``auth_mode`` is ``None``,
|
|
returns PAYG-equivalent (defensive default for the unclassified
|
|
/ batch-row path).
|
|
- Otherwise returns the per-mode policy.
|
|
|
|
This is the single public entry point handlers should call when
|
|
deriving the policy from a request's classification result.
|
|
"""
|
|
if auth_mode is None or not is_enforcement_enabled():
|
|
return policy_default_payg()
|
|
return policy_for_mode(auth_mode)
|