fix(e2e_live): --self-mod seeds the owner id only, never a campaign

The rc.15 paid run2 (2026-09-05, SM1_a1/a2) showed the stand's --self-mod
lanes pre-seeding the benchmark helper's ACTIVE campaign ("benchmark
evidence" objective, evolution_mode_enabled) instead of relying on the
post-task promotion path the flag is meant to exercise:

- generic cycles ran from t=0 next to the scenario task (three no_op
  cycles, $16 on one lane), each counted as a consecutive failure;
- the promotion the scenario task wrote (post_task_evolution_request.json)
  was refused by apply_pending_request because evolution was already
  enabled, and the kept request file also blocked wait_for_absorb's
  no_promotion exit — every SM1 lane waited the full task timeout with
  absorbed_cycles_done at 0 although its reviewed commit had landed.

seed_owner_state(data_root, evolution_enabled=False) under every profile:
--self-mod stays a settings fact (OUROBOROS_POST_TASK_EVOLUTION + cadence
every_n:1) and the scenario task's own promotion enables the one-shot
campaign whose cycle lands, restarts and absorbs. The reservation rule
keeps +1 root (the one post-task cycle); its wording, the RunBudget
docstring and the DEVELOPMENT.md stand section say so, and a runner test
pins the seeded state (owner_chat_id only, no evolution_campaign.json,
no evolution_mode_enabled) with and without --self-mod.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
Ouroboros 2026-09-05 23:20:22 +00:00
parent f450953d5c
commit c6f002c122
3 changed files with 28 additions and 23 deletions

View file

