ouroboros/tests/test_owner_wait_cold_loop.py
Ouroboros 98ffcf2a62 Size the automatic context-reclaim pass to a low-water margin
The trigger is unchanged: a positive deficit against the binding boundary
(the smaller known of owner target and route capacity), one pass per
route+round. The requested goal becomes deficit +
ceil(boundary / RECLAIM_LOW_WATER_DIVISOR), so a pass lands about an eighth
of the boundary below it instead of exactly at it, where the next round's
ordinary growth re-armed it nearly every round. RECLAIM_LOW_WATER_DIVISOR = 8
is a documented structural constant in context_budget (not a setting; pinned
by test_context_budget_ssot so it stays a one-constant change).
MainFitMeasurement gains low_water_margin_tokens, appended last.

The context_reclaim checkpoint event records deficit_tokens,
requested_margin_tokens, achieved_headroom_tokens (the landing re-measured on
the same fit basis as the trigger), boundary_reached, below_boundary
(reclaiming exactly the deficit reaches the boundary, it does not get below
it) and rounds_since_previous_pass; an unmeasurable landing is reported as
unknown, never as "not reached". The actual-overflow path requests the same
low-water minimum instead of a token-sized pass before its one strict-shrink
retry. Materializer, receipts and the route+round latch are untouched.

Tests: margin arithmetic in both directions (binding boundary, no deficit,
divisor as the one knob), the anti-thrash class test with the worst-case
full-budget summarizer stub (4 passes in 40 rounds against 39 with the
margin removed; the stub halves every selected unit, so the asserted bound
is ceil(N*g / (margin/2)) + 1 plus the achieved-headroom form), the
checkpoint telemetry, the unmeasured landing, and the overflow-minimum pins.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-26 08:16:04 +03:00

238 lines
13 KiB
Python

