From 1dc220f37053cc0330ea2175996fefa01bef59a4 Mon Sep 17 00:00:00 2001 From: James Wiesebron Date: Thu, 25 Jun 2026 20:45:12 -0700 Subject: [PATCH] fix(#3277): forward EGG_CONTEXT_MEASUREMENT into agent pods The orchestrator's `_FORWARDED_DISCIPLINE_ENV_KEYS` forwarded `EGG_CONTEXT_DISCIPLINE` and `EGG_SESSION_RESUME` but omitted `EGG_CONTEXT_MEASUREMENT`, the flag #3271's `record_measurement()` gates on. With the flag absent in-pod, the #3249 emit-only measurement surfaces no-op in every agent, so an instrumented proving run captures zero metrics even while context discipline is active. The omission dated to #3272, whose comment said the measurement knob had no in-pod consumer yet. That consumer landed in #3271 as `egg_agent.measurement` under the fixed name `EGG_CONTEXT_MEASUREMENT`, so the rationale no longer holds. - Add `EGG_CONTEXT_MEASUREMENT` to the forward tuple; rewrite the stale "not forwarded yet" comment. - Pin the regression in test_kubernetes_spawner.py: assert the key forwards when set and is absent when unset. - Update docs/architecture/context-discipline.md to list the flag among those forwarded from the orchestrator deployment. --- docs/architecture/context-discipline.md | 11 +++++++---- orchestrator/kubernetes_spawner.py | 13 ++++++------- orchestrator/tests/test_kubernetes_spawner.py | 6 ++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/architecture/context-discipline.md b/docs/architecture/context-discipline.md index 30d9e7db25..86b826fe8d 100644 --- a/docs/architecture/context-discipline.md +++ b/docs/architecture/context-discipline.md @@ -201,12 +201,15 @@ switches, each read in exactly one place: | `EGG_CONTEXT_MEASUREMENT` | `egg_agent.measurement.measurement_enabled` | Opt-in for the emit-only per-event measurement surfaces (#3249). ON (and `EGG_PIPELINE_ID` set) → after each BRC event the agent emits the six context-discipline metrics through the progress + heartbeat surfaces; OFF (default) → the legacy path is byte-identical (no measurement emit). | | `EGG_REAL_BACKEND_WINDOW` | `egg_agent.measurement._resolve_real_window` | Cross-boundary integer override of the model's real backend window, mirroring `EGG_RESEED_THRESHOLD`. Because `orchestrator` is off `PYTHONPATH` in-pod, this is the channel that populates the window-relative metrics (`real_backend_window` / `window_utilization`) in production; the orchestrator import is a dev/CI-only fallback, and both metrics degrade to `None` when neither yields a value. | -**Setting the flags in production.** `EGG_CONTEXT_DISCIPLINE` and -`EGG_SESSION_RESUME` are read in-pod but must be set on the **orchestrator -deployment** — `orchestrator/kubernetes_spawner.py` forwards them from the +**Setting the flags in production.** `EGG_CONTEXT_DISCIPLINE`, +`EGG_CONTEXT_MEASUREMENT`, and `EGG_SESSION_RESUME` are read in-pod but must be +set on the **orchestrator deployment** — +`orchestrator/kubernetes_spawner.py` forwards them from the orchestrator's own environment into every spawned agent Job (`_FORWARDED_DISCIPLINE_ENV_KEYS`, added in -[#3272](https://github.com/jwbron/egg/issues/3272)). Nothing else wires them +[#3272](https://github.com/jwbron/egg/issues/3272); `EGG_CONTEXT_MEASUREMENT` +added in [#3277](https://github.com/jwbron/egg/issues/3277) once #3271's in-pod +consumer landed). Nothing else wires them into the Job env (`envFrom` is absent; `sandbox_env` is built from pipeline fields), so a `kubectl set env` on the pods directly has no effect. The forward runs before the per-spawn `extra_env` merge, so a targeted diff --git a/orchestrator/kubernetes_spawner.py b/orchestrator/kubernetes_spawner.py index c1b8d27301..7ea6f3c13b 100644 --- a/orchestrator/kubernetes_spawner.py +++ b/orchestrator/kubernetes_spawner.py @@ -221,15 +221,14 @@ def _resolve_wait_producer_allowlist(phase: str | None, role: str, repo: str | N # the per-spawn substrate (``EGG_SESSION_STATE_FILE`` / ``EGG_RESEED_THRESHOLD``) # needs real per-pod wiring and is deliberately NOT a blind forward. # -# NOTE: the #3249 emit-only measurement knob (``EGG_CONTEXT_MEASUREMENT``) is -# deliberately NOT forwarded yet — it has no in-pod consumer (no -# ``egg_agent.measurement`` module exists), and the flag-name auto-discovery -# convention (``sandbox/tests/test_context_discipline_flag.py``) means the -# eventual consumer's flag name isn't pinned. Forwarding a guessed name now would -# be a silent no-op. Add it here once #3249's emit consumer lands under a fixed -# name. +# ``EGG_CONTEXT_MEASUREMENT`` forwards the #3249 emit-only measurement knob: its +# in-pod consumer landed in #3271 as ``egg_agent.measurement`` under the fixed +# name ``MEASUREMENT_ENV = "EGG_CONTEXT_MEASUREMENT"``, which ``record_measurement`` +# gates on. Without forwarding it the surfaces no-op in every pod (#3277), so the +# instrumented proving run captures zero metrics even with discipline active. _FORWARDED_DISCIPLINE_ENV_KEYS: tuple[str, ...] = ( "EGG_CONTEXT_DISCIPLINE", + "EGG_CONTEXT_MEASUREMENT", "EGG_SESSION_RESUME", ) diff --git a/orchestrator/tests/test_kubernetes_spawner.py b/orchestrator/tests/test_kubernetes_spawner.py index 0587864a62..6b5f773542 100644 --- a/orchestrator/tests/test_kubernetes_spawner.py +++ b/orchestrator/tests/test_kubernetes_spawner.py @@ -975,6 +975,7 @@ def test_flags_forwarded_into_pod_env_when_set( self, spawner, mock_k8s_client, mock_gateway, monkeypatch ): monkeypatch.setenv("EGG_CONTEXT_DISCIPLINE", "true") + monkeypatch.setenv("EGG_CONTEXT_MEASUREMENT", "true") monkeypatch.setenv("EGG_SESSION_RESUME", "1") result = spawner.spawn_agent_job( pipeline_id="pipe-cd", @@ -985,10 +986,14 @@ def test_flags_forwarded_into_pod_env_when_set( ) env = result.environment assert env["EGG_CONTEXT_DISCIPLINE"] == "true" + # #3277: the measurement knob must ride along, else #3271's emit + # surfaces no-op in-pod and the proving run captures zero metrics. + assert env["EGG_CONTEXT_MEASUREMENT"] == "true" assert env["EGG_SESSION_RESUME"] == "1" def test_flags_absent_when_unset(self, spawner, mock_k8s_client, mock_gateway, monkeypatch): monkeypatch.delenv("EGG_CONTEXT_DISCIPLINE", raising=False) + monkeypatch.delenv("EGG_CONTEXT_MEASUREMENT", raising=False) monkeypatch.delenv("EGG_SESSION_RESUME", raising=False) result = spawner.spawn_agent_job( pipeline_id="pipe-cd2", @@ -999,6 +1004,7 @@ def test_flags_absent_when_unset(self, spawner, mock_k8s_client, mock_gateway, m ) env = result.environment assert "EGG_CONTEXT_DISCIPLINE" not in env + assert "EGG_CONTEXT_MEASUREMENT" not in env assert "EGG_SESSION_RESUME" not in env def test_extra_env_overrides_forwarded_flag(