ouroboros/tests/test_task_authored_messages.py
Ouroboros 79b9b4b11c The pooled-root steer test also runs on a promoted root with an inherited stamp
The steer-level pin modelled only a headless pooled root (no stamp, no client
id). The common geometry is a root promoted from an owner message: it inherits
the door's stamp and client id by value but is not a direct turn, and its
steer must still travel as the task's own words. Parametrized over both
shapes (found by the Fable verification workflow and the astra delta review).
2026-09-24 23:42:24 +03:00

829 lines
42 KiB
Python

"""Task-authored messages never travel as owner text (14.09 incident, wave 2).
``_handle_steer_task`` used to decide WHO was speaking five times from proxies:
a routing-contract lane a Swarm root never has, a room veto keyed on the chat, an
empty client id read as "agent-issued", and every steer written as owner text —
so a Project root's words reached the Main root as ``[Message from my human]``,
entered the owner-directive corpus and superseded a paid acceptance panel. The
host now mints ONE issuer fact by value (``control_routing._routing_issuer``): an
owner turn keeps today's path; a task's own words are written as a task-message
row with ``independent_task`` provenance, render under their own prefix, enter no
owner corpus, bump no owner generation, and are confirmed (or
refused) to the issuer as WRITTEN with the host's reason. Fixture geometry
matters: the issuing roots below are POOLED/Swarm roots with no chat ingress id.
A drained owner message keys the visible receipt without changing authorship.
"""
from __future__ import annotations
import json
import queue
import types
import pytest
from ouroboros.utils import append_jsonl
_OWNER_ORIGIN = "the owner's twenty-minute-old Swarm message"
def _pooled_root_ctx(tmp_path, *, task_id="swarm-root", chat_id=1, metadata=None):
"""A pooled/Swarm root: force_plan metadata, an origin the agent copied by value,
NO client_message_id, NO is_direct_chat, nothing drained yet."""
md = {
"force_plan": True, "force_plan_source": "swarm", "root_task_id": task_id,
"origin_message_text": _OWNER_ORIGIN,
}
md.update(metadata or {})
return types.SimpleNamespace(
pending_events=[], event_queue=None, current_chat_id=chat_id, drive_root=tmp_path,
task_id=task_id, task_metadata=md, last_owner_delivery=None, project_id="",
)
def _owner_turn_ctx(tmp_path, *, client_message_id="cm-1"):
"""A direct turn the owner door stamped (``origin_message_ref``): the one shape
that speaks as an owner turn; a client id alone never does."""
return types.SimpleNamespace(
pending_events=[], event_queue=None, current_chat_id=1, drive_root=tmp_path,
task_id="turn-1", is_direct_chat=True, last_owner_delivery=None,
task_metadata={"client_message_id": client_message_id, "origin_message_text": _OWNER_ORIGIN,
"origin_message_ref": {"chat_id": 1, "client_message_id": client_message_id}},
)
def _owner_started_receiver():
"""The receiving root was started by the owner: its first text keeps the owner label."""
return types.SimpleNamespace(
task_attempt=1, task_metadata={"origin_message_ref": {"chat_id": 42, "client_message_id": "t-target-origin"}},
)
def _drain_owner_followup(tmp_path, ctx, *, client_message_id="owner-followup"):
from ouroboros.loop_round_limits import _drain_incoming_messages
from ouroboros.owner_mailbox import write_owner_message
write_owner_message(tmp_path, "Please coordinate the review", task_id=ctx.task_id,
msg_id="followup-mailbox", client_message_id=client_message_id)
_drain_incoming_messages([], queue.Queue(), tmp_path, ctx.task_id, None, set(), owner_ctx=ctx)
assert ctx.last_owner_delivery["client_message_id"] == client_message_id
@pytest.fixture(params=["pooled", "direct"])
def target_lane(request):
import threading
from supervisor.active_activity import get_direct_activity_registry
actor = types.SimpleNamespace(
_owner_message_admission_lock=threading.RLock(), _busy=True,
_accepting_owner_messages=True, _current_task_id="t-target", _current_chat_id=1,
_current_task_metadata={}, _current_task_text="Review", _owner_message_generation=3,
)
registry = get_direct_activity_registry()
if request.param == "direct":
registry.register("t-target", 1, actor=actor)
try:
yield actor
finally:
if request.param == "direct":
registry.unregister("t-target")
def _supervisor(tmp_path, *, running=None, acks=None, notices=None):
acks = [] if acks is None else acks
notices = [] if notices is None else notices
return types.SimpleNamespace(
DRIVE_ROOT=tmp_path, RUNNING=dict(running or {}), PENDING=[],
send_with_budget=lambda _chat_id, text, *a, **k: notices.append(text),
bridge=types.SimpleNamespace(send_routing_ack=lambda _chat_id, **payload: acks.append(payload)),
append_jsonl=append_jsonl,
persist_queue_snapshot=lambda **_k: None,
)
def _wire(ctx, supervisor, emitted):
from supervisor.events import _handle_steer_task
ctx.event_queue = types.SimpleNamespace(
put_nowait=lambda event: (emitted.append(event), _handle_steer_task(event, supervisor))[0],
)
return ctx
def _events(tmp_path, kind):
path = tmp_path / "logs" / "events.jsonl"
if not path.exists():
return []
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
return [row for row in rows if row.get("type") == kind]
@pytest.fixture(autouse=True)
def _queue_root(tmp_path, monkeypatch):
import supervisor.queue as queue_mod
monkeypatch.setattr(queue_mod, "DRIVE_ROOT", str(tmp_path))
monkeypatch.setattr(queue_mod, "ACCEPTANCE_FENCES", {})
return tmp_path
# --- (a) a pooled root's words are a task message, not the owner's -----------
@pytest.mark.parametrize("drained_owner", [None, "owner-followup", ""], ids=["standalone", "owner-id", "legacy-no-id"])
@pytest.mark.parametrize("project_sender", [False, True])
@pytest.mark.parametrize("root_shape", ["headless", "promoted"])
def test_a_pooled_root_steer_is_written_as_its_own_words_and_supersedes_nothing(
tmp_path, target_lane, drained_owner, project_sender, root_shape,
):
"""``promoted`` is the common geometry: the root inherited the owner door's stamp
and client id from the message that promoted it (ancestry, by value) but is not
a direct turn, so it still speaks as a task; ``headless`` carries no stamp at all."""
import supervisor.queue as queue_mod
from ouroboros.loop_messages import _initialize_owner_directives, owner_source_sha256
from ouroboros.loop_round_limits import _drain_incoming_messages
from ouroboros.owner_mailbox import KIND_TASK_MESSAGE, acknowledged_task_message_ids, drain_owner_entries
from ouroboros.project_dialogue import latest_chat_annotations
from ouroboros.projects_registry import create_project
from ouroboros.tools.control import _steer_task
fence = {"status": "active", "owner_message_generation": 3}
queue_mod.ACCEPTANCE_FENCES["t-target"] = fence
acks, notices, emitted = [], [], []
supervisor = _supervisor(
tmp_path, running={"t-target": {"task": {"id": "t-target", "chat_id": 1, "root_task_id": "t-target"}}},
acks=acks, notices=notices,
)
chat_id = create_project(tmp_path, "source", name="Source")["chat_id"] if project_sender else 1
inherited = {
"client_message_id": "cm-origin",
"origin_message_ref": {"chat_id": 1, "client_message_id": "cm-origin", "ts": "t", "text_sha256": "x" * 64},
} if root_shape == "promoted" else {}
ctx = _wire(_pooled_root_ctx(tmp_path, chat_id=chat_id, metadata=inherited), supervisor, emitted)
if drained_owner is not None:
_drain_owner_followup(tmp_path, ctx, client_message_id=drained_owner)
out = _steer_task(ctx, "t-target", "the PR is ready; please review it")
# The issuer fact is the host's, by value; the message is the task's OWN
# words -- never the owner origin the root carries in its metadata.
assert emitted[0]["issuer"] == {"kind": "task", "task_id": "swarm-root", "root_task_id": "swarm-root"}
assert emitted[0]["message"] == "the PR is ready; please review it"
assert "attachment_uploads" not in emitted[0]
assert out.startswith("✉️ Message to task t-target written to its mailbox (durably confirmed")
assert "not as owner text" in out and "cannot be attached" in out
[row] = drain_owner_entries(tmp_path, "t-target")
assert (row["kind"], row["provenance"], row["source_task_id"]) == (
KIND_TASK_MESSAGE, "independent_task", "swarm-root",
)
assert "client_message_id" not in row
# A drained owner message gets its receipt, never authorship of the relay.
assert fence["owner_message_generation"] == 3
assert target_lane._owner_message_generation == 3
assert notices == []
if drained_owner:
assert emitted[0]["client_message_id"] == "owner-followup"
assert len(acks) == 1 and acks[0]["client_message_id"] == "owner-followup"
assert acks[0]["status"] == "delivered"
else:
assert emitted[0]["client_message_id"].startswith("agent-steer:")
assert acks == []
receipt = latest_chat_annotations(tmp_path)[emitted[0]["client_message_id"]]
assert (receipt["action"], receipt["status"], receipt["target"]) == ("steer_task", "delivered", "t-target")
assert "options" not in receipt
[logged] = _events(tmp_path, "task_message_routed")
assert (logged["task_id"], logged["target_task_id"], logged["status"]) == ("swarm-root", "t-target", "written")
# The RECEIVER drains it as context: rendered under its own prefix, the owner
# corpus untouched (so owner_source_sha256 cannot supersede a reviewed answer),
# no owner delivery stamped, the row acknowledged, the injected event typed.
receiver = _owner_started_receiver()
messages = [{"role": "user", "content": "Initial requirement verbatim"}]
_initialize_owner_directives(receiver, messages)
corpus_before = owner_source_sha256(receiver)
events: queue.Queue = queue.Queue()
_drain_incoming_messages(messages, queue.Queue(), tmp_path, "t-target", events, set(), owner_ctx=receiver)
delivered = str(messages[-1]["content"])
assert "[Message from independent task swarm-root]\nthe PR is ready; please review it" in delivered
assert "[Message from my human]" not in delivered and "ancestor" not in delivered
assert owner_source_sha256(receiver) == corpus_before
assert [d["source"] for d in receiver._owner_directives] == ["initial_user"]
assert getattr(receiver, "last_owner_delivery", None) is None
assert row["msg_id"] in acknowledged_task_message_ids(tmp_path, "t-target", attempt_key=1)
injected = events.get_nowait()
assert (injected["type"], injected["source_task_id"], injected["provenance"]) == (
"task_message_injected", "swarm-root", "independent_task",
)
# --- (b) an owner turn keeps today's exact path -------------------------------
@pytest.mark.parametrize("stamp", ["logged-ref", "suppressed-log"])
def test_an_owner_turn_from_main_still_steers_with_owner_text_and_bumps_the_generation(tmp_path, stamp):
import supervisor.queue as queue_mod
from ouroboros.loop_messages import _initialize_owner_directives, owner_source_sha256
from ouroboros.loop_round_limits import _drain_incoming_messages
from ouroboros.owner_mailbox import KIND_OWNER_TEXT, drain_owner_entries
from ouroboros.tools.control import _steer_task
fence = {"status": "active", "owner_message_generation": 3}
queue_mod.ACCEPTANCE_FENCES["t-target"] = fence
acks, notices, emitted = [], [], []
supervisor = _supervisor(
tmp_path, running={"t-target": {"task": {"id": "t-target", "chat_id": 42, "root_task_id": "t-target"}}},
acks=acks, notices=notices,
)
ctx = _wire(_owner_turn_ctx(tmp_path), supervisor, emitted)
if stamp == "suppressed-log": # a never-logged owner message: the door's designed absence of a ref
del ctx.task_metadata["origin_message_ref"]
ctx.task_metadata["origin_suppressed"] = True
out = _steer_task(ctx, "t-target", "model paraphrase")
assert out.startswith("✉️ Steering task t-target: mailbox delivery is durably confirmed")
assert emitted[0]["issuer"] == {"kind": "owner_turn"}
[row] = drain_owner_entries(tmp_path, "t-target")
# The first act relays the owner's exact bytes, as owner text, under the owner id.
assert (row["kind"], row["text"], row["client_message_id"]) == (KIND_OWNER_TEXT, _OWNER_ORIGIN, "cm-1")
assert fence["owner_message_generation"] == 4
assert acks[-1]["status"] == "delivered" and acks[-1]["client_message_id"] == "cm-1"
# Main addresses a root in another chat: the lane is the registry's answer.
assert notices == []
assert _events(tmp_path, "task_message_routed") == []
receiver = _owner_started_receiver()
messages = [{"role": "user", "content": "Initial requirement verbatim"}]
_initialize_owner_directives(receiver, messages)
corpus_before = owner_source_sha256(receiver)
_drain_incoming_messages(messages, queue.Queue(), tmp_path, "t-target", None, set(), owner_ctx=receiver)
assert "[Message from my human]" in messages[-1]["content"]
assert owner_source_sha256(receiver) != corpus_before
assert [directive["source"] for directive in receiver._owner_directives] == ["initial_user", "owner_mailbox"]
assert receiver.last_owner_delivery["client_message_id"] == "cm-1"
def test_a_relay_retry_deduplicates_without_losing_the_owner_receipt(tmp_path):
from ouroboros.owner_mailbox import KIND_TASK_MESSAGE, drain_owner_entries
from ouroboros.tools.control import _steer_task
from supervisor.steering import _handle_steer_task
emitted, acks = [], []
supervisor = _supervisor(tmp_path, acks=acks,
running={"t-target": {"task": {"id": "t-target", "chat_id": 1}}})
ctx = _pooled_root_ctx(tmp_path)
_drain_owner_followup(tmp_path, ctx)
_wire(ctx, supervisor, emitted)
_steer_task(ctx, "t-target", "My review is complete")
_handle_steer_task(emitted[0], supervisor)
[row] = drain_owner_entries(tmp_path, "t-target")
assert row["kind"] == KIND_TASK_MESSAGE and "client_message_id" not in row
assert row["text"] == "My review is complete"
assert acks and all(ack["client_message_id"] == "owner-followup" for ack in acks)
assert all(ack["status"] == "delivered" for ack in acks)
# --- (c) refusals to a task issuer: typed, silent in chat, one Logs row -------
@pytest.mark.parametrize("drained_owner", [False, True])
def test_a_task_steer_of_a_cancelling_target_is_refused_typed_with_no_chat_and_no_picker(
tmp_path, monkeypatch, drained_owner,
):
import ouroboros.cancel_intents as cancel_intents
from ouroboros.project_dialogue import latest_chat_annotations
from ouroboros.tools.control import _steer_task
monkeypatch.setattr(cancel_intents, "cancel_pending", lambda _root, task_id, **_k: task_id == "t-target")
acks, notices, emitted = [], [], []
supervisor = _supervisor(
tmp_path, running={"t-target": {"task": {"id": "t-target", "chat_id": 1}}}, acks=acks, notices=notices,
)
ctx = _wire(_pooled_root_ctx(tmp_path), supervisor, emitted)
if drained_owner:
_drain_owner_followup(tmp_path, ctx)
out = _steer_task(ctx, "t-target", "stop after this file")
assert out.startswith("⚠️ STEER_REJECTED: task t-target was not steered (cancel_pending)")
assert notices == []
assert [ack["client_message_id"] for ack in acks] == (["owner-followup"] if drained_owner else [])
receipt = latest_chat_annotations(tmp_path)[emitted[0]["client_message_id"]]
assert (receipt["status"], receipt["reason"]) == ("rejected", "cancel_pending")
assert "options" not in receipt
[logged] = _events(tmp_path, "task_message_routed")
assert (logged["task_id"], logged["target_task_id"], logged["status"], logged["reason"]) == (
"swarm-root", "t-target", "refused", "cancel_pending",
)
def test_a_task_relay_to_a_closing_direct_turn_keeps_the_owner_receipt(tmp_path, monkeypatch):
import threading
from ouroboros.owner_mailbox import drain_owner_entries
from ouroboros.project_dialogue import latest_chat_annotations, routing_refusal_cause
from ouroboros.tools.control import _steer_task
from supervisor import workers
actor = types.SimpleNamespace(_owner_message_admission_lock=threading.RLock(),
_busy=True, _accepting_owner_messages=False,
_current_task_id="t-target", _owner_message_generation=3)
# Discovery succeeded, but admission closed before the transaction acquired its lock.
monkeypatch.setattr(workers, "get_direct_chat_agent", lambda _target: actor)
monkeypatch.setattr(workers, "direct_chat_turn", lambda _target: {
"id": "t-target", "chat_id": 1, "_is_direct_chat": True,
})
acks, notices, emitted = [], [], []
supervisor = _supervisor(tmp_path, acks=acks, notices=notices)
ctx = _wire(_pooled_root_ctx(tmp_path), supervisor, emitted)
_drain_owner_followup(tmp_path, ctx)
out = _steer_task(ctx, "t-target", "My review is complete")
assert "STEER_REJECTED" in out and "target_closed" in out
assert drain_owner_entries(tmp_path, "t-target") == [] and notices == []
assert actor._owner_message_generation == 3
assert len(acks) == 1 and acks[0]["client_message_id"] == "owner-followup"
assert latest_chat_annotations(tmp_path)["owner-followup"]["reason"] == "target_closed"
assert acks[0]["cause"] == routing_refusal_cause("steer_task", "needs_manual_target", "target_closed")
def test_a_headless_root_steering_a_finished_target_is_refused_without_any_chat_row(tmp_path):
"""The issuer lives in the hidden partition (chat_id 0): its refusal is its
tool result and its Logs row, and nothing is addressed to any chat."""
from ouroboros.tools.control import _steer_task
acks, notices, emitted = [], [], []
supervisor = _supervisor(tmp_path, acks=acks, notices=notices)
ctx = _wire(_pooled_root_ctx(tmp_path, task_id="headless-root", chat_id=0), supervisor, emitted)
out = _steer_task(ctx, "t-gone", "are you there")
assert out.startswith("⚠️ STEER_REJECTED: task t-gone was not steered (target_unknown)")
assert notices == [] and acks == []
[logged] = _events(tmp_path, "task_message_routed")
assert (logged["task_id"], logged["target_task_id"], logged["reason"]) == ("headless-root", "t-gone", "target_unknown")
def test_a_task_may_message_a_hidden_partition_root(tmp_path):
"""Owner 6=A: a headless (chat_id 0) root is a host-listed root like any other."""
from ouroboros.owner_mailbox import drain_owner_entries
from ouroboros.tools.control import _steer_task
emitted = []
supervisor = _supervisor(tmp_path, running={"t-hidden": {"task": {"id": "t-hidden", "chat_id": 0}}})
ctx = _wire(_pooled_root_ctx(tmp_path), supervisor, emitted)
out = _steer_task(ctx, "t-hidden", "hello there")
assert out.startswith("✉️ Message to task t-hidden written")
assert [row["text"] for row in drain_owner_entries(tmp_path, "t-hidden")] == ["hello there"]
# --- (d) ProjectA root -> ProjectB root, two texts in order -------------------
def test_a_project_root_messages_another_projects_root_twice_in_order(tmp_path):
from ouroboros.owner_mailbox import drain_owner_entries
from ouroboros.projects_registry import create_project
from ouroboros.tools.control import _steer_task
room_a = create_project(tmp_path, "proj-a", name="Project A")
room_b = create_project(tmp_path, "proj-b", name="Project B")
emitted, notices = [], []
supervisor = _supervisor(
tmp_path, notices=notices,
running={"t-b": {"task": {"id": "t-b", "chat_id": room_b["chat_id"], "project_id": "proj-b"}}},
)
ctx = _wire(_pooled_root_ctx(
tmp_path, task_id="t-a", chat_id=room_a["chat_id"], metadata={"project_id": "proj-a"},
), supervisor, emitted)
first = _steer_task(ctx, "t-b", "first: the schema is frozen")
second = _steer_task(ctx, "t-b", "second: migrations may start")
assert first.startswith("✉️ Message to task t-b written") and second.startswith("✉️ Message to task t-b written")
assert [row["text"] for row in drain_owner_entries(tmp_path, "t-b")] == [
"first: the schema is frozen", "second: migrations may start",
]
assert notices == []
@pytest.mark.parametrize("stamp", ["logged-ref", "suppressed-log"])
def test_an_owner_turn_in_a_project_room_still_gets_the_room_veto(tmp_path, stamp):
"""Today's veto for owner turns, computed from the registry lane: a Project
room turn cannot steer another room's root; Main can (it sees the manifest)."""
from ouroboros.owner_mailbox import drain_owner_entries
from ouroboros.projects_registry import create_project
from ouroboros.tools.control import _steer_task
room_a = create_project(tmp_path, "proj-a", name="Project A")
room_b = create_project(tmp_path, "proj-b", name="Project B")
emitted, notices = [], []
supervisor = _supervisor(
tmp_path, notices=notices,
running={"t-b": {"task": {"id": "t-b", "chat_id": room_b["chat_id"], "project_id": "proj-b"}}},
)
ctx = _wire(_owner_turn_ctx(tmp_path), supervisor, emitted)
if stamp == "suppressed-log":
del ctx.task_metadata["origin_message_ref"]
ctx.task_metadata["origin_suppressed"] = True
ctx.current_chat_id = room_a["chat_id"]
out = _steer_task(ctx, "t-b", "cross-room owner words")
assert out.startswith("⚠️ STEER_REJECTED: task t-b was not steered (chat_mismatch)")
assert drain_owner_entries(tmp_path, "t-b") == []
# --- forward_to_worker: one writer, two verb names ----------------------------
def _snapshot(tmp_path, rows):
from ouroboros.utils import atomic_write_json, utc_now_iso
(tmp_path / "state").mkdir(parents=True, exist_ok=True)
atomic_write_json(tmp_path / "state" / "queue_snapshot.json", {
"ts": utc_now_iso(), "running": rows, "pending": [], "worker_total": 1,
})
def test_forward_to_worker_reaches_a_host_listed_root_on_its_own_drive(tmp_path):
from ouroboros.owner_mailbox import drain_owner_entries
from ouroboros.task_results import STATUS_RUNNING, write_task_result
from ouroboros.tools.core import _forward_to_worker
root_drive = tmp_path / "root-drive"
root_drive.mkdir()
write_task_result(tmp_path, "root-x", STATUS_RUNNING, root_task_id="root-x", result="running")
write_task_result(tmp_path, "stranger", STATUS_RUNNING, root_task_id="stranger", result="running")
_snapshot(tmp_path, [{"id": "root-x", "task": {"id": "root-x", "chat_id": 0, "drive_root": str(root_drive)}}])
ctx = types.SimpleNamespace(drive_root=tmp_path, task_id="sender")
out = _forward_to_worker(ctx, "root-x", "the shared schema changed")
forbidden = _forward_to_worker(ctx, "stranger", "not listed")
relayed = _forward_to_worker(ctx, "root-x", "relay", relayed_from_task_id="sibling")
assert out.startswith("Message forwarded to task root-x: written to its mailbox as a message from this task")
[row] = drain_owner_entries(root_drive, "root-x")
assert (row["provenance"], row["source_task_id"], row["text"]) == ("independent_task", "sender", "the shared schema changed")
assert "TASK_FORBIDDEN" in forbidden and "nor an active independent root" in forbidden
assert "TASK_FORBIDDEN" in relayed and "independent root" in relayed
assert drain_owner_entries(tmp_path, "stranger") == []
# --- (e) the roster note: only on change, a fresh row after a send -----------
def test_the_roster_note_is_appended_on_change_and_never_rewrites_a_sent_row(tmp_path):
from ouroboros.peer_roster import maybe_append_roster_note
from ouroboros.transcript_prefix import observe_send
_snapshot(tmp_path, [{"id": "r-1", "task": {"id": "r-1", "title": "Deploy docs", "chat_id": 0, "project_id": "docs"}}])
ctx = types.SimpleNamespace(task_id="me", task_metadata={"budget_drive_root": str(tmp_path)})
messages = [{"role": "system", "content": "s"}, {"role": "user", "content": "task"}]
assert maybe_append_roster_note(ctx, messages, tmp_path) is True
assert maybe_append_roster_note(ctx, messages, tmp_path) is False, "unchanged roster: no note"
# No send witness proves the task tail is unsent, so append the note.
assert len(messages) == 3
assert messages[1] == {"role": "user", "content": "task"}
note = str(messages[-1]["content"])
assert note.startswith("[System task message]\n[INDEPENDENT_ROOTS]")
assert "- r-1 · Deploy docs · project=docs · running" in note
assert "objective" not in note.lower()
# A root that is the reader itself and a subagent row never appear.
observe_send(ctx, messages, round_idx=1)
sent_bytes = json.dumps(messages, ensure_ascii=False).encode("utf-8")
_snapshot(tmp_path, [
{"id": "r-1", "task": {"id": "r-1", "title": "Deploy docs", "chat_id": 0, "project_id": "docs"}},
{"id": "me", "task": {"id": "me", "title": "Myself", "chat_id": 1}},
{"id": "kid", "task": {"id": "kid", "parent_task_id": "r-1", "delegation_role": "subagent"}},
{"id": "r-2", "task": {"id": "r-2", "title": "Audit", "chat_id": 7}},
])
assert maybe_append_roster_note(ctx, messages, tmp_path) is True
# The sent row is byte-frozen: the changed roster is a NEW tail row.
assert len(messages) == 4
second = str(messages[-1]["content"])
assert second.startswith("[System task message]\n[INDEPENDENT_ROOTS]")
assert "- r-2 · Audit · chat=7 · running" in second
assert "- me ·" not in second and "kid" not in second
assert note == str(messages[2]["content"])
assert json.dumps(messages[:-1], ensure_ascii=False).encode("utf-8") == sent_bytes
def test_the_roster_note_includes_direct_roots_but_skips_subagents_and_discloses_gaps(tmp_path):
from ouroboros.peer_roster import maybe_append_roster_note, render_roster_note
_snapshot(tmp_path, [{"id": "r-1", "task": {"id": "r-1", "title": "Deploy docs", "chat_id": 0}}])
direct = types.SimpleNamespace(task_id="turn", is_direct_chat=True, task_metadata={})
child = types.SimpleNamespace(task_id="kid", task_metadata={"delegation_role": "subagent"})
# Main's routing manifest does not carry authored focus. Its first roster
# view is required, just like any root's, and remains restorable.
assert maybe_append_roster_note(direct, [], tmp_path) is True
_snapshot(tmp_path, [{"id": "r-1", "task": {"id": "r-1", "title": "Deploy docs", "chat_id": 0}},
{"id": "r-2", "task": {"id": "r-2", "title": "New work", "chat_id": 0}}])
assert maybe_append_roster_note(direct, [], tmp_path) is True
assert maybe_append_roster_note(child, [], tmp_path) is False
rendered = render_roster_note({
"roots": [{"task_id": f"r-{i}", "title": "", "chat_id": 1, "project_id": "", "status": "pending"} for i in range(45)],
"incomplete": True,
})
assert "…and 5 more not shown." in rendered and "roster incomplete" in rendered
@pytest.mark.parametrize("origin", [{}, {"initiator": "consciousness"}], ids=["owner", "consciousness"])
def test_the_direct_roots_fragment_never_blocks_on_a_held_actor_lock(tmp_path, monkeypatch, origin):
import threading
from supervisor import direct_roots
from supervisor.active_activity import get_direct_activity_registry
from ouroboros.peer_roster import independent_roots
registry = get_direct_activity_registry()
lock_free = threading.RLock()
free_actor = types.SimpleNamespace(
_owner_message_admission_lock=lock_free, _busy=True, _accepting_owner_messages=True,
_current_task_id="turn-free", _current_chat_id=1, _current_task_metadata={"title": "Chat", **origin},
_current_task_text="hello",
)
lock_held = threading.Lock()
lock_held.acquire()
held_actor = types.SimpleNamespace(
_owner_message_admission_lock=lock_held, _busy=True, _accepting_owner_messages=True,
_current_task_id="turn-held", _current_chat_id=1, _current_task_metadata={},
_current_task_text="busy",
)
registry.register("turn-free", 1, actor=free_actor)
registry.register("turn-held", 1, actor=held_actor)
try:
payload = direct_roots.publish_direct_roots(tmp_path)
finally:
registry.unregister("turn-free")
registry.unregister("turn-held")
lock_held.release()
assert [row["task_id"] for row in payload["roots"]] == ["turn-free"]
assert payload["incomplete"] is True
_snapshot(tmp_path, [])
roster = independent_roots(tmp_path)
assert [row["task_id"] for row in roster["roots"]] == ["turn-free"]
assert roster["roots"][0]["direct_chat"] is True and roster["incomplete"] is True
from ouroboros.peer_roster import render_roster_note
note = render_roster_note(roster)
assert "live direct conversation" in note
assert "live owner conversation" not in note and "person is having" not in note
assert "turn-free" in note, "the direct lane remains addressable"
direct_roots.clear_direct_roots(tmp_path)
assert independent_roots(tmp_path)["roots"] == []
# --- wave 3: ensure on the rail, obligation follows the work ------------------
def _ensure_live(tmp_path, supervisor, **ctx_kw):
from supervisor.events_project_routing import _handle_ensure_project_scope
ctx = types.SimpleNamespace(
project_id="", task_metadata={}, task_contract={}, task_id="t-root", pending_events=[],
drive_root=tmp_path, event_queue=None,
)
for key, value in ctx_kw.items():
setattr(ctx, key, value)
ctx.event_queue = types.SimpleNamespace(
put_nowait=lambda event: _handle_ensure_project_scope(event, supervisor),
)
return ctx
@pytest.fixture
def _projects_root(tmp_path, monkeypatch):
import ouroboros.config as cfg
import supervisor.message_bus as mb
from supervisor import workers
monkeypatch.setattr(cfg, "DATA_DIR", tmp_path)
monkeypatch.setattr(workers, "DRIVE_ROOT", tmp_path)
monkeypatch.setattr(mb, "get_bridge", lambda: types.SimpleNamespace(broadcast=lambda payload: None))
monkeypatch.setattr(workers, "_announce_created_project", lambda *a, **kw: None)
(tmp_path / "logs").mkdir(parents=True, exist_ok=True)
return tmp_path
def test_a_refused_bind_reaches_the_ensure_caller_as_a_typed_refusal(_projects_root, monkeypatch):
"""(f) The bind is forced to fail: the tool says so, restores its scope, and
never says OK; the receipt is readable under the act's own id."""
import ouroboros.projects_registry as reg
from ouroboros.project_dialogue import latest_chat_annotations
from ouroboros.tools.control import _ensure_project_scope
tmp_path = _projects_root
def _refuse(*_a, **_k):
raise ValueError("project 'cyber-racing' is deleting; it cannot accept bindings")
monkeypatch.setattr(reg, "bind_task_to_project", _refuse)
supervisor = _supervisor(tmp_path, running={"t-root": {"task": {"id": "t-root", "project_id": ""}}})
ctx = _ensure_live(tmp_path, supervisor, project_id="")
out = _ensure_project_scope(ctx, project_name="Cyber Racing")
assert out.startswith("⚠️ SCOPE_REJECTED (project_binding_failed)")
assert "OK" not in out and "durably bound to no project" in out
assert ctx.project_id == "" # scope unchanged
assert supervisor.RUNNING["t-root"]["task"]["project_id"] == ""
[receipt] = [row for row in latest_chat_annotations(tmp_path).values() if row["action"] == "ensure_project_scope"]
assert (receipt["status"], receipt["reason"]) == ("rejected", "project_binding_failed")
assert receipt["client_message_id"].startswith("agent-steer:")
def test_a_bind_that_lands_after_a_lost_ack_is_discoverable_from_the_durable_binding(
_projects_root, monkeypatch,
):
"""(f) The wait times out while the handler still binds: the tool reports
unconfirmed (never scoped), and the next call reads the durable truth."""
import ouroboros.tools.control_events as control_events
from ouroboros.project_facts import project_id_from_display_name
from ouroboros.projects_registry import project_id_for_task
from ouroboros.tools.control import _ensure_project_scope
tmp_path = _projects_root
monkeypatch.setattr(
control_events, "_wait_for_routing_annotation",
lambda *_a, **_k: {"status": "unconfirmed", "reason": "confirmation_timeout"},
)
supervisor = _supervisor(tmp_path, running={"t-root": {"task": {"id": "t-root", "project_id": ""}}})
ctx = _ensure_live(tmp_path, supervisor)
pid = project_id_from_display_name("Cyber Racing")
out = _ensure_project_scope(ctx, project_name="Cyber Racing")
assert out.startswith("⚠️ SCOPE_UNCONFIRMED") and "do not report it as scoped" in out
assert ctx.project_id == pid # journal writes target it meanwhile
assert project_id_for_task(tmp_path, "t-root") == pid # the bind did land
again = _ensure_project_scope(_ensure_live(tmp_path, supervisor), project_name="Cyber Racing")
assert "already scoped" in again
def _promote_supervisor(tmp_path, running, enqueued):
from ouroboros.utils import append_jsonl
return types.SimpleNamespace(
DRIVE_ROOT=tmp_path, RUNNING=running, PENDING=[],
WORKERS={0: types.SimpleNamespace()},
bridge=types.SimpleNamespace(send_routing_ack=lambda *a, **k: None, broadcast=lambda *a, **k: None),
enqueue_task=lambda task: enqueued.append(task),
persist_queue_snapshot=lambda **_k: True,
load_state=lambda: {"owner_chat_id": 1},
append_jsonl=append_jsonl,
)
@pytest.mark.parametrize("drained_owner", [False, True])
def test_an_unmet_swarm_obligation_moves_to_the_promoted_root(_projects_root, monkeypatch, drained_owner):
"""(h) Through the real admission handler: the new root carries force_plan,
the promoter is released with a transferred receipt on its live row and its
task details, the tool result names the move, and the worker's own copy of
the flag is released so force_plan_decision stops requiring a plan."""
import supervisor.queue as queue_mod
from ouroboros.owner_hurry import force_plan_decision
from ouroboros.task_results import load_task_result
from ouroboros.tools.control import _promote_chat_to_task
from supervisor.events_project_routing import _handle_promote_chat_to_task
tmp_path = _projects_root
monkeypatch.setattr(queue_mod, "DRIVE_ROOT", str(tmp_path))
enqueued: list = []
running = {"swarm-root": {"task": {"id": "swarm-root", "chat_id": 1,
"metadata": {"force_plan": True, "force_plan_source": "swarm"}}}}
supervisor = _promote_supervisor(tmp_path, running, enqueued)
ctx = _pooled_root_ctx(tmp_path)
if drained_owner:
_drain_owner_followup(tmp_path, ctx)
ctx.event_queue = types.SimpleNamespace(put_nowait=lambda event: _handle_promote_chat_to_task(event, supervisor))
out = _promote_chat_to_task(ctx, "Implement the plan in a new root", predecessor_task_id="")
assert out.startswith("OK: task")
[new_root] = enqueued
assert new_root["metadata"]["force_plan"] is True and new_root["metadata"]["force_plan_source"] == "swarm"
assert f"Your planning obligation (force_plan) moved to task {new_root['id']}" in out
assert "use ensure_project_scope" in out and "unplanned" in out
promoter_row = running["swarm-root"]["task"]["metadata"]
assert promoter_row["force_plan"] is False and promoter_row["force_plan_transferred_to"] == new_root["id"]
admission = load_task_result(tmp_path, new_root["id"])["promotion_admission"]
assert admission["force_plan_transfer"]["from"] == "swarm-root"
assert admission["force_plan_transfer"]["released"] is True
assert load_task_result(tmp_path, "swarm-root")["force_plan_transfer"]["to"] == new_root["id"]
# The worker's copy is released too: no plan is required of the promoter now.
assert ctx.task_metadata["force_plan"] is False
assert ctx.task_metadata["force_plan_transferred_to"] == new_root["id"]
assert force_plan_decision(ctx, {})["status"] == "not_required"
def test_ensure_keeps_the_obligation_and_a_met_one_transfers_nothing(_projects_root, monkeypatch):
import ouroboros.task_results as task_results
from ouroboros.owner_hurry import unmet_force_plan_obligation
from ouroboros.tools.control import _ensure_project_scope
tmp_path = _projects_root
supervisor = _supervisor(tmp_path, running={"swarm-root": {"task": {"id": "swarm-root", "project_id": ""}}})
ctx = _ensure_live(tmp_path, supervisor, task_id="swarm-root",
task_metadata={"force_plan": True, "force_plan_source": "swarm"})
out = _ensure_project_scope(ctx, project_name="Cyber Racing")
assert out.startswith("OK: this task is now durably bound")
assert ctx.task_metadata["force_plan"] is True # same task: the obligation stays
assert unmet_force_plan_obligation(ctx) == {"unmet": True, "source": "swarm"}
monkeypatch.setattr(task_results, "load_plan_review_state",
lambda _root, _tid: {"schema_version": 2, "waves": [{"request_fingerprint": "f1"}]})
assert unmet_force_plan_obligation(ctx) == {"unmet": False, "reason": "plan_review_engaged"}
assert unmet_force_plan_obligation(_owner_turn_ctx(tmp_path)) == {"unmet": False, "reason": "not_required"}
def test_a_promote_into_another_project_discloses_the_second_project(_projects_root, monkeypatch):
"""(13) Owner 5A: promote stays free, so the result says a second Project
now holds the work when the request already had one."""
import ouroboros.tools.control_events as control_events
from ouroboros.projects_registry import bind_task_to_project, create_project
from ouroboros.tools.control import _promote_chat_to_task
tmp_path = _projects_root
create_project(tmp_path, "first-room", name="First Room")
create_project(tmp_path, "second-room", name="Second Room")
bind_task_to_project(tmp_path, "swarm-root", "first-room", origin={"absent": "system"})
monkeypatch.setattr(
control_events, "_wait_for_promotion_admission",
lambda *_a, **_k: {"status": "scheduled", "effective_project_id": "second-room"},
)
ctx = _pooled_root_ctx(tmp_path)
out = _promote_chat_to_task(ctx, "Do it elsewhere", project_id="second-room", predecessor_task_id="")
assert "already has project 'first-room'" in out and "a second project 'second-room' now holds this promote" in out
def test_a_transfer_admitted_after_the_wait_returned_still_releases_the_worker(tmp_path):
"""Late admission: the supervisor recorded the transfer on the promoter's task
result while the tool had already returned unconfirmed; the worker's own
obligation readers reconcile from that durable record instead of holding
finalization for a plan the new root owes."""
from ouroboros.owner_hurry import force_plan_decision, unmet_force_plan_obligation
from ouroboros.task_results import STATUS_RUNNING, write_task_result
write_task_result(tmp_path, "swarm-root", STATUS_RUNNING, result="running",
force_plan_transfer={"from": "swarm-root", "to": "new-root", "released": True})
ctx = _pooled_root_ctx(tmp_path)
assert unmet_force_plan_obligation(ctx) == {"unmet": False, "reason": "transferred"}
assert ctx.task_metadata["force_plan"] is False
assert ctx.task_metadata["force_plan_transferred_to"] == "new-root"
assert force_plan_decision(ctx, {}, enforcement="blocking")["status"] == "not_required"
def test_a_returning_roster_is_re_announced_after_an_intervening_change(tmp_path):
"""A → B → A: the OLD A row must not suppress the fresh A tail (the model
would otherwise keep reading B). Only the latest representation counts,
whether it stands alone or was merged into an unsent owner row."""
from ouroboros.peer_roster import maybe_append_roster_note
ctx = types.SimpleNamespace(task_id="me", task_metadata={"budget_drive_root": str(tmp_path)})
roster_a = [{"id": "r-1", "task": {"id": "r-1", "title": "Deploy docs", "chat_id": 0, "project_id": "docs"}}]
roster_b = roster_a + [{"id": "r-2", "task": {"id": "r-2", "title": "Audit", "chat_id": 7}}]
messages = [{"role": "system", "content": "s"}, {"role": "user", "content": "task"}]
_snapshot(tmp_path, roster_a)
assert maybe_append_roster_note(ctx, messages, tmp_path) is True
note_a = str(messages[-1]["content"])
_snapshot(tmp_path, roster_b)
assert maybe_append_roster_note(ctx, messages, tmp_path) is True
assert "- r-2 · Audit" in str(messages[-1]["content"])
_snapshot(tmp_path, roster_a)
assert maybe_append_roster_note(ctx, messages, tmp_path) is True, "the roster returned to A: announce it again"
assert str(messages[-1]["content"]) == note_a
assert maybe_append_roster_note(ctx, messages, tmp_path) is False, "unchanged since the latest note"
# The latest representation may be a note merged into an unsent owner row
# (string or text blocks); it deduplicates exactly like a standalone row.
merged = [{"role": "user", "content": "owner text\n\n" + note_a}]
assert maybe_append_roster_note(ctx, merged, tmp_path) is False
blocks = [{"role": "user", "content": [{"type": "text", "text": "owner text"}, {"type": "text", "text": note_a}]}]
assert maybe_append_roster_note(ctx, blocks, tmp_path) is False
def test_live_roots_refusals_are_typed_failures_at_the_result_boundary(tmp_path):
"""A refused or stale-snapshot catalogue read is recorded as a FAILED call,
not as a successful one carrying an ``error`` key."""
from ouroboros.tools.recent_tasks import _handle_live_roots
from ouroboros.tools.tool_result import _structured_failure
from ouroboros.tool_capabilities import tool_result_limit
child = types.SimpleNamespace(drive_root=tmp_path, task_id="kid",
task_metadata={"parent_task_id": "root", "delegation_role": "subagent"})
refused = _handle_live_roots(child)
assert _structured_failure(refused) and json.loads(refused)["host_code"] == "TOOL_FORBIDDEN"
_snapshot(tmp_path, [{"id": f"r-{i}", "task": {"id": f"r-{i}", "title": "T" * 80, "chat_id": i, "project_id": f"proj-{i}"}}
for i in range(100)])
root = types.SimpleNamespace(drive_root=tmp_path, task_id="me", task_metadata={"budget_drive_root": str(tmp_path)})
page = _handle_live_roots(root, limit=100)
assert not _structured_failure(page) and json.loads(page)["returned"] == 100
# A maximum page is structured JSON: it must fit the result bound the truncator applies.
assert len(page) < tool_result_limit("live_roots")
stale = _handle_live_roots(root, limit=100, snapshot="not-the-current-token")
assert _structured_failure(stale) and json.loads(stale)["host_code"] == "LIVE_ROOTS_SNAPSHOT_CHANGED"