[eric] agents: the two guards that could fail silently now declare themselves (ENG-388, ENG-386)

This commit is contained in:
ciregenz
2026-08-23 13:19:26 -07:00
parent d4bfc122f0
commit 8ed2330c76
4 changed files with 35 additions and 2 deletions
@@ -4,6 +4,7 @@ on boot). Split from SessionLifecycle (which handles ONE session at a time) so e
one concern. self.sessions resolves across the MRO as before."""
import logging
import os
import sys
from typeguard import typechecked
@@ -22,7 +23,15 @@ logger = logging.getLogger(__name__)
def running_under_test() -> bool:
"""Auto-resume dispatches REAL turns: live credentials, live Bash, in whatever tree the process
was started from. A test that boots the app lifespan must never do that to the developer's own
chats, so this is the one gate that keeps a suite run from becoming an agent run."""
chats, so this is the one gate that keeps a suite run from becoming an agent run.
The env var is the DECLARED signal and conftest sets it. `pytest in sys.modules` is kept only as
a belt-and-braces fallback, and deliberately not as the primary: an incidental signal fails in
the worst direction, because the day pytest becomes importable in a packaged build every
crash-interrupted turn stops resuming and NOTHING says so. Work vanishing quietly is the worst
bug this codebase can ship; a suite that loudly refuses to resume is merely annoying."""
if os.environ.get("OSW_DISABLE_AUTO_RESUME") == "1":
return True
return "pytest" in sys.modules
+4
View File
@@ -33,6 +33,10 @@ def _isolate_browser_state(monkeypatch):
monkeypatch.setenv("OPENSWARM_BROWSER_METRICS_DIR", metrics_dir)
monkeypatch.setenv("OPENSWARM_BROWSER_PLAYBOOK_DIR", playbook_dir)
# The speed levers are default-ON in prod; pin them off for the suite so mocked loop tests keep exact aux-call/turn expectations (same pattern as OPENSWARM_PERSISTENT_CLIENT). The levers are exercised by their own live gates + targeted tests that set the flag explicitly.
# DECLARED, not inferred: auto-resume sends real turns with live credentials and live Bash into
# whatever tree the suite runs from (ENG-388). The pytest-in-sys.modules fallback still exists,
# but a gate that depends on an accident fails silently the day the accident changes.
monkeypatch.setenv("OSW_DISABLE_AUTO_RESUME", "1")
monkeypatch.setenv("OSW_PRESTAGE", "0")
monkeypatch.setenv("OSW_FASTREAD_HOP", "0")
monkeypatch.setenv("OSW_PRELUDE_TRIM", "0")
@@ -47,3 +47,11 @@ def test_the_gate_is_what_stops_it(monkeypatch):
def test_the_gate_sees_pytest():
assert p_sp.running_under_test() is True
def test_the_declared_signal_works_without_the_accidental_one(monkeypatch):
"""The env var must stand on its own: the day `pytest in sys.modules` stops being true in some
runner, the gate has to keep holding, or crash-resume silently reactivates against real chats."""
monkeypatch.setenv("OSW_DISABLE_AUTO_RESUME", "1")
monkeypatch.setitem(__import__("sys").modules, "pytest", None)
assert p_sp.running_under_test() is True
+13 -1
View File
@@ -408,7 +408,19 @@ const DefaultModelGuard: React.FC<{ children: React.ReactNode }> = ({ children }
const fallback = pickFallbackModel(byProvider);
if (!fallback) return;
// A model absent from the catalog is genuinely gone; one merely absent from today's list is a provider we cannot reach this second, and moving the chat off it is never ours to do silently.
if (!catalogComplete || knownValues.length === 0) return;
// Going quiet here is itself a failure mode: a genuinely retired model then never heals and the
// user just gets an error on every send. So when the catalog cannot be vouched for, say so once
// for the sessions it would have touched instead of disabling the heal in silence.
if (!catalogComplete || knownValues.length === 0) {
const p_stranded = Object.values(store.getState().agents.sessions)
.filter((s2) => s2.model && !valid.has(s2.model));
if (p_stranded.length && !warnedSessionsRef.current.has('catalog-unverified')) {
warnedSessionsRef.current.add('catalog-unverified');
console.warn(`[models] catalog unverified (complete=${catalogComplete}, known=${knownValues.length}); ` +
`${p_stranded.length} session(s) hold a model that is not currently available and will NOT be auto-switched`);
}
return;
}
const known = new Set(knownValues);
const target = valid.has(settings.default_model) ? settings.default_model : fallback.value;
let switched = false;