"""Cold continuation reaches the real loop with its original route and ceiling."""
import asyncio
from dataclasses import replace
import json
import math
import socket
import threading
from types import SimpleNamespace
import pytest
from ouroboros import context, loop, task_pacing, usage_accounting as accounting
from ouroboros.context_budget import RECLAIM_LOW_WATER_DIVISOR
from ouroboros.contracts.task_contract import normalize_budget_profile
from ouroboros.owner_wait import checkpoint_owner_wait, set_owner_wait
from ouroboros.task_results import write_task_result
from ouroboros.tools.registry import ToolRegistry
from tests._budget_limits_helpers import _make_args
from tests.test_context_fit_v664 import _plan
from tests.test_loop_compaction import _candidate_request, _failed_capture
from tests.test_loop_transport_wait import _loop_kwargs
@pytest.fixture(autouse=True)
def no_network(monkeypatch):
"""Block provider/catalog calls, leaving asyncio's local IPC available."""
attempted = []
def refuse_provider(*args, **kwargs):
attempted.append((args, kwargs))
raise AssertionError("cold-loop fixture attempted a provider/catalog call")
monkeypatch.setattr("ouroboros.llm.LLMClient.chat", refuse_provider)
monkeypatch.setattr("ouroboros.llm.LLMClient.chat_async", refuse_provider)
monkeypatch.setattr("ouroboros.pricing._fetch_live_rows", refuse_provider)
yield
assert attempted == []
def test_provider_isolation_preserves_event_loop_self_pipe(monkeypatch):
# Windows uses this TCP fallback for asyncio's internal wakeup channel.
monkeypatch.setattr(socket, "socketpair",
getattr(socket, "_fallback_socketpair", socket.socketpair))
event_loop = asyncio.new_event_loop()
event_loop.close()
def cold_registry(tmp_path, monkeypatch, ceiling=None):
registry = ToolRegistry(repo_dir=tmp_path, drive_root=tmp_path)
ctx = registry._ctx
ctx.task_id, ctx.task_attempt = "t-wait", 1
ctx.active_model, ctx.active_effort = "same-model", "high"
ctx.active_use_local, ctx.active_context_mode = False, "max"
ctx._owner_wait_requested = "quiz"
ctx._cost_ceiling = ceiling or task_pacing.CostCeiling(state="disabled")
messages = _plan().messages_for("max") + [
{"role": "assistant", "tool_calls": [{"id": "saved", "type": "function",
"function": {"name": "save", "arguments": "{}"}}]},
{"role": "tool", "tool_call_id": "saved", "content": "Saved object 42"},
]
write_task_result(tmp_path, ctx.task_id, "running")
wait = checkpoint_owner_wait(ctx, messages, {"tool_calls": []},
{"cost": 10.0, "execution_id": "exec"}, 0, [], set())
set_owner_wait(tmp_path, ctx.task_id, {**wait, "state": "waiting"})
ctx.owner_wait_resume = {**wait, "restart_transaction_id": "observed-restart"}
ctx.owner_wait_callback = lambda *_: None # the pool grant itself has separate tests
ctx.context_fit_plan = _plan(window=500_000, known=True) # startup route A
monkeypatch.setattr(context, "_context_fit_route", lambda task, **_: (
{"model": task["model"], "provider": "openai", "use_local": False},
SimpleNamespace(status="confirmed", stale=False, window_tokens=500_000, route_fp="route-a"),
))
monkeypatch.setenv("OUROBOROS_CONTEXT_MODE", "max")
monkeypatch.setenv("OUROBOROS_TASK_REVIEW_MODE", "off")
return registry
def test_cold_switched_model_keeps_actual_overflow_reclaim_and_retry(tmp_path, monkeypatch):
registry = cold_registry(tmp_path, monkeypatch)
sends, reclaimed = [], []
def reclaim(call, disposition, **kwargs):
# An actual overflow requests a low-water-sized pass (an eighth of the
# 500K route), never a token-sized one, before its single strict-shrink retry.
assert kwargs["minimum_goal_tokens"] == math.ceil(500_000 / RECLAIM_LOW_WATER_DIVISOR)
reclaimed.append(call.active_model)
loop._context_reclaim_passes(call.tools._ctx).add(
(disposition.measurement.route_fp, disposition.measurement.round_id))
def dispatch(call, disposition, *, candidate_predicate=None, **kwargs):
assert call.active_model == call.context_fit_plan.model == "same-model"
assert disposition is not None
assert any(message.get("content") == "Saved object 42" for message in call.messages)
if not sends:
sends.append("overflow")
call.accumulated_usage["_last_llm_error_kind"] = "context_overflow"
return None, 0.0
assert candidate_predicate(_candidate_request(disposition, size=800))
sends.append("smaller retry")
return {"role": "assistant", "content": "Continued saved work", "tool_calls": []}, 0.0
monkeypatch.setattr(loop, "_run_main_reclaim", reclaim)
monkeypatch.setattr(loop, "_dispatch_round_model", dispatch)
monkeypatch.setattr(loop, "last_physical_attempt_capture", lambda: _failed_capture())
result, usage, _ = loop.run_llm_loop(**_loop_kwargs(tmp_path, registry, []))
assert result == "Continued saved work"
assert sends == ["overflow", "smaller retry"] and reclaimed == ["same-model"]
assert registry._ctx.active_context_mode == "low"
assert registry._ctx.owner_wait_resume is None
assert usage["cost"] == 10.0 # recorded spend is retained, never re-emitted
def test_cold_loop_keeps_original_ceiling_while_live_wallet_still_binds(tmp_path, monkeypatch):
scope = accounting.UsageScope(drive_root=tmp_path, task_id="t-wait", root_task_id="t-wait",
global_limit_usd=200.0, root_limit_usd=50.0)
with accounting.usage_scope(scope):
original = task_pacing.resolve_cost_ceiling(200.0, normalize_budget_profile(None), root_cap_usd=50.0)
assert original.ceiling_usd == 47.0
accounting.reserve_attempt(accounting.AttemptRequest(model="fixture", provider="openai", reservation_usd=10.0))
registry = cold_registry(tmp_path, monkeypatch, original)
others = []
for index in range(4):
with accounting.usage_scope(replace(scope, task_id=f"other-{index}", root_task_id=f"other-{index}")):
others.append(accounting.reserve_attempt(accounting.AttemptRequest(
model="fixture", provider="openai", reservation_usd=40.0)))
fresh = task_pacing.resolve_cost_ceiling(30.0, normalize_budget_profile(None), root_cap_usd=50.0)
assert fresh.ceiling_usd == 15.0
registry._ctx._cost_ceiling = fresh # new context-builder disclosure before loading saved messages
accounting.reserve_attempt(accounting.AttemptRequest(model="fixture", provider="openai", reservation_usd=6.0))
captured = []
soft_land = loop._soft_land_exhausted_ceiling
forced = []
def record_wrapup(call, *, prompt, fallback_text, reason_code):
forced.append(reason_code)
call.accumulated_usage["reason_code"] = reason_code
return fallback_text, call.accumulated_usage, call.llm_trace
# The real budget decision may request a final answer; its transport is
# irrelevant here and must not probe or price the synthetic model.
monkeypatch.setattr(loop, "_forced_final_answer", record_wrapup)
def inspect_ceiling(call, ceiling):
captured.append(ceiling)
assert ceiling == original
assert soft_land(call, ceiling) is None
args = _make_args(task_id="t-wait", accumulated_usage={"cost": 10.0},
cost_ceiling=ceiling, budget_remaining_usd=30.0, drive_logs=tmp_path)
assert loop._check_budget_limits(**args) is None
assert forced == []
assert loop._check_budget_limits(**{**args, "cost_ceiling": fresh})[1]["reason_code"] == "budget_exhausted"
assert forced == ["budget_exhausted"]
return "ceiling retained", call.accumulated_usage, {}
monkeypatch.setattr(loop, "_soft_land_exhausted_ceiling", inspect_ceiling)
result, _, _ = loop.run_llm_loop(**{**_loop_kwargs(tmp_path, registry, []), "budget_remaining_usd": 30.0})
assert result == "ceiling retained" and captured == [original]
permitted = accounting.reserve_attempt(accounting.AttemptRequest(model="fixture", provider="openai", reservation_usd=1.0))
with pytest.raises(accounting.BudgetExceeded) as global_refusal:
accounting.reserve_attempt(accounting.AttemptRequest(model="fixture", provider="openai", reservation_usd=25.0))
assert global_refusal.value.limit_scope == "global"
accounting.release_attempt(permitted, "test did not send")
for held in others:
accounting.release_attempt(held, "test did not send")
with pytest.raises(accounting.BudgetExceeded) as root_refusal:
accounting.reserve_attempt(accounting.AttemptRequest(model="fixture", provider="openai", reservation_usd=35.0))
assert root_refusal.value.limit_scope == "root"
def test_agent_cold_context_shows_saved_ceiling_and_current_wallet(tmp_path, monkeypatch):
from ouroboros import agent as agent_module
from tests._context_shared import _make_health_env
monkeypatch.setenv("TOTAL_BUDGET", "200")
monkeypatch.setenv("OUROBOROS_PER_TASK_COST_USD", "50")
scope = accounting.UsageScope(drive_root=tmp_path, task_id="t-wait", root_task_id="t-wait",
global_limit_usd=200.0, root_limit_usd=50.0)
with accounting.usage_scope(scope):
original = task_pacing.resolve_task_cost_ceiling(None, 200.0)
assert original.ceiling_usd == 47.0
registry = cold_registry(tmp_path, monkeypatch, original)
handoff = registry._ctx.owner_wait_resume
accounting.reserve_attempt(accounting.AttemptRequest(
model="fixture", provider="openai", reservation_usd=10.0))
for index in range(4):
with accounting.usage_scope(replace(scope, task_id=f"other-{index}", root_task_id=f"other-{index}")):
accounting.reserve_attempt(accounting.AttemptRequest(
model="fixture", provider="openai", reservation_usd=40.0))
env = _make_health_env(tmp_path)
env.branch_dev, env.budget_drive_root = "ouroboros", tmp_path
agent = object.__new__(agent_module.OuroborosAgent)
agent.__dict__.update(
env=env, tools=registry, memory=None, _event_queue=None,
_current_chat_id=1, _current_task_type="task", _pending_events=[],
_task_started_ts=0.0, _owner_message_admission_lock=threading.Lock(),
owner_wait_callback=lambda *_: None,
)
for name in ("_emit_live_log", "_emit_typing_start", "_emit_progress", "_capture_mutation_baseline"):
monkeypatch.setattr(agent, name, lambda *_a, **_kw: None)
monkeypatch.setattr(agent, "_run_delegate_preflight", lambda _logs, _task, dispatch: (dispatch, False))
def runtime_messages(env, task, ctx, **_kwargs):
# Omit unrelated governance assembly, retain the real Runtime builder
# and immutable projection that cold-loop rebind sends to the model.
runtime = context.build_runtime_section(env, task, ctx=ctx)
plan = _plan(window=500_000, known=True)
projection = replace(plan.max_projection, system_content_json=json.dumps(runtime))
ctx.context_fit_plan = replace(plan, max_projection=projection, low_projection=projection)
return ctx.context_fit_plan.messages_for("max"), {}
monkeypatch.setattr(agent_module, "build_llm_messages", runtime_messages)
task = {"id": "t-wait", "root_task_id": "t-wait", "delegation_role": "root",
"type": "task", "text": "Continue saved work", "_attempt": 1,
"_owner_wait_resume": handoff, "budget_drive_root": str(tmp_path)}
ctx, messages, caps = agent._prepare_task_context(task)
assert caps["budget_remaining"] == 30.0
captured = []
def dispatch(call, _disposition, **_kwargs):
assert call.messages[0]["role"] == "system"
shown = json.loads(call.messages[0]["content"].split("\n\n", 1)[1])["budget"]
captured.append(shown)
assert ctx._cost_ceiling == original
assert shown["in_task_cost_ceiling"] == task_pacing.cost_ceiling_disclosure(original)
assert shown["remaining_usd"] == 30.0 and shown["per_task_tree_cap_usd"] == 50.0
return {"role": "assistant", "content": "Continued saved work", "tool_calls": []}, 0.0
monkeypatch.setattr(loop, "_dispatch_round_model", dispatch)
result, _, _ = loop.run_llm_loop(**{
**_loop_kwargs(tmp_path, registry, []), "messages": messages,
"budget_remaining_usd": caps["budget_remaining"],
})
assert result == "Continued saved work" and len(captured) == 1
with pytest.raises(accounting.BudgetExceeded) as refused:
accounting.reserve_attempt(accounting.AttemptRequest(
model="fixture", provider="openai", reservation_usd=31.0))
assert refused.value.limit_scope == "global"