From c30556b0032ed50bea465642cda167e76644257a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 22:45:14 -0400 Subject: [PATCH 01/43] P301 checkpoint: observe live native order progress without claiming workflow parity --- src/agent_comms/commander_parity/lifecycle.py | 43 +++++++++++++++++++ src/agent_comms/commander_parity/rows.py | 5 +++ 2 files changed, 48 insertions(+) create mode 100644 src/agent_comms/commander_parity/lifecycle.py diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py new file mode 100644 index 0000000000..c7c9c0cd5d --- /dev/null +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -0,0 +1,43 @@ +"""Live order-family observations; partial observations never authorize a switch.""" + +from __future__ import annotations + +import sys +from types import FrameType + +from ..commander.config import CommanderConfig +from ..commander.order import Commander +from ..todos import Moving, NoCommitProgress, OrderProgress, Todo +from . import ParityComparison, UnverifiedParity + + +def verify_order_progress() -> ParityComparison: + """Observe the real native measurements returned through the current reader. + + This first checkpoint deliberately reports incomplete coverage: matching a + read is not proof of scheduling, strike suppression or unit-facing delivery. + """ + context = Commander.read(CommanderConfig.path()) + observed: dict[str, str] = {} + + def observe(frame: FrameType, event: str, value: object) -> None: + if event == "return" and frame.f_code is OrderProgress.read.__func__.__code__: + todo = frame.f_locals["todo"] + if not isinstance(todo, Todo) or not isinstance(value, OrderProgress): + raise TypeError("The native progress reader returned an unexpected record") + observed[todo.id] = value.line + + previous = sys.getprofile() + sys.setprofile(observe) + try: + current = {key: value.line for key, value in context.progress().items()} + finally: + sys.setprofile(previous) + return UnverifiedParity( + f"Live OrderProgress.read: {len(observed)} native observations, " + f"current/native equal={bool(observed) and current == observed}; " + f"first-commit interval={NoCommitProgress.seconds}s, " + f"later-progress interval={Moving.seconds}s. " + "Scheduling, merged-work strike suppression and unit deadline visibility " + "still require their live workflow comparison." + ) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index ab22a2feb2..d023928624 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -13,6 +13,7 @@ from . import ParityComparison, ParityRow, UnverifiedParity from .load import LoadComparison from .measurements import RuleComparison, StorageComparison, measured_load +from .lifecycle import verify_order_progress class CreateProblemParityRow(ParityRow): @@ -312,6 +313,10 @@ class OrderProgressParityRow(ParityRow): ) phases = (OneOrderOwnerPhase, ControlPlanePhase) + @classmethod + def verify(cls) -> ParityComparison: + return verify_order_progress() + class MultipleCommandersParityRow(ParityRow): number = 34 From bbec965d925a3ee505755f06e84ab5dfde524965 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:08:29 -0400 Subject: [PATCH 02/43] Exercise copied native creation assignment evidence and strikes; repair deadline projection owners --- src/agent_comms/commander/order.py | 4 +- src/agent_comms/commander_parity/lifecycle.py | 329 +++++++++++++++++- src/agent_comms/commander_parity/rows.py | 18 +- src/agent_comms/todos.py | 3 +- 4 files changed, 333 insertions(+), 21 deletions(-) diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index a2828a5fc0..d37b5b2ff5 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -730,8 +730,8 @@ def due(self, pid: str, at: str, why: str) -> None: moved = RecordedEventTime(time.time()).at_clock(at) self.call(CommsChangeProblemTool(problem=pid, operation=AssignTodo( todo.revision, todo.assignment, moved, todo.order, todo.goal))) - self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} due ' - f'{todo.deadline.clock()} moved to {moved.clock()}): {why}', 'Deadline', + self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} ' + f'deadline changed): {why}', 'Deadline', VerdictAudience.of(todo.held().owner, todo.goal)) print(f'{pid}: due {moved.clock()}') diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index c7c9c0cd5d..c35962539f 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -1,24 +1,323 @@ -"""Live order-family observations; partial observations never authorize a switch.""" +"""Exercise current order commands against real native owners on a selected bus copy.""" from __future__ import annotations +import contextlib +import io +import json +import os +import shlex import sys -from types import FrameType +import time +from abc import ABC, abstractmethod +from collections.abc import Callable, Iterator +from dataclasses import dataclass, replace +from pathlib import Path +from types import FrameType, FunctionType +from typing import ClassVar +from uuid import uuid4 +from ..change_report import FileLine +from ..check_root import CheckRoot from ..commander.config import CommanderConfig -from ..commander.order import Commander -from ..todos import Moving, NoCommitProgress, OrderProgress, Todo +from ..commander.order import Commander, OrderFile, hhmm +from ..commander.request import RequestAddVerb +from ..comms import Comms +from ..field_codec import FieldCodec +from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder +from ..tool_caller import ToolCaller +from ..tool_result import ToolResult +from ..tools import ( + CommsChangeProblemTool, CommsFireTool, CommsOrderTool, + CommsProblemCloseTool, CommsTodoCreateTool, ProblemOrdered, ThreadToolRequest, TodoChanged, TodoList, +) +from ..todos import ( + ApplicationOrderRun, MergedProgress, Moving, NoCommitProgress, + OrderProgress, PendingProblemOrder, Todo, +) from . import ParityComparison, UnverifiedParity -def verify_order_progress() -> ParityComparison: - """Observe the real native measurements returned through the current reader. +@dataclass(frozen=True) +class LifecycleObservation(ParityComparison): + owner: str + invocations: int + checks: tuple[tuple[str, bool], ...] + output: str + + @property + def passed(self) -> bool: + return bool(self.invocations and self.checks) and all(value for _, value in self.checks) + + def describe(self) -> str: + return f"owner={self.owner}; calls={self.invocations}; checks={dict(self.checks)}; output={self.output!r}" + + +@dataclass(frozen=True) +class LifecycleRun: + commander: Commander + worker: str + decisions: Path + document: Path + artifact: Path + + @classmethod + @contextlib.contextmanager + def open(cls) -> Iterator[LifecycleRun]: + config = CommanderConfig.read(CommanderConfig.path()) + source = Comms(config.root) + actor = ToolCaller.from_actor(os.environ.get("AGENT_COMMS_THREAD")).acting_name(config.commander) + worker = source.registry.require(actor) + if worker.name == config.commander: + raise ValueError("Lifecycle comparison requires an existing unit caller, not its commander") + directory = worker.storage_roots.scratch / "commander-parity" / uuid4().hex + with CheckRoot(source, (config.commander, worker.name), directory).open() as copied: + root = copied.root + commander = Commander( + commander=config.commander, root=root, cli=config.cli, + state=root / "commander-state", board=root / "board.md", + narrative=root / "narrative.md", repos=config.repos, + worktrees=root / "projects", scratch=root / "scratch", disk=root, + ) + with contextlib.redirect_stdout(io.StringIO()): + RequestAddVerb(text=( + "Compare current order commands with their real native owners in this " + "disposable copied bus, preserving the source bus and its sessions.")).run(commander) + decisions = root / "decisions.json" + decisions.write_text(json.dumps([worker.name])) + command = shlex.join((sys.executable, "-c", + "import json;from pathlib import Path;" + f"print(len(json.loads(Path({str(decisions)!r}).read_text())))")) + order = PendingProblemOrder( + owner=FileLine(str(Path(__file__).resolve()), 1), + check=OrderCheck(str(Path(__file__).resolve().parents[3]), command, CountCheckResult(0)), + edits=("Remove the copied workflow decision and verify its native order completes.",), + banned=("Do not change the source bus or its saved sessions.",), + report="Report the actual copied goal and native order state.", + run=ApplicationOrderRun( + "Read the copied native goal and confirm its recorded source location and assigned worker."), + ) + document = root / "order.json" + document.write_text(json.dumps(FieldCodec.encode(OrderFile( + order, "The copied workflow decision prevents the native completion check from reaching zero.", + "This is stupid because an unfinished copied decision must not count as delivered work.")))) + yield cls(commander, worker.name, decisions, document, root / "application.json") + + @property + def due(self) -> str: + return time.strftime("%H:%M", time.localtime(time.time() + 3600)) + + def create(self) -> Todo: + self.commander.problem(str(self.document), self.worker, self.due, + f"{Path(__file__).resolve()}:1") + rows = tuple(self.commander.comms.goals.todos.for_commander(self.commander.incarnation)) + if len(rows) != 1: + raise ValueError(f"A fresh copied workflow must create one problem, got {len(rows)}") + return rows[0] + + def complete(self, todo: Todo) -> Todo: + goal = self.commander.comms.registry.require(self.worker).goal + self.artifact.write_text(json.dumps(FieldCodec.encode(goal))) + self.decisions.write_text("[]") + self.commander.done(todo.id, "op", seen=str(self.artifact)) + return self.commander.comms.goals.todos.get(todo.id) + + +class LifecycleCase(ABC): + native_owner: ClassVar[type[ThreadToolRequest]] + additional_owners: ClassVar[tuple[type[ThreadToolRequest], ...]] = () + + @classmethod + @abstractmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: ... + + @classmethod + @abstractmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: ... + + @classmethod + def read(cls) -> ParityComparison: + owners = (cls.native_owner, *cls.additional_owners) + codes = set() + for owner in owners: + function = owner.apply + if not isinstance(function, FunctionType): + raise TypeError("A native tool must declare its executable apply method") + codes.add(function.__code__) + native: list[ToolResult] = [] + + def observe(frame: FrameType, event: str, value: object) -> None: + if event == "return" and frame.f_code in codes: + request = frame.f_locals["self"] + if type(request) in owners and isinstance(value, ToolResult): + native.append(value) + + with LifecycleRun.open() as run: + previous = sys.getprofile() + output = io.StringIO() + sys.setprofile(observe) + try: + with contextlib.redirect_stdout(output): + rows = cls.exercise(run) + finally: + sys.setprofile(previous) + text = output.getvalue() + return LifecycleObservation( + ", ".join(f"{owner.__module__}.{owner.__qualname__}" for owner in owners), len(native), + cls.checks(run, rows, tuple(native), text), text) + + +class CreateProblemCase(LifecycleCase): + native_owner = CommsTodoCreateTool + + @classmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: + return (run.create(),) + + @classmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + if len(native) != 1 or not isinstance(native[0], ProblemOrdered): + return (("one native creation result", False),) + result, saved = native[0], rows[-1] + goal = run.commander.comms.registry.require(run.worker).goal + return (("printed native identity", output.rstrip().endswith(cls.printed_identity(result))), + ("native source retained", saved.order.checked().owner == result.todo.order.checked().owner), + ("native assignment retained", saved.assignment == result.todo.assignment), + ("native goal published", goal.require().id == result.goal.id)) + + @classmethod + def printed_identity(cls, result: ProblemOrdered) -> str: + return result.todo.id + - This first checkpoint deliberately reports incomplete coverage: matching a - read is not proof of scheduling, strike suppression or unit-facing delivery. - """ +class AssignOrderCase(CreateProblemCase): + native_owner = CommsOrderTool + + @classmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: + before = run.create() + run.commander.fire(run.worker, "The copied workflow is released before its next native assignment.") + document = replace(OrderFile.read(run.document.read_text()), problems=(before.id,)) + run.commander.send(run.worker, run.due, document, clean=True) + return before, run.commander.comms.goals.todos.get(before.id) + + @classmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + inherited = super().checks(run, rows, native, output) + return (*inherited, ("same problem reassigned", rows[0].id == rows[-1].id), + ("prior assignment retained", bool(rows[-1].released))) + + @classmethod + def printed_identity(cls, result: ProblemOrdered) -> str: + return result.todo.order.checked().id + + +class InstalledEvidenceCase(LifecycleCase): + native_owner = CommsProblemCloseTool + + @classmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: + created = run.create() + delivered = run.complete(created) + run.commander.close(created.id, + "Read the copied native goal and confirm its recorded source location and assigned worker.", + str(run.artifact)) + return delivered, run.commander.comms.goals.todos.get(created.id) + + @classmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + return (("delivery alone unaccepted", not rows[0].order.accepted), + ("installed evidence accepted", rows[-1].order.accepted), + ("closed in native store", not rows[-1].state.open), + ("actual artifact exists", run.artifact.is_file()), + ("current close printed", f"{rows[-1].id} closed" in output)) + + +class StrikeLimitCase(LifecycleCase): + native_owner = CommsChangeProblemTool + additional_owners = (CommsFireTool,) + + @classmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: + created = run.create() + for number in range(ProblemGoalOrder.StrikeLimit): + run.commander.strike(run.worker, created.id, + f"The copied decision remains unfinished at its recorded review number {number + 1}.") + run.commander.fire(run.worker, + "The copied holder exhausted its declared live strikes on this problem.") + return created, run.commander.comms.goals.todos.get(created.id) + + @classmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + saved = rows[-1] + return (("declared limit recorded", saved.strike_count == ProblemGoalOrder.StrikeLimit), + ("one native fire", sum(isinstance(result, TodoList) for result in native) == 1), + ("assignment released", saved.holder is None), + ("current strike count printed", f"{ProblemGoalOrder.StrikeLimit} strike(s)" in output)) + + +class OrderProgressMutationCase(LifecycleCase): + native_owner = CommsChangeProblemTool + + @classmethod + def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: + before = run.create() + moved = time.strftime("%H:%M", time.localtime(time.time() + 7200)) + run.commander.due(before.id, moved, + "The copied workflow needs time to record its actual native application evidence.") + return before, run.commander.comms.goals.todos.get(before.id) + + @classmethod + def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], + native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + before, after = rows + messages = run.commander.comms.bus.inbox(run.worker) + unit = run.commander.comms.registry.require(run.worker) + goal = unit.goal.require() + instructions = goal.order.instructions(goal.order.todos( + run.commander.comms.goals, unit.incarnation, goal.id)) + native_deadlines = tuple(result.todo.deadline for result in native if isinstance(result, TodoChanged)) + return (("native deadline retained", bool(native_deadlines) and native_deadlines[-1] == after.deadline), + ("deadline actually changed", before.deadline != after.deadline), + ("assignment unchanged", before.assignment == after.assignment), + ("unit got the actual update", bool(messages)), + ("unit messages hide clocks", all(hhmm(clock) not in message.body + for clock in (before.deadline, after.deadline) for message in messages)), + ("unit goal hides deadline", "Deadline:" not in instructions + and hhmm(after.deadline) not in instructions), + ("commander still sees deadline", f"due {hhmm(after.deadline)}" in output)) + + +@dataclass(frozen=True) +class ProgressComparison(ParityComparison): + current: dict[str, str] + native: dict[str, str] + first_seconds: int + later_seconds: int + merged_no_strikes: tuple[bool, ...] + workflow: ParityComparison + + @property + def passed(self) -> bool: + return (self.workflow.passed and bool(self.native) and self.current == self.native + and self.first_seconds == 600 and self.later_seconds == 900 + and bool(self.merged_no_strikes) and all(self.merged_no_strikes)) + + def describe(self) -> str: + return (f"{self.workflow.describe()}; live_progress={len(self.native)}; " + f"read_equal={self.current == self.native}; intervals={self.first_seconds}/{self.later_seconds}; " + f"live_merged_no_strikes={self.merged_no_strikes}") + + +def verify_order_progress() -> ParityComparison: context = Commander.read(CommanderConfig.path()) observed: dict[str, str] = {} + merged: list[bool] = [] def observe(frame: FrameType, event: str, value: object) -> None: if event == "return" and frame.f_code is OrderProgress.read.__func__.__code__: @@ -26,6 +325,8 @@ def observe(frame: FrameType, event: str, value: object) -> None: if not isinstance(todo, Todo) or not isinstance(value, OrderProgress): raise TypeError("The native progress reader returned an unexpected record") observed[todo.id] = value.line + if isinstance(value, MergedProgress): + merged.append(not value.stalled(todo.order.checked().at.recorded_at("An order"), time.time())) previous = sys.getprofile() sys.setprofile(observe) @@ -33,11 +334,5 @@ def observe(frame: FrameType, event: str, value: object) -> None: current = {key: value.line for key, value in context.progress().items()} finally: sys.setprofile(previous) - return UnverifiedParity( - f"Live OrderProgress.read: {len(observed)} native observations, " - f"current/native equal={bool(observed) and current == observed}; " - f"first-commit interval={NoCommitProgress.seconds}s, " - f"later-progress interval={Moving.seconds}s. " - "Scheduling, merged-work strike suppression and unit deadline visibility " - "still require their live workflow comparison." - ) + return ProgressComparison(current, observed, NoCommitProgress.seconds, Moving.seconds, + tuple(merged), OrderProgressMutationCase.read()) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index d023928624..aed40ce0be 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -13,7 +13,7 @@ from . import ParityComparison, ParityRow, UnverifiedParity from .load import LoadComparison from .measurements import RuleComparison, StorageComparison, measured_load -from .lifecycle import verify_order_progress +from .lifecycle import AssignOrderCase, CreateProblemCase, InstalledEvidenceCase, StrikeLimitCase, verify_order_progress class CreateProblemParityRow(ParityRow): @@ -23,6 +23,10 @@ class CreateProblemParityRow(ParityRow): native = "`CommsTodoCreateTool`" phases = (OneOrderOwnerPhase,) + @classmethod + def verify(cls) -> ParityComparison: + return CreateProblemCase.read() + class AssignOrderParityRow(ParityRow): number = 2 @@ -31,6 +35,10 @@ class AssignOrderParityRow(ParityRow): native = "`CommsOrderTool`, `CommsTodoTool` assignment and release" phases = (OneOrderOwnerPhase,) + @classmethod + def verify(cls) -> ParityComparison: + return AssignOrderCase.read() + class DeliveredCommitParityRow(ParityRow): number = 3 @@ -47,6 +55,10 @@ class InstalledEvidenceParityRow(ParityRow): native = "`CommsProblemCloseTool`" phases = (OneOrderOwnerPhase,) + @classmethod + def verify(cls) -> ParityComparison: + return InstalledEvidenceCase.read() + class BoardParityRow(ParityRow): number = 5 @@ -71,6 +83,10 @@ class StrikeLimitParityRow(ParityRow): native = "`Strike` rows, author-declared `StrikeLimit`, one `comms_fire`" phases = (RecordPlanePhase,) + @classmethod + def verify(cls) -> ParityComparison: + return StrikeLimitCase.read() + class VerdictParityRow(ParityRow): number = 8 diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 22bc688b77..e1366a8233 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -723,6 +723,7 @@ def stalled_between(self, ordered: float, since: float, now: float) -> bool: @dataclass(frozen=True) class Moving(Stalling): """Work whose last commit, push or PR change was observed at ``at``.""" + seconds: ClassVar[int] = 15 * 60 at: float = field(kw_only=True) readings: DistanceReadings = field(default=DistanceReadings(), kw_only=True, compare=False) @@ -1535,7 +1536,7 @@ def work_instructions(self) -> tuple[str, ...]: def instructions(self, todo): return "\n".join((f"Problem: {todo.id}: {todo.text}", *self.finding.instructions(), f"Order: {self.id}", self.pull_request_instruction(todo.id), - *self.work_instructions(), f"Deadline: {FieldCodec.encode(todo.deadline)}")) + *self.work_instructions())) def delivered(self, delivery): delivery.at.require_delivery() From 9a3fa0da8cd28ec6b42d8876bb2fad29d3c3199f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:24:33 -0400 Subject: [PATCH 03/43] Own candidate worker retirement and measure native calls through Commander inheritance --- src/agent_comms/candidate_maintenance.py | 112 +++++++++--------- src/agent_comms/check_root.py | 2 + src/agent_comms/commander_parity/lifecycle.py | 107 +++++++++-------- src/agent_comms/commander_parity/rows.py | 6 +- tests/test_candidate_maintenance.py | 34 +++--- ...est_candidate_publication_notifications.py | 17 +-- 6 files changed, 146 insertions(+), 132 deletions(-) diff --git a/src/agent_comms/candidate_maintenance.py b/src/agent_comms/candidate_maintenance.py index c2186860b6..ddd4e8e424 100644 --- a/src/agent_comms/candidate_maintenance.py +++ b/src/agent_comms/candidate_maintenance.py @@ -1,16 +1,10 @@ -"""Best-effort indexing of successful appends after the outer locks release. - -Losing a hint or crashing the daemon leaves the optional index unavailable; -canonical bus and sealed SQL still drive original delivery. Byte positions -include observations as well as messages, without inventing message sequences. -""" +"""Index successful appends after outer locks release, within the owning root's lifetime.""" from __future__ import annotations import logging import sqlite3 import threading -from contextlib import ExitStack from pathlib import Path from .bus_route_counts import BusRouteCounts @@ -22,7 +16,7 @@ _LOG = logging.getLogger(__name__) _MAX_ACTIVE_ROOTS = 32 _guard = threading.Lock() -_pending: dict[Path, tuple[WireLog, int]] = {} +_pending: dict[Path, CandidateMaintenance] = {} class CandidateMaintenanceFailed(RuntimeError): # noqa: N818 - named worker failure @@ -33,72 +27,74 @@ def __init__(self, root: Path) -> None: super().__init__(f"Candidate maintenance failed for {root}") -def schedule_candidate_catchup(log: WireLog, through_bytes: int) -> None: - """Prepare writer indexes, then signal their existing maintenance worker.""" - if through_bytes <= 0: - raise ValueError("candidate notification needs an actual committed byte position") - BusRouteCounts(log.path).prepare() - root = log.path.parent - with _guard: - current = _pending.get(root) - if current is not None: - _pending[root] = (current[0], max(current[1], through_bytes)) - return - if len(_pending) >= _MAX_ACTIVE_ROOTS: - raise CandidateMaintenanceFailed(root) - _pending[root] = (log, through_bytes) - try: - threading.Thread( - target=_drain_candidate, - args=(root,), - name="agent-comms-candidate-maintenance", - daemon=True, - ).start() - except RuntimeError as error: - _pending.pop(root, None) - raise CandidateMaintenanceFailed(root) from error +class CandidateMaintenance(threading.Thread): + """One root's coalesced cursor and actual worker; retirement joins this owner.""" + def __init__(self, log: WireLog, through_bytes: int) -> None: + super().__init__(name="agent-comms-candidate-maintenance", daemon=True) + self.log = log + self.through_bytes = through_bytes -def _retire_candidate(root: Path) -> None: - with _guard: - _pending.pop(root, None) - - -def _drain_candidate(root: Path) -> None: - with ExitStack() as cleanup: - cleanup.callback(_retire_candidate, root) + def run(self) -> None: + root = self.log.path.parent try: - with _guard: - log, _bytes = _pending[root] - # Source ownership is checked off the producer path. Maintenance cannot - # create the protocol marker or authorize delivery. - with log.checked_read() as source: + with self.log.checked_read() as source: metadata = source.marker root_id = metadata.root_id - index = WakeCandidateIndex(log) - routes = BusRouteCounts(log.path) + index = WakeCandidateIndex(self.log) + routes = BusRouteCounts(self.log.path) while True: with _guard: - target = _pending[root][1] + target = self.through_bytes hint = index.notify_committed_append(root_id=root_id, through_bytes=target) - result = index.catch_up_committed_append( - hint, start=FirstBuild() - ) - with log.opened_wire_snapshot(opening=CurrentSnapshotOpening()) as snapshot: + result = index.catch_up_committed_append(hint, start=FirstBuild()) + with self.log.opened_wire_snapshot(opening=CurrentSnapshotOpening()) as snapshot: route_result = routes.sync( snapshot, lambda record: DeliveryMessage.from_wire(record, root_id=snapshot.metadata.root_id), ) if result.caught_up and route_result.complete: with _guard: - if _pending[root][1] <= result.checkpoint_bytes: - # Disarm cleanup before a successor can own this root. - cleanup.pop_all() + if self.through_bytes <= result.checkpoint_bytes: _pending.pop(root) return - except ( - OSError, ValueError, sqlite3.Error, CoordinationError, ProjectionUnavailableError, - ) as error: + except (OSError, ValueError, sqlite3.Error, CoordinationError, ProjectionUnavailableError) as error: failure = CandidateMaintenanceFailed(root) _LOG.exception("%s", failure) raise failure from error + finally: + with _guard: + if root in _pending and _pending[root] is self: + _pending.pop(root) + + +def schedule_candidate_catchup(log: WireLog, through_bytes: int) -> None: + """Prepare writer indexes, then coalesce into the root's actual worker.""" + if through_bytes <= 0: + raise ValueError("candidate notification needs an actual committed byte position") + BusRouteCounts(log.path).prepare() + root = log.path.parent + with _guard: + if root in _pending: + worker = _pending[root] + worker.through_bytes = max(worker.through_bytes, through_bytes) + return + if len(_pending) >= _MAX_ACTIVE_ROOTS: + raise CandidateMaintenanceFailed(root) + worker = CandidateMaintenance(log, through_bytes) + _pending[root] = worker + try: + worker.start() + except RuntimeError as error: + _pending.pop(root) + raise CandidateMaintenanceFailed(root) from error + + +def drain_candidate_catchup(root: Path) -> None: + """A stopped root cannot be renamed or removed while its worker still writes.""" + while True: + with _guard: + if root not in _pending: + return + worker = _pending[root] + worker.join() diff --git a/src/agent_comms/check_root.py b/src/agent_comms/check_root.py index 02de2fb15a..b261755d01 100644 --- a/src/agent_comms/check_root.py +++ b/src/agent_comms/check_root.py @@ -15,6 +15,7 @@ from .child_process import NoProcess from .cli_result import CliResult from .comms import Comms +from .candidate_maintenance import drain_candidate_catchup from .fresh_private_session import create_fresh_private_session from .goals import NoGoal from .launch_runtime import RestartEnvironment, ThreadLaunch @@ -74,6 +75,7 @@ def open(self) -> Iterator[Comms]: ): copied.owners.stop(thread.name) finally: + drain_candidate_catchup(root) RecoveryMonitorCapability.retire_check_root(root) def _populate(self, copied: Comms) -> None: diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index c35962539f..4309985644 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -10,13 +10,14 @@ import sys import time from abc import ABC, abstractmethod -from collections.abc import Callable, Iterator +from collections.abc import Iterator from dataclasses import dataclass, replace from pathlib import Path -from types import FrameType, FunctionType from typing import ClassVar from uuid import uuid4 +from pydantic import PrivateAttr + from ..change_report import FileLine from ..check_root import CheckRoot from ..commander.config import CommanderConfig @@ -25,15 +26,16 @@ from ..comms import Comms from ..field_codec import FieldCodec from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder -from ..tool_caller import ToolCaller +from ..tool_caller import ThreadCaller, ToolCaller from ..tool_result import ToolResult from ..tools import ( CommsChangeProblemTool, CommsFireTool, CommsOrderTool, - CommsProblemCloseTool, CommsTodoCreateTool, ProblemOrdered, ThreadToolRequest, TodoChanged, TodoList, + CommsProblemCloseTool, CommsTodoCreateTool, CommsTodoListTool, ProblemOrdered, + ThreadToolRequest, TodoChanged, TodoList, ToolRequest, ) from ..todos import ( ApplicationOrderRun, MergedProgress, Moving, NoCommitProgress, - OrderProgress, PendingProblemOrder, Todo, + PendingProblemOrder, Todo, ) from . import ParityComparison, UnverifiedParity @@ -53,9 +55,30 @@ def describe(self) -> str: return f"owner={self.owner}; calls={self.invocations}; checks={dict(self.checks)}; output={self.output!r}" +@dataclass(frozen=True) +class NativeInvocation: + request: ToolRequest[ThreadCaller] + result: ToolResult + + +class MeasuredCommander(Commander): + """Keep actual native returns at the existing command boundary, not frame locals.""" + + _invocations: list[NativeInvocation] = PrivateAttr(default_factory=list) + + @property + def invocations(self) -> tuple[NativeInvocation, ...]: + return tuple(self._invocations) + + def call(self, tool: ToolRequest[ThreadCaller]) -> ToolResult: + result = super().call(tool) + self._invocations.append(NativeInvocation(tool, result)) + return result + + @dataclass(frozen=True) class LifecycleRun: - commander: Commander + commander: MeasuredCommander worker: str decisions: Path document: Path @@ -73,7 +96,7 @@ def open(cls) -> Iterator[LifecycleRun]: directory = worker.storage_roots.scratch / "commander-parity" / uuid4().hex with CheckRoot(source, (config.commander, worker.name), directory).open() as copied: root = copied.root - commander = Commander( + commander = MeasuredCommander( commander=config.commander, root=root, cli=config.cli, state=root / "commander-state", board=root / "board.md", narrative=root / "narrative.md", repos=config.repos, @@ -139,33 +162,16 @@ def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], @classmethod def read(cls) -> ParityComparison: owners = (cls.native_owner, *cls.additional_owners) - codes = set() - for owner in owners: - function = owner.apply - if not isinstance(function, FunctionType): - raise TypeError("A native tool must declare its executable apply method") - codes.add(function.__code__) - native: list[ToolResult] = [] - - def observe(frame: FrameType, event: str, value: object) -> None: - if event == "return" and frame.f_code in codes: - request = frame.f_locals["self"] - if type(request) in owners and isinstance(value, ToolResult): - native.append(value) - with LifecycleRun.open() as run: - previous = sys.getprofile() output = io.StringIO() - sys.setprofile(observe) - try: - with contextlib.redirect_stdout(output): - rows = cls.exercise(run) - finally: - sys.setprofile(previous) + with contextlib.redirect_stdout(output): + rows = cls.exercise(run) + native = tuple(invocation.result for invocation in run.commander.invocations + if type(invocation.request) in owners) text = output.getvalue() return LifecycleObservation( ", ".join(f"{owner.__module__}.{owner.__qualname__}" for owner in owners), len(native), - cls.checks(run, rows, tuple(native), text), text) + cls.checks(run, rows, native, text), text) class CreateProblemCase(LifecycleCase): @@ -237,6 +243,16 @@ def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], ("current close printed", f"{rows[-1].id} closed" in output)) +class BuildBoundProofCase(InstalledEvidenceCase): + @classmethod + def read(cls) -> ParityComparison: + closing = super().read() + return UnverifiedParity( + "Missing native owner: a build-bound application-run record linking the " + "runtime and Core/Toad revisions to closing evidence. The actual close " + "owner currently accepts a bare artifact path. " + closing.describe()) + + class StrikeLimitCase(LifecycleCase): native_owner = CommsChangeProblemTool additional_owners = (CommsFireTool,) @@ -315,24 +331,17 @@ def describe(self) -> str: def verify_order_progress() -> ParityComparison: - context = Commander.read(CommanderConfig.path()) - observed: dict[str, str] = {} - merged: list[bool] = [] - - def observe(frame: FrameType, event: str, value: object) -> None: - if event == "return" and frame.f_code is OrderProgress.read.__func__.__code__: - todo = frame.f_locals["todo"] - if not isinstance(todo, Todo) or not isinstance(value, OrderProgress): - raise TypeError("The native progress reader returned an unexpected record") - observed[todo.id] = value.line - if isinstance(value, MergedProgress): - merged.append(not value.stalled(todo.order.checked().at.recorded_at("An order"), time.time())) - - previous = sys.getprofile() - sys.setprofile(observe) - try: - current = {key: value.line for key, value in context.progress().items()} - finally: - sys.setprofile(previous) + context = MeasuredCommander.read(CommanderConfig.path()) + current = {key: value.line for key, value in context.progress().items()} + results = tuple(invocation.result for invocation in context.invocations + if type(invocation.request) is CommsTodoListTool) + if len(results) != 1 or not isinstance(results[0], TodoList): + return UnverifiedParity("Current progress did not return one native TodoList measurement") + result = results[0] + observed = {key: value.line for key, value in result.progress.items()} + merged = tuple(not result.progress[todo.id].stalled( + todo.order.checked().at.recorded_at("An order"), time.time()) + for todo in result.todos if todo.id in result.progress + and isinstance(result.progress[todo.id], MergedProgress)) return ProgressComparison(current, observed, NoCommitProgress.seconds, Moving.seconds, - tuple(merged), OrderProgressMutationCase.read()) + merged, OrderProgressMutationCase.read()) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index aed40ce0be..0595f31cd0 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -13,7 +13,7 @@ from . import ParityComparison, ParityRow, UnverifiedParity from .load import LoadComparison from .measurements import RuleComparison, StorageComparison, measured_load -from .lifecycle import AssignOrderCase, CreateProblemCase, InstalledEvidenceCase, StrikeLimitCase, verify_order_progress +from .lifecycle import AssignOrderCase, BuildBoundProofCase, CreateProblemCase, InstalledEvidenceCase, StrikeLimitCase, verify_order_progress class CreateProblemParityRow(ParityRow): @@ -295,6 +295,10 @@ class BuildBoundProofParityRow(ParityRow): native = "Build run records runtime and Core/Toad revisions; close requires delivered commit" phases = (RecordPlanePhase,) + @classmethod + def verify(cls) -> ParityComparison: + return BuildBoundProofCase.read() + class DeletionTargetParityRow(ParityRow): number = 31 diff --git a/tests/test_candidate_maintenance.py b/tests/test_candidate_maintenance.py index 645154eaf8..2bde1e99f0 100644 --- a/tests/test_candidate_maintenance.py +++ b/tests/test_candidate_maintenance.py @@ -53,15 +53,15 @@ def test_ordinary_private_send_deferred_index_catches_up_without_wake_authority( _SCHEDULE, ) indexed = threading.Event() - original = maintenance._drain_candidate + original = maintenance.CandidateMaintenance.run - def observe(root): - original(root) + def observe(worker): + original(worker) # Candidate catch-up alone is not completion: the same worker # still owns route preparation and its final pending-root check. indexed.set() - monkeypatch.setattr(maintenance, "_drain_candidate", observe) + monkeypatch.setattr(maintenance.CandidateMaintenance, "run", observe) message = comms.messaging.send("sender", "beta", "selected original").message assert indexed.wait(timeout=5), "deferred checkpoint did not catch up" page = WakeCandidateIndex(comms.bus.log).page( @@ -111,14 +111,15 @@ def probe(): def test_failed_drain_is_named_and_retires_pending_state(monkeypatch, tmp_path, caplog) -> None: comms, _root_id = _wire(tmp_path) root = comms.bus.log.path.parent - maintenance._pending[root] = (comms.bus.log, 1) + worker = maintenance.CandidateMaintenance(comms.bus.log, 1) + maintenance._pending[root] = worker def fail(*args, **kwargs): raise ValueError("synthetic index failure") monkeypatch.setattr(WakeCandidateIndex, "catch_up_committed_append", fail) with pytest.raises(maintenance.CandidateMaintenanceFailed) as failed: - maintenance._drain_candidate(root) + worker.run() assert failed.value.root == root assert root not in maintenance._pending assert "Candidate maintenance failed for" in caplog.text @@ -152,8 +153,9 @@ def test_one_background_drain_catches_up_a_backlog_of_oversized_records( ).checkpoint_bytes == first_bytes through_bytes = index.log.path.stat().st_size hint = index.notify_committed_append(root_id=root_id, through_bytes=through_bytes) - maintenance._pending[index.log.path.parent] = (index.log, through_bytes) - maintenance._drain_candidate(comms.bus.log.path.parent) + worker = maintenance.CandidateMaintenance(index.log, through_bytes) + maintenance._pending[index.log.path.parent] = worker + worker.run() assert not maintenance._pending assert index.catch_up_committed_append(hint).caught_up page = index.page( @@ -172,7 +174,7 @@ def test_source_replacement_during_replay_does_not_kill_the_worker(monkeypatch, replay = WakeCandidateIndex._replay_prefix replaced, finished = threading.Event(), threading.Condition() failures = [] - drain = maintenance._drain_candidate + drain = maintenance.CandidateMaintenance.run def replace_once(cls, source, *args, **kwargs): batch = replay(source, *args, **kwargs) @@ -185,9 +187,9 @@ def replace_once(cls, source, *args, **kwargs): replaced.set() return batch - def observed(path): + def observed(worker): try: - drain(path) + drain(worker) except Exception as error: failures.append(error) finally: @@ -199,7 +201,7 @@ def retired(): return root not in maintenance._pending monkeypatch.setattr(WakeCandidateIndex, "_replay_prefix", classmethod(replace_once)) - monkeypatch.setattr(maintenance, "_drain_candidate", observed) + monkeypatch.setattr(maintenance.CandidateMaintenance, "run", observed) monkeypatch.setattr(maintenance, "schedule_candidate_catchup", _SCHEDULE) comms.messaging.send("sender", "beta", "first original") assert replaced.wait(5) @@ -215,11 +217,11 @@ def test_concurrent_ordinary_publication_keeps_maintenance_alive(monkeypatch, tm root = comms.bus.log.path.parent finished = threading.Condition() failures = [] - drain = maintenance._drain_candidate + drain = maintenance.CandidateMaintenance.run - def observed_drain(path): + def observed_drain(worker): try: - drain(path) + drain(worker) except Exception as error: failures.append(error) raise @@ -231,7 +233,7 @@ def retired(): with maintenance._guard: return root not in maintenance._pending - monkeypatch.setattr(maintenance, "_drain_candidate", observed_drain) + monkeypatch.setattr(maintenance.CandidateMaintenance, "run", observed_drain) monkeypatch.setattr(maintenance, "schedule_candidate_catchup", _SCHEDULE) messages = [comms.messaging.send("sender", "beta", str(n) + "x" * (128 * 1024)).message for n in range(128)] diff --git a/tests/test_candidate_publication_notifications.py b/tests/test_candidate_publication_notifications.py index 7c761dda2d..39d1a8a15a 100644 --- a/tests/test_candidate_publication_notifications.py +++ b/tests/test_candidate_publication_notifications.py @@ -65,11 +65,11 @@ def test_nested_release_scope_runs_only_after_outer_release(): def notifications(monkeypatch): def activate(comms): calls, finished = [], threading.Event() - drain = candidate_maintenance._drain_candidate + drain = candidate_maintenance.CandidateMaintenance.run - def complete(root): + def complete(worker): try: - drain(root) + drain(worker) finally: finished.set() @@ -94,7 +94,7 @@ def probe(): calls.append(through_bytes) _SCHEDULE(log, through_bytes) - monkeypatch.setattr(candidate_maintenance, "_drain_candidate", complete) + monkeypatch.setattr(candidate_maintenance.CandidateMaintenance, "run", complete) monkeypatch.setattr(candidate_maintenance, "schedule_candidate_catchup", notify) return calls, finished @@ -250,7 +250,8 @@ def test_retiring_worker_does_not_delete_successor_notification(tmp_path, monkey publish_initial(comms, "sender", "member000", "first") assert index.maintain(start=RebuildIndex()) root = index.log.path.parent - candidate_maintenance._pending[root] = (index.log, index.log.path.stat().st_size) + worker = candidate_maintenance.CandidateMaintenance(index.log, index.log.path.stat().st_size) + candidate_maintenance._pending[root] = worker original_guard = candidate_maintenance._guard successor = [] @@ -269,11 +270,11 @@ def __exit__(self, *_): monkeypatch.setattr(candidate_maintenance, "_guard", InterleavedLock()) monkeypatch.setattr(candidate_maintenance, "schedule_candidate_catchup", _SCHEDULE) monkeypatch.setattr(threading.Thread, "start", lambda _: None) - candidate_maintenance._drain_candidate(root) + worker.run() assert successor - assert candidate_maintenance._pending[root][1] == index.log.path.stat().st_size + assert candidate_maintenance._pending[root].through_bytes == index.log.path.stat().st_size # Complete the retained successor without launching another daemon. - candidate_maintenance._drain_candidate(root) + candidate_maintenance._pending[root].run() assert root not in candidate_maintenance._pending assert index._verified_checkpoint(root_id).last_seq == 2 finally: From fdaf3b1a09d6f0a75bb36cfbc489d5de8ec39713 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:33:28 -0400 Subject: [PATCH 04/43] Let parity declarations own their comparison callable --- src/agent_comms/commander_parity/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/commander_parity/__init__.py b/src/agent_comms/commander_parity/__init__.py index 22073f86a4..efe7d431bc 100644 --- a/src/agent_comms/commander_parity/__init__.py +++ b/src/agent_comms/commander_parity/__init__.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Iterable +from collections.abc import Callable, Iterable from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, ClassVar @@ -43,6 +43,7 @@ class ParityRow( current: ClassVar[str] native: ClassVar[str] phases: ClassVar[tuple[type[PlanPhase], ...]] + comparison: ClassVar[Callable[[], ParityComparison]] = UnverifiedParity @classmethod def compare(cls, todos: Iterable[Todo]) -> ParityComparison: @@ -51,7 +52,7 @@ def compare(cls, todos: Iterable[Todo]) -> ParityComparison: @classmethod def verify(cls) -> ParityComparison: """Run this declared workflow now; a run is not durable cutover authority.""" - return UnverifiedParity() + return cls.comparison() @classmethod def rows(cls) -> tuple[type[ParityRow], ...]: From 13e06473732dcfd9947b2a651948698f0b0a84d4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:34:34 -0400 Subject: [PATCH 05/43] Declare comparisons once and keep lifecycle observations typed at their owners --- src/agent_comms/candidate_maintenance.py | 14 +- src/agent_comms/check_root.py | 2 +- src/agent_comms/commander_parity/lifecycle.py | 352 ++++++++++++------ src/agent_comms/commander_parity/rows.py | 33 +- 4 files changed, 274 insertions(+), 127 deletions(-) diff --git a/src/agent_comms/candidate_maintenance.py b/src/agent_comms/candidate_maintenance.py index ddd4e8e424..9a0fb16953 100644 --- a/src/agent_comms/candidate_maintenance.py +++ b/src/agent_comms/candidate_maintenance.py @@ -10,8 +10,8 @@ from .bus_route_counts import BusRouteCounts from .coordination_errors import CoordinationError from .routing import DeliveryMessage -from .wire_log import CurrentSnapshotOpening, WireLog from .wake_candidate_index import FirstBuild, ProjectionUnavailableError, WakeCandidateIndex +from .wire_log import CurrentSnapshotOpening, WireLog _LOG = logging.getLogger(__name__) _MAX_ACTIVE_ROOTS = 32 @@ -51,14 +51,22 @@ def run(self) -> None: with self.log.opened_wire_snapshot(opening=CurrentSnapshotOpening()) as snapshot: route_result = routes.sync( snapshot, - lambda record: DeliveryMessage.from_wire(record, root_id=snapshot.metadata.root_id), + lambda record: DeliveryMessage.from_wire( + record, root_id=snapshot.metadata.root_id + ), ) if result.caught_up and route_result.complete: with _guard: if self.through_bytes <= result.checkpoint_bytes: _pending.pop(root) return - except (OSError, ValueError, sqlite3.Error, CoordinationError, ProjectionUnavailableError) as error: + except ( + OSError, + ValueError, + sqlite3.Error, + CoordinationError, + ProjectionUnavailableError, + ) as error: failure = CandidateMaintenanceFailed(root) _LOG.exception("%s", failure) raise failure from error diff --git a/src/agent_comms/check_root.py b/src/agent_comms/check_root.py index b261755d01..7ace5f24d1 100644 --- a/src/agent_comms/check_root.py +++ b/src/agent_comms/check_root.py @@ -12,10 +12,10 @@ from typing import ClassVar from .attempt_recovery import RecoveryMonitorCapability +from .candidate_maintenance import drain_candidate_catchup from .child_process import NoProcess from .cli_result import CliResult from .comms import Comms -from .candidate_maintenance import drain_candidate_catchup from .fresh_private_session import create_fresh_private_session from .goals import NoGoal from .launch_runtime import RestartEnvironment, ThreadLaunch diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 4309985644..e6309cd30a 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -26,16 +26,29 @@ from ..comms import Comms from ..field_codec import FieldCodec from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder +from ..registry_document import RosterVisibility +from ..todos import ( + ApplicationOrderRun, + MergedProgress, + Moving, + NoCommitProgress, + PendingProblemOrder, + Todo, +) from ..tool_caller import ThreadCaller, ToolCaller from ..tool_result import ToolResult from ..tools import ( - CommsChangeProblemTool, CommsFireTool, CommsOrderTool, - CommsProblemCloseTool, CommsTodoCreateTool, CommsTodoListTool, ProblemOrdered, - ThreadToolRequest, TodoChanged, TodoList, ToolRequest, -) -from ..todos import ( - ApplicationOrderRun, MergedProgress, Moving, NoCommitProgress, - PendingProblemOrder, Todo, + CommsChangeProblemTool, + CommsFireTool, + CommsOrderTool, + CommsProblemCloseTool, + CommsTodoCreateTool, + CommsTodoListTool, + ProblemOrdered, + ThreadToolRequest, + TodoChanged, + TodoList, + ToolRequest, ) from . import ParityComparison, UnverifiedParity @@ -52,7 +65,10 @@ def passed(self) -> bool: return bool(self.invocations and self.checks) and all(value for _, value in self.checks) def describe(self) -> str: - return f"owner={self.owner}; calls={self.invocations}; checks={dict(self.checks)}; output={self.output!r}" + return ( + f"owner={self.owner}; calls={self.invocations}; " + f"checks={dict(self.checks)}; output={self.output!r}" + ) @dataclass(frozen=True) @@ -71,7 +87,7 @@ def invocations(self) -> tuple[NativeInvocation, ...]: return tuple(self._invocations) def call(self, tool: ToolRequest[ThreadCaller]) -> ToolResult: - result = super().call(tool) + result: ToolResult = super().call(tool) self._invocations.append(NativeInvocation(tool, result)) return result @@ -89,41 +105,83 @@ class LifecycleRun: def open(cls) -> Iterator[LifecycleRun]: config = CommanderConfig.read(CommanderConfig.path()) source = Comms(config.root) - actor = ToolCaller.from_actor(os.environ.get("AGENT_COMMS_THREAD")).acting_name(config.commander) - worker = source.registry.require(actor) - if worker.name == config.commander: - raise ValueError("Lifecycle comparison requires an existing unit caller, not its commander") - directory = worker.storage_roots.scratch / "commander-parity" / uuid4().hex + actor = ToolCaller.from_actor(os.environ.get("AGENT_COMMS_THREAD")).acting_name( + config.commander + ) + caller = source.registry.require(actor) + candidates = tuple( + thread + for thread in source.registry.snapshot().visible_threads(RosterVisibility()) + if thread.parent == config.commander and thread.name != config.commander + ) + if not candidates: + raise ValueError("Lifecycle comparison requires an existing registered unit") + worker = ( + caller + if actor != config.commander + else sorted(candidates, key=lambda thread: thread.name)[0] + ) + directory = caller.storage_roots.scratch / "commander-parity" / uuid4().hex with CheckRoot(source, (config.commander, worker.name), directory).open() as copied: root = copied.root commander = MeasuredCommander( - commander=config.commander, root=root, cli=config.cli, - state=root / "commander-state", board=root / "board.md", - narrative=root / "narrative.md", repos=config.repos, - worktrees=root / "projects", scratch=root / "scratch", disk=root, + commander=config.commander, + root=root, + cli=config.cli, + state=root / "commander-state", + board=root / "board.md", + narrative=root / "narrative.md", + repos=config.repos, + worktrees=root / "projects", + scratch=root / "scratch", + disk=root, ) with contextlib.redirect_stdout(io.StringIO()): - RequestAddVerb(text=( - "Compare current order commands with their real native owners in this " - "disposable copied bus, preserving the source bus and its sessions.")).run(commander) + RequestAddVerb( + text=( + "Compare current order commands with their real native owners in this " + "disposable copied bus, preserving the source bus and its sessions." + ) + ).run(commander) decisions = root / "decisions.json" decisions.write_text(json.dumps([worker.name])) - command = shlex.join((sys.executable, "-c", - "import json;from pathlib import Path;" - f"print(len(json.loads(Path({str(decisions)!r}).read_text())))")) + command = shlex.join( + ( + sys.executable, + "-c", + "import json;from pathlib import Path;" + f"print(len(json.loads(Path({str(decisions)!r}).read_text())))", + ) + ) order = PendingProblemOrder( owner=FileLine(str(Path(__file__).resolve()), 1), - check=OrderCheck(str(Path(__file__).resolve().parents[3]), command, CountCheckResult(0)), - edits=("Remove the copied workflow decision and verify its native order completes.",), + check=OrderCheck( + str(Path(__file__).resolve().parents[3]), command, CountCheckResult(0) + ), + edits=( + "Remove the copied workflow decision and verify its native order completes.", + ), banned=("Do not change the source bus or its saved sessions.",), report="Report the actual copied goal and native order state.", run=ApplicationOrderRun( - "Read the copied native goal and confirm its recorded source location and assigned worker."), + "Read the copied native goal and confirm its recorded source location " + "and assigned worker." + ), ) document = root / "order.json" - document.write_text(json.dumps(FieldCodec.encode(OrderFile( - order, "The copied workflow decision prevents the native completion check from reaching zero.", - "This is stupid because an unfinished copied decision must not count as delivered work.")))) + document.write_text( + json.dumps( + FieldCodec.encode( + OrderFile( + order, + "The copied workflow decision prevents the native completion check " + "from reaching zero.", + "This is stupid because an unfinished copied decision must not count " + "as delivered work.", + ) + ) + ) + ) yield cls(commander, worker.name, decisions, document, root / "application.json") @property @@ -131,8 +189,9 @@ def due(self) -> str: return time.strftime("%H:%M", time.localtime(time.time() + 3600)) def create(self) -> Todo: - self.commander.problem(str(self.document), self.worker, self.due, - f"{Path(__file__).resolve()}:1") + self.commander.problem( + str(self.document), self.worker, self.due, f"{Path(__file__).resolve()}:1" + ) rows = tuple(self.commander.comms.goals.todos.for_commander(self.commander.incarnation)) if len(rows) != 1: raise ValueError(f"A fresh copied workflow must create one problem, got {len(rows)}") @@ -156,8 +215,9 @@ def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: ... @classmethod @abstractmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: ... + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: ... @classmethod def read(cls) -> ParityComparison: @@ -166,32 +226,44 @@ def read(cls) -> ParityComparison: output = io.StringIO() with contextlib.redirect_stdout(output): rows = cls.exercise(run) - native = tuple(invocation.result for invocation in run.commander.invocations - if type(invocation.request) in owners) + native = tuple( + invocation.result + for invocation in run.commander.invocations + if isinstance(invocation.request, owners) + ) text = output.getvalue() return LifecycleObservation( - ", ".join(f"{owner.__module__}.{owner.__qualname__}" for owner in owners), len(native), - cls.checks(run, rows, native, text), text) + ", ".join(f"{owner.__module__}.{owner.__qualname__}" for owner in owners), + len(native), + cls.checks(run, rows, native, text), + text, + ) class CreateProblemCase(LifecycleCase): - native_owner = CommsTodoCreateTool + native_owner: ClassVar[type[ThreadToolRequest]] = CommsTodoCreateTool @classmethod def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: return (run.create(),) @classmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: - if len(native) != 1 or not isinstance(native[0], ProblemOrdered): - return (("one native creation result", False),) - result, saved = native[0], rows[-1] + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: + if not native or not isinstance(native[-1], ProblemOrdered): + return (("native order result returned", False),) + result, saved = native[-1], rows[-1] goal = run.commander.comms.registry.require(run.worker).goal - return (("printed native identity", output.rstrip().endswith(cls.printed_identity(result))), - ("native source retained", saved.order.checked().owner == result.todo.order.checked().owner), - ("native assignment retained", saved.assignment == result.todo.assignment), - ("native goal published", goal.require().id == result.goal.id)) + return ( + ("printed native identity", output.rstrip().endswith(cls.printed_identity(result))), + ( + "native source retained", + saved.order.checked().owner == result.todo.order.checked().owner, + ), + ("native assignment retained", saved.assignment == result.todo.assignment), + ("native goal published", goal.require().id == result.goal.id), + ) @classmethod def printed_identity(cls, result: ProblemOrdered) -> str: @@ -204,17 +276,23 @@ class AssignOrderCase(CreateProblemCase): @classmethod def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: before = run.create() - run.commander.fire(run.worker, "The copied workflow is released before its next native assignment.") + run.commander.fire( + run.worker, "The copied workflow is released before its next native assignment." + ) document = replace(OrderFile.read(run.document.read_text()), problems=(before.id,)) run.commander.send(run.worker, run.due, document, clean=True) return before, run.commander.comms.goals.todos.get(before.id) @classmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: inherited = super().checks(run, rows, native, output) - return (*inherited, ("same problem reassigned", rows[0].id == rows[-1].id), - ("prior assignment retained", bool(rows[-1].released))) + return ( + *inherited, + ("same problem reassigned", rows[0].id == rows[-1].id), + ("prior assignment retained", bool(rows[-1].released)), + ) @classmethod def printed_identity(cls, result: ProblemOrdered) -> str: @@ -228,19 +306,25 @@ class InstalledEvidenceCase(LifecycleCase): def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: created = run.create() delivered = run.complete(created) - run.commander.close(created.id, - "Read the copied native goal and confirm its recorded source location and assigned worker.", - str(run.artifact)) + run.commander.close( + created.id, + "Read the copied native goal and confirm its recorded source location " + "and assigned worker.", + str(run.artifact), + ) return delivered, run.commander.comms.goals.todos.get(created.id) @classmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: - return (("delivery alone unaccepted", not rows[0].order.accepted), - ("installed evidence accepted", rows[-1].order.accepted), - ("closed in native store", not rows[-1].state.open), - ("actual artifact exists", run.artifact.is_file()), - ("current close printed", f"{rows[-1].id} closed" in output)) + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: + return ( + ("delivery alone unaccepted", not rows[0].order.accepted), + ("installed evidence accepted", rows[-1].order.accepted), + ("closed in native store", not rows[-1].state.open), + ("actual artifact exists", run.artifact.is_file()), + ("current close printed", f"{rows[-1].id} closed" in output), + ) class BuildBoundProofCase(InstalledEvidenceCase): @@ -250,7 +334,8 @@ def read(cls) -> ParityComparison: return UnverifiedParity( "Missing native owner: a build-bound application-run record linking the " "runtime and Core/Toad revisions to closing evidence. The actual close " - "owner currently accepts a bare artifact path. " + closing.describe()) + "owner currently accepts a bare artifact path. " + closing.describe() + ) class StrikeLimitCase(LifecycleCase): @@ -261,20 +346,37 @@ class StrikeLimitCase(LifecycleCase): def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: created = run.create() for number in range(ProblemGoalOrder.StrikeLimit): - run.commander.strike(run.worker, created.id, - f"The copied decision remains unfinished at its recorded review number {number + 1}.") - run.commander.fire(run.worker, - "The copied holder exhausted its declared live strikes on this problem.") + run.commander.strike( + run.worker, + created.id, + "The copied decision remains unfinished at its recorded review " + f"number {number + 1}.", + ) + run.commander.fire( + run.worker, "The copied holder exhausted its declared live strikes on this problem." + ) return created, run.commander.comms.goals.todos.get(created.id) @classmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: saved = rows[-1] - return (("declared limit recorded", saved.strike_count == ProblemGoalOrder.StrikeLimit), - ("one native fire", sum(isinstance(result, TodoList) for result in native) == 1), - ("assignment released", saved.holder is None), - ("current strike count printed", f"{ProblemGoalOrder.StrikeLimit} strike(s)" in output)) + return ( + ("declared limit recorded", saved.strike_count == ProblemGoalOrder.StrikeLimit), + ("one native fire", sum(isinstance(result, TodoList) for result in native) == 1), + ( + "assignment released", + saved.id + not in { + todo.id + for todo in run.commander.comms.goals.todos.held_for_commander( + run.commander.incarnation + ) + }, + ), + ("current strike count printed", f"{ProblemGoalOrder.StrikeLimit} strike(s)" in output), + ) class OrderProgressMutationCase(LifecycleCase): @@ -284,29 +386,49 @@ class OrderProgressMutationCase(LifecycleCase): def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: before = run.create() moved = time.strftime("%H:%M", time.localtime(time.time() + 7200)) - run.commander.due(before.id, moved, - "The copied workflow needs time to record its actual native application evidence.") + run.commander.due( + before.id, + moved, + "The copied workflow needs time to record its actual native application evidence.", + ) return before, run.commander.comms.goals.todos.get(before.id) @classmethod - def checks(cls, run: LifecycleRun, rows: tuple[Todo, ...], - native: tuple[ToolResult, ...], output: str) -> tuple[tuple[str, bool], ...]: + def checks( + cls, run: LifecycleRun, rows: tuple[Todo, ...], native: tuple[ToolResult, ...], output: str + ) -> tuple[tuple[str, bool], ...]: before, after = rows messages = run.commander.comms.bus.inbox(run.worker) unit = run.commander.comms.registry.require(run.worker) goal = unit.goal.require() - instructions = goal.order.instructions(goal.order.todos( - run.commander.comms.goals, unit.incarnation, goal.id)) - native_deadlines = tuple(result.todo.deadline for result in native if isinstance(result, TodoChanged)) - return (("native deadline retained", bool(native_deadlines) and native_deadlines[-1] == after.deadline), - ("deadline actually changed", before.deadline != after.deadline), - ("assignment unchanged", before.assignment == after.assignment), - ("unit got the actual update", bool(messages)), - ("unit messages hide clocks", all(hhmm(clock) not in message.body - for clock in (before.deadline, after.deadline) for message in messages)), - ("unit goal hides deadline", "Deadline:" not in instructions - and hhmm(after.deadline) not in instructions), - ("commander still sees deadline", f"due {hhmm(after.deadline)}" in output)) + instructions = goal.order.instructions( + goal.order.todos(run.commander.comms.goals, unit.incarnation, goal.id) + ) + native_deadlines = tuple( + result.todo.deadline for result in native if isinstance(result, TodoChanged) + ) + return ( + ( + "native deadline retained", + bool(native_deadlines) and native_deadlines[-1] == after.deadline, + ), + ("deadline actually changed", before.deadline != after.deadline), + ("assignment unchanged", before.assignment == after.assignment), + ("unit got the actual update", bool(messages)), + ( + "unit messages hide clocks", + all( + hhmm(clock) not in message.body + for clock in (before.deadline, after.deadline) + for message in messages + ), + ), + ( + "unit goal hides deadline", + "Deadline:" not in instructions and hhmm(after.deadline) not in instructions, + ), + ("commander still sees deadline", f"due {hhmm(after.deadline)}" in output), + ) @dataclass(frozen=True) @@ -320,28 +442,50 @@ class ProgressComparison(ParityComparison): @property def passed(self) -> bool: - return (self.workflow.passed and bool(self.native) and self.current == self.native - and self.first_seconds == 600 and self.later_seconds == 900 - and bool(self.merged_no_strikes) and all(self.merged_no_strikes)) + return all( + ( + self.workflow.passed, + bool(self.native), + self.current == self.native, + self.first_seconds == 600, + self.later_seconds == 900, + bool(self.merged_no_strikes), + all(self.merged_no_strikes), + ) + ) def describe(self) -> str: - return (f"{self.workflow.describe()}; live_progress={len(self.native)}; " - f"read_equal={self.current == self.native}; intervals={self.first_seconds}/{self.later_seconds}; " - f"live_merged_no_strikes={self.merged_no_strikes}") + return ( + f"{self.workflow.describe()}; live_progress={len(self.native)}; " + f"read_equal={self.current == self.native}; " + f"intervals={self.first_seconds}/{self.later_seconds}; " + f"live_merged_no_strikes={self.merged_no_strikes}" + ) def verify_order_progress() -> ParityComparison: context = MeasuredCommander.read(CommanderConfig.path()) current = {key: value.line for key, value in context.progress().items()} - results = tuple(invocation.result for invocation in context.invocations - if type(invocation.request) is CommsTodoListTool) + results = tuple( + invocation.result + for invocation in context.invocations + if isinstance(invocation.request, CommsTodoListTool) + ) if len(results) != 1 or not isinstance(results[0], TodoList): return UnverifiedParity("Current progress did not return one native TodoList measurement") result = results[0] observed = {key: value.line for key, value in result.progress.items()} - merged = tuple(not result.progress[todo.id].stalled( - todo.order.checked().at.recorded_at("An order"), time.time()) - for todo in result.todos if todo.id in result.progress - and isinstance(result.progress[todo.id], MergedProgress)) - return ProgressComparison(current, observed, NoCommitProgress.seconds, Moving.seconds, - merged, OrderProgressMutationCase.read()) + todos = {todo.id: todo for todo in result.todos} + merged = tuple( + not progress.stalled(todos[pid].order.checked().at.recorded_at("An order"), time.time()) + for pid, progress in result.progress.items() + if isinstance(progress, MergedProgress) + ) + return ProgressComparison( + current, + observed, + NoCommitProgress.seconds, + Moving.seconds, + merged, + OrderProgressMutationCase.read(), + ) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index 0595f31cd0..169a19bc74 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -11,9 +11,16 @@ StoryPhase, ) from . import ParityComparison, ParityRow, UnverifiedParity +from .lifecycle import ( + AssignOrderCase, + BuildBoundProofCase, + CreateProblemCase, + InstalledEvidenceCase, + StrikeLimitCase, + verify_order_progress, +) from .load import LoadComparison from .measurements import RuleComparison, StorageComparison, measured_load -from .lifecycle import AssignOrderCase, BuildBoundProofCase, CreateProblemCase, InstalledEvidenceCase, StrikeLimitCase, verify_order_progress class CreateProblemParityRow(ParityRow): @@ -23,9 +30,7 @@ class CreateProblemParityRow(ParityRow): native = "`CommsTodoCreateTool`" phases = (OneOrderOwnerPhase,) - @classmethod - def verify(cls) -> ParityComparison: - return CreateProblemCase.read() + comparison = CreateProblemCase.read class AssignOrderParityRow(ParityRow): @@ -35,9 +40,7 @@ class AssignOrderParityRow(ParityRow): native = "`CommsOrderTool`, `CommsTodoTool` assignment and release" phases = (OneOrderOwnerPhase,) - @classmethod - def verify(cls) -> ParityComparison: - return AssignOrderCase.read() + comparison = AssignOrderCase.read class DeliveredCommitParityRow(ParityRow): @@ -55,9 +58,7 @@ class InstalledEvidenceParityRow(ParityRow): native = "`CommsProblemCloseTool`" phases = (OneOrderOwnerPhase,) - @classmethod - def verify(cls) -> ParityComparison: - return InstalledEvidenceCase.read() + comparison = InstalledEvidenceCase.read class BoardParityRow(ParityRow): @@ -83,9 +84,7 @@ class StrikeLimitParityRow(ParityRow): native = "`Strike` rows, author-declared `StrikeLimit`, one `comms_fire`" phases = (RecordPlanePhase,) - @classmethod - def verify(cls) -> ParityComparison: - return StrikeLimitCase.read() + comparison = StrikeLimitCase.read class VerdictParityRow(ParityRow): @@ -295,9 +294,7 @@ class BuildBoundProofParityRow(ParityRow): native = "Build run records runtime and Core/Toad revisions; close requires delivered commit" phases = (RecordPlanePhase,) - @classmethod - def verify(cls) -> ParityComparison: - return BuildBoundProofCase.read() + comparison = BuildBoundProofCase.read class DeletionTargetParityRow(ParityRow): @@ -333,9 +330,7 @@ class OrderProgressParityRow(ParityRow): ) phases = (OneOrderOwnerPhase, ControlPlanePhase) - @classmethod - def verify(cls) -> ParityComparison: - return verify_order_progress() + comparison = staticmethod(verify_order_progress) class MultipleCommandersParityRow(ParityRow): From 65998b3e835405f019aa299b8a4d3fe6dbec5648 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:41:01 -0400 Subject: [PATCH 06/43] Select lifecycle units from the caller or canonical held orders, not presentation parents --- src/agent_comms/commander_parity/lifecycle.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index e6309cd30a..627627c9b9 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -26,7 +26,6 @@ from ..comms import Comms from ..field_codec import FieldCodec from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder -from ..registry_document import RosterVisibility from ..todos import ( ApplicationOrderRun, MergedProgress, @@ -109,18 +108,14 @@ def open(cls) -> Iterator[LifecycleRun]: config.commander ) caller = source.registry.require(actor) - candidates = tuple( - thread - for thread in source.registry.snapshot().visible_threads(RosterVisibility()) - if thread.parent == config.commander and thread.name != config.commander - ) - if not candidates: - raise ValueError("Lifecycle comparison requires an existing registered unit") - worker = ( - caller - if actor != config.commander - else sorted(candidates, key=lambda thread: thread.name)[0] - ) + if actor == config.commander: + authority = MeasuredCommander(config=config) + held = source.goals.todos.held_for_commander(authority.incarnation) + if not held: + raise ValueError("Lifecycle comparison requires an existing native order holder") + worker = source.registry.require(held[-1].held().owner) + else: + worker = caller directory = caller.storage_roots.scratch / "commander-parity" / uuid4().hex with CheckRoot(source, (config.commander, worker.name), directory).open() as copied: root = copied.root From 6cf69c4b07783e81a635c18541e5570b23bdf481 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:44:34 -0400 Subject: [PATCH 07/43] Route native closing through the existing commander invocation owner --- src/agent_comms/commander/order.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index d37b5b2ff5..e52f1e0ce2 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -480,8 +480,7 @@ def problem(self, order_file, unit, due, where, disk=False, def close(self, pid, works, artifact=''): """Core owns closing: the installed evidence, the artifact and the no-open-order rule live on the Todo.""" evidence = artifact or self.comms.goals.todos.get(pid).order.closing_artifact() - ThreadCaller(self.commander).invoke(CommsProblemCloseTool(problem=pid, - works=works, artifact=evidence), self.comms) + self.call(CommsProblemCloseTool(problem=pid, works=works, artifact=evidence)) self.write_board() print(f'{pid} closed') From b133760999b93c0026dfdf5cf180405433e17888 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:48:58 -0400 Subject: [PATCH 08/43] Let the existing lifecycle copier include real receiver threads for transfer comparisons --- src/agent_comms/commander_parity/lifecycle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 627627c9b9..394b6a56c3 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -101,7 +101,7 @@ class LifecycleRun: @classmethod @contextlib.contextmanager - def open(cls) -> Iterator[LifecycleRun]: + def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: config = CommanderConfig.read(CommanderConfig.path()) source = Comms(config.root) actor = ToolCaller.from_actor(os.environ.get("AGENT_COMMS_THREAD")).acting_name( @@ -117,7 +117,8 @@ def open(cls) -> Iterator[LifecycleRun]: else: worker = caller directory = caller.storage_roots.scratch / "commander-parity" / uuid4().hex - with CheckRoot(source, (config.commander, worker.name), directory).open() as copied: + threads = tuple(dict.fromkeys((config.commander, worker.name, *extra_threads))) + with CheckRoot(source, threads, directory).open() as copied: root = copied.root commander = MeasuredCommander( commander=config.commander, From 1335050b582faa6171d5193a1b3e02bf025cfa6f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:50:14 -0400 Subject: [PATCH 09/43] Read commander authority through its existing configured context instead of an invalid wrapper constructor --- src/agent_comms/commander_parity/lifecycle.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 394b6a56c3..4143fe0d3e 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -23,7 +23,6 @@ from ..commander.config import CommanderConfig from ..commander.order import Commander, OrderFile, hhmm from ..commander.request import RequestAddVerb -from ..comms import Comms from ..field_codec import FieldCodec from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder from ..todos import ( @@ -102,15 +101,14 @@ class LifecycleRun: @classmethod @contextlib.contextmanager def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: - config = CommanderConfig.read(CommanderConfig.path()) - source = Comms(config.root) + config = MeasuredCommander.read(CommanderConfig.path()) + source = config.comms actor = ToolCaller.from_actor(os.environ.get("AGENT_COMMS_THREAD")).acting_name( config.commander ) caller = source.registry.require(actor) if actor == config.commander: - authority = MeasuredCommander(config=config) - held = source.goals.todos.held_for_commander(authority.incarnation) + held = source.goals.todos.held_for_commander(config.incarnation) if not held: raise ValueError("Lifecycle comparison requires an existing native order holder") worker = source.registry.require(held[-1].held().owner) From 46d848124d4a9056bde3a93619b8e15c70e1954d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:53:16 -0400 Subject: [PATCH 10/43] Accept read and write request capabilities in lifecycle comparison declarations --- src/agent_comms/commander_parity/lifecycle.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 4143fe0d3e..c47216d63e 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -43,7 +43,6 @@ CommsTodoCreateTool, CommsTodoListTool, ProblemOrdered, - ThreadToolRequest, TodoChanged, TodoList, ToolRequest, @@ -200,8 +199,8 @@ def complete(self, todo: Todo) -> Todo: class LifecycleCase(ABC): - native_owner: ClassVar[type[ThreadToolRequest]] - additional_owners: ClassVar[tuple[type[ThreadToolRequest], ...]] = () + native_owner: ClassVar[type[ToolRequest[ThreadCaller]]] + additional_owners: ClassVar[tuple[type[ToolRequest[ThreadCaller]], ...]] = () @classmethod @abstractmethod @@ -235,7 +234,7 @@ def read(cls) -> ParityComparison: class CreateProblemCase(LifecycleCase): - native_owner: ClassVar[type[ThreadToolRequest]] = CommsTodoCreateTool + native_owner: ClassVar[type[ToolRequest[ThreadCaller]]] = CommsTodoCreateTool @classmethod def exercise(cls, run: LifecycleRun) -> tuple[Todo, ...]: From b365563ad5080ea44dcba8813e1b677b1b764a16 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:57:47 -0400 Subject: [PATCH 11/43] Compare real current and native refusals to hand delivered checked work to another unit --- src/agent_comms/commander_parity/handoff.py | 144 ++++++++++++++++++ src/agent_comms/commander_parity/lifecycle.py | 12 ++ src/agent_comms/commander_parity/rows.py | 3 + 3 files changed, 159 insertions(+) create mode 100644 src/agent_comms/commander_parity/handoff.py diff --git a/src/agent_comms/commander_parity/handoff.py b/src/agent_comms/commander_parity/handoff.py new file mode 100644 index 0000000000..70b951ec78 --- /dev/null +++ b/src/agent_comms/commander_parity/handoff.py @@ -0,0 +1,144 @@ +"""Actual current/native refusals to transfer delivered checked work.""" + +from __future__ import annotations + +import contextlib +import io +import json +import subprocess +from dataclasses import dataclass, replace +from pathlib import Path +from uuid import uuid4 + +from ..change_report import FileLine +from ..commander.config import CommanderConfig +from ..commander.order import OrderFile, deadline +from ..field_codec import FieldCodec +from ..goal_actions import OrderGoalAction +from ..tools import CommsOrderTool +from . import ParityComparison, UnverifiedParity +from .lifecycle import LifecycleRun, MeasuredCommander + + +@dataclass(frozen=True) +class HandoffComparison(ParityComparison): + cases: tuple[tuple[str, int, int, tuple[str, ...], tuple[str, ...], bool], ...] + + @property + def passed(self) -> bool: + return len(self.cases) == 2 and all( + all((expected != 0, actual != 0, bool(reference), reference == refusal, unchanged)) + for _, expected, actual, reference, refusal, unchanged in self.cases + ) + + def describe(self) -> str: + return f"actual finished-order transfer refusals (current/native): {self.cases!r}" + + +class FinishedHandoffCase: + @staticmethod + def state(run: LifecycleRun, pid: str, receiver: str) -> str: + return json.dumps( + FieldCodec.encode( + ( + run.commander.comms.goals.todos.get(pid), + run.commander.comms.registry.require(run.worker).goal, + run.commander.comms.registry.require(receiver).goal, + ) + ), + sort_keys=True, + ) + + @staticmethod + def current( + run: LifecycleRun, receiver: str, document: OrderFile, pid: str + ) -> subprocess.CompletedProcess[str]: + return run.invoke( + "import sys; from agent_comms.commander.order import Commander, OrderFile; " + "Commander.model_validate_json(sys.argv[1]).send(sys.argv[2], sys.argv[3], " + "OrderFile.read(sys.argv[4]), reassign=sys.argv[5], traced=sys.argv[6])", + run.commander.model_dump_json(), + receiver, + run.due, + json.dumps(FieldCodec.encode(document)), + "Compare the actual refusal to hand completed checked work to another real unit.", + f"{Path(__file__).resolve()}:1", + ) + + @staticmethod + def native( + run: LifecycleRun, receiver: str, document: OrderFile, pid: str + ) -> subprocess.CompletedProcess[str]: + context = run.commander + todo = context.comms.goals.todos.get(pid) + tool = CommsOrderTool( + owner=receiver, + order=OrderGoalAction( + problem=pid, + expected_revision=todo.revision, + assigner=context.incarnation, + order=document.order, + deadline=deadline(run.due), + commander=context.commander, + request=document.order.request( + context.comms.registry.require(context.commander), (pid,) + ), + traced=FileLine.from_wire(f"{Path(__file__).resolve()}:1"), + ), + ) + return run.invoke( + "from pathlib import Path; import sys; from agent_comms.comms import Comms; " + "from agent_comms.field_codec import FieldCodec; " + "from agent_comms.tool_caller import ThreadCaller; " + "from agent_comms.tools import CommsOrderTool; " + "ThreadCaller(sys.argv[2]).invoke(FieldCodec.decode(CommsOrderTool, " + "FieldCodec.parse(sys.argv[3])), Comms(Path(sys.argv[1])))", + str(context.root), + context.commander, + json.dumps(FieldCodec.encode(tool)), + ) + + @classmethod + def read(cls) -> ParityComparison: + authority = MeasuredCommander.read(CommanderConfig.path()) + owners = tuple( + dict.fromkeys( + todo.held().owner + for todo in authority.comms.goals.todos.held_for_commander(authority.incarnation) + ) + )[-2:] + if len(owners) < 2: + return UnverifiedParity("Finished-work transfer needs two real registered unit holders") + cases = [] + for label, execute in (("current", cls.current), ("native", cls.native)): + with LifecycleRun.open(extra_threads=owners) as run: + with contextlib.redirect_stdout(io.StringIO()): + receiver = next(owner for owner in owners if owner != run.worker) + delivered = run.complete(run.create()) + before = cls.state(run, delivered.id, receiver) + document = OrderFile.read(run.document.read_text()) + document = replace( + document, + problems=(delivered.id,), + order=replace(document.order, id=uuid4().hex), + ) + run.decisions.write_text(json.dumps([receiver])) + reference = run.invoke( + "from pathlib import Path; import sys; from agent_comms.comms import Comms; " + "todo=Comms(Path(sys.argv[1])).goals.todos.get(sys.argv[2]); " + "todo.order.require_transfer(todo)", + str(run.commander.root), + delivered.id, + ) + attempt = execute(run, receiver, document, delivered.id) + cases.append( + ( + label, + reference.returncode, + attempt.returncode, + tuple(reference.stderr.splitlines()[-1:]), + tuple(attempt.stderr.splitlines()[-1:]), + before == cls.state(run, delivered.id, receiver), + ) + ) + return HandoffComparison(tuple(cases)) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index c47216d63e..4d20dda45a 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -7,6 +7,7 @@ import json import os import shlex +import subprocess import sys import time from abc import ABC, abstractmethod @@ -177,6 +178,17 @@ def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: ) yield cls(commander, worker.name, decisions, document, root / "application.json") + @staticmethod + def invoke(program: str, *arguments: str) -> subprocess.CompletedProcess[str]: + source = str(Path(__file__).resolve().parents[2]) + return subprocess.run( + (sys.executable, "-c", program, *arguments), + capture_output=True, + text=True, + timeout=120, + env={**os.environ, "PYTHONPATH": source}, + ) + @property def due(self) -> str: return time.strftime("%H:%M", time.localtime(time.time() + 3600)) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index 169a19bc74..97ab0b9321 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -11,6 +11,7 @@ StoryPhase, ) from . import ParityComparison, ParityRow, UnverifiedParity +from .handoff import FinishedHandoffCase from .lifecycle import ( AssignOrderCase, BuildBoundProofCase, @@ -286,6 +287,8 @@ class FinishedWorkTransferParityRow(ParityRow): native = "Transfer refuses delivered checked work; that owner merges; check is reused" phases = (RecordPlanePhase,) + comparison = FinishedHandoffCase.read + class BuildBoundProofParityRow(ParityRow): number = 30 From 165d7a969339ca892111420f01dbf539ab2e2594 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:58:30 -0400 Subject: [PATCH 12/43] Admit both read and thread-only tools in lifecycle observations --- src/agent_comms/commander_parity/lifecycle.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 4d20dda45a..0e633e1bf0 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -69,9 +69,12 @@ def describe(self) -> str: ) +type LifecycleRequest = ToolRequest[ToolCaller] | ToolRequest[ThreadCaller] + + @dataclass(frozen=True) class NativeInvocation: - request: ToolRequest[ThreadCaller] + request: LifecycleRequest result: ToolResult @@ -84,7 +87,7 @@ class MeasuredCommander(Commander): def invocations(self) -> tuple[NativeInvocation, ...]: return tuple(self._invocations) - def call(self, tool: ToolRequest[ThreadCaller]) -> ToolResult: + def call(self, tool: LifecycleRequest) -> ToolResult: result: ToolResult = super().call(tool) self._invocations.append(NativeInvocation(tool, result)) return result @@ -211,8 +214,8 @@ def complete(self, todo: Todo) -> Todo: class LifecycleCase(ABC): - native_owner: ClassVar[type[ToolRequest[ThreadCaller]]] - additional_owners: ClassVar[tuple[type[ToolRequest[ThreadCaller]], ...]] = () + native_owner: ClassVar[type[LifecycleRequest]] + additional_owners: ClassVar[tuple[type[LifecycleRequest], ...]] = () @classmethod @abstractmethod From 5c74c4770255704449535006a6f102a60b7bd1ab Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:02:41 -0400 Subject: [PATCH 13/43] Let BoundedRun own native comparison process outcomes and retirement for all argv forms --- src/agent_comms/commander_parity/handoff.py | 32 +++++++++++-------- src/agent_comms/commander_parity/lifecycle.py | 21 +++++++----- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/agent_comms/commander_parity/handoff.py b/src/agent_comms/commander_parity/handoff.py index 70b951ec78..2507f40859 100644 --- a/src/agent_comms/commander_parity/handoff.py +++ b/src/agent_comms/commander_parity/handoff.py @@ -5,12 +5,12 @@ import contextlib import io import json -import subprocess from dataclasses import dataclass, replace from pathlib import Path from uuid import uuid4 from ..change_report import FileLine +from ..child_process import ChildOutcome, ChildResult, ExitedOutcome from ..commander.config import CommanderConfig from ..commander.order import OrderFile, deadline from ..field_codec import FieldCodec @@ -22,12 +22,22 @@ @dataclass(frozen=True) class HandoffComparison(ParityComparison): - cases: tuple[tuple[str, int, int, tuple[str, ...], tuple[str, ...], bool], ...] + cases: tuple[ + tuple[str, ChildOutcome, ChildOutcome, tuple[str, ...], tuple[str, ...], bool], ... + ] @property def passed(self) -> bool: return len(self.cases) == 2 and all( - all((expected != 0, actual != 0, bool(reference), reference == refusal, unchanged)) + all( + ( + expected == ExitedOutcome(1), + actual == ExitedOutcome(1), + bool(reference), + reference == refusal, + unchanged, + ) + ) for _, expected, actual, reference, refusal, unchanged in self.cases ) @@ -50,9 +60,7 @@ def state(run: LifecycleRun, pid: str, receiver: str) -> str: ) @staticmethod - def current( - run: LifecycleRun, receiver: str, document: OrderFile, pid: str - ) -> subprocess.CompletedProcess[str]: + def current(run: LifecycleRun, receiver: str, document: OrderFile, pid: str) -> ChildResult: return run.invoke( "import sys; from agent_comms.commander.order import Commander, OrderFile; " "Commander.model_validate_json(sys.argv[1]).send(sys.argv[2], sys.argv[3], " @@ -66,9 +74,7 @@ def current( ) @staticmethod - def native( - run: LifecycleRun, receiver: str, document: OrderFile, pid: str - ) -> subprocess.CompletedProcess[str]: + def native(run: LifecycleRun, receiver: str, document: OrderFile, pid: str) -> ChildResult: context = run.commander todo = context.comms.goals.todos.get(pid) tool = CommsOrderTool( @@ -134,10 +140,10 @@ def read(cls) -> ParityComparison: cases.append( ( label, - reference.returncode, - attempt.returncode, - tuple(reference.stderr.splitlines()[-1:]), - tuple(attempt.stderr.splitlines()[-1:]), + reference.outcome, + attempt.outcome, + tuple(reference.stderr.decode(errors="replace").splitlines()[-1:]), + tuple(attempt.stderr.decode(errors="replace").splitlines()[-1:]), before == cls.state(run, delivered.id, receiver), ) ) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 0e633e1bf0..2331074ec3 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -2,12 +2,12 @@ from __future__ import annotations +import asyncio import contextlib import io import json import os import shlex -import subprocess import sys import time from abc import ABC, abstractmethod @@ -21,6 +21,7 @@ from ..change_report import FileLine from ..check_root import CheckRoot +from ..child_process import BoundedRun, ChildResult from ..commander.config import CommanderConfig from ..commander.order import Commander, OrderFile, hhmm from ..commander.request import RequestAddVerb @@ -181,15 +182,19 @@ def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: ) yield cls(commander, worker.name, decisions, document, root / "application.json") + @classmethod + def invoke(cls, program: str, *arguments: str) -> ChildResult: + return cls.invoke_command((sys.executable, "-c", program, *arguments)) + @staticmethod - def invoke(program: str, *arguments: str) -> subprocess.CompletedProcess[str]: + def invoke_command(command: tuple[str, ...]) -> ChildResult: source = str(Path(__file__).resolve().parents[2]) - return subprocess.run( - (sys.executable, "-c", program, *arguments), - capture_output=True, - text=True, - timeout=120, - env={**os.environ, "PYTHONPATH": source}, + return asyncio.run( + BoundedRun.run( + command, + timeout=120, + env={**os.environ, "PYTHONPATH": source}, + ) ) @property From 9f3c32e879d68d042edeb943eabe43b439814de0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:04:52 -0400 Subject: [PATCH 14/43] Preserve caller-owned cwd and sanitized environments in the bounded process owner --- src/agent_comms/commander_parity/lifecycle.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 2331074ec3..60117649bd 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -182,20 +182,24 @@ def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: ) yield cls(commander, worker.name, decisions, document, root / "application.json") + @staticmethod + def python_environment() -> dict[str, str]: + return {**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parents[2])} + @classmethod def invoke(cls, program: str, *arguments: str) -> ChildResult: - return cls.invoke_command((sys.executable, "-c", program, *arguments)) + return cls.invoke_command( + (sys.executable, "-c", program, *arguments), env=cls.python_environment() + ) @staticmethod - def invoke_command(command: tuple[str, ...]) -> ChildResult: - source = str(Path(__file__).resolve().parents[2]) - return asyncio.run( - BoundedRun.run( - command, - timeout=120, - env={**os.environ, "PYTHONPATH": source}, - ) - ) + def invoke_command( + command: tuple[str, ...], + *, + cwd: str | Path | None = None, + env: dict[str, str] | None = None, + ) -> ChildResult: + return asyncio.run(BoundedRun.run(command, timeout=120, cwd=cwd, env=env)) @property def due(self) -> str: From cb7ecb0b2c96b9ecaffb3dc58bbdb40eeffbe37d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:07:12 -0400 Subject: [PATCH 15/43] Confirm real child environment and project preservation through the shared process owner --- src/agent_comms/commander_parity/handoff.py | 60 +++++++++++---------- tests/test_commander_parity_lifecycle.py | 30 +++++++++++ 2 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 tests/test_commander_parity_lifecycle.py diff --git a/src/agent_comms/commander_parity/handoff.py b/src/agent_comms/commander_parity/handoff.py index 2507f40859..5f6b6ef720 100644 --- a/src/agent_comms/commander_parity/handoff.py +++ b/src/agent_comms/commander_parity/handoff.py @@ -117,34 +117,36 @@ def read(cls) -> ParityComparison: return UnverifiedParity("Finished-work transfer needs two real registered unit holders") cases = [] for label, execute in (("current", cls.current), ("native", cls.native)): - with LifecycleRun.open(extra_threads=owners) as run: - with contextlib.redirect_stdout(io.StringIO()): - receiver = next(owner for owner in owners if owner != run.worker) - delivered = run.complete(run.create()) - before = cls.state(run, delivered.id, receiver) - document = OrderFile.read(run.document.read_text()) - document = replace( - document, - problems=(delivered.id,), - order=replace(document.order, id=uuid4().hex), - ) - run.decisions.write_text(json.dumps([receiver])) - reference = run.invoke( - "from pathlib import Path; import sys; from agent_comms.comms import Comms; " - "todo=Comms(Path(sys.argv[1])).goals.todos.get(sys.argv[2]); " - "todo.order.require_transfer(todo)", - str(run.commander.root), - delivered.id, - ) - attempt = execute(run, receiver, document, delivered.id) - cases.append( - ( - label, - reference.outcome, - attempt.outcome, - tuple(reference.stderr.decode(errors="replace").splitlines()[-1:]), - tuple(attempt.stderr.decode(errors="replace").splitlines()[-1:]), - before == cls.state(run, delivered.id, receiver), - ) + with ( + LifecycleRun.open(extra_threads=owners) as run, + contextlib.redirect_stdout(io.StringIO()), + ): + receiver = next(owner for owner in owners if owner != run.worker) + delivered = run.complete(run.create()) + before = cls.state(run, delivered.id, receiver) + document = OrderFile.read(run.document.read_text()) + document = replace( + document, + problems=(delivered.id,), + order=replace(document.order, id=uuid4().hex), + ) + run.decisions.write_text(json.dumps([receiver])) + reference = run.invoke( + "from pathlib import Path; import sys; from agent_comms.comms import Comms; " + "todo=Comms(Path(sys.argv[1])).goals.todos.get(sys.argv[2]); " + "todo.order.require_transfer(todo)", + str(run.commander.root), + delivered.id, + ) + attempt = execute(run, receiver, document, delivered.id) + cases.append( + ( + label, + reference.outcome, + attempt.outcome, + tuple(reference.stderr.decode(errors="replace").splitlines()[-1:]), + tuple(attempt.stderr.decode(errors="replace").splitlines()[-1:]), + before == cls.state(run, delivered.id, receiver), ) + ) return HandoffComparison(tuple(cases)) diff --git a/tests/test_commander_parity_lifecycle.py b/tests/test_commander_parity_lifecycle.py new file mode 100644 index 0000000000..0f2a827c05 --- /dev/null +++ b/tests/test_commander_parity_lifecycle.py @@ -0,0 +1,30 @@ +"""The common comparison process owner preserves its callers' process contract.""" + +import json +import sys +from pathlib import Path + +from agent_comms.child_process import ExitedOutcome +from agent_comms.commander_parity import lifecycle +from agent_comms.commander_parity.lifecycle import LifecycleRun + + +def test_command_preserves_explicit_project_and_sanitized_environment(tmp_path: Path): + result = LifecycleRun.invoke_command( + ( + sys.executable, + "-c", + "import json,os; print(json.dumps([os.getcwd(), " + "os.environ.get('PARITY_PROCESS'), 'NODE_OPTIONS' in os.environ]))", + ), + cwd=tmp_path, + env={"PARITY_PROCESS": "actual child"}, + ) + assert result.outcome == ExitedOutcome(0) + assert json.loads(result.stdout) == [str(tmp_path), "actual child", False] + + +def test_python_adapter_uses_the_candidate_source_environment(): + result = LifecycleRun.invoke("import os; print(os.environ['PYTHONPATH'])") + assert result.outcome == ExitedOutcome(0) + assert result.stdout.decode().strip() == str(Path(lifecycle.__file__).resolve().parents[2]) From 78bab1619de917cac98b54b5df1f8f8dcf2c9805 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:36:55 -0400 Subject: [PATCH 16/43] Place checked-commit worktrees under the configured checkout owner --- src/agent_comms/goals.py | 5 +++-- src/agent_comms/todos.py | 10 +++++----- tests/test_order_progress.py | 4 ++-- tests/test_standing_rules.py | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index 5cd5f236cf..be2daa6035 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -331,12 +331,13 @@ def recorded(self, sha: str, repository: str) -> tuple[ChildResult, ...]: return tuple(checked.result for checked in CheckedCommit.stored(repository, sha) if checked.command == self.command)[:1] - def at(self, revision: str, repository: str) -> ChildResult: + def at(self, revision: str, repository: str, checkout_base: Path) -> ChildResult: """Read the result on ``revision``, or explicitly run the check in a detached checkout.""" sha = CheckedCommit.git(repository, "rev-parse", f"{revision}^{{commit}}").strip() for recorded in self.recorded(sha, repository): return recorded - with tempfile.TemporaryDirectory(prefix="order-check-") as scratch: + checkout_base.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="order-check-", dir=checkout_base) as scratch: tree = str(Path(scratch) / "tree") CheckedCommit.git(repository, "worktree", "add", "--detach", tree, sha) try: diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index e1366a8233..1c8f06da40 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -98,14 +98,14 @@ async def verified(self, todo: Todo, registry: Registration) -> OrderDelivery: check = todo.order.check for each in (check, *todo.order.deletes.checks): await self.require_source(each) - each.result.require(await self.checked(each)) + each.result.require(await self.checked(each, registry.storage_rule.checkout_base)) return replace(self, at=await self.delivery_time(check)) @abstractmethod async def require_source(self, check): ... @abstractmethod - async def checked(self, check: OrderCheck) -> ChildResult: ... + async def checked(self, check: OrderCheck, checkout_base: Path) -> ChildResult: ... @abstractmethod async def delivery_time(self, check): ... @@ -140,8 +140,8 @@ def work(self) -> str: async def require_source(self, check: OrderCheck) -> None: raise ValueError("A commit delivery names the unit's delivered commit and the checked one.") - async def checked(self, check: OrderCheck) -> ChildResult: - return await asyncio.to_thread(check.at, self.commit, check.directory) + async def checked(self, check: OrderCheck, checkout_base: Path) -> ChildResult: + return await asyncio.to_thread(check.at, self.commit, check.directory, checkout_base) async def delivery_time(self, check: OrderCheck) -> EventTime: return EventTime.capture(float( @@ -281,7 +281,7 @@ def line(self) -> str: async def require_source(self, check): pass - async def checked(self, check: OrderCheck) -> ChildResult: + async def checked(self, check: OrderCheck, checkout_base: Path) -> ChildResult: return await check.run() async def delivery_time(self, check): diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index da1e20fce6..25a51e2ab8 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -115,7 +115,7 @@ def test_uncounted_check_keeps_other_overdue_orders_visible(commander, commander replace(todo, order=broken).upsert(db) create(commander, 'P2', deadline=1) commit(unit) - broken.check.at(git(unit, 'rev-parse', 'HEAD'), str(unit)) + broken.check.at(git(unit, 'rev-parse', 'HEAD'), str(unit), unit.parent) progress = read((comms.goals.todos.get('P1'), unit)) assert progress.line.endswith("; check prints no integer: printf 'no count here'") @@ -213,7 +213,7 @@ def test_progress_reads_only_explicit_check_receipts(checkout, tmp_path): source = ProgressSource.of(todo, StorageRule(checkout_base=unit.parent), Listed()) assert not source.readings().readings assert not marker.exists() - check.at(git(unit, 'rev-parse', 'HEAD').strip(), str(unit)) + check.at(git(unit, 'rev-parse', 'HEAD').strip(), str(unit), unit.parent) assert marker.read_text() == 'x' assert len(source.readings().readings) == 1 assert source.readings().readings[0].distance == CountPrinted(todo.order.checked().untouched(())) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 14858627b5..71b080c8b0 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -206,17 +206,17 @@ def commit(left): check = f"echo run >> {runs}; cat left" todo = create(commander, "P1", command=check, expected=0, deadline=time.time() + 3600) ordered_check = todo.order.checked().check - ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit), unit.parent) def progress(): return OrderProgress.read(comms.goals.todos.get("P1"), storage, PullRequests()) commit(2) - ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit), unit.parent) assert "; distance 2, projected done " in progress().line assert isinstance(progress().distance_readings().trend, MovingTrend) commit(2) - ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit), unit.parent) assert progress().line.endswith("; distance 2, not falling") assert runs.read_text().count("run") == 3 # once per commit: base, then each branch commit From 122955a06f75f503949c8ca776f7cc09ced3a841 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:37:16 -0400 Subject: [PATCH 17/43] Exercise current and native delivered-commit outcomes on the copied bus --- src/agent_comms/commander_parity/delivery.py | 143 +++++++++++++++++++ src/agent_comms/commander_parity/rows.py | 2 + 2 files changed, 145 insertions(+) create mode 100644 src/agent_comms/commander_parity/delivery.py diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py new file mode 100644 index 0000000000..b5c2a5ae49 --- /dev/null +++ b/src/agent_comms/commander_parity/delivery.py @@ -0,0 +1,143 @@ +"""Compare commit delivery through the current command and the native tool.""" + +from __future__ import annotations + +import contextlib +import io +import json +import subprocess +from dataclasses import dataclass, replace +from pathlib import Path + +from ..commander.order import OrderFile +from ..field_codec import FieldCodec +from ..progress_claims import MergedClaim +from ..todos import CommitDelivery, FileDeliveryEvidence, Todo +from ..tools import CommsOrderDoneTool +from . import ParityComparison +from .lifecycle import LifecycleRun + + +@dataclass(frozen=True) +class DeliveryOutcome: + pending: bool + order_line: str + refusal: str + + @classmethod + def saved(cls, todo: Todo, refusal: str) -> DeliveryOutcome: + return cls(todo.pending, todo.order.line, refusal) + + +@dataclass(frozen=True) +class DeliveryComparison(ParityComparison): + cases: tuple[tuple[str, str, DeliveryOutcome, DeliveryOutcome], ...] + + @property + def passed(self) -> bool: + return len(self.cases) == 3 and not self.cases[0][2].pending and all( + current.pending == native.pending + and current.order_line == native.order_line + and (not current.pending or (commit in current.refusal and commit in native.refusal + and "not in" in current.refusal and "not in" in native.refusal)) + for _, commit, current, native in self.cases) + + def describe(self) -> str: + return repr(self.cases) + + +@dataclass(frozen=True) +class DeliveryRepository: + path: Path + + def git(self, *arguments: str) -> str: + return subprocess.check_output( + ("git", "-C", str(self.path), "-c", "user.name=parity", + "-c", "user.email=parity@invalid", *arguments), + text=True, stderr=subprocess.PIPE).strip() + + @classmethod + def create(cls, root: Path) -> DeliveryRepository: + path = root / "delivery-repository" + path.mkdir() + repo = cls(path) + remote = root / "delivery-origin.git" + subprocess.run(("git", "init", "--bare", "-q", str(remote)), check=True) + repo.git("init", "-q", "-b", "main") + url = "https://github.com/parity-local/delivery.git" + repo.git("config", f"url.{remote}.insteadOf", url) + repo.git("remote", "add", "origin", url) + repo.git("commit", "-q", "--allow-empty", "-m", "copied delivery base") + repo.git("push", "-q", "origin", "main") + repo.git("symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main") + return repo + + def cases(self) -> tuple[tuple[str, str], ...]: + base = self.git("rev-parse", "HEAD") + self.git("switch", "-q", "-c", "main-answer") + self.git("commit", "-q", "--allow-empty", "-m", "answer integrated on main") + main = self.git("rev-parse", "HEAD") + self.git("switch", "-q", "main") + self.git("merge", "-q", "--no-ff", "main-answer", "-m", "integrate main answer") + self.git("push", "-q", "origin", "main") + self.git("switch", "-q", "-c", "branch-answer", base) + self.git("commit", "-q", "--allow-empty", "-m", "answer integrated elsewhere") + branch = self.git("rev-parse", "HEAD") + self.git("switch", "-q", "-c", "integration", base) + self.git("merge", "-q", "--no-ff", "branch-answer", "-m", "integrate branch answer") + self.git("push", "-q", "origin", "integration") + self.git("switch", "-q", "-c", "unmerged-answer", base) + self.git("commit", "-q", "--allow-empty", "-m", "published but not integrated") + unmerged = self.git("rev-parse", "HEAD") + self.git("push", "-q", "origin", "unmerged-answer") + self.git("switch", "-q", "main") + return (("merged to main", main), ("merged to another branch", branch), + ("not merged", unmerged)) + + +class DeliveredCommitCase: + @classmethod + def create_order(cls, run: LifecycleRun, repository: DeliveryRepository) -> Todo: + document = OrderFile.read(run.document.read_text()) + document = replace(document, order=replace(document.order, + check=replace(document.order.check, directory=str(repository.path)))) + run.document.write_text(json.dumps(FieldCodec.encode(document))) + before = {todo.id for todo in run.commander.comms.goals.todos.for_commander( + run.commander.incarnation)} + run.commander.problem(str(run.document), run.worker, run.due, + f"{Path(__file__).resolve()}:1") + created, = (todo for todo in run.commander.comms.goals.todos.for_commander( + run.commander.incarnation) if todo.id not in before) + return created + + @classmethod + def read(cls) -> ParityComparison: + cases = [] + with LifecycleRun.open() as run, contextlib.redirect_stdout(io.StringIO()): + repository = DeliveryRepository.create(run.commander.root) + run.decisions.write_text("[]") + run.artifact.write_text("Copied-bus delivered-commit comparison; no source input executed.") + for name, commit in repository.cases(): + current_order = cls.create_order(run, repository) + refusal = "" + try: + run.commander.done(current_order.id, commit, seen=str(run.artifact)) + except (SystemExit, ValueError) as error: + refusal = str(error) + current = DeliveryOutcome.saved( + run.commander.comms.goals.todos.get(current_order.id), refusal) + native_order = cls.create_order(run, repository) + refusal = "" + try: + run.commander.call(CommsOrderDoneTool( + problem=native_order.id, order_id=native_order.order.id, + delivery=CommitDelivery(commit=repository.git("rev-parse", "HEAD"), + delivered_commit=commit, evidence=FileDeliveryEvidence(str(run.artifact))), + claims=(MergedClaim(commit),))) + except ValueError as error: + refusal = str(error) + native = DeliveryOutcome.saved( + run.commander.comms.goals.todos.get(native_order.id), refusal) + cases.append((name, commit, current, native)) + return DeliveryComparison(tuple(cases)) + diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index 97ab0b9321..5a3b5b61c4 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -11,6 +11,7 @@ StoryPhase, ) from . import ParityComparison, ParityRow, UnverifiedParity +from .delivery import DeliveredCommitCase from .handoff import FinishedHandoffCase from .lifecycle import ( AssignOrderCase, @@ -50,6 +51,7 @@ class DeliveredCommitParityRow(ParityRow): current = "`order.py done`" native = "`CommsOrderDoneTool`: merged to main, another branch, or not merged" phases = (OneOrderOwnerPhase, RecordPlanePhase) + comparison = DeliveredCommitCase.read class InstalledEvidenceParityRow(ParityRow): From 418e7f90a08db161ffa0a36446bc97c970317809 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:40:40 -0400 Subject: [PATCH 18/43] Keep every copied delivery order failing until its actual run --- src/agent_comms/commander_parity/delivery.py | 2 ++ tests/test_commander_parity_delivery.py | 34 ++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/test_commander_parity_delivery.py diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py index b5c2a5ae49..022de0615a 100644 --- a/src/agent_comms/commander_parity/delivery.py +++ b/src/agent_comms/commander_parity/delivery.py @@ -102,12 +102,14 @@ def create_order(cls, run: LifecycleRun, repository: DeliveryRepository) -> Todo document = replace(document, order=replace(document.order, check=replace(document.order.check, directory=str(repository.path)))) run.document.write_text(json.dumps(FieldCodec.encode(document))) + run.decisions.write_text(json.dumps([run.worker])) before = {todo.id for todo in run.commander.comms.goals.todos.for_commander( run.commander.incarnation)} run.commander.problem(str(run.document), run.worker, run.due, f"{Path(__file__).resolve()}:1") created, = (todo for todo in run.commander.comms.goals.todos.for_commander( run.commander.incarnation) if todo.id not in before) + run.decisions.write_text("[]") return created @classmethod diff --git a/tests/test_commander_parity_delivery.py b/tests/test_commander_parity_delivery.py new file mode 100644 index 0000000000..09d3e736fc --- /dev/null +++ b/tests/test_commander_parity_delivery.py @@ -0,0 +1,34 @@ +from agent_comms.commander_parity.delivery import DeliveryRepository, DeliveredCommitCase +from agent_comms.commander_parity.rows import DeliveredCommitParityRow +from agent_comms.goals import CountCheckResult, OrderCheck + + +def test_delivery_graph_is_pushed_and_distinguishes_integration(tmp_path): + repository = DeliveryRepository.create(tmp_path) + main, branch, unmerged = repository.cases() + for _, commit in (main, branch, unmerged): + assert repository.git("branch", "--remotes", "--contains", commit, "--list", "origin/*") + assert repository.git("merge-base", main[1], "origin/main") == main[1] + assert repository.git("merge-base", branch[1], "origin/integration") == branch[1] + assert repository.git("merge-base", branch[1], "origin/main") != branch[1] + assert repository.git("merge-base", unmerged[1], "origin/main") != unmerged[1] + assert repository.git("merge-base", unmerged[1], "origin/integration") != unmerged[1] + + +def test_check_uses_configured_persistent_checkout_and_retains_real_result(tmp_path): + repository = DeliveryRepository.create(tmp_path) + checkout_base = tmp_path / "persistent-checkouts" + marker = tmp_path / "observed-checkouts" + check = OrderCheck(str(repository.path), f'pwd >> "{marker}"; echo 0', CountCheckResult(0)) + commit = repository.git("rev-parse", "HEAD") + first = check.at(commit, str(repository.path), checkout_base) + assert check.at(commit, str(repository.path), checkout_base) == first + actual, = marker.read_text().splitlines() + assert actual.startswith(str(checkout_base) + "/order-check-") + assert not tuple(checkout_base.iterdir()) + assert len(check.recorded(commit, str(repository.path))) == 1 + + +def test_delivery_row_declares_comparison_once(): + assert "verify" not in vars(DeliveredCommitParityRow) + assert DeliveredCommitParityRow.comparison == DeliveredCommitCase.read From 0d2d8d08cc701c08c6e7091951c2b2234fc17c74 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:45:22 -0400 Subject: [PATCH 19/43] State the checked main commit and migrate the explicit check proof --- src/agent_comms/commander_parity/delivery.py | 11 +++++++---- tools/proofs/cn_progress_read_only.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py index 022de0615a..fe69c920fc 100644 --- a/src/agent_comms/commander_parity/delivery.py +++ b/src/agent_comms/commander_parity/delivery.py @@ -31,6 +31,7 @@ def saved(cls, todo: Todo, refusal: str) -> DeliveryOutcome: @dataclass(frozen=True) class DeliveryComparison(ParityComparison): + checked_commit: str cases: tuple[tuple[str, str, DeliveryOutcome, DeliveryOutcome], ...] @property @@ -38,12 +39,13 @@ def passed(self) -> bool: return len(self.cases) == 3 and not self.cases[0][2].pending and all( current.pending == native.pending and current.order_line == native.order_line - and (not current.pending or (commit in current.refusal and commit in native.refusal - and "not in" in current.refusal and "not in" in native.refusal)) + and (all(commit in outcome.refusal and "not in" in outcome.refusal + for outcome in (current, native)) + if current.pending else commit in current.order_line) for _, commit, current, native in self.cases) def describe(self) -> str: - return repr(self.cases) + return f"checked main={self.checked_commit}; outcomes={self.cases!r}" @dataclass(frozen=True) @@ -141,5 +143,6 @@ def read(cls) -> ParityComparison: native = DeliveryOutcome.saved( run.commander.comms.goals.todos.get(native_order.id), refusal) cases.append((name, commit, current, native)) - return DeliveryComparison(tuple(cases)) + checked_commit = repository.git("rev-parse", "HEAD") + return DeliveryComparison(checked_commit, tuple(cases)) diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index 5065895634..05d2364053 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -63,7 +63,7 @@ def verify() -> None: assert not MARKER.exists() sha = CheckedCommit.git(str(checkout), "rev-parse", "HEAD").strip() assert not check.recorded(sha, str(checkout)) - check.at(sha, str(checkout)) # Deliberately explicit, never a board read. + check.at(sha, str(checkout), comms.registry.storage_rule.checkout_base) assert MARKER.read_bytes() == MARKER_CONTENT assert len(board(todo).readings) == 1 assert MARKER.read_bytes() == MARKER_CONTENT From b6751ac087328a4017b5813b1c73cfbdbd94e7f9 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:48:27 -0400 Subject: [PATCH 20/43] Compare real command process exits without swallowing native refusals --- src/agent_comms/commander_parity/delivery.py | 147 ++++++++++++++----- tests/test_commander_parity_delivery.py | 2 +- 2 files changed, 108 insertions(+), 41 deletions(-) diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py index fe69c920fc..be0a9ce940 100644 --- a/src/agent_comms/commander_parity/delivery.py +++ b/src/agent_comms/commander_parity/delivery.py @@ -5,7 +5,9 @@ import contextlib import io import json +import os import subprocess +import sys from dataclasses import dataclass, replace from pathlib import Path @@ -20,13 +22,14 @@ @dataclass(frozen=True) class DeliveryOutcome: + exit_code: int pending: bool order_line: str refusal: str @classmethod - def saved(cls, todo: Todo, refusal: str) -> DeliveryOutcome: - return cls(todo.pending, todo.order.line, refusal) + def saved(cls, todo: Todo, process: subprocess.CompletedProcess[str]) -> DeliveryOutcome: + return cls(process.returncode, todo.pending, todo.order.line, process.stderr) @dataclass(frozen=True) @@ -36,13 +39,23 @@ class DeliveryComparison(ParityComparison): @property def passed(self) -> bool: - return len(self.cases) == 3 and not self.cases[0][2].pending and all( - current.pending == native.pending - and current.order_line == native.order_line - and (all(commit in outcome.refusal and "not in" in outcome.refusal - for outcome in (current, native)) - if current.pending else commit in current.order_line) - for _, commit, current, native in self.cases) + return ( + len(self.cases) == 3 + and not self.cases[0][2].pending + and all( + (current.exit_code, current.pending) == (native.exit_code, native.pending) + and current.order_line == native.order_line + and ( + all( + commit in outcome.refusal and "not in" in outcome.refusal + for outcome in (current, native) + ) + if current.pending + else commit in current.order_line + ) + for _, commit, current, native in self.cases + ) + ) def describe(self) -> str: return f"checked main={self.checked_commit}; outcomes={self.cases!r}" @@ -54,9 +67,19 @@ class DeliveryRepository: def git(self, *arguments: str) -> str: return subprocess.check_output( - ("git", "-C", str(self.path), "-c", "user.name=parity", - "-c", "user.email=parity@invalid", *arguments), - text=True, stderr=subprocess.PIPE).strip() + ( + "git", + "-C", + str(self.path), + "-c", + "user.name=parity", + "-c", + "user.email=parity@invalid", + *arguments, + ), + text=True, + stderr=subprocess.PIPE, + ).strip() @classmethod def create(cls, root: Path) -> DeliveryRepository: @@ -93,24 +116,48 @@ def cases(self) -> tuple[tuple[str, str], ...]: unmerged = self.git("rev-parse", "HEAD") self.git("push", "-q", "origin", "unmerged-answer") self.git("switch", "-q", "main") - return (("merged to main", main), ("merged to another branch", branch), - ("not merged", unmerged)) + return ( + ("merged to main", main), + ("merged to another branch", branch), + ("not merged", unmerged), + ) class DeliveredCommitCase: + @staticmethod + def invoke(program: str, *arguments: str) -> subprocess.CompletedProcess[str]: + source = str(Path(__file__).resolve().parents[2]) + return subprocess.run( + (sys.executable, "-c", program, *arguments), + capture_output=True, + text=True, + timeout=120, + env={**os.environ, "PYTHONPATH": source}, + ) + @classmethod def create_order(cls, run: LifecycleRun, repository: DeliveryRepository) -> Todo: document = OrderFile.read(run.document.read_text()) - document = replace(document, order=replace(document.order, - check=replace(document.order.check, directory=str(repository.path)))) + document = replace( + document, + order=replace( + document.order, check=replace(document.order.check, directory=str(repository.path)) + ), + ) run.document.write_text(json.dumps(FieldCodec.encode(document))) run.decisions.write_text(json.dumps([run.worker])) - before = {todo.id for todo in run.commander.comms.goals.todos.for_commander( - run.commander.incarnation)} - run.commander.problem(str(run.document), run.worker, run.due, - f"{Path(__file__).resolve()}:1") - created, = (todo for todo in run.commander.comms.goals.todos.for_commander( - run.commander.incarnation) if todo.id not in before) + before = { + todo.id + for todo in run.commander.comms.goals.todos.for_commander(run.commander.incarnation) + } + run.commander.problem( + str(run.document), run.worker, run.due, f"{Path(__file__).resolve()}:1" + ) + (created,) = ( + todo + for todo in run.commander.comms.goals.todos.for_commander(run.commander.incarnation) + if todo.id not in before + ) run.decisions.write_text("[]") return created @@ -120,29 +167,49 @@ def read(cls) -> ParityComparison: with LifecycleRun.open() as run, contextlib.redirect_stdout(io.StringIO()): repository = DeliveryRepository.create(run.commander.root) run.decisions.write_text("[]") - run.artifact.write_text("Copied-bus delivered-commit comparison; no source input executed.") + run.artifact.write_text( + "Copied-bus delivered-commit comparison; no source input executed." + ) for name, commit in repository.cases(): current_order = cls.create_order(run, repository) - refusal = "" - try: - run.commander.done(current_order.id, commit, seen=str(run.artifact)) - except (SystemExit, ValueError) as error: - refusal = str(error) + process = cls.invoke( + "from agent_comms.commander.order import Commander; import sys; " + "Commander.model_validate_json(sys.argv[1]).done(" + "sys.argv[2],sys.argv[3],seen=sys.argv[4])", + run.commander.model_dump_json(), + current_order.id, + commit, + str(run.artifact), + ) current = DeliveryOutcome.saved( - run.commander.comms.goals.todos.get(current_order.id), refusal) + run.commander.comms.goals.todos.get(current_order.id), process + ) native_order = cls.create_order(run, repository) - refusal = "" - try: - run.commander.call(CommsOrderDoneTool( - problem=native_order.id, order_id=native_order.order.id, - delivery=CommitDelivery(commit=repository.git("rev-parse", "HEAD"), - delivered_commit=commit, evidence=FileDeliveryEvidence(str(run.artifact))), - claims=(MergedClaim(commit),))) - except ValueError as error: - refusal = str(error) + tool = CommsOrderDoneTool( + problem=native_order.id, + order_id=native_order.order.checked().id, + delivery=CommitDelivery( + commit=repository.git("rev-parse", "HEAD"), + delivered_commit=commit, + evidence=FileDeliveryEvidence(str(run.artifact)), + ), + claims=(MergedClaim(commit),), + ) + process = cls.invoke( + "from pathlib import Path; import sys; " + "from agent_comms.comms import Comms; " + "from agent_comms.field_codec import FieldCodec; " + "from agent_comms.tool_caller import ThreadCaller; " + "from agent_comms.tools import CommsOrderDoneTool; " + "ThreadCaller(sys.argv[2]).invoke(FieldCodec.decode(CommsOrderDoneTool, " + "FieldCodec.parse(sys.argv[3])),Comms(Path(sys.argv[1])))", + str(run.commander.root), + run.commander.commander, + json.dumps(FieldCodec.encode(tool)), + ) native = DeliveryOutcome.saved( - run.commander.comms.goals.todos.get(native_order.id), refusal) + run.commander.comms.goals.todos.get(native_order.id), process + ) cases.append((name, commit, current, native)) checked_commit = repository.git("rev-parse", "HEAD") return DeliveryComparison(checked_commit, tuple(cases)) - diff --git a/tests/test_commander_parity_delivery.py b/tests/test_commander_parity_delivery.py index 09d3e736fc..023bf6faa1 100644 --- a/tests/test_commander_parity_delivery.py +++ b/tests/test_commander_parity_delivery.py @@ -23,7 +23,7 @@ def test_check_uses_configured_persistent_checkout_and_retains_real_result(tmp_p commit = repository.git("rev-parse", "HEAD") first = check.at(commit, str(repository.path), checkout_base) assert check.at(commit, str(repository.path), checkout_base) == first - actual, = marker.read_text().splitlines() + (actual,) = marker.read_text().splitlines() assert actual.startswith(str(checkout_base) + "/order-check-") assert not tuple(checkout_base.iterdir()) assert len(check.recorded(commit, str(repository.path))) == 1 From 31eb28426e954bcea6a94da57c0cf7ba7eea7f6e Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:51:59 -0400 Subject: [PATCH 21/43] Keep child timeout and retirement outcomes in the delivery comparison --- src/agent_comms/commander_parity/delivery.py | 36 +++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py index be0a9ce940..859109a597 100644 --- a/src/agent_comms/commander_parity/delivery.py +++ b/src/agent_comms/commander_parity/delivery.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio import contextlib import io import json @@ -11,6 +12,7 @@ from dataclasses import dataclass, replace from pathlib import Path +from ..child_process import BoundedRun, ChildOutcome, ChildResult, ExitedOutcome from ..commander.order import OrderFile from ..field_codec import FieldCodec from ..progress_claims import MergedClaim @@ -22,14 +24,20 @@ @dataclass(frozen=True) class DeliveryOutcome: - exit_code: int + outcome: ChildOutcome pending: bool order_line: str refusal: str @classmethod - def saved(cls, todo: Todo, process: subprocess.CompletedProcess[str]) -> DeliveryOutcome: - return cls(process.returncode, todo.pending, todo.order.line, process.stderr) + def saved(cls, todo: Todo, process: ChildResult) -> DeliveryOutcome: + return cls( + process.outcome, todo.pending, todo.order.line, process.stderr.decode(errors="replace") + ) + + @property + def delivered(self) -> bool: + return self.outcome.successful and not self.pending @dataclass(frozen=True) @@ -41,13 +49,15 @@ class DeliveryComparison(ParityComparison): def passed(self) -> bool: return ( len(self.cases) == 3 - and not self.cases[0][2].pending + and self.cases[0][2].delivered and all( - (current.exit_code, current.pending) == (native.exit_code, native.pending) + (current.outcome, current.pending) == (native.outcome, native.pending) and current.order_line == native.order_line and ( all( - commit in outcome.refusal and "not in" in outcome.refusal + outcome.outcome == ExitedOutcome(1) + and commit in outcome.refusal + and "not in" in outcome.refusal for outcome in (current, native) ) if current.pending @@ -125,14 +135,14 @@ def cases(self) -> tuple[tuple[str, str], ...]: class DeliveredCommitCase: @staticmethod - def invoke(program: str, *arguments: str) -> subprocess.CompletedProcess[str]: + def invoke(program: str, *arguments: str) -> ChildResult: source = str(Path(__file__).resolve().parents[2]) - return subprocess.run( - (sys.executable, "-c", program, *arguments), - capture_output=True, - text=True, - timeout=120, - env={**os.environ, "PYTHONPATH": source}, + return asyncio.run( + BoundedRun.run( + (sys.executable, "-c", program, *arguments), + timeout=120, + env={**os.environ, "PYTHONPATH": source}, + ) ) @classmethod From d687f456cd591a107b8237f3791d5b14ac359995 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:04:22 -0400 Subject: [PATCH 22/43] Use the lifecycle-owned candidate process launcher for delivered-commit parity --- src/agent_comms/commander_parity/delivery.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/agent_comms/commander_parity/delivery.py b/src/agent_comms/commander_parity/delivery.py index 859109a597..0026115cd7 100644 --- a/src/agent_comms/commander_parity/delivery.py +++ b/src/agent_comms/commander_parity/delivery.py @@ -2,17 +2,14 @@ from __future__ import annotations -import asyncio import contextlib import io import json -import os import subprocess -import sys from dataclasses import dataclass, replace from pathlib import Path -from ..child_process import BoundedRun, ChildOutcome, ChildResult, ExitedOutcome +from ..child_process import ChildOutcome, ChildResult, ExitedOutcome from ..commander.order import OrderFile from ..field_codec import FieldCodec from ..progress_claims import MergedClaim @@ -134,17 +131,6 @@ def cases(self) -> tuple[tuple[str, str], ...]: class DeliveredCommitCase: - @staticmethod - def invoke(program: str, *arguments: str) -> ChildResult: - source = str(Path(__file__).resolve().parents[2]) - return asyncio.run( - BoundedRun.run( - (sys.executable, "-c", program, *arguments), - timeout=120, - env={**os.environ, "PYTHONPATH": source}, - ) - ) - @classmethod def create_order(cls, run: LifecycleRun, repository: DeliveryRepository) -> Todo: document = OrderFile.read(run.document.read_text()) @@ -182,7 +168,7 @@ def read(cls) -> ParityComparison: ) for name, commit in repository.cases(): current_order = cls.create_order(run, repository) - process = cls.invoke( + process = run.invoke( "from agent_comms.commander.order import Commander; import sys; " "Commander.model_validate_json(sys.argv[1]).done(" "sys.argv[2],sys.argv[3],seen=sys.argv[4])", @@ -205,7 +191,7 @@ def read(cls) -> ParityComparison: ), claims=(MergedClaim(commit),), ) - process = cls.invoke( + process = run.invoke( "from pathlib import Path; import sys; " "from agent_comms.comms import Comms; " "from agent_comms.field_codec import FieldCodec; " From 416cc64274149833c676f4eaa8d1076a2cff05af Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:40:30 -0400 Subject: [PATCH 23/43] Compare deletion-intent refusal and acceptance through both real delivery adapters --- src/agent_comms/commander_parity/deletion.py | 185 +++++++++++++++++++ src/agent_comms/commander_parity/rows.py | 8 - 2 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 src/agent_comms/commander_parity/deletion.py diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py new file mode 100644 index 0000000000..060026a5c1 --- /dev/null +++ b/src/agent_comms/commander_parity/deletion.py @@ -0,0 +1,185 @@ +"""Deletion intent parity through real orders, pushed commits and native delivery checks.""" + +from __future__ import annotations + +import contextlib +import io +import json +import shlex +import subprocess +import sys +import tempfile +from abc import ABC, abstractmethod +from dataclasses import dataclass, replace +from pathlib import Path + +from ..change_report import FileLine +from ..child_process import CountPrinted +from ..commander.order import OrderFile, deadline +from ..field_codec import FieldCodec +from ..goal_actions import CreateProblemGoalAction +from ..goals import CountCheckResult, OrderCheck +from ..progress_claims import MergedClaim +from ..plan_phases import RecordPlanePhase +from ..repo import Repo +from ..todos import CommitDelivery, FamilyDeletion, MechanicalOrderRun, Todo +from ..tools import CommsOrderDoneTool, CommsTodoCreateTool, ProblemOrdered +from . import ParityComparison, ParityRow +from .lifecycle import LifecycleObservation, LifecycleRun, MeasuredCommander + + +@dataclass(frozen=True) +class DeletionComparison(ParityComparison): + current: LifecycleObservation + native: LifecycleObservation + + @property + def passed(self) -> bool: + return (self.current.passed and self.native.passed + and self.current.checks == self.native.checks) + + def describe(self) -> str: + return f"current: {self.current.describe()}; native: {self.native.describe()}" + + @classmethod + def read(cls) -> DeletionComparison: + return cls(CurrentDeletionPath.read(), NativeDeletionPath.read()) + + +class DeletionPath(ABC): + """Both adapters exercise the same scenario on independent LifecycleRun copies.""" + + @classmethod + @abstractmethod + def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: ... + + @classmethod + @abstractmethod + def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: ... + + @staticmethod + def commit(repository: Path, text: str, message: str) -> str: + (repository / 'family.py').write_text(text) + Repo.git(repository, 'add', 'family.py') + Repo.git(repository, 'commit', '-qm', message) + Repo.git(repository, 'push', '-q', 'origin', 'main') + return Repo.git(repository, 'rev-parse', 'HEAD').strip() + + @classmethod + def read(cls) -> LifecycleObservation: + with LifecycleRun.open() as original: + # Keep source checkouts out of the copied worker's storage accounting. + # Limits remain unchanged; all detached checks use this persistent owned base. + rule = original.commander.comms.registry.storage_rule + with tempfile.TemporaryDirectory(prefix='parity-deletion-', + dir=rule.checkout_base) as checkouts: + root = original.commander.root + (root / 'storage.json').write_text(json.dumps(FieldCodec.encode( + replace(rule, checkout_base=Path(checkouts))))) + (root / 'commander.json').write_text(original.commander.model_dump_json()) + run = replace(original, commander=MeasuredCommander.read_root(root)) + return cls.exercise(run) + + @classmethod + def exercise(cls, run: LifecycleRun) -> LifecycleObservation: + repository = Path(run.commander.comms.registry.require(run.worker).worktree) + remote = repository.parent / 'deletion-origin.git' + Repo.git(repository, 'init', '-q', '-b', 'main') + Repo.git(repository, 'config', 'user.name', 'Deletion parity') + Repo.git(repository, 'config', 'user.email', 'deletion-parity@localhost') + Repo.git(repository, 'init', '--bare', '-q', '--initial-branch=main', str(remote)) + Repo.git(repository, 'remote', 'add', 'origin', str(remote)) + family = 'class RetiredDecision:\n pass\n\n' + cls.commit(repository, family + 'def decisions():\n return [RetiredDecision()]\n', + 'Declare the obsolete decision family and its caller') + count = OrderCheck(str(repository), shlex.join((sys.executable, '-c', + "import ast;from pathlib import Path;" + "print(sum(isinstance(node,ast.ClassDef) for node in " + "ast.walk(ast.parse(Path('family.py').read_text()))))")), CountCheckResult(0)) + deletion = FamilyDeletion('obsolete decision family', + FileLine(str(repository / 'family.py'), 1), count) + initial = OrderFile.read(run.document.read_text()) + order = replace(initial.order, + check=OrderCheck(str(repository), shlex.join((sys.executable, '-c', + 'import family;assert not family.decisions()'))), + run=MechanicalOrderRun(), deletes=deletion) + document = replace(initial, order=order) + run.document.write_text(json.dumps(FieldCodec.encode(document))) + output = io.StringIO() + with contextlib.redirect_stdout(output): + before = cls.send(run, document) + partial = cls.commit(repository, family + 'def decisions():\n return []\n', + 'Repair the caller but leave one obsolete family site') + refused = subprocess.run(cls.delivery_command(run, before, partial), + capture_output=True, text=True, timeout=90) + after_refusal = run.commander.comms.goals.todos.get(before.id) + remaining = count.recorded(partial, str(repository)) + complete = cls.commit(repository, 'def decisions():\n return []\n', + 'Delete the last obsolete family declaration') + accepted = subprocess.run(cls.delivery_command(run, before, complete), + capture_output=True, text=True, timeout=90) + after_acceptance = run.commander.comms.goals.todos.get(before.id) + zero = count.recorded(complete, str(repository)) + primary = order.check.recorded(partial, str(repository)) + checks = ( + ('native order retained deletion intent', before.order.checked().deletes == deletion), + ('partial commit passed primary check', bool(primary) + and order.check.result.distance(primary[0]) == CountPrinted(0)), + ('partial commit retained one family site', bool(remaining) + and count.result.distance(remaining[0]) == CountPrinted(1)), + ('nonzero delivery refused', refused.returncode != 0), + ('refusal preserved pending order', before == after_refusal and after_refusal.pending), + ('zero count checked on delivered commit', bool(zero) + and count.result.distance(zero[0]) == CountPrinted(0)), + ('zero delivery accepted', accepted.returncode == 0 and not after_acceptance.pending), + ) + return LifecycleObservation( + CommsOrderDoneTool.__qualname__, len((refused, accepted)), checks, + output.getvalue() + f'partial={partial}; zero={complete}; ' + f'refused={refused.returncode}: {refused.stderr}; ' + f'accepted={accepted.returncode}: {accepted.stdout}{accepted.stderr}') + + +class CurrentDeletionPath(DeletionPath): + @classmethod + def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: + return run.create() + + @classmethod + def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: + return (sys.executable, '-c', 'from agent_comms.commander.cli import main;main()', + '--config', str(run.commander.root / 'commander.json'), + 'order', 'done', todo.id, commit) + + +class NativeDeletionPath(DeletionPath): + @classmethod + def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: + result: ProblemOrdered = run.commander.call(CommsTodoCreateTool( + owner=run.worker, order=CreateProblemGoalAction( + problem='P1', repo=Repo.of(Path(document.order.check.directory)).github, + text=document.defect, order=document.order, + request=document.order.request( + run.commander.comms.registry.require(run.commander.commander), ('P1',)), + deadline=deadline(run.due), commander=run.commander.commander))) + return result.todo + + @classmethod + def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: + request = CommsOrderDoneTool(problem=todo.id, order_id=todo.order.id, + delivery=CommitDelivery(commit=commit, delivered_commit=commit), + claims=(MergedClaim(commit),)) + arguments = FieldCodec.encode(request) + arguments.pop(request.family_discriminator) + return (sys.executable, '-m', 'agent_comms', '--root', str(run.commander.root), + 'tool', '--from', run.commander.commander, '--tool', request.declared_name, + '--arguments', json.dumps(arguments)) + + +class DeletionTargetParityRow(ParityRow): + number = 31 + capability = 'An order states its intent as a deletion target' + current = 'Current order command with a declared FamilyDeletion' + native = 'CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count' + phases = (RecordPlanePhase,) + comparison = DeletionComparison.read diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index 5a3b5b61c4..bdace4acfd 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -302,14 +302,6 @@ class BuildBoundProofParityRow(ParityRow): comparison = BuildBoundProofCase.read -class DeletionTargetParityRow(ParityRow): - number = 31 - capability = "An order states its intent as a deletion target" - current = "none; P520 renamed a decision rather than deleting it" - native = "Order owns deletion family/site/count; delivery refuses nonzero or renamed decisions" - phases = (RecordPlanePhase,) - - class UnitTimeParityRow(ParityRow): number = 32 capability = "Where units' time goes, classified" From 6286f26e02299bbef7d6cd311ffbf7e5fb70a135 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:47:25 -0400 Subject: [PATCH 24/43] Exercise current command and native entrypoints with exact delivered-commit observations --- src/agent_comms/commander_parity/deletion.py | 232 +++++++++++++------ 1 file changed, 159 insertions(+), 73 deletions(-) diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index 060026a5c1..d4655a9c13 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -19,10 +19,17 @@ from ..field_codec import FieldCodec from ..goal_actions import CreateProblemGoalAction from ..goals import CountCheckResult, OrderCheck -from ..progress_claims import MergedClaim from ..plan_phases import RecordPlanePhase +from ..progress_claims import MergedClaim from ..repo import Repo -from ..todos import CommitDelivery, FamilyDeletion, MechanicalOrderRun, Todo +from ..todos import ( + CheckedCommitDelivery, + CommitDelivery, + DeliveredProblemOrder, + FamilyDeletion, + MechanicalOrderRun, + Todo, +) from ..tools import CommsOrderDoneTool, CommsTodoCreateTool, ProblemOrdered from . import ParityComparison, ParityRow from .lifecycle import LifecycleObservation, LifecycleRun, MeasuredCommander @@ -35,8 +42,9 @@ class DeletionComparison(ParityComparison): @property def passed(self) -> bool: - return (self.current.passed and self.native.passed - and self.current.checks == self.native.checks) + return ( + self.current.passed and self.native.passed and self.current.checks == self.native.checks + ) def describe(self) -> str: return f"current: {self.current.describe()}; native: {self.native.describe()}" @@ -59,11 +67,11 @@ def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[s @staticmethod def commit(repository: Path, text: str, message: str) -> str: - (repository / 'family.py').write_text(text) - Repo.git(repository, 'add', 'family.py') - Repo.git(repository, 'commit', '-qm', message) - Repo.git(repository, 'push', '-q', 'origin', 'main') - return Repo.git(repository, 'rev-parse', 'HEAD').strip() + (repository / "family.py").write_text(text) + Repo.git(repository, "add", "family.py") + Repo.git(repository, "commit", "-qm", message) + Repo.git(repository, "push", "-q", "origin", "main") + return Repo.git(repository, "rev-parse", "HEAD").strip() @classmethod def read(cls) -> LifecycleObservation: @@ -71,73 +79,118 @@ def read(cls) -> LifecycleObservation: # Keep source checkouts out of the copied worker's storage accounting. # Limits remain unchanged; all detached checks use this persistent owned base. rule = original.commander.comms.registry.storage_rule - with tempfile.TemporaryDirectory(prefix='parity-deletion-', - dir=rule.checkout_base) as checkouts: + with tempfile.TemporaryDirectory( + prefix="parity-deletion-", dir=rule.checkout_base + ) as checkouts: root = original.commander.root - (root / 'storage.json').write_text(json.dumps(FieldCodec.encode( - replace(rule, checkout_base=Path(checkouts))))) - (root / 'commander.json').write_text(original.commander.model_dump_json()) + (root / "storage.json").write_text( + json.dumps(FieldCodec.encode(replace(rule, checkout_base=Path(checkouts)))) + ) + (root / "commander.json").write_text(original.commander.model_dump_json()) run = replace(original, commander=MeasuredCommander.read_root(root)) return cls.exercise(run) @classmethod def exercise(cls, run: LifecycleRun) -> LifecycleObservation: repository = Path(run.commander.comms.registry.require(run.worker).worktree) - remote = repository.parent / 'deletion-origin.git' - Repo.git(repository, 'init', '-q', '-b', 'main') - Repo.git(repository, 'config', 'user.name', 'Deletion parity') - Repo.git(repository, 'config', 'user.email', 'deletion-parity@localhost') - Repo.git(repository, 'init', '--bare', '-q', '--initial-branch=main', str(remote)) - Repo.git(repository, 'remote', 'add', 'origin', str(remote)) - family = 'class RetiredDecision:\n pass\n\n' - cls.commit(repository, family + 'def decisions():\n return [RetiredDecision()]\n', - 'Declare the obsolete decision family and its caller') - count = OrderCheck(str(repository), shlex.join((sys.executable, '-c', - "import ast;from pathlib import Path;" - "print(sum(isinstance(node,ast.ClassDef) for node in " - "ast.walk(ast.parse(Path('family.py').read_text()))))")), CountCheckResult(0)) - deletion = FamilyDeletion('obsolete decision family', - FileLine(str(repository / 'family.py'), 1), count) + remote = repository.parent / "deletion-origin.git" + Repo.git(repository, "init", "-q", "-b", "main") + Repo.git(repository, "config", "user.name", "Deletion parity") + Repo.git(repository, "config", "user.email", "deletion-parity@localhost") + Repo.git(repository, "init", "--bare", "-q", "--initial-branch=main", str(remote)) + Repo.git(repository, "remote", "add", "origin", str(remote)) + family = "class RetiredDecision:\n pass\n\n" + cls.commit( + repository, + family + "def decisions():\n return [RetiredDecision()]\n", + "Declare the obsolete decision family and its caller", + ) + count = OrderCheck( + str(repository), + shlex.join( + ( + sys.executable, + "-c", + "import ast;from pathlib import Path;" + "print(sum(isinstance(node,ast.ClassDef) for node in " + "ast.walk(ast.parse(Path('family.py').read_text()))))", + ) + ), + CountCheckResult(0), + ) + deletion = FamilyDeletion( + "obsolete decision family", FileLine(str(repository / "family.py"), 1), count + ) initial = OrderFile.read(run.document.read_text()) - order = replace(initial.order, - check=OrderCheck(str(repository), shlex.join((sys.executable, '-c', - 'import family;assert not family.decisions()'))), - run=MechanicalOrderRun(), deletes=deletion) + order = replace( + initial.order, + check=OrderCheck( + str(repository), + shlex.join((sys.executable, "-c", "import family;assert not family.decisions()")), + ), + run=MechanicalOrderRun(), + deletes=deletion, + ) document = replace(initial, order=order) run.document.write_text(json.dumps(FieldCodec.encode(document))) output = io.StringIO() with contextlib.redirect_stdout(output): before = cls.send(run, document) - partial = cls.commit(repository, family + 'def decisions():\n return []\n', - 'Repair the caller but leave one obsolete family site') - refused = subprocess.run(cls.delivery_command(run, before, partial), - capture_output=True, text=True, timeout=90) + partial = cls.commit( + repository, + family + "def decisions():\n return []\n", + "Repair the caller but leave one obsolete family site", + ) + refused = subprocess.run( + cls.delivery_command(run, before, partial), capture_output=True, text=True, timeout=90 + ) after_refusal = run.commander.comms.goals.todos.get(before.id) remaining = count.recorded(partial, str(repository)) - complete = cls.commit(repository, 'def decisions():\n return []\n', - 'Delete the last obsolete family declaration') - accepted = subprocess.run(cls.delivery_command(run, before, complete), - capture_output=True, text=True, timeout=90) + complete = cls.commit( + repository, + "def decisions():\n return []\n", + "Delete the last obsolete family declaration", + ) + accepted = subprocess.run( + cls.delivery_command(run, before, complete), capture_output=True, text=True, timeout=90 + ) after_acceptance = run.commander.comms.goals.todos.get(before.id) zero = count.recorded(complete, str(repository)) primary = order.check.recorded(partial, str(repository)) + delivered = after_acceptance.order checks = ( - ('native order retained deletion intent', before.order.checked().deletes == deletion), - ('partial commit passed primary check', bool(primary) - and order.check.result.distance(primary[0]) == CountPrinted(0)), - ('partial commit retained one family site', bool(remaining) - and count.result.distance(remaining[0]) == CountPrinted(1)), - ('nonzero delivery refused', refused.returncode != 0), - ('refusal preserved pending order', before == after_refusal and after_refusal.pending), - ('zero count checked on delivered commit', bool(zero) - and count.result.distance(zero[0]) == CountPrinted(0)), - ('zero delivery accepted', accepted.returncode == 0 and not after_acceptance.pending), + ("native order retained deletion intent", before.order.checked().deletes == deletion), + ( + "partial commit passed primary check", + bool(primary) and order.check.result.distance(primary[0]) == CountPrinted(0), + ), + ( + "partial commit retained one family site", + bool(remaining) and count.result.distance(remaining[0]) == CountPrinted(1), + ), + ("nonzero delivery refused", refused.returncode != 0), + ("refusal preserved pending order", before == after_refusal and after_refusal.pending), + ( + "zero count checked on delivered commit", + bool(zero) and count.result.distance(zero[0]) == CountPrinted(0), + ), + ("zero delivery accepted", accepted.returncode == 0 and not after_acceptance.pending), + ( + "accepted delivery names the zero-site commit", + isinstance(delivered, DeliveredProblemOrder) + and isinstance(delivered.delivery, CheckedCommitDelivery) + and delivered.delivery.delivered_commit == complete, + ), + ("accepted delivery retained deletion intent", delivered.checked().deletes == deletion), ) return LifecycleObservation( - CommsOrderDoneTool.__qualname__, len((refused, accepted)), checks, - output.getvalue() + f'partial={partial}; zero={complete}; ' - f'refused={refused.returncode}: {refused.stderr}; ' - f'accepted={accepted.returncode}: {accepted.stdout}{accepted.stderr}') + CommsOrderDoneTool.__qualname__, + len((refused, accepted)), + checks, + output.getvalue() + f"partial={partial}; zero={complete}; " + f"refused={refused.returncode}: {refused.stdout}{refused.stderr}; " + f"accepted={accepted.returncode}: {accepted.stdout}{accepted.stderr}", + ) class CurrentDeletionPath(DeletionPath): @@ -147,39 +200,72 @@ def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: @classmethod def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: - return (sys.executable, '-c', 'from agent_comms.commander.cli import main;main()', - '--config', str(run.commander.root / 'commander.json'), - 'order', 'done', todo.id, commit) + return ( + sys.executable, + "-c", + "import sys;from agent_comms.commander.order import Commander,DoneVerb;" + "DoneVerb(unit=sys.argv[2],message=sys.argv[3],sha=sys.argv[4]).run(" + "Commander.read_root(sys.argv[1]))", + str(run.commander.root), + run.worker, + todo.order.checked().id, + commit, + ) class NativeDeletionPath(DeletionPath): @classmethod def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: - result: ProblemOrdered = run.commander.call(CommsTodoCreateTool( - owner=run.worker, order=CreateProblemGoalAction( - problem='P1', repo=Repo.of(Path(document.order.check.directory)).github, - text=document.defect, order=document.order, - request=document.order.request( - run.commander.comms.registry.require(run.commander.commander), ('P1',)), - deadline=deadline(run.due), commander=run.commander.commander))) + result = run.commander.call( + CommsTodoCreateTool( + owner=run.worker, + order=CreateProblemGoalAction( + problem="P1", + repo=Repo.of(Path(document.order.check.directory)).github, + text=document.defect, + order=document.order, + request=document.order.request( + run.commander.comms.registry.require(run.commander.commander), ("P1",) + ), + deadline=deadline(run.due), + commander=run.commander.commander, + ), + ) + ) + if not isinstance(result, ProblemOrdered): + raise TypeError("Native creation returned no ordered problem") return result.todo @classmethod def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: - request = CommsOrderDoneTool(problem=todo.id, order_id=todo.order.id, + request = CommsOrderDoneTool( + problem=todo.id, + order_id=todo.order.checked().id, delivery=CommitDelivery(commit=commit, delivered_commit=commit), - claims=(MergedClaim(commit),)) + claims=(MergedClaim(commit),), + ) arguments = FieldCodec.encode(request) arguments.pop(request.family_discriminator) - return (sys.executable, '-m', 'agent_comms', '--root', str(run.commander.root), - 'tool', '--from', run.commander.commander, '--tool', request.declared_name, - '--arguments', json.dumps(arguments)) + return ( + sys.executable, + "-m", + "agent_comms.cli", + "--root", + str(run.commander.root), + "tool", + "--from", + run.commander.commander, + "--tool", + request.declared_name, + "--arguments", + json.dumps(arguments), + ) class DeletionTargetParityRow(ParityRow): number = 31 - capability = 'An order states its intent as a deletion target' - current = 'Current order command with a declared FamilyDeletion' - native = 'CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count' + capability = "An order states its intent as a deletion target" + current = "Current order command with a declared FamilyDeletion" + native = "CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count" phases = (RecordPlanePhase,) comparison = DeletionComparison.read From 8d178ce10899abefaa06d62a6e43e09ddb715298 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:55:35 -0400 Subject: [PATCH 25/43] Let declared requests compose argument projection and let the CLI own tool argv --- src/agent_comms/tools.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index cf39fb398f..2ef7eec76c 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import sys import threading from abc import abstractmethod from collections.abc import Mapping @@ -68,7 +69,7 @@ TodoOperation, VerdictAudience) from .change_report import ChangeReport from .messages import InfoKind, MessageKind, NoticeRole, SendableMessageKind -from .native_tools import NativeToolDeclaration, NativeRequestTools +from .native_tools import ArgumentTool, NativeToolDeclaration, NativeRequestTools from .tool_caller import ToolCaller, ThreadCaller, NoThreadCaller, ThreadListing from .thread_identity import ThreadIncarnation from .cli_result import ToolCatalogResult @@ -292,7 +293,7 @@ def tool_field( @dataclass(frozen=True, kw_only=True) class ToolRequest[Caller: ToolCaller = ToolCaller]( - ThreadCommandAvailability, NativeRequestTools, NativeToolDeclaration + ArgumentTool, ThreadCommandAvailability, NativeRequestTools, NativeToolDeclaration ): description: ClassVar[str] bus_effect: ClassVar[str] = ("Changes the bus. Tristan has authorized claude-ai " @@ -328,6 +329,10 @@ def schema(cls) -> dict[str, object]: ) return result + def cli_command(self, caller: str | None = None) -> InvokeCliCommand: + """Project this declaration into Core's existing external tool command.""" + return InvokeCliCommand(caller=caller, tool=self.declared_name, arguments=self.arguments) + @classmethod def invoke( cls, comms: Comms, arguments: Mapping[str, object], *, caller: ToolCaller @@ -1362,6 +1367,15 @@ class InvokeCliCommand(CliCommand, declared_name="tool"): "--arguments", help="Tool arguments as a JSON object", default_factory=dict, parser_default="{}") + def argv(self, root: Path) -> tuple[str, ...]: + """The CLI boundary owns its module, grammar and argument serialization.""" + from . import cli + + caller = ('--from', self.caller) if self.caller is not None else () + return (sys.executable, '-m', cli.__name__, '--root', str(root), + self.declared_name, *caller, '--tool', self.tool, + '--arguments', json.dumps(self.arguments)) + def apply(self, ctx: Comms) -> dict[str, JsonValue]: return invoke_tool(ctx, self.tool, self.arguments, caller=ToolCaller.from_actor(self.caller)) From ae0ccaa1d997a27a0fd38ae4e6e00d0430f7b79c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:56:33 -0400 Subject: [PATCH 26/43] Use caller-owned identity and declared native CLI projection in deletion parity --- plans/commander-native-plan.md | 2 +- src/agent_comms/commander_parity/deletion.py | 18 ++---------------- src/agent_comms/tools.py | 9 +++++---- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/plans/commander-native-plan.md b/plans/commander-native-plan.md index 05042371b2..4fbae94e3a 100644 --- a/plans/commander-native-plan.md +++ b/plans/commander-native-plan.md @@ -61,7 +61,7 @@ | 28 | Measured load and self-report | `capacity.py` unit count | `commander.capacity.Load.of` reports orders, overdue, edits and forks; self-report comparison unverified | `control_plane` | | 29 | No handoff of finished, checked work | none; scratch transfer moved P507 after its fix was pushed and checked | Transfer refuses delivered checked work; that owner merges; check is reused | `record_plane` | | 30 | A proof is bound to the build it ran on | none; screenshots and logs are bare paths | Build run records runtime and Core/Toad revisions; close requires delivered commit | `record_plane` | -| 31 | An order states its intent as a deletion target | none; P520 renamed a decision rather than deleting it | Order owns deletion family/site/count; delivery refuses nonzero or renamed decisions | `record_plane` | +| 31 | An order states its intent as a deletion target | Current order command with a declared FamilyDeletion | CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count | `record_plane` | | 32 | Where units' time goes, classified | none; activity.log lacks durations and turn boundaries | `UnitTime`: model, build/check/test/shell, dependency/commander waits and idle; classify large waits | `commander_page`, `control_plane` | | 33 | Order progress beside its deadline; the unit never sees a due time | `order.py board`, `overdue`, `due`, `send`; `rotate.py` | Derived `OrderProgress`; merged work draws no strike; first commit 10 min, later progress 15 min STALL; units see no due time | `one_order_owner`, `control_plane` | | 34 | Several commanders on one machine and bus, one per project | none; a second commander.json install overwrites the first | Registry commander/project ownership; caller from --from; scoped orders, strikes, load, board, alarms and forks; raw-send guard; duplicate project commander refused | `one_order_owner`, `cutover` | diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index d4655a9c13..e66014991a 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -30,6 +30,7 @@ MechanicalOrderRun, Todo, ) +from ..tool_caller import ThreadCaller from ..tools import CommsOrderDoneTool, CommsTodoCreateTool, ProblemOrdered from . import ParityComparison, ParityRow from .lifecycle import LifecycleObservation, LifecycleRun, MeasuredCommander @@ -244,22 +245,7 @@ def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[s delivery=CommitDelivery(commit=commit, delivered_commit=commit), claims=(MergedClaim(commit),), ) - arguments = FieldCodec.encode(request) - arguments.pop(request.family_discriminator) - return ( - sys.executable, - "-m", - "agent_comms.cli", - "--root", - str(run.commander.root), - "tool", - "--from", - run.commander.commander, - "--tool", - request.declared_name, - "--arguments", - json.dumps(arguments), - ) + return request.cli_command(ThreadCaller(run.commander.commander)).argv(run.commander.root) class DeletionTargetParityRow(ParityRow): diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index 2ef7eec76c..b4742c291d 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -329,9 +329,10 @@ def schema(cls) -> dict[str, object]: ) return result - def cli_command(self, caller: str | None = None) -> InvokeCliCommand: + def cli_command(self, caller: ToolCaller) -> InvokeCliCommand: """Project this declaration into Core's existing external tool command.""" - return InvokeCliCommand(caller=caller, tool=self.declared_name, arguments=self.arguments) + return InvokeCliCommand(caller=caller.acting_name(''), + tool=self.declared_name, arguments=self.arguments) @classmethod def invoke( @@ -1371,9 +1372,9 @@ def argv(self, root: Path) -> tuple[str, ...]: """The CLI boundary owns its module, grammar and argument serialization.""" from . import cli - caller = ('--from', self.caller) if self.caller is not None else () + caller = ToolCaller.from_actor(self.caller) return (sys.executable, '-m', cli.__name__, '--root', str(root), - self.declared_name, *caller, '--tool', self.tool, + self.declared_name, '--from', caller.acting_name(''), '--tool', self.tool, '--arguments', json.dumps(self.arguments)) def apply(self, ctx: Comms) -> dict[str, JsonValue]: From 5f9571de8597f79cce3bc323f252528247a2592f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:58:03 -0400 Subject: [PATCH 27/43] Bind the native CLI module at its ordinary module import boundary --- src/agent_comms/tools.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index b4742c291d..673bbf334a 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -13,6 +13,7 @@ from pydantic import JsonValue +from . import cli as comms_cli from .commander.capacity import Load from .progress_claims import ProgressClaim from .pi_vocabulary import ServiceTier, ThinkingLevel, ThinkingLevelField @@ -1370,10 +1371,8 @@ class InvokeCliCommand(CliCommand, declared_name="tool"): def argv(self, root: Path) -> tuple[str, ...]: """The CLI boundary owns its module, grammar and argument serialization.""" - from . import cli - caller = ToolCaller.from_actor(self.caller) - return (sys.executable, '-m', cli.__name__, '--root', str(root), + return (sys.executable, '-m', comms_cli.__name__, '--root', str(root), self.declared_name, '--from', caller.acting_name(''), '--tool', self.tool, '--arguments', json.dumps(self.arguments)) From 9c013076986eb219db6904412fa32b53583145ab Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:59:15 -0400 Subject: [PATCH 28/43] Delete the other hand-built native tool argv and envelope projection --- tools/proofs/cn_phase3_record.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index d925660ffd..54cda2f8d8 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -11,12 +11,10 @@ import os import shutil import subprocess -import sys import time from dataclasses import replace from pathlib import Path -from agent_comms import cli as comms_cli from agent_comms.change_report import FileLine from agent_comms.comms import Comms from agent_comms.field_codec import FieldCodec @@ -50,22 +48,7 @@ def cli[Caller: ToolCaller](caller: str, tool: ToolRequest[Caller]) -> str: - arguments = FieldCodec.encode(tool) - del arguments[tool.family_discriminator] - command = [ - sys.executable, - "-m", - comms_cli.__name__, - "--root", - str(ROOT), - "tool", - "--from", - caller, - "--tool", - tool.declared_name, - "--arguments", - json.dumps(arguments), - ] + command = tool.cli_command(ToolCaller.from_actor(caller)).argv(ROOT) result = subprocess.run( command, capture_output=True, From 5f578b5be55f4cc5eff6e251da22b7c389c38855 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:04:10 -0400 Subject: [PATCH 29/43] Use the shared bounded lifecycle producer and typed delivery outcomes --- src/agent_comms/commander_parity/deletion.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index e66014991a..f199ea9733 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -6,7 +6,6 @@ import io import json import shlex -import subprocess import sys import tempfile from abc import ABC, abstractmethod @@ -14,7 +13,7 @@ from pathlib import Path from ..change_report import FileLine -from ..child_process import CountPrinted +from ..child_process import CountPrinted, ExitedOutcome from ..commander.order import OrderFile, deadline from ..field_codec import FieldCodec from ..goal_actions import CreateProblemGoalAction @@ -142,9 +141,7 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: family + "def decisions():\n return []\n", "Repair the caller but leave one obsolete family site", ) - refused = subprocess.run( - cls.delivery_command(run, before, partial), capture_output=True, text=True, timeout=90 - ) + refused = run.invoke_command(cls.delivery_command(run, before, partial)) after_refusal = run.commander.comms.goals.todos.get(before.id) remaining = count.recorded(partial, str(repository)) complete = cls.commit( @@ -152,9 +149,7 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: "def decisions():\n return []\n", "Delete the last obsolete family declaration", ) - accepted = subprocess.run( - cls.delivery_command(run, before, complete), capture_output=True, text=True, timeout=90 - ) + accepted = run.invoke_command(cls.delivery_command(run, before, complete)) after_acceptance = run.commander.comms.goals.todos.get(before.id) zero = count.recorded(complete, str(repository)) primary = order.check.recorded(partial, str(repository)) @@ -169,13 +164,16 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: "partial commit retained one family site", bool(remaining) and count.result.distance(remaining[0]) == CountPrinted(1), ), - ("nonzero delivery refused", refused.returncode != 0), + ("nonzero delivery refused", refused.outcome == ExitedOutcome(1)), ("refusal preserved pending order", before == after_refusal and after_refusal.pending), ( "zero count checked on delivered commit", bool(zero) and count.result.distance(zero[0]) == CountPrinted(0), ), - ("zero delivery accepted", accepted.returncode == 0 and not after_acceptance.pending), + ( + "zero delivery accepted", + accepted.outcome == ExitedOutcome(0) and not after_acceptance.pending, + ), ( "accepted delivery names the zero-site commit", isinstance(delivered, DeliveredProblemOrder) @@ -189,8 +187,10 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: len((refused, accepted)), checks, output.getvalue() + f"partial={partial}; zero={complete}; " - f"refused={refused.returncode}: {refused.stdout}{refused.stderr}; " - f"accepted={accepted.returncode}: {accepted.stdout}{accepted.stderr}", + f"refused={refused.outcome.summary}: " + f"{refused.stdout.decode(errors='replace')}{refused.stderr.decode(errors='replace')}; " + f"accepted={accepted.outcome.summary}: " + f"{accepted.stdout.decode(errors='replace')}{accepted.stderr.decode(errors='replace')}", ) From 999a312789e231d97414b8433ba8f9e70f73d5e8 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:04:45 -0400 Subject: [PATCH 30/43] Remove codec imports made obsolete by native declaration ownership --- tools/proofs/cn_phase3_record.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index 54cda2f8d8..bbadb40bd5 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -7,7 +7,6 @@ from __future__ import annotations import argparse -import json import os import shutil import subprocess @@ -17,7 +16,6 @@ from agent_comms.change_report import FileLine from agent_comms.comms import Comms -from agent_comms.field_codec import FieldCodec from agent_comms.goal_actions import CreateProblemGoalAction, SetGoalAction from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder from agent_comms.progress_claims import MergedClaim From 11d89475c7959e3ed2765046316a10d510cd547f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:07:04 -0400 Subject: [PATCH 31/43] Pass candidate Python environment through the shared process owner explicitly --- src/agent_comms/commander_parity/deletion.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index f199ea9733..78834661c6 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -141,7 +141,9 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: family + "def decisions():\n return []\n", "Repair the caller but leave one obsolete family site", ) - refused = run.invoke_command(cls.delivery_command(run, before, partial)) + refused = run.invoke_command( + cls.delivery_command(run, before, partial), env=run.python_environment() + ) after_refusal = run.commander.comms.goals.todos.get(before.id) remaining = count.recorded(partial, str(repository)) complete = cls.commit( @@ -149,7 +151,9 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: "def decisions():\n return []\n", "Delete the last obsolete family declaration", ) - accepted = run.invoke_command(cls.delivery_command(run, before, complete)) + accepted = run.invoke_command( + cls.delivery_command(run, before, complete), env=run.python_environment() + ) after_acceptance = run.commander.comms.goals.todos.get(before.id) zero = count.recorded(complete, str(repository)) primary = order.check.recorded(partial, str(repository)) From f0ef11b37d8c31bfcd7cea75cf9c3e4cd34f0385 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:15:06 -0400 Subject: [PATCH 32/43] Require explicit project and environment at the common process boundary --- src/agent_comms/commander_parity/deletion.py | 6 ++++-- src/agent_comms/commander_parity/lifecycle.py | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index 78834661c6..fdfbb19ad0 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -142,7 +142,7 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: "Repair the caller but leave one obsolete family site", ) refused = run.invoke_command( - cls.delivery_command(run, before, partial), env=run.python_environment() + cls.delivery_command(run, before, partial), cwd=repository, env=run.python_environment() ) after_refusal = run.commander.comms.goals.todos.get(before.id) remaining = count.recorded(partial, str(repository)) @@ -152,7 +152,9 @@ def exercise(cls, run: LifecycleRun) -> LifecycleObservation: "Delete the last obsolete family declaration", ) accepted = run.invoke_command( - cls.delivery_command(run, before, complete), env=run.python_environment() + cls.delivery_command(run, before, complete), + cwd=repository, + env=run.python_environment(), ) after_acceptance = run.commander.comms.goals.todos.get(before.id) zero = count.recorded(complete, str(repository)) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 60117649bd..f5f2d68034 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -189,15 +189,17 @@ def python_environment() -> dict[str, str]: @classmethod def invoke(cls, program: str, *arguments: str) -> ChildResult: return cls.invoke_command( - (sys.executable, "-c", program, *arguments), env=cls.python_environment() + (sys.executable, "-c", program, *arguments), + cwd=Path.cwd(), + env=cls.python_environment(), ) @staticmethod def invoke_command( command: tuple[str, ...], *, - cwd: str | Path | None = None, - env: dict[str, str] | None = None, + cwd: str | Path, + env: dict[str, str], ) -> ChildResult: return asyncio.run(BoundedRun.run(command, timeout=120, cwd=cwd, env=env)) From b9257f340ed15ed6a4999a49664905c2c37d0123 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:25:56 -0400 Subject: [PATCH 33/43] Use the current recorded-time owner throughout native lifecycle comparisons --- src/agent_comms/commander_parity/deletion.py | 4 ++-- src/agent_comms/commander_parity/handoff.py | 4 ++-- src/agent_comms/commander_parity/lifecycle.py | 15 ++++++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/agent_comms/commander_parity/deletion.py b/src/agent_comms/commander_parity/deletion.py index fdfbb19ad0..e1dd7a6d9d 100644 --- a/src/agent_comms/commander_parity/deletion.py +++ b/src/agent_comms/commander_parity/deletion.py @@ -14,7 +14,7 @@ from ..change_report import FileLine from ..child_process import CountPrinted, ExitedOutcome -from ..commander.order import OrderFile, deadline +from ..commander.order import OrderFile from ..field_codec import FieldCodec from ..goal_actions import CreateProblemGoalAction from ..goals import CountCheckResult, OrderCheck @@ -234,7 +234,7 @@ def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: request=document.order.request( run.commander.comms.registry.require(run.commander.commander), ("P1",) ), - deadline=deadline(run.due), + deadline=run.deadline, commander=run.commander.commander, ), ) diff --git a/src/agent_comms/commander_parity/handoff.py b/src/agent_comms/commander_parity/handoff.py index 5f6b6ef720..4657912be7 100644 --- a/src/agent_comms/commander_parity/handoff.py +++ b/src/agent_comms/commander_parity/handoff.py @@ -12,7 +12,7 @@ from ..change_report import FileLine from ..child_process import ChildOutcome, ChildResult, ExitedOutcome from ..commander.config import CommanderConfig -from ..commander.order import OrderFile, deadline +from ..commander.order import OrderFile from ..field_codec import FieldCodec from ..goal_actions import OrderGoalAction from ..tools import CommsOrderTool @@ -84,7 +84,7 @@ def native(run: LifecycleRun, receiver: str, document: OrderFile, pid: str) -> C expected_revision=todo.revision, assigner=context.incarnation, order=document.order, - deadline=deadline(run.due), + deadline=run.deadline, commander=context.commander, request=document.order.request( context.comms.registry.require(context.commander), (pid,) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index f5f2d68034..c876893b9d 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -23,10 +23,11 @@ from ..check_root import CheckRoot from ..child_process import BoundedRun, ChildResult from ..commander.config import CommanderConfig -from ..commander.order import Commander, OrderFile, hhmm +from ..commander.order import Commander, OrderFile from ..commander.request import RequestAddVerb from ..field_codec import FieldCodec from ..goals import CountCheckResult, OrderCheck, ProblemGoalOrder +from ..recorded_time import RecordedEventTime from ..todos import ( ApplicationOrderRun, MergedProgress, @@ -205,7 +206,11 @@ def invoke_command( @property def due(self) -> str: - return time.strftime("%H:%M", time.localtime(time.time() + 3600)) + return RecordedEventTime(time.time() + 3600).clock() + + @property + def deadline(self) -> RecordedEventTime: + return RecordedEventTime(time.time()).at_clock(self.due) def create(self) -> Todo: self.commander.problem( @@ -437,16 +442,16 @@ def checks( ( "unit messages hide clocks", all( - hhmm(clock) not in message.body + clock.clock() not in message.body for clock in (before.deadline, after.deadline) for message in messages ), ), ( "unit goal hides deadline", - "Deadline:" not in instructions and hhmm(after.deadline) not in instructions, + "Deadline:" not in instructions and after.deadline.clock() not in instructions, ), - ("commander still sees deadline", f"due {hhmm(after.deadline)}" in output), + ("commander still sees deadline", f"due {after.deadline.clock()}" in output), ) From 0ef4b2b64d9653bac387c5ea46af4eb7db83397c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:28:50 -0400 Subject: [PATCH 34/43] Drop the deleted commander CLI field from the lifecycle constructor --- src/agent_comms/commander_parity/lifecycle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index c876893b9d..6ae95221a0 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -126,7 +126,6 @@ def open(cls, *, extra_threads: tuple[str, ...] = ()) -> Iterator[LifecycleRun]: commander = MeasuredCommander( commander=config.commander, root=root, - cli=config.cli, state=root / "commander-state", board=root / "board.md", narrative=root / "narrative.md", From 9243f5845e6468c5c1f85c94fbeb85082edb86a5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:31:36 -0400 Subject: [PATCH 35/43] Keep generated deadline input in the actual CLI HH:MM grammar --- src/agent_comms/commander_parity/lifecycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent_comms/commander_parity/lifecycle.py b/src/agent_comms/commander_parity/lifecycle.py index 6ae95221a0..d4b89594cd 100644 --- a/src/agent_comms/commander_parity/lifecycle.py +++ b/src/agent_comms/commander_parity/lifecycle.py @@ -205,7 +205,7 @@ def invoke_command( @property def due(self) -> str: - return RecordedEventTime(time.time() + 3600).clock() + return time.strftime("%H:%M", time.localtime(time.time() + 3600)) @property def deadline(self) -> RecordedEventTime: From 31d9f3a82423be15b8b54e1ea03da84ee03b30af Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:39:10 -0400 Subject: [PATCH 36/43] Compare judge and native Verdict on actual copied-session edits --- src/agent_comms/commander_parity/rows.py | 2 + .../commander_parity/verdict_edit.mjs | 21 ++ src/agent_comms/commander_parity/verdicts.py | 205 ++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 src/agent_comms/commander_parity/verdict_edit.mjs create mode 100644 src/agent_comms/commander_parity/verdicts.py diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index bdace4acfd..a02f302ef7 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -22,6 +22,7 @@ verify_order_progress, ) from .load import LoadComparison +from .verdicts import VerdictComparison from .measurements import RuleComparison, StorageComparison, measured_load @@ -96,6 +97,7 @@ class VerdictParityRow(ParityRow): current = "`judge.py ok`, `back`; `verdicts.jsonl`" native = "Order `Verdict`; send-back revises the Goal and records its strike atomically" phases = (RecordPlanePhase,) + comparison = VerdictComparison.read class PlainWordsParityRow(ParityRow): diff --git a/src/agent_comms/commander_parity/verdict_edit.mjs b/src/agent_comms/commander_parity/verdict_edit.mjs new file mode 100644 index 0000000000..37570d850b --- /dev/null +++ b/src/agent_comms/commander_parity/verdict_edit.mjs @@ -0,0 +1,21 @@ +// A controlled request to the installed native edit tool; persist its actual result. +// No provider call, replacement tool, fabricated result, or original session write. +import { pathToFileURL } from "node:url"; +const [packagePath, sessionPath, path, callId, replacement] = process.argv.slice(2); +const { createEditTool, SessionManager } = await import(pathToFileURL(`${packagePath}/dist/index.js`).href); +const { readFile } = await import("node:fs/promises"); +const oldText = await readFile(path, "utf8"); +const args = { path, edits: [{ oldText, newText: replacement }] }; +const session = SessionManager.open(sessionPath); +session.appendMessage({ role: "user", content: "Execute one isolated verdict-check edit.", timestamp: Date.now() }); +session.appendMessage({ + role: "assistant", content: [{ type: "toolCall", id: callId, name: "edit", arguments: args }], + api: "controlled-verification", provider: "controlled-verification", model: "local-native-edit", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "toolUse", timestamp: Date.now(), +}); +const result = await createEditTool(process.cwd()).execute(callId, args); +session.appendMessage({ role: "toolResult", toolCallId: callId, toolName: "edit", + content: result.content, details: result.details, isError: false, timestamp: Date.now() }); +console.log(JSON.stringify({ callId, changed: (await readFile(path, "utf8")) === replacement })); diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py new file mode 100644 index 0000000000..03146f861a --- /dev/null +++ b/src/agent_comms/commander_parity/verdicts.py @@ -0,0 +1,205 @@ +"""Judge/native verdicts on actual native edits in LifecycleRun's disposable bus.""" + +from __future__ import annotations + +import contextlib +import io +import json +import os +import subprocess +from abc import ABC, abstractmethod +from collections.abc import Callable +from dataclasses import dataclass +from pathlib import Path +from typing import ClassVar +from uuid import uuid4 + +from ..active_route import resolve_comms_route +from ..commander.judge import BackVerb, Edit, OkVerb, VerdictVerb +from ..todos import Todo +from ..tools import CommsVerdictTool, TodoChanged +from ..turn_context import GoalSegment +from . import ParityComparison +from .lifecycle import LifecycleRun + + +@dataclass(frozen=True) +class VerdictEffect: + call_id: str + verdicts: int + todo_revisions: int + strikes: int + goal_revisions: int + goal_transitions: int + goal_retained: bool + goal_state_retained: bool + progress: str + native_calls: int + native_result_retained: bool + + +class VerdictCase(ABC): + changes: ClassVar[int] + + @classmethod + @abstractmethod + def replacement(cls, run: LifecycleRun) -> str: ... + + @classmethod + @abstractmethod + def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> VerdictVerb: ... + + @staticmethod + def judge(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> object: + return command.apply(run.commander) + + @staticmethod + def native(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> object: + return run.commander.call( + CommsVerdictTool( + problem=todo.id, operation=command.operation(run.commander, todo, edit.id) + ) + ) + + @classmethod + def capture( + cls, call_id: str, invoke: Callable[[LifecycleRun, VerdictVerb, Todo, Edit], object] + ) -> VerdictEffect: + with LifecycleRun.open() as run, contextlib.redirect_stdout(io.StringIO()): + before = run.create() + worker = run.commander.comms.registry.require(run.worker) + goal = worker.goal.require() + history = len(run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)) + package = resolve_comms_route().launch().require_private().validate() + subprocess.run( + ( + "node", + "--no-global-search-paths", + str(Path(__file__).with_name("verdict_edit.mjs")), + str(package.path), + worker.session_file, + str(run.decisions), + call_id, + cls.replacement(run), + ), + cwd=worker.worktree, + env=package.node_environment(dict(os.environ)), + capture_output=True, + text=True, + timeout=30, + check=True, + ) + edits = Edit.unjudged(run.commander.comms, run.commander.commander) + if len(edits) != 1 or edits[0].id != call_id: + raise ValueError("The copied unit must expose exactly its one real native edit") + edit = edits[0] + invoke(run, cls.command(run, before, edit), before, edit) + saved = run.commander.comms.goals.todos.get(before.id) + worker = run.commander.comms.registry.require(run.worker) + after = worker.goal.require() + native = tuple( + item + for item in run.commander.invocations + if isinstance(item.request, CommsVerdictTool) + ) + return VerdictEffect( + call_id, + len(saved.verdicts) - len(before.verdicts), + saved.revision - before.revision, + saved.strike_count - before.strike_count, + after.revision - goal.revision, + len(run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)) - history, + after.id == goal.id, + after.state == goal.state, + GoalSegment.capture(worker, after, run.commander.comms.goals).progress, + len(native), + len(native) == 1 and native[0].result == TodoChanged(saved, saved.strike_count), + ) + + @classmethod + def valid(cls, effect: VerdictEffect) -> bool: + return ( + effect.verdicts == effect.todo_revisions == effect.native_calls == 1 + and effect.strikes == effect.goal_revisions == effect.goal_transitions == cls.changes + and effect.goal_retained + and effect.goal_state_retained + and effect.native_result_retained + ) + + +class AcceptedCase(VerdictCase): + changes = 0 + + @classmethod + def replacement(cls, run: LifecycleRun) -> str: + return "[]" + + @classmethod + def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> OkVerb: + return OkVerb( + ids=(f"{edit.id}:{edit.code()}",), + why="The native edit removes the copied workflow decision.", + serves="Remove the copied workflow decision", + lines=(edit.hunks[0].new_text,), + ) + + +class SendBackCase(VerdictCase): + changes = 1 + + @classmethod + def replacement(cls, run: LifecycleRun) -> str: + return json.dumps([run.worker, run.worker]) + + @classmethod + def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> BackVerb: + return BackVerb( + ids=(f"{edit.id}:{edit.code()}",), + msg=todo.messages[-1].message_id, + lines=(edit.hunks[0].new_text,), + why="The edit duplicates the copied workflow decision instead of removing it.", + roast=( + "This is stupid because the edit keeps two copies of the same decision " + "instead of deleting it." + ), + fix="Remove the copied decision and let the existing check measure its absence.", + pattern="AGENT-2", + fact="the copied workflow decision collection", + at=f"{run.decisions}:1", + derive="delete-copy", + by=f"{Path(__file__).resolve()}:1", + ) + + +@dataclass(frozen=True) +class VerdictComparison(ParityComparison): + accepted_current: VerdictEffect + accepted_native: VerdictEffect + back_current: VerdictEffect + back_native: VerdictEffect + + @property + def passed(self) -> bool: + return ( + self.accepted_current == self.accepted_native + and self.back_current == self.back_native + and AcceptedCase.valid(self.accepted_native) + and SendBackCase.valid(self.back_native) + ) + + def describe(self) -> str: + return ( + f"accepted current/native equal={self.accepted_current == self.accepted_native}; " + f"send-back current/native equal={self.back_current == self.back_native}; " + f"accepted={self.accepted_native!r}; send-back={self.back_native!r}" + ) + + @classmethod + def read(cls) -> VerdictComparison: + call_id = uuid4().hex + return cls( + AcceptedCase.capture(call_id, AcceptedCase.judge), + AcceptedCase.capture(call_id, AcceptedCase.native), + SendBackCase.capture(call_id, SendBackCase.judge), + SendBackCase.capture(call_id, SendBackCase.native), + ) From e60f41498320fa74c038221d3eb59ca4be2da0f5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:41:30 -0400 Subject: [PATCH 37/43] Verify persisted verdict call identity and the strike event --- src/agent_comms/commander_parity/verdicts.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index 03146f861a..fcb3c59d86 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -36,6 +36,7 @@ class VerdictEffect: progress: str native_calls: int native_result_retained: bool + strike_matches_verdict_event: bool class VerdictCase(ABC): @@ -98,12 +99,15 @@ def capture( worker = run.commander.comms.registry.require(run.worker) after = worker.goal.require() native = tuple( - item + (item.request, item.result) for item in run.commander.invocations if isinstance(item.request, CommsVerdictTool) ) + if len(native) != 1 or native[0][0].operation.tool_call_id != call_id: + raise ValueError("The native verdict must judge this one original copied tool call") + request, result = native[0] return VerdictEffect( - call_id, + request.operation.tool_call_id, len(saved.verdicts) - len(before.verdicts), saved.revision - before.revision, saved.strike_count - before.strike_count, @@ -113,7 +117,13 @@ def capture( after.state == goal.state, GoalSegment.capture(worker, after, run.commander.comms.goals).progress, len(native), - len(native) == 1 and native[0].result == TodoChanged(saved, saved.strike_count), + result == TodoChanged(saved, saved.strike_count) + and request.operation in saved.verdicts, + all( + strike.at == request.operation.at + and strike.previous == request.operation.previous + for strike in saved.strikes[len(before.strikes) :] + ), ) @classmethod @@ -124,6 +134,7 @@ def valid(cls, effect: VerdictEffect) -> bool: and effect.goal_retained and effect.goal_state_retained and effect.native_result_retained + and effect.strike_matches_verdict_event ) From abd953d3c43d3bbfc524e7e242b2e8b5f95f298a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:44:07 -0400 Subject: [PATCH 38/43] Check the concrete verdict kind and its committed Goal transition --- src/agent_comms/commander_parity/verdicts.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index fcb3c59d86..bae6e12111 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -16,7 +16,7 @@ from ..active_route import resolve_comms_route from ..commander.judge import BackVerb, Edit, OkVerb, VerdictVerb -from ..todos import Todo +from ..todos import AcceptedVerdict, SendBackVerdict, Todo, ToolCallVerdict from ..tools import CommsVerdictTool, TodoChanged from ..turn_context import GoalSegment from . import ParityComparison @@ -37,10 +37,13 @@ class VerdictEffect: native_calls: int native_result_retained: bool strike_matches_verdict_event: bool + declared_verdict_recorded: bool + goal_transition_retained: bool class VerdictCase(ABC): changes: ClassVar[int] + native_owner: ClassVar[type[ToolCallVerdict]] @classmethod @abstractmethod @@ -71,6 +74,8 @@ def capture( worker = run.commander.comms.registry.require(run.worker) goal = worker.goal.require() history = len(run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)) + for path in (Path(worker.session_file), run.decisions, Path(worker.worktree)): + path.resolve().relative_to(run.commander.root.resolve()) package = resolve_comms_route().launch().require_private().validate() subprocess.run( ( @@ -106,13 +111,16 @@ def capture( if len(native) != 1 or native[0][0].operation.tool_call_id != call_id: raise ValueError("The native verdict must judge this one original copied tool call") request, result = native[0] + transitions = run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)[ + history: + ] return VerdictEffect( request.operation.tool_call_id, len(saved.verdicts) - len(before.verdicts), saved.revision - before.revision, saved.strike_count - before.strike_count, after.revision - goal.revision, - len(run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)) - history, + len(transitions), after.id == goal.id, after.state == goal.state, GoalSegment.capture(worker, after, run.commander.comms.goals).progress, @@ -124,6 +132,8 @@ def capture( and strike.previous == request.operation.previous for strike in saved.strikes[len(before.strikes) :] ), + isinstance(request.operation, cls.native_owner), + all(entry.before == goal and entry.after == after for entry in transitions), ) @classmethod @@ -135,11 +145,14 @@ def valid(cls, effect: VerdictEffect) -> bool: and effect.goal_state_retained and effect.native_result_retained and effect.strike_matches_verdict_event + and effect.declared_verdict_recorded + and effect.goal_transition_retained ) class AcceptedCase(VerdictCase): changes = 0 + native_owner = AcceptedVerdict @classmethod def replacement(cls, run: LifecycleRun) -> str: @@ -157,6 +170,7 @@ def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> OkVerb: class SendBackCase(VerdictCase): changes = 1 + native_owner = SendBackVerdict @classmethod def replacement(cls, run: LifecycleRun) -> str: From 0430570e8965e17b2b684d52a47fbfd804117738 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:46:51 -0400 Subject: [PATCH 39/43] Prepare the real copied-root rule review before invoking judge --- .../commander_parity/verdict_edit.mjs | 1 + src/agent_comms/commander_parity/verdicts.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/agent_comms/commander_parity/verdict_edit.mjs b/src/agent_comms/commander_parity/verdict_edit.mjs index 37570d850b..a3ec46c81f 100644 --- a/src/agent_comms/commander_parity/verdict_edit.mjs +++ b/src/agent_comms/commander_parity/verdict_edit.mjs @@ -16,6 +16,7 @@ session.appendMessage({ stopReason: "toolUse", timestamp: Date.now(), }); const result = await createEditTool(process.cwd()).execute(callId, args); +if ((await readFile(path, "utf8")) !== replacement) throw new Error("Native edit did not apply its request"); session.appendMessage({ role: "toolResult", toolCallId: callId, toolName: "edit", content: result.content, details: result.details, isError: false, timestamp: Date.now() }); console.log(JSON.stringify({ callId, changed: (await readFile(path, "utf8")) === replacement })); diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index bae6e12111..e885610a4c 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -16,6 +16,8 @@ from ..active_route import resolve_comms_route from ..commander.judge import BackVerb, Edit, OkVerb, VerdictVerb +from ..commander.rules import AnswerVerb, ReviewVerb +from ..rule_reviews import DrawnRule, RuleReviews from ..todos import AcceptedVerdict, SendBackVerdict, Todo, ToolCallVerdict from ..tools import CommsVerdictTool, TodoChanged from ..turn_context import GoalSegment @@ -70,6 +72,21 @@ def capture( cls, call_id: str, invoke: Callable[[LifecycleRun, VerdictVerb, Todo, Edit], object] ) -> VerdictEffect: with LifecycleRun.open() as run, contextlib.redirect_stdout(io.StringIO()): + # Satisfy the actual current command's gate in this copy; never bypass it + # or transfer the original commander's review authority into a new root. + ReviewVerb().apply(run.commander) + with RuleReviews(run.commander.root).snapshot() as db: + drawn = DrawnRule.select(db, reviewer=run.commander.commander, order_by=("n",)) + for rule in drawn: + AnswerVerb( + n=rule.n, + text=( + f'"{rule.rule}" This isolated comparison keeps verdict mutation in ' + "its existing native owner, preserves original sessions, and checks " + "the actual stored Goal and strike rather than a second state store: " + f"{Path(__file__).resolve()}:1" + ), + ).apply(run.commander) before = run.create() worker = run.commander.comms.registry.require(run.worker) goal = worker.goal.require() From bac04dbbc920c0e9a704a212ce6c04ba7ae78952 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 23:49:19 -0400 Subject: [PATCH 40/43] Require the send-back reason in the derived Goal and test the verdict family --- src/agent_comms/commander_parity/rows.py | 2 +- src/agent_comms/commander_parity/verdicts.py | 7 ++- tests/test_commander_parity_verdicts.py | 55 ++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/test_commander_parity_verdicts.py diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index a02f302ef7..4d6cf110fe 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -22,8 +22,8 @@ verify_order_progress, ) from .load import LoadComparison -from .verdicts import VerdictComparison from .measurements import RuleComparison, StorageComparison, measured_load +from .verdicts import VerdictComparison class CreateProblemParityRow(ParityRow): diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index e885610a4c..43fd259ba5 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -188,6 +188,11 @@ def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> OkVerb: class SendBackCase(VerdictCase): changes = 1 native_owner = SendBackVerdict + why: ClassVar[str] = "The edit duplicates the copied workflow decision instead of removing it." + + @classmethod + def valid(cls, effect: VerdictEffect) -> bool: + return super().valid(effect) and cls.why in effect.progress @classmethod def replacement(cls, run: LifecycleRun) -> str: @@ -199,7 +204,7 @@ def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> BackVerb: ids=(f"{edit.id}:{edit.code()}",), msg=todo.messages[-1].message_id, lines=(edit.hunks[0].new_text,), - why="The edit duplicates the copied workflow decision instead of removing it.", + why=cls.why, roast=( "This is stupid because the edit keeps two copies of the same decision " "instead of deleting it." diff --git a/tests/test_commander_parity_verdicts.py b/tests/test_commander_parity_verdicts.py new file mode 100644 index 0000000000..75676f9cf5 --- /dev/null +++ b/tests/test_commander_parity_verdicts.py @@ -0,0 +1,55 @@ +"""Comparison invariants; the configured LifecycleRun journey supplies native proof.""" + +from dataclasses import replace + +from agent_comms.commander_parity.rows import VerdictParityRow +from agent_comms.commander_parity.verdicts import ( + AcceptedCase, + SendBackCase, + VerdictComparison, + VerdictEffect, +) + + +def test_verdict_family_requires_the_actual_call_event_goal_and_strike_together(): + accepted = VerdictEffect( + call_id="one-call", + verdicts=1, + todo_revisions=1, + strikes=0, + goal_revisions=0, + goal_transitions=0, + goal_retained=True, + goal_state_retained=True, + progress="P1: Open", + native_calls=1, + native_result_retained=True, + strike_matches_verdict_event=True, + declared_verdict_recorded=True, + goal_transition_retained=True, + ) + back = replace( + accepted, strikes=1, goal_revisions=1, goal_transitions=1, progress=SendBackCase.why + ) + compared = VerdictComparison(accepted, accepted, back, back) + assert compared.passed and AcceptedCase.valid(accepted) and SendBackCase.valid(back) + for changed in ( + replace(back, verdicts=0), + replace(back, todo_revisions=2), + replace(back, strikes=0), + replace(back, goal_revisions=0), + replace(back, goal_transitions=2), + replace(back, native_calls=0), + replace(back, goal_retained=False), + replace(back, goal_state_retained=False), + replace(back, native_result_retained=False), + replace(back, strike_matches_verdict_event=False), + replace(back, declared_verdict_recorded=False), + replace(back, goal_transition_retained=False), + replace(back, progress=""), + ): + assert not SendBackCase.valid(changed) + assert not replace(compared, back_current=changed, back_native=changed).passed + assert not replace(compared, accepted_native=replace(accepted, call_id="other-call")).passed + assert "verify" not in VerdictParityRow.__dict__ + assert VerdictParityRow.comparison == VerdictComparison.read From 1549f62e8c6bb195f79023fdf06208821217d3e1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:00:53 -0400 Subject: [PATCH 41/43] Use LifecycleObservation for verdict proof and retain control input with its Python owner --- .../commander_parity/verdict_edit.mjs | 22 --- src/agent_comms/commander_parity/verdicts.py | 161 +++++++++++------- tests/test_commander_parity_verdicts.py | 56 +++--- 3 files changed, 125 insertions(+), 114 deletions(-) delete mode 100644 src/agent_comms/commander_parity/verdict_edit.mjs diff --git a/src/agent_comms/commander_parity/verdict_edit.mjs b/src/agent_comms/commander_parity/verdict_edit.mjs deleted file mode 100644 index a3ec46c81f..0000000000 --- a/src/agent_comms/commander_parity/verdict_edit.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// A controlled request to the installed native edit tool; persist its actual result. -// No provider call, replacement tool, fabricated result, or original session write. -import { pathToFileURL } from "node:url"; -const [packagePath, sessionPath, path, callId, replacement] = process.argv.slice(2); -const { createEditTool, SessionManager } = await import(pathToFileURL(`${packagePath}/dist/index.js`).href); -const { readFile } = await import("node:fs/promises"); -const oldText = await readFile(path, "utf8"); -const args = { path, edits: [{ oldText, newText: replacement }] }; -const session = SessionManager.open(sessionPath); -session.appendMessage({ role: "user", content: "Execute one isolated verdict-check edit.", timestamp: Date.now() }); -session.appendMessage({ - role: "assistant", content: [{ type: "toolCall", id: callId, name: "edit", arguments: args }], - api: "controlled-verification", provider: "controlled-verification", model: "local-native-edit", - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, - stopReason: "toolUse", timestamp: Date.now(), -}); -const result = await createEditTool(process.cwd()).execute(callId, args); -if ((await readFile(path, "utf8")) !== replacement) throw new Error("Native edit did not apply its request"); -session.appendMessage({ role: "toolResult", toolCallId: callId, toolName: "edit", - content: result.content, details: result.details, isError: false, timestamp: Date.now() }); -console.log(JSON.stringify({ callId, changed: (await readFile(path, "utf8")) === replacement })); diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index 43fd259ba5..06b8140e0a 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -22,25 +22,59 @@ from ..tools import CommsVerdictTool, TodoChanged from ..turn_context import GoalSegment from . import ParityComparison -from .lifecycle import LifecycleRun +from .lifecycle import LifecycleObservation, LifecycleRun + +# Comparison-side controlled input, like load.OBSERVE_CAPACITY, not a new native +# runtime bootstrap/resource. The unchanged pinned SDK owns execution and storage. +NATIVE_EDIT = """ +import { pathToFileURL } from 'node:url'; +import { readFile } from 'node:fs/promises'; +const [packagePath, sessionPath, path, callId, replacement] = process.argv.slice(1); +const sdk = pathToFileURL(`${packagePath}/dist/index.js`).href; +const { createEditTool, SessionManager } = await import(sdk); +const args = {path, edits: [{oldText: await readFile(path, 'utf8'), newText: replacement}]}; +const session = SessionManager.open(sessionPath); +session.appendMessage({role: 'user', content: 'Execute one isolated verdict-check edit.', + timestamp: Date.now()}); +session.appendMessage({ + role: 'assistant', content: [{type: 'toolCall', id: callId, name: 'edit', arguments: args}], + api: 'controlled-verification', provider: 'controlled-verification', model: 'local-native-edit', + usage: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, + cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0}}, + stopReason: 'toolUse', timestamp: Date.now(), +}); +const result = await createEditTool(process.cwd()).execute(callId, args); +if ((await readFile(path, 'utf8')) !== replacement) { + throw new Error('Native edit did not apply its request'); +} +session.appendMessage({role: 'toolResult', toolCallId: callId, toolName: 'edit', + content: result.content, details: result.details, isError: false, timestamp: Date.now()}); +""" @dataclass(frozen=True) -class VerdictEffect: +class VerdictMutation: call_id: str verdicts: int todo_revisions: int strikes: int goal_revisions: int goal_transitions: int - goal_retained: bool - goal_state_retained: bool progress: str - native_calls: int - native_result_retained: bool - strike_matches_verdict_event: bool - declared_verdict_recorded: bool - goal_transition_retained: bool + + def checks(self, changes: int) -> tuple[tuple[str, bool], ...]: + return ( + ("one verdict", self.verdicts == 1), + ("one Todo revision", self.todo_revisions == 1), + ("strike delta", self.strikes == changes), + ("Goal revision delta", self.goal_revisions == changes), + ("committed Goal transition delta", self.goal_transitions == changes), + ) + + +@dataclass(frozen=True) +class VerdictObservation(LifecycleObservation): + mutation: VerdictMutation class VerdictCase(ABC): @@ -56,8 +90,8 @@ def replacement(cls, run: LifecycleRun) -> str: ... def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> VerdictVerb: ... @staticmethod - def judge(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> object: - return command.apply(run.commander) + def judge(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> None: + command.apply(run.commander) @staticmethod def native(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> object: @@ -70,10 +104,9 @@ def native(run: LifecycleRun, command: VerdictVerb, todo: Todo, edit: Edit) -> o @classmethod def capture( cls, call_id: str, invoke: Callable[[LifecycleRun, VerdictVerb, Todo, Edit], object] - ) -> VerdictEffect: + ) -> VerdictObservation: with LifecycleRun.open() as run, contextlib.redirect_stdout(io.StringIO()): - # Satisfy the actual current command's gate in this copy; never bypass it - # or transfer the original commander's review authority into a new root. + # Satisfy the actual gate in this copy, without transferring original authority. ReviewVerb().apply(run.commander) with RuleReviews(run.commander.root).snapshot() as db: drawn = DrawnRule.select(db, reviewer=run.commander.commander, order_by=("n",)) @@ -91,16 +124,21 @@ def capture( worker = run.commander.comms.registry.require(run.worker) goal = worker.goal.require() history = len(run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)) - for path in (Path(worker.session_file), run.decisions, Path(worker.worktree)): + session_file = worker.session_file + if not session_file: + raise ValueError("The copied unit must have its own recorded native session") + for path in (Path(session_file), run.decisions, Path(worker.worktree)): path.resolve().relative_to(run.commander.root.resolve()) package = resolve_comms_route().launch().require_private().validate() subprocess.run( ( "node", "--no-global-search-paths", - str(Path(__file__).with_name("verdict_edit.mjs")), + "--input-type=module", + "-e", + NATIVE_EDIT, str(package.path), - worker.session_file, + session_file, str(run.decisions), call_id, cls.replacement(run), @@ -131,40 +169,44 @@ def capture( transitions = run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)[ history: ] - return VerdictEffect( + progress = GoalSegment.capture(worker, after, run.commander.comms.goals).progress + mutation = VerdictMutation( request.operation.tool_call_id, len(saved.verdicts) - len(before.verdicts), saved.revision - before.revision, saved.strike_count - before.strike_count, after.revision - goal.revision, len(transitions), - after.id == goal.id, - after.state == goal.state, - GoalSegment.capture(worker, after, run.commander.comms.goals).progress, - len(native), - result == TodoChanged(saved, saved.strike_count) - and request.operation in saved.verdicts, - all( - strike.at == request.operation.at - and strike.previous == request.operation.previous - for strike in saved.strikes[len(before.strikes) :] + progress, + ) + checks = ( + *mutation.checks(cls.changes), + ("same Goal identity", after.id == goal.id), + ("same Goal state", after.state == goal.state), + ("native result retained", result == TodoChanged(saved, saved.strike_count)), + ("native verdict retained", request.operation in saved.verdicts), + ( + "strike is the verdict event", + all( + strike.at == request.operation.at + and strike.previous == request.operation.previous + for strike in saved.strikes[len(before.strikes) :] + ), ), - isinstance(request.operation, cls.native_owner), - all(entry.before == goal and entry.after == after for entry in transitions), + ("declared verdict kind", isinstance(request.operation, cls.native_owner)), + ( + "Goal publication retained", + all(entry.before == goal and entry.after == after for entry in transitions), + ), + ("native verdict visible in Goal", request.operation.line in progress), + ) + return VerdictObservation( + f"{cls.native_owner.__module__}.{cls.native_owner.__qualname__}", + len(native), + checks, + "", + mutation, ) - - @classmethod - def valid(cls, effect: VerdictEffect) -> bool: - return ( - effect.verdicts == effect.todo_revisions == effect.native_calls == 1 - and effect.strikes == effect.goal_revisions == effect.goal_transitions == cls.changes - and effect.goal_retained - and effect.goal_state_retained - and effect.native_result_retained - and effect.strike_matches_verdict_event - and effect.declared_verdict_recorded - and effect.goal_transition_retained - ) class AcceptedCase(VerdictCase): @@ -190,10 +232,6 @@ class SendBackCase(VerdictCase): native_owner = SendBackVerdict why: ClassVar[str] = "The edit duplicates the copied workflow decision instead of removing it." - @classmethod - def valid(cls, effect: VerdictEffect) -> bool: - return super().valid(effect) and cls.why in effect.progress - @classmethod def replacement(cls, run: LifecycleRun) -> str: return json.dumps([run.worker, run.worker]) @@ -220,25 +258,32 @@ def command(cls, run: LifecycleRun, todo: Todo, edit: Edit) -> BackVerb: @dataclass(frozen=True) class VerdictComparison(ParityComparison): - accepted_current: VerdictEffect - accepted_native: VerdictEffect - back_current: VerdictEffect - back_native: VerdictEffect + accepted_current: VerdictObservation + accepted_native: VerdictObservation + back_current: VerdictObservation + back_native: VerdictObservation @property def passed(self) -> bool: + observations = ( + self.accepted_current, + self.accepted_native, + self.back_current, + self.back_native, + ) return ( - self.accepted_current == self.accepted_native - and self.back_current == self.back_native - and AcceptedCase.valid(self.accepted_native) - and SendBackCase.valid(self.back_native) + all(observation.passed for observation in observations) + and self.accepted_current.mutation == self.accepted_native.mutation + and self.back_current.mutation == self.back_native.mutation ) def describe(self) -> str: return ( - f"accepted current/native equal={self.accepted_current == self.accepted_native}; " - f"send-back current/native equal={self.back_current == self.back_native}; " - f"accepted={self.accepted_native!r}; send-back={self.back_native!r}" + "accepted current/native equal=" + f"{self.accepted_current.mutation == self.accepted_native.mutation}; " + "send-back current/native equal=" + f"{self.back_current.mutation == self.back_native.mutation}; " + f"accepted={self.accepted_native.describe()}; send-back={self.back_native.describe()}" ) @classmethod diff --git a/tests/test_commander_parity_verdicts.py b/tests/test_commander_parity_verdicts.py index 75676f9cf5..38385b033f 100644 --- a/tests/test_commander_parity_verdicts.py +++ b/tests/test_commander_parity_verdicts.py @@ -1,55 +1,43 @@ -"""Comparison invariants; the configured LifecycleRun journey supplies native proof.""" +"""Observation invariants; the configured LifecycleRun journey supplies native proof.""" from dataclasses import replace from agent_comms.commander_parity.rows import VerdictParityRow from agent_comms.commander_parity.verdicts import ( - AcceptedCase, - SendBackCase, VerdictComparison, - VerdictEffect, + VerdictMutation, + VerdictObservation, ) -def test_verdict_family_requires_the_actual_call_event_goal_and_strike_together(): - accepted = VerdictEffect( - call_id="one-call", - verdicts=1, - todo_revisions=1, - strikes=0, - goal_revisions=0, - goal_transitions=0, - goal_retained=True, - goal_state_retained=True, - progress="P1: Open", - native_calls=1, - native_result_retained=True, - strike_matches_verdict_event=True, - declared_verdict_recorded=True, - goal_transition_retained=True, - ) +def observation(mutation: VerdictMutation, changes: int) -> VerdictObservation: + return VerdictObservation("native verdict owner", 1, mutation.checks(changes), "", mutation) + + +def test_verdict_requires_the_actual_call_event_goal_and_strike_together(): + accepted = VerdictMutation("one-call", 1, 1, 0, 0, 0, "P1: Open · Accepted") back = replace( - accepted, strikes=1, goal_revisions=1, goal_transitions=1, progress=SendBackCase.why + accepted, strikes=1, goal_revisions=1, goal_transitions=1, progress="P1: Open · Sent back" ) - compared = VerdictComparison(accepted, accepted, back, back) - assert compared.passed and AcceptedCase.valid(accepted) and SendBackCase.valid(back) + ok, sent_back = observation(accepted, 0), observation(back, 1) + compared = VerdictComparison(ok, ok, sent_back, sent_back) + assert compared.passed for changed in ( replace(back, verdicts=0), replace(back, todo_revisions=2), replace(back, strikes=0), replace(back, goal_revisions=0), replace(back, goal_transitions=2), - replace(back, native_calls=0), - replace(back, goal_retained=False), - replace(back, goal_state_retained=False), - replace(back, native_result_retained=False), - replace(back, strike_matches_verdict_event=False), - replace(back, declared_verdict_recorded=False), - replace(back, goal_transition_retained=False), - replace(back, progress=""), ): - assert not SendBackCase.valid(changed) + observed = observation(changed, 1) + assert not observed.passed + assert not replace(compared, back_current=observed, back_native=observed).passed + for changed in ( + replace(sent_back, invocations=0), + replace(sent_back, checks=(*sent_back.checks, ("retained native identity", False))), + ): assert not replace(compared, back_current=changed, back_native=changed).passed - assert not replace(compared, accepted_native=replace(accepted, call_id="other-call")).passed + for changed in (replace(back, call_id="other-call"), replace(back, progress="")): + assert not replace(compared, back_native=observation(changed, 1)).passed assert "verify" not in VerdictParityRow.__dict__ assert VerdictParityRow.comparison == VerdictComparison.read From c2ab0ed39b213cff02dc51402348d45f795d82f0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:09:58 -0400 Subject: [PATCH 42/43] Use the shared bounded producer for real native edit execution --- src/agent_comms/commander_parity/verdicts.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index 06b8140e0a..3810a3eaea 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -6,7 +6,6 @@ import io import json import os -import subprocess from abc import ABC, abstractmethod from collections.abc import Callable from dataclasses import dataclass @@ -130,7 +129,7 @@ def capture( for path in (Path(session_file), run.decisions, Path(worker.worktree)): path.resolve().relative_to(run.commander.root.resolve()) package = resolve_comms_route().launch().require_private().validate() - subprocess.run( + child = run.invoke_command( ( "node", "--no-global-search-paths", @@ -145,11 +144,11 @@ def capture( ), cwd=worker.worktree, env=package.node_environment(dict(os.environ)), - capture_output=True, - text=True, - timeout=30, - check=True, ) + if not child.outcome.successful: + raise RuntimeError( + f"Native edit {child.outcome.summary}: {child.stderr.decode('utf-8')}" + ) edits = Edit.unjudged(run.commander.comms, run.commander.commander) if len(edits) != 1 or edits[0].id != call_id: raise ValueError("The copied unit must expose exactly its one real native edit") @@ -196,7 +195,10 @@ def capture( ("declared verdict kind", isinstance(request.operation, cls.native_owner)), ( "Goal publication retained", - all(entry.before == goal and entry.after == after for entry in transitions), + all( + entry.kind == "transition" and entry.before == goal and entry.after == after + for entry in transitions + ), ), ("native verdict visible in Goal", request.operation.line in progress), ) From f0900066c38fdc4e94da9e2d71e9ebddb09dca35 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 20 Oct 2026 00:26:46 -0400 Subject: [PATCH 43/43] Keep verdict proof on committed Goal history and correct its owner declaration --- plans/commander-native-plan.md | 2 +- src/agent_comms/commander_parity/rows.py | 2 +- src/agent_comms/commander_parity/verdicts.py | 14 ++++++-------- tests/test_commander_parity_verdicts.py | 8 ++++++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/plans/commander-native-plan.md b/plans/commander-native-plan.md index 4fbae94e3a..d1c870e1d3 100644 --- a/plans/commander-native-plan.md +++ b/plans/commander-native-plan.md @@ -38,7 +38,7 @@ | 5 | Board, overdue, silent, spinning | `order.py board`, `overdue`; `fleet-board.md` | `CommsTodoListTool` and the fleet tool, derived | `one_order_owner` | | 6 | Branch hygiene | `branches-*.txt` by hand | `CommsWorkBranchesTool` | `one_order_owner` | | 7 | Strikes and the strike limit | `order.py strike`; `strikes.jsonl` | `Strike` rows, author-declared `StrikeLimit`, one `comms_fire` | `record_plane` | -| 8 | Verdicts on a unit's tool calls | `judge.py ok`, `back`; `verdicts.jsonl` | Order `Verdict`; send-back revises the Goal and records its strike atomically | `record_plane` | +| 8 | Verdicts on a unit's tool calls | `judge.py ok`, `back`; both invoke `CommsVerdictTool` | Order `Verdict`; send-back revises the Goal and records its strike atomically | `record_plane` | | 9 | Plain words in verdicts and strikes | judge and order checks | `PlainText` | `record_plane` | | 10 | Refuse soft or vague orders | `order.py` | `OrderText` on every order action | `record_plane` | | 11 | What a unit did during its order | `tool_watch.py`; `edits.jsonl`; `judge.py show` | `comms_order_work`: both transcript families by `tool_call_id` | `review_queue` | diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index 4d6cf110fe..458b1fdd1a 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -94,7 +94,7 @@ class StrikeLimitParityRow(ParityRow): class VerdictParityRow(ParityRow): number = 8 capability = "Verdicts on a unit's tool calls" - current = "`judge.py ok`, `back`; `verdicts.jsonl`" + current = "`judge.py ok`, `back`; both invoke `CommsVerdictTool`" native = "Order `Verdict`; send-back revises the Goal and records its strike atomically" phases = (RecordPlanePhase,) comparison = VerdictComparison.read diff --git a/src/agent_comms/commander_parity/verdicts.py b/src/agent_comms/commander_parity/verdicts.py index 3810a3eaea..e194d7fbf7 100644 --- a/src/agent_comms/commander_parity/verdicts.py +++ b/src/agent_comms/commander_parity/verdicts.py @@ -58,7 +58,7 @@ class VerdictMutation: todo_revisions: int strikes: int goal_revisions: int - goal_transitions: int + goal_history_entries: int progress: str def checks(self, changes: int) -> tuple[tuple[str, bool], ...]: @@ -67,7 +67,7 @@ def checks(self, changes: int) -> tuple[tuple[str, bool], ...]: ("one Todo revision", self.todo_revisions == 1), ("strike delta", self.strikes == changes), ("Goal revision delta", self.goal_revisions == changes), - ("committed Goal transition delta", self.goal_transitions == changes), + ("committed Goal history delta", self.goal_history_entries == changes), ) @@ -165,7 +165,8 @@ def capture( if len(native) != 1 or native[0][0].operation.tool_call_id != call_id: raise ValueError("The native verdict must judge this one original copied tool call") request, result = native[0] - transitions = run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)[ + # goal_history returns committed revisions; its internal tags stay there. + history_entries = run.commander.comms.goals.goal_history(run.worker, goal_id=goal.id)[ history: ] progress = GoalSegment.capture(worker, after, run.commander.comms.goals).progress @@ -175,7 +176,7 @@ def capture( saved.revision - before.revision, saved.strike_count - before.strike_count, after.revision - goal.revision, - len(transitions), + len(history_entries), progress, ) checks = ( @@ -195,10 +196,7 @@ def capture( ("declared verdict kind", isinstance(request.operation, cls.native_owner)), ( "Goal publication retained", - all( - entry.kind == "transition" and entry.before == goal and entry.after == after - for entry in transitions - ), + all(entry.before == goal and entry.after == after for entry in history_entries), ), ("native verdict visible in Goal", request.operation.line in progress), ) diff --git a/tests/test_commander_parity_verdicts.py b/tests/test_commander_parity_verdicts.py index 38385b033f..c5264b93cb 100644 --- a/tests/test_commander_parity_verdicts.py +++ b/tests/test_commander_parity_verdicts.py @@ -17,7 +17,11 @@ def observation(mutation: VerdictMutation, changes: int) -> VerdictObservation: def test_verdict_requires_the_actual_call_event_goal_and_strike_together(): accepted = VerdictMutation("one-call", 1, 1, 0, 0, 0, "P1: Open · Accepted") back = replace( - accepted, strikes=1, goal_revisions=1, goal_transitions=1, progress="P1: Open · Sent back" + accepted, + strikes=1, + goal_revisions=1, + goal_history_entries=1, + progress="P1: Open · Sent back", ) ok, sent_back = observation(accepted, 0), observation(back, 1) compared = VerdictComparison(ok, ok, sent_back, sent_back) @@ -27,7 +31,7 @@ def test_verdict_requires_the_actual_call_event_goal_and_strike_together(): replace(back, todo_revisions=2), replace(back, strikes=0), replace(back, goal_revisions=0), - replace(back, goal_transitions=2), + replace(back, goal_history_entries=2), ): observed = observation(changed, 1) assert not observed.passed