@ -93,7 +93,7 @@ PROCFS_AVAILABLE = os.path.isdir("/proc") # the orphan scan reads /proc enviro
LANE_BUDGET_FLOOR_USD = 0.01
RESERVATION_RULE = (f"max({LANE_BUDGET_FLOOR_USD:g}, per_task_usd x (root_tasks + 1 if --self-mod else root_tasks)) — the "
"runtime fences each root task tree at OUROBOROS_PER_TASK_COST_USD and --self-mod adds one root for the "
"evolution cycle (rc.14: up to two cycles per lane, one at t=0 and one post-task, under the same lane fence). "
"one post-task evolution cycle (the lane seeds no campaign: the promotion enables a one-shot one). "
"The lane's TOTAL_BUDGET is that reservation — the true fence — so settled spend + in-flight ceilings <= cap")
@ -257,8 +257,7 @@ class RunBudget:
Reservation rule, per attempt: ``per_task_usd x (root_tasks + int(self_mod))`` — the runtime fences each ROOT
task tree at ``OUROBOROS_PER_TASK_COST_USD`` and children spend under their root's ceiling, so SW1 (one root,
two scouts) reserves for one root, SK1 (author + dispatch) for two, and ``--self-mod`` adds one root for the
evolution cycle: rc.14 showed up to TWO cycles per lane, one at t=0 next to the scenario task and one
post-task, all under the lane fence — the lane's TOTAL_BUDGET is the true fence. Admission asks, PER attempt:
one post-task evolution cycle, all under the lane fence — the true fence. Admission asks, PER attempt:
``spent + reservation > cap`` — it can NEVER fit, refused and recorded ``not_run`` (a later, smaller attempt is
asked on its own; nothing halts the run); otherwise it reserves only when no earlier-dispatched attempt is
still asking (FIFO by ``dispatch_index``, see ``admit``) and ``spent + reserved(in flight) + reservation <=
@ -705,7 +704,8 @@ def run_lane(job: tuple[str, int], args: argparse.Namespace, out: pathlib.Path,
row["budget"] = {"reservation_usd": budget.reservation(scenario.root_tasks),
"lane_total_budget_usd": cfg["TOTAL_BUDGET"], "per_task_usd": float(args.per_task_usd)}
sha = write_settings(settings_path, cfg)
seed_owner_state(data_root, evolution_enabled=args.self_mod)
# Owner id only, never a campaign: the task's post-task promotion enables the one-shot one (rc.15 run2 pin).
seed_owner_state(data_root, evolution_enabled=False)
from supervisor import state as sstate
(data_root / sstate.ISOLATED_BENCHMARK_SENTINEL).write_text("isolated e2e_live data root\n", encoding="utf-8")
oracle = harness.ArtifactOracle(data_root)

View file

@ -1296,8 +1296,8 @@ refuses below `--min-credit-usd`. `--total-budget` (default 100) is the RUN-WIDE
cap: a ledger sums the lanes' durable `llm_usage` costs, reserves
`max(0.01, --per-task-usd × (root tasks + 1 with --self-mod))` per attempt (SM1 and SW1 one root — scouts spend
under their root's `OUROBOROS_PER_TASK_COST_USD` fence — SK1 two; with `--self-mod` the
evolution cycle is one more root: the rc.14 paid run showed up to TWO cycles per lane, one
starting at t=0 next to the scenario task and one post-task, all under the lane fence — SM1_a1
one post-task evolution cycle is one more root (the rc.14 paid run showed a second, generic
cycle at t=0 from the benchmark campaign the lane pre-seeded then), all under the lane fence — SM1_a1
task $3.84 + cycles $12.40 + $2.84 of $20; the lane's TOTAL_BUDGET is the true fence and the
+1 root is its reservation), admits an
attempt only while `spent + reserved(in flight) + reservation ≤ cap` — an attempt that
@ -1366,7 +1366,12 @@ server and resolves `-n auto` to the host CPU count, so the stand sets
`preflight_runner._preflight_env` still scrubs it from the candidate suite) and
records the applied value as `extra.preflight_test_workers` in the manifest and
`preflight_test_workers` in every lane row. `--self-mod` enables post-task evolution with the real
re-exec restart and REQUIRES a confirmed absorb per lane whose scenario
re-exec restart — a settings fact (`OUROBOROS_POST_TASK_EVOLUTION` + cadence `every_n:1`) while the
lane seeds `owner_chat_id` ONLY, never a campaign: the scenario task's post-task promotion
(`apply_pending_request`) enables the one-shot campaign whose cycle lands, restarts and absorbs.
A pre-seeded active campaign (the benchmark helper's form, rc.15 paid run2) runs generic cycles from
t=0, gets the promotion refused (evolution already enabled) and its kept request file blocks the
`no_promotion` exit of the absorb wait. `--self-mod` REQUIRES a confirmed absorb per lane whose scenario
`expects_absorb` (SM1, the one that lands a commit): a pre-task snapshot
(clone HEAD, served sha, uptime, absorbed-cycle counter) and, afterwards, the
counter advanced, the served sha moved, the uptime reset and the server ready;

View file

@ -388,9 +388,8 @@ def test_admission_is_fifo_by_dispatch_index_and_a_refused_head_frees_the_line(t
def test_reservation_counts_roots_plus_the_evolution_root_and_is_the_lane_total_budget(tmp_path, monkeypatch):
"""EQUALITY pins of the rc.14/rc.15 finding: the reservation is per-task x root tasks, +1 with --self-mod (rc.14
showed up to TWO evolution cycles per lane, one at t=0 next to the scenario task and one post-task, all under the
lane fence: SM1_a1 task $3.84 + cycles $12.40 + $2.84 of $20 — the lane's TOTAL_BUDGET is the true fence); the 2x
"""EQUALITY pins of the rc.14/rc.15 finding: the reservation is per-task x root tasks, +1 with --self-mod (the one
post-task cycle; rc.14: SM1_a1 task $3.84 + cycles $12.40 + $2.84 of $20 — the lane's TOTAL_BUDGET is the fence); the 2x
factor and its product import are gone and no bench budget profile is projected. Per-task $20 and one root reserve
$20 ($40 with --self-mod, $60 for SK1 + evolution) and that exact number reaches the lane's settings file as
TOTAL_BUDGET through ``run_lane`` (never the run-wide cap)."""
@ -398,7 +397,7 @@ def test_reservation_counts_roots_plus_the_evolution_root_and_is_the_lane_total_
rule = run_live_lanes.RESERVATION_RULE
assert not hasattr(run_live_lanes, "HARD_STOP_INVERSE") and rule == run_live_lanes.RunBudget(1, 1).snapshot()["reservation_rule"]
assert rule.startswith("max(0.01, per_task_usd x (root_tasks + 1 if --self-mod else root_tasks))")
assert "up to two cycles per lane" in rule and "the true fence" in rule and "cost_hard_stop" not in rule
assert "one post-task evolution cycle" in rule and "the true fence" in rule and "cost_hard_stop" not in rule
budget = run_live_lanes.RunBudget(100.0, 20.0, reader=lambda root: (0.0, 0))
assert budget.reservation(1) == 20.0 and budget.reservation(2) == 40.0 and not budget.self_mod
evolving = run_live_lanes.RunBudget(100.0, 20.0, reader=lambda root: (0.0, 0), self_mod=True)
@ -408,10 +407,7 @@ def test_reservation_counts_roots_plus_the_evolution_root_and_is_the_lane_total_
ok, facts = budget.admit(job, 1, out / "lanes" / "SM1_a1" / "data", dispatch_index=0)
assert ok and facts["reservation_usd"] == 20.0 and budget.ceiling(job) == 20.0
class _NoServer: # the real path up to the written settings, then stop
def __init__(self, *_a, **_k) -> None:
self.base_url = "http://127.0.0.1:0"
class _NoServer(_NoopServer): # the real path up to the written settings, then stop
def start(self, **_k) -> None:
raise RuntimeError("no server in this pin: the settings file on disk is the evidence")
@ -877,8 +873,8 @@ def test_lane_with_a_dead_browser_target_is_checks_failed_not_infra_error(tmp_pa
def test_absorb_wait_and_check_follow_the_scenarios_expects_absorb(tmp_path, monkeypatch):
"""The rc.15 paid stand (2026-09-05, SK1_a1): every ``--self-mod`` lane waited ``--task-timeout`` for an absorb
only SM1's commit could trigger, then failed ``self_mod_absorb_confirmed`` by construction. Now SM1 waits and
carries the check; SW1/SK1 stop right after the scenario with ``{"expected": False}``, no check, and post-task
evolution still ON in their settings (the campaign may run during the scenario; the stand does not wait)."""
carries the check; SW1/SK1 stop right after the scenario with ``{"expected": False}``, no check, post-task
evolution ON in their settings; every lane seeds ``owner_chat_id`` ONLY, never a campaign (run2's t=0 cycles)."""
waits: list = []
monkeypatch.setattr(run_live_lanes, "resolve_ui_client", lambda base_url: (None, "ui_unavailable:test"))
monkeypatch.setattr(run_live_lanes, "self_mod_snapshot", lambda server, clone, data_root: {"pre": True})
@ -887,12 +883,16 @@ def test_absorb_wait_and_check_follow_the_scenarios_expects_absorb(tmp_path, mon
sm1 = _attempt_row(tmp_path, monkeypatch, "SM1", flags="--self-mod")
assert waits == [{"pre": True}] and sm1["status"] == "fail" and sm1["checks"]["self_mod_absorb_confirmed"] is False
assert sm1["self_mod_absorb"] == {"expected": True, "confirmed": False, "reason": "no_promotion", "healthy": True}
for sid in ("SW1", "SK1"):
row = _attempt_row(tmp_path, monkeypatch, sid, flags="--self-mod")
assert row["status"] == "pass" and "self_mod_absorb_confirmed" not in row["checks"], row["checks"]
assert row["self_mod_absorb"] == {"expected": False} and row["self_mod"] is True and waits == [{"pre": True}]
applied = json.loads((tmp_path / sid / "out" / "lanes" / f"{sid}_a1" / "data" / "settings.json").read_text())
assert applied["OUROBOROS_POST_TASK_EVOLUTION"] == "true"
for sid in ("SM1", "SW1", "SK1"):
if sid != "SM1":
row = _attempt_row(tmp_path, monkeypatch, sid, flags="--self-mod")
assert row["status"] == "pass" and "self_mod_absorb_confirmed" not in row["checks"], row["checks"]
assert row["self_mod_absorb"] == {"expected": False} and row["self_mod"] is True and waits == [{"pre": True}]
lane = tmp_path / sid / "out" / "lanes" / f"{sid}_a1" / "data"
state = json.loads((lane / "state" / "state.json").read_text(encoding="utf-8"))
assert json.loads((lane / "settings.json").read_text())["OUROBOROS_POST_TASK_EVOLUTION"] == "true"
assert state["owner_chat_id"] == 1 and "evolution_mode_enabled" not in state, state
assert not (lane / "state" / "evolution_campaign.json").exists(), sid
def test_wait_task_namespaces_checks_per_task_and_check_refuses_overwrites():