mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
The supervisor asked a light model whether every new `schedule_subagent` child was "materially the same" as any active task and rejected it as `rejected_duplicate` on a positive answer. The judge never saw the selected subagent, model, lane or executor, so it rejected an intentional cross-model check (one objective sent to two configured subagents). In 46 days of live logs it adjudicated 147 spawns, rejected one (that false positive) and never caught a real duplicate; the only real duplicate in the window was a root task the gate cannot see. Each call ran synchronously on the supervisor main loop (2.4 s median, 20 s max), and a BudgetExceeded inside it dropped the whole schedule event. Capability change, owner-accepted 2026-09-14: admission no longer vetoes a child for resembling another task. Exact task-id fencing, the per-root active-child cap, depth caps and cost ceilings remain the floor; the parent decides what to spawn and already receives task_id, objective and subagent_id for every child. Residual risk, disclosed: a parent re-queued after a mid-wave crash may pay a wave twice, bounded by the cap and the per-tree ceiling. - supervisor/events_schedule_task.py: drop `_find_duplicate_task`, `_format_task_for_dedup` and the gate call; keep the cap logic. - supervisor/events.py: drop the re-export. - tests: drop the judge's unit tests and the 26 monkeypatch stubs that silenced it; add a regression test that identical siblings with different (and with identical) configured subagents are both admitted. - The `rejected_duplicate` status constant and its projections stay so old task records and history replay keep reading correctly. - docs/ARCHITECTURE.md: the module row no longer claims a duplicate gate; docs/CHECKLISTS_ARCHIVE.md: standing disclosure for item 21. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
227 lines
12 KiB
Python
227 lines
12 KiB
Python
"""Role-owned subscription options; model identity never smuggles an account."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from tests.test_llm_claudexor import MODEL, setup as subscription_transport # noqa: F401
|
|
from tests.test_model_wait import live_wait as wait_fixture, _action_for
|
|
|
|
setup = subscription_transport # The imported live-wait fixture depends on this name.
|
|
reviewer_wait = wait_fixture
|
|
|
|
from ouroboros.model_slots import (
|
|
MODEL_ACCOUNTS_KEY, MODEL_CONTEXT_WINDOWS_KEY,
|
|
model_role_option, normalize_model_role_options,
|
|
)
|
|
from ouroboros.provider_models import (
|
|
model_has_credentials_in_settings, parse_claudexor_model, provider_for_model,
|
|
resolve_model_target,
|
|
)
|
|
|
|
|
|
def test_equal_model_names_keep_distinct_main_and_light_accounts():
|
|
settings = {
|
|
"OUROBOROS_MODEL": "claudexor::codex=one-model",
|
|
"OUROBOROS_MODEL_LIGHT": "claudexor::codex=one-model",
|
|
MODEL_ACCOUNTS_KEY: {"main": "personal", "light": "work"},
|
|
}
|
|
assert model_role_option(MODEL_ACCOUNTS_KEY, "main", settings=settings) == "personal"
|
|
assert model_role_option(MODEL_ACCOUNTS_KEY, "light", settings=settings) == "work"
|
|
assert model_role_option(MODEL_ACCOUNTS_KEY, "", settings=settings) == ""
|
|
assert parse_claudexor_model(settings["OUROBOROS_MODEL"]) == ("codex", "one-model")
|
|
assert resolve_model_target(settings["OUROBOROS_MODEL"]).credential_ref == ""
|
|
|
|
|
|
def test_fallback_options_preserve_order_and_explicit_auto():
|
|
raw = {"main": "", "fallback": ["profile-B", "", "profile-A"]}
|
|
parsed, encoded = normalize_model_role_options(MODEL_ACCOUNTS_KEY, raw)
|
|
assert parsed == raw == json.loads(encoded)
|
|
settings = {MODEL_ACCOUNTS_KEY: encoded}
|
|
assert [model_role_option(MODEL_ACCOUNTS_KEY, f"fallback:{i}", settings=settings)
|
|
for i in range(4)] == ["profile-B", "", "profile-A", ""]
|
|
|
|
|
|
def test_manual_context_above_catalog_is_an_assertion_not_an_adapter_cap():
|
|
settings = {MODEL_CONTEXT_WINDOWS_KEY: {"main": 1_000_000, "light": 872_000, "fallback": [0]}}
|
|
assert model_role_option(MODEL_CONTEXT_WINDOWS_KEY, "main", settings=settings) == 1_000_000
|
|
assert model_role_option(MODEL_CONTEXT_WINDOWS_KEY, "vision", settings=settings) == 0
|
|
assert model_role_option(MODEL_CONTEXT_WINDOWS_KEY, "fallback:0", settings=settings) == 0
|
|
|
|
|
|
@pytest.mark.parametrize("key, raw", [
|
|
(MODEL_ACCOUNTS_KEY, {"main": True}),
|
|
(MODEL_ACCOUNTS_KEY, {"main": {"pin": "profile"}}),
|
|
(MODEL_ACCOUNTS_KEY, {"fallback": "a,b"}),
|
|
(MODEL_ACCOUNTS_KEY, {"unknown": "profile"}),
|
|
(MODEL_ACCOUNTS_KEY, "not JSON"),
|
|
(MODEL_CONTEXT_WINDOWS_KEY, {"main": -1}),
|
|
(MODEL_CONTEXT_WINDOWS_KEY, {"main": True}),
|
|
(MODEL_CONTEXT_WINDOWS_KEY, {"main": 1.2}),
|
|
(MODEL_CONTEXT_WINDOWS_KEY, {"main": "1000000"}),
|
|
])
|
|
def test_malformed_options_cannot_silently_drop_a_pin_or_invent_capacity(key, raw):
|
|
with pytest.raises(ValueError):
|
|
normalize_model_role_options(key, raw)
|
|
|
|
|
|
def test_config_read_accepts_object_and_json_and_is_idempotent():
|
|
from ouroboros.config import normalize_settings_raw
|
|
|
|
raw = {MODEL_ACCOUNTS_KEY: {"main": "profile"}, MODEL_CONTEXT_WINDOWS_KEY: '{"light":872000}'}
|
|
normalized = normalize_settings_raw(raw)
|
|
assert normalized == normalize_settings_raw(normalized)
|
|
assert json.loads(normalized[MODEL_ACCOUNTS_KEY]) == {"main": "profile"}
|
|
assert json.loads(normalized[MODEL_CONTEXT_WINDOWS_KEY]) == {"light": 872000}
|
|
|
|
|
|
def test_managed_selection_does_not_need_or_fabricate_an_api_key():
|
|
model = "claudexor::codex=model-A"
|
|
assert provider_for_model(model) == "claudexor"
|
|
assert model_has_credentials_in_settings(model, {})
|
|
assert not model_has_credentials_in_settings("openai::model-A", {})
|
|
assert resolve_model_target(model).provider_route == "claudexor"
|
|
with pytest.raises(ValueError):
|
|
parse_claudexor_model("claudexor::codex")
|
|
|
|
|
|
def test_persisted_wait_switch_changes_only_the_named_model_role():
|
|
from ouroboros.model_slots import apply_model_role_override
|
|
initial = {"OUROBOROS_MODEL": "claudexor::codex=same", "OUROBOROS_MODEL_LIGHT": "claudexor::codex=same",
|
|
MODEL_ACCOUNTS_KEY: {"main": "first", "light": "second"}}
|
|
saved = apply_model_role_override(initial, role="light", model="openai::other",
|
|
credential_profile_id="", use_local=False)
|
|
assert saved["OUROBOROS_MODEL"] == initial["OUROBOROS_MODEL"]
|
|
assert saved["OUROBOROS_MODEL_LIGHT"] == "openai::other"
|
|
assert json.loads(saved[MODEL_ACCOUNTS_KEY]) == {"main": "first", "light": ""}
|
|
assert initial[MODEL_ACCOUNTS_KEY]["light"] == "second"
|
|
|
|
|
|
def test_managed_model_account_roundtrips_actor_and_reviewer_configuration():
|
|
from ouroboros.configured_subagents import normalize_configured_subagents
|
|
from ouroboros.reviewer_slot_config import parse_reviewer_slots
|
|
route = {"kind": "api_model", "target_id": "claudexor::codex=same", "credential_profile_id": "named"}
|
|
actor = {"subagent_id": "actor", "recommended_use": "Review", "route": route}
|
|
parsed, encoded = normalize_configured_subagents({"enabled": True, "items": [actor]})
|
|
assert parsed.items[0].route.credential_profile_id == "named"
|
|
assert json.loads(encoded)['items'][0]['route'] == route
|
|
slots = parse_reviewer_slots(json.dumps({
|
|
group: [{"slot_id": group, "route": {"kind": "api_chat", "target_id": route['target_id'], "profile_id": "named"}}]
|
|
for group in ("triad", "scope")
|
|
}))
|
|
assert slots.triad[0].profile_id == slots.scope[0].profile_id == "named"
|
|
|
|
|
|
@pytest.mark.parametrize("pin", ["", "named"])
|
|
def test_empty_subscription_model_is_rejected_before_actor_or_reviewer_serialization(pin):
|
|
from ouroboros.configured_subagents import normalize_configured_subagents
|
|
from ouroboros.reviewer_slot_config import parse_reviewer_slots
|
|
|
|
target = "claudexor::codex="
|
|
with pytest.raises(ValueError):
|
|
normalize_configured_subagents({"enabled": True, "items": [{
|
|
"subagent_id": "actor", "recommended_use": "Review", "route": {
|
|
"kind": "api_model", "target_id": target, "credential_profile_id": pin}}]})
|
|
with pytest.raises(ValueError):
|
|
parse_reviewer_slots(json.dumps({group: [{"slot_id": group, "route": {
|
|
"kind": "api_chat", "target_id": target, "profile_id": pin}}]
|
|
for group in ("triad", "scope")}))
|
|
|
|
|
|
def test_persist_referenced_reviewer_keeps_other_roles_and_native_delivery():
|
|
from ouroboros.model_slots import apply_model_role_override
|
|
from ouroboros.reviewer_slot_config import parse_reviewer_slots, roster_env_override
|
|
actor = {"subagent_id": "shared", "recommended_use": "Review", "route": {
|
|
"kind": "api_model", "target_id": "claudexor::codex=old", "credential_profile_id": "old"}}
|
|
original = {"OUROBOROS_SUBAGENTS": json.dumps({"enabled": True, "items": [actor]}),
|
|
"OUROBOROS_REVIEWER_SLOTS": json.dumps({group: [{"slot_id": group, "subagent_id": "shared"}]
|
|
for group in ("triad", "scope")})}
|
|
saved = apply_model_role_override(original, role="reviewer:triad", model="claudexor::codex=new",
|
|
credential_profile_id="new", use_local=False)
|
|
roster = json.loads(saved['OUROBOROS_SUBAGENTS'])
|
|
assert roster['items'][0] == actor
|
|
with roster_env_override(saved['OUROBOROS_SUBAGENTS']):
|
|
slots = parse_reviewer_slots(saved['OUROBOROS_REVIEWER_SLOTS'])
|
|
assert slots.triad[0].native_retrieval and slots.scope[0].native_retrieval
|
|
assert slots.triad[0].target_id == "claudexor::codex=new" and slots.triad[0].profile_id == "new"
|
|
assert slots.scope[0].target_id == "claudexor::codex=old" and slots.scope[0].profile_id == "old"
|
|
assert apply_model_role_override(saved, role="reviewer:triad", model="claudexor::codex=new",
|
|
credential_profile_id="new", use_local=False) == saved
|
|
|
|
|
|
@pytest.mark.parametrize("roster", [None, "", '{"enabled":false,"items":[]}'])
|
|
def test_persist_default_reviewer_omits_untouched_deep_row_and_roster(roster):
|
|
from ouroboros.model_slots import apply_model_role_override
|
|
from ouroboros.subscription_install_presets import preview_api_reviewer_slots
|
|
|
|
original = {"OUROBOROS_REVIEWER_SLOTS": "", "OUROBOROS_MODEL_DEEP_SELF_REVIEW": "openai::owner-deep"}
|
|
if roster is not None:
|
|
original["OUROBOROS_SUBAGENTS"] = roster
|
|
before = json.loads(preview_api_reviewer_slots(original))
|
|
identity = before["triad"][0]["slot_id"]
|
|
saved = apply_model_role_override(original, role=f"reviewer:{identity}", model="claudexor::codex=new",
|
|
credential_profile_id="new-pin", use_local=False)
|
|
after = json.loads(saved["OUROBOROS_REVIEWER_SLOTS"])
|
|
expected = dict(before)
|
|
expected.pop("deep_review")
|
|
expected["triad"][0]["route"] = {"kind": "api_chat", "target_id": "claudexor::codex=new", "profile_id": "new-pin"}
|
|
assert after == expected
|
|
assert saved.get("OUROBOROS_SUBAGENTS") == original.get("OUROBOROS_SUBAGENTS")
|
|
assert ("OUROBOROS_SUBAGENTS" in saved) is (roster is not None)
|
|
assert saved["OUROBOROS_MODEL_DEEP_SELF_REVIEW"] == "openai::owner-deep"
|
|
assert original["OUROBOROS_REVIEWER_SLOTS"] == ""
|
|
|
|
|
|
def test_persist_default_deep_reviewer_authors_only_its_selected_assignment():
|
|
from ouroboros.model_slots import apply_model_role_override
|
|
from ouroboros.subscription_install_presets import preview_api_reviewer_slots
|
|
|
|
original = {"OUROBOROS_REVIEWER_SLOTS": "", "OUROBOROS_MODEL_DEEP_SELF_REVIEW": "openai::owner-deep"}
|
|
before = json.loads(preview_api_reviewer_slots(original))
|
|
saved = apply_model_role_override(original, role="reviewer:deep_review_slot_1", model="claudexor::codex=review",
|
|
credential_profile_id="review-pin", use_local=False)
|
|
after = json.loads(saved["OUROBOROS_REVIEWER_SLOTS"])
|
|
before["deep_review"]["route"] = {"kind": "api_chat", "target_id": "claudexor::codex=review", "profile_id": "review-pin"}
|
|
assert after == before
|
|
assert "OUROBOROS_SUBAGENTS" not in saved
|
|
|
|
|
|
def test_default_reviewer_wait_persists_through_the_real_owner_writer_without_deep_materialization(reviewer_wait, monkeypatch):
|
|
from ouroboros import config, model_wait
|
|
from ouroboros.subscription_install_presets import preview_api_reviewer_slots
|
|
|
|
root, _, _, controller, _, decide = reviewer_wait
|
|
monkeypatch.setattr(config, "SETTINGS_PATH", root / "settings.json")
|
|
initial = {"OUROBOROS_REVIEWER_SLOTS": "", "OUROBOROS_MODEL_DEEP_SELF_REVIEW": "openai::owner-deep"}
|
|
(root / "settings.json").write_text(json.dumps(initial))
|
|
defaults = json.loads(preview_api_reviewer_slots(initial))
|
|
slot = defaults["triad"][0]["slot_id"]
|
|
row = {"wait_id": "reviewer-wait", "revision": 1, "task_attempt": 1,
|
|
"state": "waiting", "role": f"reviewer:{slot}"}
|
|
model_wait.mutate_wait(root, "task-one", row["wait_id"], lambda _: row)
|
|
controller.waits[row["wait_id"]] = dict(row)
|
|
body = _action_for({**row, "task_id": "task-one"}, "switch", model=MODEL,
|
|
credential_profile_id="reviewer-pin", use_local=False, persist_role=True)
|
|
response = decide(body)
|
|
assert response.status_code == 202 and json.loads(response.body)["saved"] is True
|
|
saved = json.loads((root / "settings.json").read_text())
|
|
panel = json.loads(saved["OUROBOROS_REVIEWER_SLOTS"])
|
|
assert panel["triad"][0]["route"]["profile_id"] == "reviewer-pin"
|
|
assert panel["triad"][1:] == defaults["triad"][1:] and panel["scope"] == defaults["scope"]
|
|
assert "deep_review" not in panel and saved["OUROBOROS_MODEL_DEEP_SELF_REVIEW"] == initial["OUROBOROS_MODEL_DEEP_SELF_REVIEW"]
|
|
assert not saved.get("OUROBOROS_SUBAGENTS")
|
|
stamp = (root / "settings.json").stat().st_mtime_ns
|
|
assert decide(body).status_code == 200
|
|
assert (root / "settings.json").stat().st_mtime_ns == stamp
|
|
|
|
|
|
def test_public_and_summary_projection_drop_opaque_model_state_only():
|
|
from ouroboros.anthropic_native_custody import public_custody_projection
|
|
from ouroboros.context_compaction import _summary_projection
|
|
value = {"role": "assistant", "content": "kept", "nativeContinuation": {"payload": "opaque"},
|
|
"tool_calls": [{"id": "kept-tool"}]}
|
|
for projection in (public_custody_projection, _summary_projection):
|
|
result = projection(value)
|
|
assert 'nativeContinuation' not in result
|
|
assert result['content'] == 'kept' and result['tool_calls'] == value['tool_calls']
|
|
assert 'nativeContinuation' in value
|