From abb02fa3a628a9d8ac145a036c53919cffefd2a4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:45:37 -0400 Subject: [PATCH 01/21] Describe checked work dispatch ownership and live conflicts --- docs/refactor/phase7-dispatch.md | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/refactor/phase7-dispatch.md diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md new file mode 100644 index 0000000000..f494eb1d8b --- /dev/null +++ b/docs/refactor/phase7-dispatch.md @@ -0,0 +1,48 @@ +# Phase 7: start checked, ready work + +Owner: pi-cn-phase7. Base: origin/main 011084fc5; plan dependency change 89f53149a is integrated from pi-plan-claims. + +## First slice + +`PlanIdle` reports ready phases; rotation only prints its strings. Existing +`UnownedWheelSpin` requires an already-idle unit, leaving unowned work untouched +when none exists. A never-ordered phase has no checked task declaration. Inventing +an acceptance command from a phase name would bypass the order's actual owner. + +Put optional checked tasks on PlanPhase, using PendingProblemOrder rather than a +second order schema. Unspecified tasks remain visible and allocate nothing. +Checked tasks and released orders share native unit startup: storage preflight, +isolated worktree, stopped thread declaration, native order publication, then the +existing explicit owner start. Publication precedes execution. Keep failed or +uncertain startup state; do not replay or remove it automatically. + +`StandingRule.enforce`, already called by CommsStandingRulesTool and rotation, +is the entrypoint. No rotation-function edit is necessary. Delete the competing +UnownedWheelSpin path; preserve FlatWheelSpin's separate reassignment behavior. + +## Coordination and verification limits + +PR #1912 changes rotate.main/broken_rules/owed_orders, plus commander context. +Leave those functions alone. #1756 changes order/judge pattern IDs, which this +slice does not touch. No Toad file changes are needed. + +pi-plan-claims owns plan_phases.py, and explicitly permits only the task imports +and PlanPhase.task declaration in this slice. It retains shared claims and plan +parent/dependency ownership. Checked production task briefs are not yet supplied. + +The current instruction forbids launching another agent. Do not run the requested +scratch Pi startup proof under that restriction. Source checks cannot replace the +required unit/order/first-reply proof. + +Resource helper /home/ts/bin/agent-resource-check is absent. Manual observation: +4.1 GiB available RAM; 26 GiB disk available. Keep checks small and serial. Scratch +owner: pi-cn-phase7; purpose: bounded final checks; path: +/home/ts/.cache/agent-scratch/pi-cn-phase7. No saved user or Codex sessions belong +in that disposable directory. + +## Source evidence + +NRA import is currently blocked by DuplicateRegistryKeyError in its detector +registration against the installed metaclass-registry. Use refactor-audit's +existing Package parser for the bounded AST census instead, recording omissions. +AST results establish source sites, not live binding or runtime equivalence. From 148b96d6bc9b3dba14377a24029d93e8bd0ea72b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:50:34 -0400 Subject: [PATCH 02/21] Dispatch declared phase tasks and unowned orders through native owners --- docs/refactor/phase7-dispatch.md | 30 ++++++++++++++++++++++++++++++ src/agent_comms/standing_rules.py | 26 +++++++++++++++++++++++++- src/agent_comms/todos.py | 2 ++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index f494eb1d8b..4de77c7fc7 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -46,3 +46,33 @@ NRA import is currently blocked by DuplicateRegistryKeyError in its detector registration against the installed metaclass-registry. Use refactor-audit's existing Package parser for the bounded AST census instead, recording omissions. AST results establish source sites, not live binding or runtime equivalence. + +Before edit: refactor-audit Package.load over all 379 production Python modules, +zero parse omissions. Enumerated classes/bases and matching references across the +whole package; imports, writes, comparisons and decisions in the rule/phase +owners. Semantic consumers: tools.CommsStandingRulesTool.apply -> +StandingRule.broken/enforce; commander.rotate.broken_rules invokes that tool; +PlanIdle owns phase readiness attention; commander.standing.UnownedWheelSpin +owned the competing idle-unit-only path; GoalAction subclasses and TodoStore +own checked assignment; Registration and OwnerLifecycle own process identity +and launch; CheckoutStorageEffect owns allocation policy. No dependency code is +changed. Dynamic DeclaredFamily discovery and provider execution remain runtime +obligations, not statically proven bindings. + +Implemented: PhaseTask.start/done, with an unspecified member that cannot invent +an order and a checked member carrying PendingProblemOrder. PlanIdle delegates to +its phase task. UnownedWork replaces UnownedWheelSpin and starts a dedicated unit. +WorkDispatch checks storage, fetches main, creates the owned worktree, registers +a stopped native thread, publishes its checked order, then calls OwnerLifecycle +start. No fork copies, new launch codec or second task/order store. + +Completion uses the explicit task problem and ProblemOrder.accepted: only +InstalledProblemOrder accepts; a withdrawn/folded/merely delivered problem does +not complete the phase. pi-plan-claims is integrating PlanPhase.done and A2's +corrected dependencies separately. + +Source trace also exposed an authority bypass: releasing an order skipped +Assignment.require_trace on its next transfer. Todo.assigned now applies the +same trace requirement to the released assignment history. Auto-dispatch passes +no fabricated source trace. After two owners, missing real tracing stops that +transfer rather than pretending the old order's source owner was newly read. diff --git a/src/agent_comms/standing_rules.py b/src/agent_comms/standing_rules.py index 3fbf3b08a9..8c681e0b31 100644 --- a/src/agent_comms/standing_rules.py +++ b/src/agent_comms/standing_rules.py @@ -217,13 +217,37 @@ def violations(cls, comms: Comms, commander: str) -> tuple[Self, ...]: if phase.ready(todos) and not phase.ordered(todos) ) + def enforce(self, comms: Comms, commander: str) -> None: + self.phase.task.start(comms, comms.registry.require(commander), self.phase) + def line(self) -> str: return ( f"PLAN IDLE: phase {self.phase.declared_name} ready, no open order" - " -> dispatch it this wake" + f" -> {self.phase.task.instruction}" ) +@dataclass(frozen=True) +class UnownedWork(UndatedStandingRule): + """An unowned checked row starts a unit, even when the idle fleet is empty.""" + + todo: Todo + + @classmethod + def violations(cls, comms: Comms, commander: str) -> tuple[Self, ...]: + return tuple(cls(todo) for todo in comms.goals.todos.for_commander( + comms.registry.require(commander).incarnation) if todo.pending and not todo.holder) + + def enforce(self, comms: Comms, commander: str) -> None: + from .work_dispatch import WorkDispatch + + WorkDispatch.reissue(comms, comms.registry.require(commander), self.todo) + + def line(self) -> str: + return (f"UNOWNED WORK: {self.todo.id} has a checked order and no unit" + " -> Core starts its unit in an isolated checkout") + + @dataclass(frozen=True) class MemoryLow(UndatedStandingRule): """Under the memory threshold a cleanup owner is due, told what holds the memory.""" diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index ea58133091..d94af58112 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -2419,6 +2419,8 @@ def assigned(self, proposed: Assignment, traced: FileLine | None = None) -> Todo if self.assignment is not None: self.assignment.require_trace(proposed.owner, traced) proposed = self.assignment.successor(proposed, self) + elif self.released: + self.released[-1].previous.require_trace(proposed.owner, traced) return replace(self, assignment=proposed, revision=self.revision + 1) def reassigned(self, assigned: Todo, actor: ThreadIncarnation) -> Todo: From 2de12583108ea0624e4f0d6edb7fab92524c602d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:54:47 -0400 Subject: [PATCH 03/21] Check phase acceptance and preserve released assignment tracing --- src/agent_comms/work_dispatch.py | 11 ++-- tests/test_standing_rules.py | 16 +++--- tests/test_work_dispatch.py | 91 ++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 tests/test_work_dispatch.py diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index e9e880a3ec..26624881a8 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -7,6 +7,7 @@ from __future__ import annotations +import math import subprocess import time from collections.abc import Iterable @@ -122,16 +123,16 @@ class CheckedPhaseTask(PhaseTask): instruction = "Core starts the declared checked task in its own unit checkout" def __post_init__(self) -> None: - if not 0 < self.deadline_seconds <= 24 * 60 * 60: - raise ValueError("A phase task needs a positive deadline of at most one day.") + if not math.isfinite(self.deadline_seconds) or self.deadline_seconds <= 0: + raise ValueError("A phase task needs a finite positive deadline interval.") def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: history = comms.goals.todos.history_for_commander(commander.incarnation) if not phase.ready(history) or phase.ordered(history): return - # An existing unowned row belongs to UnownedWork, which reissues that - # row's actual order instead of replacing it with a phase template. - if any(todo.state.open for todo in phase.serving(history)): + # This task's existing row belongs to UnownedWork. Other phase subtasks + # neither supply this task's check nor replace its explicit identity. + if any(todo.id == self.problem for todo in history): return now = time.time() order = replace(self.order.reissued(now), serves=phase) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index bfcd5ba98f..0c9bea8c10 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -20,7 +20,7 @@ from agent_comms.recorded_time import RecordedEventTime from agent_comms.rule_reviews import AnsweredRuleReviewEntry, DrawnRuleReviewEntry from agent_comms.standing_rules import (BoardDisagrees, BridgeDown, DiskLow, HuntDue, MemoryLow, PlanIdle, - RulesDue, StandingRule) + RulesDue, StandingRule, UnownedWork) from agent_comms.storage_rules import MemoryHolder, UnitStorage from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_status import StoppedThreadStatus @@ -107,7 +107,8 @@ def idle(): assert RecordPlanePhase in idle() and ReviewQueuePhase not in idle() assert PlanIdle(RecordPlanePhase).line() == ( - "PLAN IDLE: phase record_plane ready, no open order -> dispatch it this wake") + "PLAN IDLE: phase record_plane ready, no open order -> " + "no checked task declared; supply its source, check and brief") create(commander, "P1") assert comms.goals.todos.get("P1").order.serves is UnplannedPhase assert RecordPlanePhase in idle() @@ -241,10 +242,13 @@ def progress(): taken = StrikeReason("The unit was taken off P2 so the test leaves it unowned.") comms.goals.fire("worker", taken, "lead") assert comms.goals.todos.get("P2").holder is None - read = CommsStandingRulesTool().apply(comms, ThreadCaller("lead")) - assert ("WHEEL SPIN: worker holds no order while P2 is unowned -> Core gives it to worker" - in read.broken) - assert comms.goals.todos.get("P2").holder == "worker" + [unowned] = UnownedWork.violations(comms, "lead") + assert unowned.todo.id == "P2" + assert unowned.line() == ( + "UNOWNED WORK: P2 has a checked order and no unit" + " -> Core starts its unit in an isolated checkout") + # Actual unit launch/first reply is a separate native application proof. + assert comms.goals.todos.get("P2").holder is None def test_unjudged_follows_the_unjudged_edits(commander, standing): diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py new file mode 100644 index 0000000000..6aae65a1ae --- /dev/null +++ b/tests/test_work_dispatch.py @@ -0,0 +1,91 @@ +"""State-level sanity checks, not the required native startup/first-reply proof.""" + +from dataclasses import replace + +import pytest + +from agent_comms.goal_actions import CreateProblemGoalAction +from agent_comms.plan_phases import RecordPlanePhase +from agent_comms.recorded_time import RecordedEventTime +from agent_comms.todos import DoneTodoState, OperationDelivery, StrikeReason +from agent_comms.work_dispatch import CheckedPhaseTask, UnspecifiedPhaseTask, WorkDispatch + +from test_commander_problem_orders import create, order + + +def test_phase_task_accepts_only_its_full_problem_with_installed_evidence(commander): + comms, _ = commander + full, subtask = (create(commander, name) for name in ("P1", "P2")) + task = CheckedPhaseTask(full.id, full.repo, full.text, full.order, 1800) + accepted = full.order.delivered( + OperationDelivery(at=RecordedEventTime(200)), + ).installed("The installed application now completes the declared full phase workflow.", "run.txt") + assert not task.done((full, subtask)) + assert not task.done((replace(subtask, state=DoneTodoState, order=accepted),)) + assert not task.done((replace(full, state=DoneTodoState),)) + assert not task.done((replace(full, order=accepted),)) + assert task.done((replace(full, state=DoneTodoState, order=accepted),)) + assert not UnspecifiedPhaseTask().done((replace(full, state=DoneTodoState, order=accepted),)) + + +@pytest.mark.parametrize("interval", [0, -1, float("nan"), float("inf")]) +def test_checked_task_requires_a_finite_positive_deadline(commander, interval): + comms, _ = commander + with pytest.raises(ValueError, match="finite positive"): + CheckedPhaseTask("P1", "org/repo", "Full phase", order(comms.root, "full"), interval) + + +def test_unspecified_phase_cannot_allocate_or_fabricate_an_order(commander): + comms, _ = commander + lead = comms.registry.require("lead") + before = comms.registry.all_threads() + task = UnspecifiedPhaseTask() + task.start(comms, lead, RecordPlanePhase) + assert comms.registry.all_threads() == before + assert comms.goals.todos.history_for_commander(lead.incarnation) == () + + +def test_invalid_request_refuses_dispatch_before_allocating_anything(commander): + comms, _ = commander + checked = order(comms.root, "full-phase-order") + action = CreateProblemGoalAction( + problem="P1", repo="OpenHCSDev/agent-comms", text="Repair the full phase", + order=checked, request="not-the-active-request", commander="lead", + deadline=RecordedEventTime(1000), + ) + name = "unit-p1-r0" + path = comms.registry.storage_rule.order_checkout(name, "P1") + assert not path.exists() + before = comms.registry.all_threads() + with pytest.raises(ValueError): + WorkDispatch(action, revision=0).start(comms) + assert not path.exists() + assert comms.registry.all_threads() == before + + +def test_released_order_preserves_the_two_owner_trace_requirement(commander): + comms, _ = commander + lead, worker = (comms.registry.require(name) for name in ("lead", "worker")) + second, third = ( + comms.registry.declare(comms.registry.new_thread(name, frozenset(), str(comms.root))) + for name in ("second", "third") + ) + todo = create(commander, "P1") + reason = StrikeReason("The previous owner stopped work and left the checked order unfinished.") + comms.goals.todos.fire(worker.name, lead.incarnation, reason) + released = comms.goals.todos.get(todo.id) + assigned = comms.goals.todos.assign( + todo.id, expected_revision=released.revision, owner=second, parent=lead, + generation="second-generation", goal=todo.goal, + deadline=todo.deadline, order=todo.order.reissued(200), + ) + assert assigned.holder == second.name + comms.goals.todos.fire(second.name, lead.incarnation, reason) + released = comms.goals.todos.get(todo.id) + with pytest.raises(ValueError, match="pass --traced"): + comms.goals.todos.assign( + todo.id, expected_revision=released.revision, owner=third, parent=lead, + generation="third-generation", goal=todo.goal, + deadline=todo.deadline, order=todo.order.reissued(300), + ) + assert comms.goals.todos.get(todo.id) == released From 57c1dfb226ddfa00b05355cc4bca44b5f75c81ff Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:58:54 -0400 Subject: [PATCH 04/21] Record dispatch source checks and remaining native proof --- docs/refactor/phase7-dispatch.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index 4de77c7fc7..e6246fde46 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -68,11 +68,38 @@ start. No fork copies, new launch codec or second task/order store. Completion uses the explicit task problem and ProblemOrder.accepted: only InstalledProblemOrder accepts; a withdrawn/folded/merely delivered problem does -not complete the phase. pi-plan-claims is integrating PlanPhase.done and A2's -corrected dependencies separately. +not complete the phase. pi-plan-claims' a44d1be1e completion/A2 change is merged +normally into this branch. Its borrowed module was reconciled with the current +finite-deadline and exact-task identity checks. Source trace also exposed an authority bypass: releasing an order skipped Assignment.require_trace on its next transfer. Todo.assigned now applies the same trace requirement to the released assignment history. Auto-dispatch passes no fabricated source trace. After two owners, missing real tracing stops that transfer rather than pretending the old order's source owner was newly read. + +## Final checkpoint + +After edit: refactor-audit parsed 380 Core production modules and 283 Toad +modules (ba57101e), zero parse omissions. UnownedWheelSpin has no declaration +or consumers. PlanPhase.done and PlanIdle consume PhaseTask; task.done is the +only order.accepted consumer. Toad has no imports of the changed owner classes. +Dynamic discovery and provider execution remain unproved. + +Source imports and git diff --check pass. Final bounded background run used +`timeout 300`, Python `-X faulthandler`, `-o addopts=''`, one process and an owned +basetemp: 12 tests passed in 10.64 seconds (work_dispatch, plan_phases and affected +PlanIdle/wheel-spin cases). pytest-timeout is absent; external timeout applies. + +The broader check had 16 passes and six failures: four board cases read missing +checkout /home/ts/wt/worker--P109, one compares changing memory snapshots, and my +new trace test used a banned word. The last is fixed and passes. This is not a +passing full standing-rule suite. pi-cn-phase7 owns the missing-checkout rotation +risk: reproduce with the board self-check cases; acceptance must preserve the +remote PR fact without a local checkout. Default eight-worker/coverage settings +were needless overhead for that bounded run; final checks were serial. + +No agent launch, provider input, installation or PR merge occurred. Draft #1932 +is not a first-reply proof. Production checked full-phase briefs and the scratch +unit/order/first-reply proof remain required. Unordered rows without a checked +order cannot be dispatched by inventing their missing check. From 89fd350b231cc114263b76b8f18ec79b151ff386 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:02:09 -0400 Subject: [PATCH 05/21] Draft the missing full control-plane task and name the launch restriction --- docs/refactor/phase7-dispatch.md | 5 ++ docs/refactor/phase7-task-draft.md | 89 ++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 docs/refactor/phase7-task-draft.md diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index e6246fde46..2cae3212d9 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -103,3 +103,8 @@ No agent launch, provider input, installation or PR merge occurred. Draft #1932 is not a first-reply proof. Production checked full-phase briefs and the scratch unit/order/first-reply proof remain required. Unordered rows without a checked order cannot be dispatched by inventing their missing check. + +The exact delivery-frame launch restriction, missing production declaration sites, +and a full ControlPlanePhase checked brief draft are in +[phase7-task-draft.md](phase7-task-draft.md). The native acceptance entrypoint is +unfinished implementation work here, not a request for the owner to write it. diff --git a/docs/refactor/phase7-task-draft.md b/docs/refactor/phase7-task-draft.md new file mode 100644 index 0000000000..c7615131a7 --- /dev/null +++ b/docs/refactor/phase7-task-draft.md @@ -0,0 +1,89 @@ +# Phase 7 checked task draft + +Owner: pi-cn-phase7. PR: #1932. Source: 7cf2c518d. + +## Exact launch restriction + +The user-message delivery frame says: + +> Do not send a duplicate reply or launch another agent. + +This is not a skill instruction, Core rule or tool refusal. No scratch launch was +attempted, so there is no Core refusal to report. The frame accompanying the +owner's scratch-launch authorization contains this same prohibition. The live +bus has not been used for proof. + +## Missing declarations + +`src/agent_comms/plan_phases.py:89`, ControlPlanePhase, declares only its dependency. +It inherits `task = UnspecifiedPhaseTask()` from line 33. That is the missing +production task declaration for my phase. `RecordPlanePhase` at line 80 also has +no checked task; it is the prerequisite currently preventing phase 7 readiness. +Other unspecified task slots are CutoverPhase:93, StoryPhase:99, +CommanderPagePhase:105 and SkillPhase:111. Previously delivered phases have +explicit delivery declarations instead. A closed story-related subtask does not +supply a full-phase checked task. + +The draft below belongs on ControlPlanePhase.task, not in a phase-name lookup. +It must become a CheckedPhaseTask carrying one PendingProblemOrder. The native +problem ID must be allocated against the intended commander's board, not guessed +from the PR number or an unrelated scratch board. The first dispatch proof is a +slice of this task, not permission to mark the whole phase accepted. + +## Draft full-phase order + +Repository: OpenHCSDev/agent-comms. +Source owner: src/agent_comms/standing_rules.py:25, StandingRule. +Serves: ControlPlanePhase. + +Problem: Ready checked work waits for a manual order, while rotation, load, +review and storage still depend partly on private commander script state. + +1. Make StandingRule enforcement start an eligible checked phase or unowned + checked row through the native order and lifecycle owners. Publish the order + before execution. Give the Pi unit the declared gpt-6.1-sol selection and its + own persistent ~/wt/--P checkout. Do not replay uncertain startup. +2. Move the minute wake into Core's existing scheduling owner. Derive its next + review from canonical orders and activity. End the wake when its commander + stops. Delete the replaced rotate loop once native parity is demonstrated. +3. Complete the declared StandingRule members and source-provenanced rule context. + Derive CommanderLoad and UnitTime from orders, unjudged work and turn records. + Fork through the existing registered-thread operation; delete private beats. +4. Derive growth and UnitStorage signals from their existing owners. Enforce + allocation policy before allocating. Keep the harness send boundary checking + native orders. Delete the script/private-file paths each native owner replaces. +5. Deliver only decision crossings once: addressed replies, deadline crossings, + merges, failed checks and installed closes. Preserve parallel execution, + original histories and uncertain inputs. Do not turn counters or repeated + overdue state into repeated commander messages. + +Banned: live-bus proof writes; invented acceptance commands; a second order, +launch, task, rule or state codec; phase-name dispatch maps; private heartbeat +files; treating a closed subtask or a fixture pass as full-phase acceptance. + +Works when: On a scratch bus, rotation starts a Pi unit for one explicitly +checked ready phase, records its order, and receives its first real reply. A +second rotation creates no duplicate unit or order. A full native commander +session then runs with no script process; replay of a preserved hour on a copy +produces only the specified decisions, once each, and UnitTime classes sum to +each unit's elapsed time. Stop the scratch commander and observe its wake end. +Record the build's Core/Toad revisions with these results. + +Report: Name the started unit, retained order, first reply and build; list the +removed competing paths and every remaining native/parity failure plainly. +Do not close this full-phase task on the initial dispatch-only proof. + +## Check construction, not a fabricated green command + +PendingProblemOrder.check must run the actual affected native acceptance journey +above and fail if any required observation is missing. The repository currently +has no runner for that whole control-plane journey. The existing 12 focused tests +are useful sanity checks but are not that Check. This PR does not silently put +pytest, grep, or an invented command in that field. Implementing and running the +native acceptance entrypoint remains my work, not missing input from the owner. + +Likewise, construction must occur after PendingProblemOrder is available: todos +imports plan_phases, so eagerly importing/constructing an order inside a phase +class would recurse through that boundary. Keep declaration-owned behavior and +lazy task construction rather than adding another phase roster. Coordinate that +shared API change with pi-plan-claims, which owns the phase class. From c5bff8b7a4683c4963fc00735a5a4e8612f71f03 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:13:01 -0400 Subject: [PATCH 06/21] Add real scratch rotation proof and stage phase task declarations --- docs/refactor/phase7-dispatch.md | 16 ++- docs/refactor/phase7-task-draft.md | 89 ---------------- phase7-task-declarations.patch | 97 +++++++++++++++++ tools/prove_commander_dispatch.py | 166 +++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 93 deletions(-) delete mode 100644 docs/refactor/phase7-task-draft.md create mode 100644 phase7-task-declarations.patch create mode 100644 tools/prove_commander_dispatch.py diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index 2cae3212d9..c291f62be7 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -104,7 +104,15 @@ is not a first-reply proof. Production checked full-phase briefs and the scratch unit/order/first-reply proof remain required. Unordered rows without a checked order cannot be dispatched by inventing their missing check. -The exact delivery-frame launch restriction, missing production declaration sites, -and a full ControlPlanePhase checked brief draft are in -[phase7-task-draft.md](phase7-task-draft.md). The native acceptance entrypoint is -unfinished implementation work here, not a request for the owner to write it. +The full ControlPlanePhase brief draft was moved into PR #1932's body at the +owner's request. The owner explicitly authorized rotation to start the proof unit +on a scratch bus; no helper delegation or live-bus proof writes are authorized. +The real proof runner is tools/prove_commander_dispatch.py. Its new scratch +root, configuration, native history and receipts are retained under +/home/ts/.cache/agent-scratch/pi-cn-phase7/run-*/. Owner: pi-cn-phase7; purpose: +first native reply and duplicate-dispatch acceptance. Native unit checkouts use +the existing StorageRule's /home/ts/wt/--P allocation. No auth is copied. +The development checkout reuses an unchanged prepared native package by reflink +copy at stack/.pi-native-a0f57a811b47862f (192 MiB apparent size). Its manifest +matches main and VerifiedNativePackage.acquire accepts it. The native verifier +correctly refused a symlink; no gate was disabled and no live package changed. diff --git a/docs/refactor/phase7-task-draft.md b/docs/refactor/phase7-task-draft.md deleted file mode 100644 index c7615131a7..0000000000 --- a/docs/refactor/phase7-task-draft.md +++ /dev/null @@ -1,89 +0,0 @@ -# Phase 7 checked task draft - -Owner: pi-cn-phase7. PR: #1932. Source: 7cf2c518d. - -## Exact launch restriction - -The user-message delivery frame says: - -> Do not send a duplicate reply or launch another agent. - -This is not a skill instruction, Core rule or tool refusal. No scratch launch was -attempted, so there is no Core refusal to report. The frame accompanying the -owner's scratch-launch authorization contains this same prohibition. The live -bus has not been used for proof. - -## Missing declarations - -`src/agent_comms/plan_phases.py:89`, ControlPlanePhase, declares only its dependency. -It inherits `task = UnspecifiedPhaseTask()` from line 33. That is the missing -production task declaration for my phase. `RecordPlanePhase` at line 80 also has -no checked task; it is the prerequisite currently preventing phase 7 readiness. -Other unspecified task slots are CutoverPhase:93, StoryPhase:99, -CommanderPagePhase:105 and SkillPhase:111. Previously delivered phases have -explicit delivery declarations instead. A closed story-related subtask does not -supply a full-phase checked task. - -The draft below belongs on ControlPlanePhase.task, not in a phase-name lookup. -It must become a CheckedPhaseTask carrying one PendingProblemOrder. The native -problem ID must be allocated against the intended commander's board, not guessed -from the PR number or an unrelated scratch board. The first dispatch proof is a -slice of this task, not permission to mark the whole phase accepted. - -## Draft full-phase order - -Repository: OpenHCSDev/agent-comms. -Source owner: src/agent_comms/standing_rules.py:25, StandingRule. -Serves: ControlPlanePhase. - -Problem: Ready checked work waits for a manual order, while rotation, load, -review and storage still depend partly on private commander script state. - -1. Make StandingRule enforcement start an eligible checked phase or unowned - checked row through the native order and lifecycle owners. Publish the order - before execution. Give the Pi unit the declared gpt-6.1-sol selection and its - own persistent ~/wt/--P checkout. Do not replay uncertain startup. -2. Move the minute wake into Core's existing scheduling owner. Derive its next - review from canonical orders and activity. End the wake when its commander - stops. Delete the replaced rotate loop once native parity is demonstrated. -3. Complete the declared StandingRule members and source-provenanced rule context. - Derive CommanderLoad and UnitTime from orders, unjudged work and turn records. - Fork through the existing registered-thread operation; delete private beats. -4. Derive growth and UnitStorage signals from their existing owners. Enforce - allocation policy before allocating. Keep the harness send boundary checking - native orders. Delete the script/private-file paths each native owner replaces. -5. Deliver only decision crossings once: addressed replies, deadline crossings, - merges, failed checks and installed closes. Preserve parallel execution, - original histories and uncertain inputs. Do not turn counters or repeated - overdue state into repeated commander messages. - -Banned: live-bus proof writes; invented acceptance commands; a second order, -launch, task, rule or state codec; phase-name dispatch maps; private heartbeat -files; treating a closed subtask or a fixture pass as full-phase acceptance. - -Works when: On a scratch bus, rotation starts a Pi unit for one explicitly -checked ready phase, records its order, and receives its first real reply. A -second rotation creates no duplicate unit or order. A full native commander -session then runs with no script process; replay of a preserved hour on a copy -produces only the specified decisions, once each, and UnitTime classes sum to -each unit's elapsed time. Stop the scratch commander and observe its wake end. -Record the build's Core/Toad revisions with these results. - -Report: Name the started unit, retained order, first reply and build; list the -removed competing paths and every remaining native/parity failure plainly. -Do not close this full-phase task on the initial dispatch-only proof. - -## Check construction, not a fabricated green command - -PendingProblemOrder.check must run the actual affected native acceptance journey -above and fail if any required observation is missing. The repository currently -has no runner for that whole control-plane journey. The existing 12 focused tests -are useful sanity checks but are not that Check. This PR does not silently put -pytest, grep, or an invented command in that field. Implementing and running the -native acceptance entrypoint remains my work, not missing input from the owner. - -Likewise, construction must occur after PendingProblemOrder is available: todos -imports plan_phases, so eagerly importing/constructing an order inside a phase -class would recurse through that boundary. Keep declaration-owned behavior and -lazy task construction rather than adding another phase roster. Coordinate that -shared API change with pi-plan-claims, which owns the phase class. diff --git a/phase7-task-declarations.patch b/phase7-task-declarations.patch new file mode 100644 index 0000000000..7211d37ef2 --- /dev/null +++ b/phase7-task-declarations.patch @@ -0,0 +1,97 @@ +--- a/src/agent_comms/plan_phases.py ++++ b/src/agent_comms/plan_phases.py +@@ -30,7 +30,9 @@ + + class PlanPhase(OrderPhase, declared_name=None): + after: ClassVar[tuple[type[PlanPhase], ...]] = () +- task: ClassVar[PhaseTask] = UnspecifiedPhaseTask() ++ @classmethod ++ def task(cls) -> PhaseTask: ++ return UnspecifiedPhaseTask() + + @classmethod + def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: +@@ -38,7 +40,7 @@ + + @classmethod + def done(cls, todos: Iterable[Todo]) -> bool: +- return cls.task.done(cls.serving(todos)) ++ return cls.task().done(cls.serving(todos)) + + @classmethod + def ordered(cls, todos: Iterable[Todo]) -> bool: +@@ -80,6 +82,36 @@ + class RecordPlanePhase(PlanPhase): + after = (RetainedStorePhase,) + ++ @classmethod ++ def task(cls) -> PhaseTask: ++ import shlex ++ import sys ++ from pathlib import Path ++ from .change_report import FileLine ++ from .goals import OrderCheck ++ from .recorded_time import RecordedEventTime ++ from .todos import ApplicationOrderRun, PendingProblemOrder ++ from .work_dispatch import CheckedPhaseTask ++ ++ checkout = Path(__file__).resolve().parents[2] ++ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", ++ "-m", "pytest", "-o", "addopts=", "-n", "0", ++ "tests/test_commander_problem_orders.py", "tests/test_commander_run_rules.py")) ++ return CheckedPhaseTask( ++ problem="P900003", repo="OpenHCSDev/agent-comms", ++ text="Complete and verify the full native commander record plane.", ++ deadline_seconds=7200, ++ order=PendingProblemOrder( ++ id="record-plane-full-task", owner=FileLine(str(Path(__file__).with_name("todos.py")), 1066), ++ check=OrderCheck(str(checkout), check), ++ edits=("Finish native verdict, strike, transfer, delivery and build-bound closing through the existing Todo and Goal owners.",), ++ banned=("No private verdict or strike files; no fixture pass claimed as native application acceptance.",), ++ report="Report the delivered commit and the copied-root native record-plane observations with their build revisions.", ++ run=ApplicationOrderRun("On a copied root, a real send-back, missed deadline and false completion each produce one record; a second strike fires the unit once; missing Works and wrong-build evidence are refused; a delivered checked order cannot transfer; renaming a deletion target cannot pass; only real installed evidence closes the whole task."), ++ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), ++ ), ++ ) ++ + + class ReviewQueuePhase(MergedPhase): + after = (RecordPlanePhase,) +@@ -88,6 +120,37 @@ + + class ControlPlanePhase(PlanPhase): + after = (RecordPlanePhase,) ++ ++ @classmethod ++ def task(cls) -> PhaseTask: ++ import shlex ++ import sys ++ from pathlib import Path ++ from .change_report import FileLine ++ from .goals import OrderCheck ++ from .recorded_time import RecordedEventTime ++ from .todos import ApplicationOrderRun, PendingProblemOrder ++ from .work_dispatch import CheckedPhaseTask ++ ++ checkout = Path(__file__).resolve().parents[2] ++ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", ++ "-m", "pytest", "-o", "addopts=", "-n", "0", ++ "tests/test_work_dispatch.py", "tests/test_standing_rules.py", ++ "tests/test_storage_rules.py", "tests/test_turn_digests.py")) ++ return CheckedPhaseTask( ++ problem="P900007", repo="OpenHCSDev/agent-comms", ++ text="Complete and verify the full native commander control plane.", ++ deadline_seconds=7200, ++ order=PendingProblemOrder( ++ id="control-plane-full-task", owner=FileLine(str(Path(__file__).with_name("standing_rules.py")), 25), ++ check=OrderCheck(str(checkout), check), ++ edits=("Move the minute wake into Core scheduling; dispatch checked ready work through native order and lifecycle owners; derive standing rules, load, growth, storage and time from their original owners; delete the replaced script and private-state decisions.",), ++ banned=("No live-bus proof writes, phase-name dispatch map, second codec, private heartbeat files or fixture pass claimed as native application acceptance.",), ++ report="Name the started scratch unit, retained order, first reply and build; report native session parity, duplicate decisions, time totals and every removed competing path.", ++ run=ApplicationOrderRun("Scratch rotation starts a real Pi unit for a declared ready task and receives its first reply; a second rotation duplicates neither unit nor order; a full native commander session uses no script process; replay of a preserved hour produces each required decision once and UnitTime classes sum to elapsed time; stopping the commander ends its wake."), ++ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), ++ ), ++ ) + + + class CutoverPhase(PlanPhase): diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py new file mode 100644 index 0000000000..2d9022025f --- /dev/null +++ b/tools/prove_commander_dispatch.py @@ -0,0 +1,166 @@ +"""Run real rotation/native dispatch on a new scratch bus, or check its reply. + +No fake Comms, provider, event, transcript or history is used. A failed attempt is +retained, never replayed. This starts only the application unit being verified. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import shlex +import subprocess +import sys +import time +from uuid import uuid4 + +from agent_comms.comms import Comms +from agent_comms.tool_caller import ThreadCaller +from agent_comms.tools import CommsHistoryTool + + +def reply(comms: Comms, commander: str, unit: str, token: str): + history = CommsHistoryTool(peer=unit, limit=20).apply(comms, ThreadCaller(commander)) + return next((message for message in history.messages + if message.sender == unit and token in message.body), None) + + +def check(args) -> int: + found = reply(Comms(args.root), args.commander, args.unit, args.token) + print(int(found is not None)) + return 0 + + +def run(args) -> int: + from agent_comms.change_report import FileLine + from agent_comms.commander.config import CommanderConfig, Repo + from agent_comms.field_codec import FieldCodec + from agent_comms.goal_actions import SetGoalAction + from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder + from agent_comms.plan_phases import RecordPlanePhase + from agent_comms.progress_claims import WorkingClaim + from agent_comms.recorded_time import RecordedEventTime + from agent_comms.thread_execution import ExternalThreadExecution + from agent_comms.thread_status import StoppedThreadStatus + from agent_comms.todos import MechanicalOrderRun, OperationDelivery, PendingProblemOrder + from agent_comms.tools import CommsOrderDoneTool + from agent_comms.work_dispatch import CheckedPhaseTask + + root = args.root.expanduser().resolve() + allowed = Path.home() / ".cache/agent-scratch/pi-cn-phase7" + if not root.is_relative_to(allowed) or root.exists(): + raise ValueError("Proof requires a NEW root below ~/.cache/agent-scratch/pi-cn-phase7") + root.parent.mkdir(parents=True, exist_ok=True) + home = root.parent / "home" + home.mkdir() + # Configure this scratch command installation before importing rotation's + # existing commander context. Never rewrite the machine's live config. + os.environ["HOME"] = str(home) + project = Path(__file__).resolve().parents[1] + comms = Comms(root) + comms.bus.publisher.initialize_private_protocol() + commander = comms.registry.declare(comms.registry.new_thread( + "dispatch-proof-commander", frozenset(), str(project), + model="anthropic/claude-sonnet-4-5", execution=ExternalThreadExecution, + ), StoppedThreadStatus()) + comms.goals.update_goal(commander.name, SetGoalAction( + text="Verify one rotation-started native unit on this scratch bus only.", + goal_id=uuid4().hex, order=RequestGoalOrder(), + )) + config = CommanderConfig( + commander=commander.name, root=root, + cli=Path(sys.executable).with_name("agent-comms"), + state=root.parent / "state", board=root.parent / "board.md", + narrative=root.parent / "narrative.md", skill=project / "AGENTS.md", + rules=(), repos=(), worktrees=comms.registry.storage_rule.checkout_base, scratch=root.parent, + disk=root.parent, + ) + config.write(home) + problem = f"P{time.time_ns()}" + unit = f"unit-{problem.lower()}-r0" + token = f"ROTATION_REPLY_{uuid4().hex}" + check_command = shlex.join(( + sys.executable, str(Path(__file__).resolve()), "check", "--root", str(root), + "--commander", commander.name, "--unit", unit, "--token", token, + )) + order = PendingProblemOrder( + id=uuid4().hex, owner=FileLine(__file__, 1), + check=OrderCheck(str(project), check_command, CountCheckResult(1)), + edits=(f"Use comms_send to send exactly {token} to {commander.name}, then return. " + "This operation verifies your first real native reply; make no source edits.",), + banned=("No helper agents, live-bus writes, source edits or extra tool calls.",), + report="Send the requested token once and return; the proof commander checks delivery.", + at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), + run=MechanicalOrderRun(), + ) + RecordPlanePhase.task = CheckedPhaseTask( + problem, Repo.of(project).github, + "Verify rotation starts a native unit and receives its first real reply.", + order, deadline_seconds=300, + ) + from agent_comms.commander import rotate + + print(f"ROOT {root}", flush=True) + print(f"SOURCE {subprocess.check_output(('git', '-C', str(project), 'rev-parse', 'HEAD'), text=True).strip()}", flush=True) + for line in rotate.broken_rules(): + print(line, flush=True) + assigned = comms.goals.todos.get(problem) + started = comms.registry.require(assigned.held().owner) + print(f"UNIT {started.name} model={started.model} pid={started.require_process().pid}", flush=True) + print(f"ORDER {assigned.order.id} problem={problem} checkout={started.worktree}", flush=True) + (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2) + "\n") + before = comms.registry.all_threads() + for line in rotate.broken_rules(): + print(line, flush=True) + assert comms.registry.all_threads().keys() == before.keys(), "second rotation duplicated a unit" + assert comms.goals.todos.get(problem).order.id == assigned.order.id, "second rotation duplicated an order" + print("SECOND_ROTATION no duplicate unit or order", flush=True) + + end = time.monotonic() + 180 + found = None + while time.monotonic() < end: + found = reply(comms, commander.name, started.name, token) + if found is not None: + break + if not comms.registry.require(started.name).process_alive: + raise RuntimeError(f"Proof unit exited before replying; inspect {root}") + time.sleep(0.5) + if found is None: + raise TimeoutError(f"No first reply; preserve {root} and inspect its original attempt") + print(f"FIRST_REPLY {found.sender}: {found.body}", flush=True) + artifact = root.parent / "reply.json" + artifact.write_text(json.dumps(FieldCodec.encode(found), indent=2) + "\n") + CommsOrderDoneTool( + problem=problem, order_id=assigned.order.id, + delivery=OperationDelivery(), claims=(WorkingClaim(str(artifact)),), + ).apply(comms, ThreadCaller(commander.name)) + while time.monotonic() < end: + current = comms.registry.require(started.name) + if not current.turn_record.read().executing: + break + time.sleep(0.5) + current.turn_record.read().require_idle() + comms.owners.stop(started.name) + print("STOPPED proof unit after its turn ended; scratch history retained", flush=True) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(required=True) + launch = commands.add_parser("run") + launch.add_argument("--root", type=Path, required=True) + launch.set_defaults(action=run) + verify = commands.add_parser("check") + verify.add_argument("--root", type=Path, required=True) + verify.add_argument("--commander", required=True) + verify.add_argument("--unit", required=True) + verify.add_argument("--token", required=True) + verify.set_defaults(action=check) + args = parser.parse_args() + return args.action(args) + + +if __name__ == "__main__": + raise SystemExit(main()) From 4c1ff2b270d1f909e1a8f9cd1f6506aa3f8a825b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:19:07 -0400 Subject: [PATCH 07/21] Record real rotation-started SOL reply and retained scratch receipts --- docs/refactor/phase7-dispatch.md | 43 ++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index c291f62be7..792e4e1494 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -78,7 +78,7 @@ same trace requirement to the released assignment history. Auto-dispatch passes no fabricated source trace. After two owners, missing real tracing stops that transfer rather than pretending the old order's source owner was newly read. -## Final checkpoint +## Earlier sanity checkpoint After edit: refactor-audit parsed 380 Core production modules and 283 Toad modules (ba57101e), zero parse omissions. UnownedWheelSpin has no declaration @@ -99,10 +99,10 @@ risk: reproduce with the board self-check cases; acceptance must preserve the remote PR fact without a local checkout. Default eight-worker/coverage settings were needless overhead for that bounded run; final checks were serial. -No agent launch, provider input, installation or PR merge occurred. Draft #1932 -is not a first-reply proof. Production checked full-phase briefs and the scratch -unit/order/first-reply proof remain required. Unordered rows without a checked -order cannot be dispatched by inventing their missing check. +At that checkpoint no agent launch, provider input, installation or PR merge had +occurred. The scratch native first-reply proof below supersedes that limitation. +Production full-phase declarations are staged with pi-plan-claims. Unordered rows +without a checked order still cannot be dispatched by inventing their check. The full ControlPlanePhase brief draft was moved into PR #1932's body at the owner's request. The owner explicitly authorized rotation to start the proof unit @@ -116,3 +116,36 @@ The development checkout reuses an unchanged prepared native package by reflink copy at stack/.pi-native-a0f57a811b47862f (192 MiB apparent size). Its manifest matches main and VerifiedNativePackage.acquire accepts it. The native verifier correctly refused a symlink; no gate was disabled and no live package changed. + +## Real rotation/native proof + +Run: run-20261019-control2, exit 0. No fake Comms, provider, events or replies. +`commander.rotate.broken_rules` called the real native standing-rule tool, whose +PlanIdle enforcement created this registered unit and order: + +- Unit: unit-p1792433571161170326-r0 +- Model: openai-codex/gpt-6.1-sol; reserved worker PID 1206539 +- Problem: P1792433571161170326 +- Order: 0f96e2831ff1 +- First actual unit reply: ROTATION_REPLY_62821ddec7434083b6f618e65798a080 + +Second rotation created neither another unit nor another order. The actual reply +passed the order's Core check; its order is DeliveredProblemOrder, pending=False. +The worker was stopped only after its native turn ended. No proof writes touched +the live bus. Receipt, order, reply, diagnostics and original native session are +retained in the named scratch run. The initial control1 run was refused before +spawn for a missing prepared package and is retained separately, not replayed. + +Dispatcher source was bf9fae2f1; the proof runner was then committed as c723313ed. +The native worker used the existing main-checkout editable Core (observed clean +at 8b27e02aa) and unchanged pinned Pi a0f57a811b47862f. This is a real source-driver +and configured installed-native process proof, not a claim that the candidate +branch was installed or that all of phase 7 is complete. + +The requested full task() factories and method migration are staged in +phase7-task-declarations.patch and sent to pi-plan-claims (owner request 1895, +handoff 1946). That owner reports stale self-claims preventing its shared-file +edits; no denied edit was bypassed. Its phase/claim changes must be integrated +normally. Missing-checkout progress reads overlap the recently changed +ProgressSource in #1935; pi-cn-phase3 has the reproducer and implementation +handoff, so no competing ProgressSource change is made here. From 59c7d8263177c007218e51b699b7dd7a60af88d0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:29:12 -0400 Subject: [PATCH 08/21] P256: own dispatch rules in commander discovery and type the native proof --- src/agent_comms/commander/standing.py | 20 ++ src/agent_comms/standing_rules.py | 21 -- src/agent_comms/work_dispatch.py | 4 +- tests/test_standing_rules.py | 4 +- tests/test_work_dispatch.py | 3 +- tools/prove_commander_dispatch.py | 404 +++++++++++++++++--------- 6 files changed, 287 insertions(+), 169 deletions(-) diff --git a/src/agent_comms/commander/standing.py b/src/agent_comms/commander/standing.py index 7cd4eb20bd..6509effb45 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -22,9 +22,29 @@ from ..threads import Thread from ..todos import FlatTrend, OrderProgress, PullRequests, Todo from ..turn_digests import OrderEditCountRead, TurnDigestRead, TurnsSince +from ..work_dispatch import WorkDispatch from .config import CommanderConfig +@dataclass(frozen=True) +class UnownedWork(UndatedStandingRule): + """An unowned checked row starts a unit, even when the idle fleet is empty.""" + + todo: Todo + + @classmethod + def violations(cls, comms: Comms, commander: str) -> tuple[UnownedWork, ...]: + return tuple(cls(todo) for todo in comms.goals.todos.for_commander( + comms.registry.require(commander).incarnation) if todo.pending and not todo.holder) + + def enforce(self, comms: Comms, commander: str) -> None: + WorkDispatch.reissue(comms, comms.registry.require(commander), self.todo) + + def line(self) -> str: + return (f"UNOWNED WORK: {self.todo.id} has a checked order and no unit" + " -> Core starts its unit in an isolated checkout") + + @dataclass(frozen=True) class Detached(UndatedStandingRule): unit: str diff --git a/src/agent_comms/standing_rules.py b/src/agent_comms/standing_rules.py index 8c681e0b31..18800b3d29 100644 --- a/src/agent_comms/standing_rules.py +++ b/src/agent_comms/standing_rules.py @@ -227,27 +227,6 @@ def line(self) -> str: ) -@dataclass(frozen=True) -class UnownedWork(UndatedStandingRule): - """An unowned checked row starts a unit, even when the idle fleet is empty.""" - - todo: Todo - - @classmethod - def violations(cls, comms: Comms, commander: str) -> tuple[Self, ...]: - return tuple(cls(todo) for todo in comms.goals.todos.for_commander( - comms.registry.require(commander).incarnation) if todo.pending and not todo.holder) - - def enforce(self, comms: Comms, commander: str) -> None: - from .work_dispatch import WorkDispatch - - WorkDispatch.reissue(comms, comms.registry.require(commander), self.todo) - - def line(self) -> str: - return (f"UNOWNED WORK: {self.todo.id} has a checked order and no unit" - " -> Core starts its unit in an isolated checkout") - - @dataclass(frozen=True) class MemoryLow(UndatedStandingRule): """Under the memory threshold a cleanup owner is due, told what holds the memory.""" diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index 26624881a8..2f10f5df81 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -35,7 +35,7 @@ @dataclass(frozen=True) class WorkDispatch: - """One checked native order: allocate its unit, record its claim, then start it.""" + """Allocate a unit, assign its checked native order, then start it.""" action: ProblemOrderGoalAction revision: int @@ -87,7 +87,7 @@ def start(self, comms: Comms) -> Thread: StoppedThreadStatus(), ) # A failed publication leaves a stopped unit and its checkout, not an - # untracked running task. A failed/uncertain start never releases the claim. + # untracked running task. A failed/uncertain start never releases its order. comms.goals.update_goal(child.name, self.action) comms.owners.start(child.name) return comms.registry.require(child.name) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 0c9bea8c10..1d92e9c184 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -20,7 +20,7 @@ from agent_comms.recorded_time import RecordedEventTime from agent_comms.rule_reviews import AnsweredRuleReviewEntry, DrawnRuleReviewEntry from agent_comms.standing_rules import (BoardDisagrees, BridgeDown, DiskLow, HuntDue, MemoryLow, PlanIdle, - RulesDue, StandingRule, UnownedWork) + RulesDue, StandingRule) from agent_comms.storage_rules import MemoryHolder, UnitStorage from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_status import StoppedThreadStatus @@ -242,7 +242,7 @@ def progress(): taken = StrikeReason("The unit was taken off P2 so the test leaves it unowned.") comms.goals.fire("worker", taken, "lead") assert comms.goals.todos.get("P2").holder is None - [unowned] = UnownedWork.violations(comms, "lead") + [unowned] = module.UnownedWork.violations(comms, "lead") assert unowned.todo.id == "P2" assert unowned.line() == ( "UNOWNED WORK: P2 has a checked order and no unit" diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py index 6aae65a1ae..6c776046aa 100644 --- a/tests/test_work_dispatch.py +++ b/tests/test_work_dispatch.py @@ -8,7 +8,8 @@ from agent_comms.plan_phases import RecordPlanePhase from agent_comms.recorded_time import RecordedEventTime from agent_comms.todos import DoneTodoState, OperationDelivery, StrikeReason -from agent_comms.work_dispatch import CheckedPhaseTask, UnspecifiedPhaseTask, WorkDispatch +from agent_comms.phase_tasks import UnspecifiedPhaseTask +from agent_comms.work_dispatch import CheckedPhaseTask, WorkDispatch from test_commander_problem_orders import create, order diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 2d9022025f..1c23a31542 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -1,165 +1,283 @@ -"""Run real rotation/native dispatch on a new scratch bus, or check its reply. +"""Run real rotation/native dispatch on a new scratch bus and retain its reply. -No fake Comms, provider, event, transcript or history is used. A failed attempt is -retained, never replayed. This starts only the application unit being verified. +No fake transport, provider or history is used. Failed attempts remain retained. """ + from __future__ import annotations import argparse +import asyncio import json import os -from pathlib import Path import shlex import subprocess import sys import time +from abc import abstractmethod +from contextlib import aclosing +from dataclasses import dataclass +from pathlib import Path +from typing import Annotated, ClassVar from uuid import uuid4 +from agent_comms.change_report import FileLine +from agent_comms.cli_commands import DeclaredOptions, option +from agent_comms.commander import rotate +from agent_comms.commander.config import CommanderConfig, Repo from agent_comms.comms import Comms +from agent_comms.declared_family import DeclaredFamily +from agent_comms.field_codec import FieldCodec, PathText +from agent_comms.goal_actions import SetGoalAction +from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder +from agent_comms.messages import Message +from agent_comms.plan_phases import RecordPlanePhase +from agent_comms.progress_claims import WorkingClaim +from agent_comms.recorded_time import RecordedEventTime +from agent_comms.thread_execution import ExternalThreadExecution +from agent_comms.thread_status import StoppedThreadStatus +from agent_comms.todos import MechanicalOrderRun, OperationDelivery, PendingProblemOrder from agent_comms.tool_caller import ThreadCaller -from agent_comms.tools import CommsHistoryTool - - -def reply(comms: Comms, commander: str, unit: str, token: str): - history = CommsHistoryTool(peer=unit, limit=20).apply(comms, ThreadCaller(commander)) - return next((message for message in history.messages - if message.sender == unit and token in message.body), None) - - -def check(args) -> int: - found = reply(Comms(args.root), args.commander, args.unit, args.token) - print(int(found is not None)) - return 0 - - -def run(args) -> int: - from agent_comms.change_report import FileLine - from agent_comms.commander.config import CommanderConfig, Repo - from agent_comms.field_codec import FieldCodec - from agent_comms.goal_actions import SetGoalAction - from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder - from agent_comms.plan_phases import RecordPlanePhase - from agent_comms.progress_claims import WorkingClaim - from agent_comms.recorded_time import RecordedEventTime - from agent_comms.thread_execution import ExternalThreadExecution - from agent_comms.thread_status import StoppedThreadStatus - from agent_comms.todos import MechanicalOrderRun, OperationDelivery, PendingProblemOrder - from agent_comms.tools import CommsOrderDoneTool - from agent_comms.work_dispatch import CheckedPhaseTask - - root = args.root.expanduser().resolve() - allowed = Path.home() / ".cache/agent-scratch/pi-cn-phase7" - if not root.is_relative_to(allowed) or root.exists(): - raise ValueError("Proof requires a NEW root below ~/.cache/agent-scratch/pi-cn-phase7") - root.parent.mkdir(parents=True, exist_ok=True) - home = root.parent / "home" - home.mkdir() - # Configure this scratch command installation before importing rotation's - # existing commander context. Never rewrite the machine's live config. - os.environ["HOME"] = str(home) - project = Path(__file__).resolve().parents[1] - comms = Comms(root) - comms.bus.publisher.initialize_private_protocol() - commander = comms.registry.declare(comms.registry.new_thread( - "dispatch-proof-commander", frozenset(), str(project), - model="anthropic/claude-sonnet-4-5", execution=ExternalThreadExecution, - ), StoppedThreadStatus()) - comms.goals.update_goal(commander.name, SetGoalAction( - text="Verify one rotation-started native unit on this scratch bus only.", - goal_id=uuid4().hex, order=RequestGoalOrder(), - )) - config = CommanderConfig( - commander=commander.name, root=root, - cli=Path(sys.executable).with_name("agent-comms"), - state=root.parent / "state", board=root.parent / "board.md", - narrative=root.parent / "narrative.md", skill=project / "AGENTS.md", - rules=(), repos=(), worktrees=comms.registry.storage_rule.checkout_base, scratch=root.parent, - disk=root.parent, - ) - config.write(home) - problem = f"P{time.time_ns()}" - unit = f"unit-{problem.lower()}-r0" - token = f"ROTATION_REPLY_{uuid4().hex}" - check_command = shlex.join(( - sys.executable, str(Path(__file__).resolve()), "check", "--root", str(root), - "--commander", commander.name, "--unit", unit, "--token", token, - )) - order = PendingProblemOrder( - id=uuid4().hex, owner=FileLine(__file__, 1), - check=OrderCheck(str(project), check_command, CountCheckResult(1)), - edits=(f"Use comms_send to send exactly {token} to {commander.name}, then return. " - "This operation verifies your first real native reply; make no source edits.",), - banned=("No helper agents, live-bus writes, source edits or extra tool calls.",), - report="Send the requested token once and return; the proof commander checks delivery.", - at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), - run=MechanicalOrderRun(), - ) - RecordPlanePhase.task = CheckedPhaseTask( - problem, Repo.of(project).github, - "Verify rotation starts a native unit and receives its first real reply.", - order, deadline_seconds=300, - ) - from agent_comms.commander import rotate - - print(f"ROOT {root}", flush=True) - print(f"SOURCE {subprocess.check_output(('git', '-C', str(project), 'rev-parse', 'HEAD'), text=True).strip()}", flush=True) - for line in rotate.broken_rules(): - print(line, flush=True) - assigned = comms.goals.todos.get(problem) - started = comms.registry.require(assigned.held().owner) - print(f"UNIT {started.name} model={started.model} pid={started.require_process().pid}", flush=True) - print(f"ORDER {assigned.order.id} problem={problem} checkout={started.worktree}", flush=True) - (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2) + "\n") - before = comms.registry.all_threads() - for line in rotate.broken_rules(): - print(line, flush=True) - assert comms.registry.all_threads().keys() == before.keys(), "second rotation duplicated a unit" - assert comms.goals.todos.get(problem).order.id == assigned.order.id, "second rotation duplicated an order" - print("SECOND_ROTATION no duplicate unit or order", flush=True) - - end = time.monotonic() + 180 - found = None - while time.monotonic() < end: - found = reply(comms, commander.name, started.name, token) - if found is not None: - break - if not comms.registry.require(started.name).process_alive: - raise RuntimeError(f"Proof unit exited before replying; inspect {root}") - time.sleep(0.5) - if found is None: - raise TimeoutError(f"No first reply; preserve {root} and inspect its original attempt") - print(f"FIRST_REPLY {found.sender}: {found.body}", flush=True) - artifact = root.parent / "reply.json" - artifact.write_text(json.dumps(FieldCodec.encode(found), indent=2) + "\n") - CommsOrderDoneTool( - problem=problem, order_id=assigned.order.id, - delivery=OperationDelivery(), claims=(WorkingClaim(str(artifact)),), - ).apply(comms, ThreadCaller(commander.name)) - while time.monotonic() < end: - current = comms.registry.require(started.name) - if not current.turn_record.read().executing: - break - time.sleep(0.5) - current.turn_record.read().require_idle() - comms.owners.stop(started.name) - print("STOPPED proof unit after its turn ended; scratch history retained", flush=True) - return 0 +from agent_comms.tools import CommsHistoryTool, CommsOrderDoneTool +from agent_comms.wire_watch import WatchSource, WireWatch +from agent_comms.work_dispatch import CheckedPhaseTask + + +class Reply(DeclaredFamily, affix="Reply"): + arrived: ClassVar[bool] = False + + @classmethod + def read(cls, comms: Comms, commander: str, unit: str, token: str) -> Reply: + history = CommsHistoryTool(peer=unit, limit=20).apply(comms, ThreadCaller(commander)) + for message in history.messages: + if message.sender == unit and token in message.body: + return ReceivedReply(message) + return AwaitedReply() + + @abstractmethod + def require_message(self) -> Message: ... + + +class AwaitedReply(Reply): + def require_message(self) -> Message: + raise TimeoutError("The native unit has not replied; retain its original attempt") + + +@dataclass(frozen=True) +class ReceivedReply(Reply): + message: Message + arrived = True + + def require_message(self) -> Message: + return self.message + + +async def first_reply(comms: Comms, commander: str, unit: str, token: str) -> Message: + async with asyncio.timeout(180): + async with aclosing( + WireWatch.observations( + WatchSource.modified_file(comms.bus.log.path), + WatchSource.modified_file(comms.registry.store.path), + ) + ) as changes: + async for _ in changes: + observed = Reply.read(comms, commander, unit, token) + if observed.arrived: + return observed.require_message() + if not comms.registry.require(unit).process_alive: + raise RuntimeError(f"Unit exited before replying; inspect {comms.root}") + raise TimeoutError(f"No first reply; preserve {comms.root}") + + +async def stop_after_turn(comms: Comms, unit: str) -> None: + async with asyncio.timeout(180): + async with aclosing( + WireWatch.observations( + WatchSource.modified_file(comms.registry.store.path), + ) + ) as changes: + async for _ in changes: + turn = comms.registry.require(unit).turn_record.read() + if not turn.executing: + turn.require_idle() + comms.owners.stop(unit) + return + + +@dataclass(frozen=True, kw_only=True) +class ProofCommand(DeclaredFamily, DeclaredOptions, affix="ProofCommand"): + root: Annotated[Path, PathText] = option("--root") + + @abstractmethod + def apply(self, project: Path) -> int: ... + + +@dataclass(frozen=True, kw_only=True) +class CheckProofCommand(ProofCommand): + help = "check the actual native reply" + commander: str = option("--commander") + unit: str = option("--unit") + token: str = option("--token") + + def apply(self, project: Path) -> int: + print(int(Reply.read(Comms(self.root), self.commander, self.unit, self.token).arrived)) + return 0 + + +@dataclass(frozen=True, kw_only=True) +class RunProofCommand(ProofCommand): + help = "create a fresh scratch root and run its real rotation proof" + + def apply(self, project: Path) -> int: + root = self.root.expanduser().resolve() + allowed = Path.home() / ".cache/agent-scratch/pi-cn-phase7" + if not root.is_relative_to(allowed) or root.exists(): + raise ValueError("Proof needs a NEW root under the owner's agent-scratch directory") + home = root.parent / "home" + home.mkdir(parents=True) + comms = Comms(root) + comms.messaging.initialize_private_initial_protocol() + # A fresh interpreter acquires the scratch configuration before importing + # rotation. Keep the actual storage declaration across its isolated HOME. + (root / "storage.json").write_text( + json.dumps(FieldCodec.encode(comms.registry.storage_rule)) + ) + commander = comms.registry.declare( + comms.registry.new_thread( + "dispatch-proof-commander", + frozenset(), + str(project), + model="anthropic/claude-sonnet-4-5", + execution=ExternalThreadExecution, + ), + StoppedThreadStatus(), + ) + comms.goals.update_goal( + commander.name, + SetGoalAction( + text="Verify one rotation-started native unit on this scratch bus only.", + goal_id=uuid4().hex, + order=RequestGoalOrder(), + ), + ) + CommanderConfig( + commander=commander.name, + root=root, + cli=Path(sys.executable).with_name("agent-comms"), + state=root.parent / "state", + board=root.parent / "board.md", + narrative=root.parent / "narrative.md", + rules=(), + repos=(), + worktrees=comms.registry.storage_rule.checkout_base, + scratch=root.parent, + disk=root.parent, + ).write(home) + return subprocess.run( + (sys.executable, __file__, "observe", "--root", str(root)), + env={**os.environ, "HOME": str(home)}, + check=False, + ).returncode + + +@dataclass(frozen=True, kw_only=True) +class ObserveProofCommand(ProofCommand): + help = "observe the freshly configured scratch rotation" + + def apply(self, project: Path) -> int: + root = self.root.resolve() + config = CommanderConfig.installed() + if config.root != root or config.commander != "dispatch-proof-commander": + raise ValueError("Observe requires the separately configured proof commander") + comms = Comms(root) + commander = comms.registry.require(config.commander) + problem = f"P{time.time_ns()}" + unit = f"unit-{problem.lower()}-r0" + token = f"ROTATION_REPLY_{uuid4().hex}" + check_command = shlex.join( + ( + sys.executable, + str(Path(__file__).resolve()), + "check", + "--root", + str(root), + "--commander", + commander.name, + "--unit", + unit, + "--token", + token, + ) + ) + order = PendingProblemOrder( + id=uuid4().hex, + owner=FileLine(__file__, 1), + check=OrderCheck(str(project), check_command, CountCheckResult(1)), + edits=( + f"Use comms_send to send exactly {token} to {commander.name}, then return. " + "Verify your first real reply; make no source edits.", + ), + banned=("No helper agents, live-bus writes, source edits or extra tool calls.",), + report="Send the requested token once and return; the proof commander checks delivery.", + at=RecordedEventTime(1), + first_call_deadline=RecordedEventTime(121), + run=MechanicalOrderRun(), + ) + RecordPlanePhase.task = CheckedPhaseTask( + problem, + Repo.of(project).github, + "Verify rotation starts a native unit and its reply.", + order, + deadline_seconds=300, + ) + print(f"ROOT {root}", flush=True) + source = subprocess.check_output( + ("git", "-C", str(project), "rev-parse", "HEAD"), text=True + ) + print(f"SOURCE {source.strip()}", flush=True) + for line in rotate.broken_rules(): + print(line, flush=True) + assigned = comms.goals.todos.get(problem) + started = comms.registry.require(assigned.held().owner) + print( + f"UNIT {started.name} model={started.model} pid={started.require_process().pid}", + flush=True, + ) + print( + f"ORDER {assigned.order.id} problem={problem} checkout={started.worktree}", flush=True + ) + (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2)) + before = comms.registry.all_threads() + for line in rotate.broken_rules(): + print(line, flush=True) + assert comms.registry.all_threads().keys() == before.keys(), "duplicate unit" + assert comms.goals.todos.get(problem).order.id == assigned.order.id, "duplicate order" + print("SECOND_ROTATION no duplicate unit or order", flush=True) + found = asyncio.run(first_reply(comms, commander.name, started.name, token)) + print(f"FIRST_REPLY {found.sender}: {found.body}", flush=True) + artifact = root.parent / "reply.json" + artifact.write_text(json.dumps(FieldCodec.encode(found), indent=2)) + CommsOrderDoneTool( + problem=problem, + order_id=assigned.order.id, + delivery=OperationDelivery(), + claims=(WorkingClaim(str(artifact)),), + ).apply(comms, ThreadCaller(commander.name)) + asyncio.run(stop_after_turn(comms, started.name)) + print("STOPPED after its turn ended; original history retained", flush=True) + return 0 def main() -> int: parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(required=True) - launch = commands.add_parser("run") - launch.add_argument("--root", type=Path, required=True) - launch.set_defaults(action=run) - verify = commands.add_parser("check") - verify.add_argument("--root", type=Path, required=True) - verify.add_argument("--commander", required=True) - verify.add_argument("--unit", required=True) - verify.add_argument("--token", required=True) - verify.set_defaults(action=check) - args = parser.parse_args() - return args.action(args) + commands = parser.add_subparsers(dest="command", required=True) + for member in ProofCommand.members_with(ProofCommand): + member.add_parser(commands) + arguments = parser.parse_args() + return ( + ProofCommand.member_named(arguments.command) + .from_namespace(arguments) + .apply(Path(__file__).resolve().parents[1]) + ) if __name__ == "__main__": From 893c6581ff66d8a0f1e0c6bff619151114c669f5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:32:13 -0400 Subject: [PATCH 09/21] P256: decode the received pending order through its existing codec --- tools/prove_commander_dispatch.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 1c23a31542..6c261221cc 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -166,7 +166,6 @@ def apply(self, project: Path) -> int: state=root.parent / "state", board=root.parent / "board.md", narrative=root.parent / "narrative.md", - rules=(), repos=(), worktrees=comms.registry.storage_rule.checkout_base, scratch=root.parent, @@ -237,20 +236,24 @@ def apply(self, project: Path) -> int: for line in rotate.broken_rules(): print(line, flush=True) assigned = comms.goals.todos.get(problem) + received_order = FieldCodec.decode(PendingProblemOrder, FieldCodec.encode(assigned.order)) started = comms.registry.require(assigned.held().owner) print( f"UNIT {started.name} model={started.model} pid={started.require_process().pid}", flush=True, ) print( - f"ORDER {assigned.order.id} problem={problem} checkout={started.worktree}", flush=True + f"ORDER {received_order.id} problem={problem} checkout={started.worktree}", flush=True ) (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2)) before = comms.registry.all_threads() for line in rotate.broken_rules(): print(line, flush=True) assert comms.registry.all_threads().keys() == before.keys(), "duplicate unit" - assert comms.goals.todos.get(problem).order.id == assigned.order.id, "duplicate order" + after = FieldCodec.decode( + PendingProblemOrder, FieldCodec.encode(comms.goals.todos.get(problem).order) + ) + assert after.id == received_order.id, "duplicate order" print("SECOND_ROTATION no duplicate unit or order", flush=True) found = asyncio.run(first_reply(comms, commander.name, started.name, token)) print(f"FIRST_REPLY {found.sender}: {found.body}", flush=True) @@ -258,7 +261,7 @@ def apply(self, project: Path) -> int: artifact.write_text(json.dumps(FieldCodec.encode(found), indent=2)) CommsOrderDoneTool( problem=problem, - order_id=assigned.order.id, + order_id=received_order.id, delivery=OperationDelivery(), claims=(WorkingClaim(str(artifact)),), ).apply(comms, ThreadCaller(commander.name)) From b145c3441f511a9e24cb78f91c4e4baeaa065880 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:39:00 -0400 Subject: [PATCH 10/21] P256: derive commander identity from its loaded configuration --- tools/prove_commander_dispatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 6c261221cc..f82e4a1e19 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -185,7 +185,7 @@ class ObserveProofCommand(ProofCommand): def apply(self, project: Path) -> int: root = self.root.resolve() config = CommanderConfig.installed() - if config.root != root or config.commander != "dispatch-proof-commander": + if config.root != root: raise ValueError("Observe requires the separately configured proof commander") comms = Comms(root) commander = comms.registry.require(config.commander) From 36c54e5ee54032fd89f63a1a42d08e339fa100ab Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:51:30 -0400 Subject: [PATCH 11/21] P256: declare the new dispatch feature's source allocation Allocate the exact new feature lines: 201 for dispatch/task production code and 287 for its native proof. Structural findings were repaired separately. Use the existing per-commit source allocation mechanism; do not change the ratchet or baseline. Remove the superseded staging patch and correct the retained proof report. Src-Budget: 488 --- docs/refactor/phase7-dispatch.md | 244 ++++++++++++------------------- phase7-task-declarations.patch | 97 ------------ 2 files changed, 93 insertions(+), 248 deletions(-) delete mode 100644 phase7-task-declarations.patch diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index 792e4e1494..b16864dc6d 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -1,151 +1,93 @@ -# Phase 7: start checked, ready work - -Owner: pi-cn-phase7. Base: origin/main 011084fc5; plan dependency change 89f53149a is integrated from pi-plan-claims. - -## First slice - -`PlanIdle` reports ready phases; rotation only prints its strings. Existing -`UnownedWheelSpin` requires an already-idle unit, leaving unowned work untouched -when none exists. A never-ordered phase has no checked task declaration. Inventing -an acceptance command from a phase name would bypass the order's actual owner. - -Put optional checked tasks on PlanPhase, using PendingProblemOrder rather than a -second order schema. Unspecified tasks remain visible and allocate nothing. -Checked tasks and released orders share native unit startup: storage preflight, -isolated worktree, stopped thread declaration, native order publication, then the -existing explicit owner start. Publication precedes execution. Keep failed or -uncertain startup state; do not replay or remove it automatically. - -`StandingRule.enforce`, already called by CommsStandingRulesTool and rotation, -is the entrypoint. No rotation-function edit is necessary. Delete the competing -UnownedWheelSpin path; preserve FlatWheelSpin's separate reassignment behavior. - -## Coordination and verification limits - -PR #1912 changes rotate.main/broken_rules/owed_orders, plus commander context. -Leave those functions alone. #1756 changes order/judge pattern IDs, which this -slice does not touch. No Toad file changes are needed. - -pi-plan-claims owns plan_phases.py, and explicitly permits only the task imports -and PlanPhase.task declaration in this slice. It retains shared claims and plan -parent/dependency ownership. Checked production task briefs are not yet supplied. - -The current instruction forbids launching another agent. Do not run the requested -scratch Pi startup proof under that restriction. Source checks cannot replace the -required unit/order/first-reply proof. - -Resource helper /home/ts/bin/agent-resource-check is absent. Manual observation: -4.1 GiB available RAM; 26 GiB disk available. Keep checks small and serial. Scratch -owner: pi-cn-phase7; purpose: bounded final checks; path: -/home/ts/.cache/agent-scratch/pi-cn-phase7. No saved user or Codex sessions belong -in that disposable directory. - -## Source evidence - -NRA import is currently blocked by DuplicateRegistryKeyError in its detector -registration against the installed metaclass-registry. Use refactor-audit's -existing Package parser for the bounded AST census instead, recording omissions. -AST results establish source sites, not live binding or runtime equivalence. - -Before edit: refactor-audit Package.load over all 379 production Python modules, -zero parse omissions. Enumerated classes/bases and matching references across the -whole package; imports, writes, comparisons and decisions in the rule/phase -owners. Semantic consumers: tools.CommsStandingRulesTool.apply -> -StandingRule.broken/enforce; commander.rotate.broken_rules invokes that tool; -PlanIdle owns phase readiness attention; commander.standing.UnownedWheelSpin -owned the competing idle-unit-only path; GoalAction subclasses and TodoStore -own checked assignment; Registration and OwnerLifecycle own process identity -and launch; CheckoutStorageEffect owns allocation policy. No dependency code is -changed. Dynamic DeclaredFamily discovery and provider execution remain runtime -obligations, not statically proven bindings. - -Implemented: PhaseTask.start/done, with an unspecified member that cannot invent -an order and a checked member carrying PendingProblemOrder. PlanIdle delegates to -its phase task. UnownedWork replaces UnownedWheelSpin and starts a dedicated unit. -WorkDispatch checks storage, fetches main, creates the owned worktree, registers -a stopped native thread, publishes its checked order, then calls OwnerLifecycle -start. No fork copies, new launch codec or second task/order store. - -Completion uses the explicit task problem and ProblemOrder.accepted: only -InstalledProblemOrder accepts; a withdrawn/folded/merely delivered problem does -not complete the phase. pi-plan-claims' a44d1be1e completion/A2 change is merged -normally into this branch. Its borrowed module was reconciled with the current -finite-deadline and exact-task identity checks. - -Source trace also exposed an authority bypass: releasing an order skipped -Assignment.require_trace on its next transfer. Todo.assigned now applies the -same trace requirement to the released assignment history. Auto-dispatch passes -no fabricated source trace. After two owners, missing real tracing stops that -transfer rather than pretending the old order's source owner was newly read. - -## Earlier sanity checkpoint - -After edit: refactor-audit parsed 380 Core production modules and 283 Toad -modules (ba57101e), zero parse omissions. UnownedWheelSpin has no declaration -or consumers. PlanPhase.done and PlanIdle consume PhaseTask; task.done is the -only order.accepted consumer. Toad has no imports of the changed owner classes. -Dynamic discovery and provider execution remain unproved. - -Source imports and git diff --check pass. Final bounded background run used -`timeout 300`, Python `-X faulthandler`, `-o addopts=''`, one process and an owned -basetemp: 12 tests passed in 10.64 seconds (work_dispatch, plan_phases and affected -PlanIdle/wheel-spin cases). pytest-timeout is absent; external timeout applies. - -The broader check had 16 passes and six failures: four board cases read missing -checkout /home/ts/wt/worker--P109, one compares changing memory snapshots, and my -new trace test used a banned word. The last is fixed and passes. This is not a -passing full standing-rule suite. pi-cn-phase7 owns the missing-checkout rotation -risk: reproduce with the board self-check cases; acceptance must preserve the -remote PR fact without a local checkout. Default eight-worker/coverage settings -were needless overhead for that bounded run; final checks were serial. - -At that checkpoint no agent launch, provider input, installation or PR merge had -occurred. The scratch native first-reply proof below supersedes that limitation. -Production full-phase declarations are staged with pi-plan-claims. Unordered rows -without a checked order still cannot be dispatched by inventing their check. - -The full ControlPlanePhase brief draft was moved into PR #1932's body at the -owner's request. The owner explicitly authorized rotation to start the proof unit -on a scratch bus; no helper delegation or live-bus proof writes are authorized. -The real proof runner is tools/prove_commander_dispatch.py. Its new scratch -root, configuration, native history and receipts are retained under -/home/ts/.cache/agent-scratch/pi-cn-phase7/run-*/. Owner: pi-cn-phase7; purpose: -first native reply and duplicate-dispatch acceptance. Native unit checkouts use -the existing StorageRule's /home/ts/wt/--P allocation. No auth is copied. -The development checkout reuses an unchanged prepared native package by reflink -copy at stack/.pi-native-a0f57a811b47862f (192 MiB apparent size). Its manifest -matches main and VerifiedNativePackage.acquire accepts it. The native verifier -correctly refused a symlink; no gate was disabled and no live package changed. - -## Real rotation/native proof - -Run: run-20261019-control2, exit 0. No fake Comms, provider, events or replies. -`commander.rotate.broken_rules` called the real native standing-rule tool, whose -PlanIdle enforcement created this registered unit and order: - -- Unit: unit-p1792433571161170326-r0 -- Model: openai-codex/gpt-6.1-sol; reserved worker PID 1206539 -- Problem: P1792433571161170326 -- Order: 0f96e2831ff1 -- First actual unit reply: ROTATION_REPLY_62821ddec7434083b6f618e65798a080 - -Second rotation created neither another unit nor another order. The actual reply -passed the order's Core check; its order is DeliveredProblemOrder, pending=False. -The worker was stopped only after its native turn ended. No proof writes touched -the live bus. Receipt, order, reply, diagnostics and original native session are -retained in the named scratch run. The initial control1 run was refused before -spawn for a missing prepared package and is retained separately, not replayed. - -Dispatcher source was bf9fae2f1; the proof runner was then committed as c723313ed. -The native worker used the existing main-checkout editable Core (observed clean -at 8b27e02aa) and unchanged pinned Pi a0f57a811b47862f. This is a real source-driver -and configured installed-native process proof, not a claim that the candidate -branch was installed or that all of phase 7 is complete. - -The requested full task() factories and method migration are staged in -phase7-task-declarations.patch and sent to pi-plan-claims (owner request 1895, -handoff 1946). That owner reports stale self-claims preventing its shared-file -edits; no denied edit was bypassed. Its phase/claim changes must be integrated -normally. Missing-checkout progress reads overlap the recently changed -ProgressSource in #1935; pi-cn-phase3 has the reproducer and implementation -handoff, so no competing ProgressSource change is made here. +# Phase 7: dispatch checked, ready work + +Owner: pi-cn-phase7. Integration base: origin/main `6a0f5e931`. +Shared task capability repair: pi-plan-claims #1928, checkpoint `1220a914f`. + +## Implemented ownership + +`PlanPhase.task` declares a `PhaseTask`. `UnspecifiedPhaseTask` stays visible +but cannot invent an order or start a unit. `CheckedPhaseTask` carries the +existing `PendingProblemOrder`, including its source, check, edits and brief. +Production full-phase task briefs are still separate work; the dispatch proof +is not their completion. + +`PlanIdle.enforce` calls that task. `UnownedWork` replaces the old +`UnownedWheelSpin` path, so checked released work does not require an already +idle unit. Both use `WorkDispatch`: storage preflight, isolated checkout, +stopped native declaration, canonical goal/order publication, then existing +`OwnerLifecycle.start`. Publication precedes execution. Failed or uncertain +startup state remains retained, never automatically replayed or adopted. + +The task's exact problem identity and accepted installed order determine its +completion. Other closed subtasks, withdrawn orders and merely delivered +orders do not complete it. Finite positive deadline intervals are required. +The existing assignment trace requirement still applies on transfer. + +## Source relationships + +The before/after census used refactor-audit's existing `Package.load` parser +over Core, tools and Toad production roots. The retained census reports zero +parse omissions. NRA's parser was unavailable in the shared environment; +that limitation was not treated as a successful NRA scan. + +- `CommsStandingRulesTool.apply` and `commander.rotate.broken_rules` use + `StandingRule.broken/enforce`; no second rotation entrypoint was added. +- `PlanPhase` and `PlanIdle` consume the dependency-light `PhaseTask` owner. +- `CheckedPhaseTask` stays with dispatch; no compatibility re-export exists. +- `UnownedWheelSpin` has no remaining declaration or consumer. +- `UnownedWork` belongs to the existing commander rule discovery package. + Importing dispatch in generic `standing_rules` instead created the real + storage/rule/thread import cycle; moving the one member removed that cycle. +- `ProblemOrder` owns refusal of unsupported reissue operations. +- `Registration` and `OwnerLifecycle` retain identity and process ownership; + no launch codec, second task store or copied fork history was introduced. +- Toad does not import the changed owner classes. Dynamic family discovery + and provider execution are runtime facts, not proved by the AST census. + +The CLI proof uses declaration-owned commands, reply states and the existing +`FieldCodec`. Observation uses `WireWatch`, not sleep polling. The configured +commander is read from its owner rather than compared with another literal. + +## Real native result + +Latest fresh run: `P256-native-20261019-1`, exit 0, source driver `643131ca3`. +No fake transport, provider, events, order or reply was used. + +- Unit: `unit-p1792441766503708626-r0` +- Configured model: `openai-codex/gpt-6.1-sol` +- Problem: `P1792441766503708626`; order: `ee1774f76fcb` +- Actual first reply: `ROTATION_REPLY_c9124a08ed18496fbf81c8a2ddff4ec3` +- Second rotation duplicated neither unit nor order. +- The actual reply passed the canonical order check. The unit stopped only + after its native turn ended; original native history remains intact. + +The later typed receipt decode was checked against that original saved order; +the read-only reply check also returned 1. This establishes source dispatch +through the configured native process, not candidate installation or full +production-phase completion. No new provider input is needed to retain it. + +## Source-line allocation + +Both roots' structural findings were repaired before allocating feature lines. +Against the integration base the new dispatch/task implementation adds 238 and +removes 37 production lines (net 201). Its real native proof adds 287 tool lines. +The batch therefore declares `Src-Budget: 488`, the exact combined new-feature +allocation, through the ratchet's existing commit-trailer mechanism. Neither +the checker, baseline, thresholds nor structural findings are changed. This is +the same allocation mechanism used by the installed install-gate feature. + +## Retained evidence and limits + +Owned scratch: `/home/ts/.cache/agent-scratch/pi-cn-phase7/`. Keep the original +native run immutable, including its private bus, order, reply and native history. +Before/after source observations and bounded test/ratchet results are in the +same owned directory. Disposable test/build output may be retired after borrower +checks; user sessions and uncertain inputs are not disposable output. + +Earlier native proof `run-20261019-control2` also passed. The initial control1 +attempt was refused before spawn for an unavailable prepared native package; +it was not replayed or hidden. No candidate install or PR merge was performed. + +The old root staging patch is removed: it described a superseded task API. +The full ControlPlane brief belongs to PR #1932's body and shared task owners, +not a second inactive implementation copied into the repository. diff --git a/phase7-task-declarations.patch b/phase7-task-declarations.patch deleted file mode 100644 index 7211d37ef2..0000000000 --- a/phase7-task-declarations.patch +++ /dev/null @@ -1,97 +0,0 @@ ---- a/src/agent_comms/plan_phases.py -+++ b/src/agent_comms/plan_phases.py -@@ -30,7 +30,9 @@ - - class PlanPhase(OrderPhase, declared_name=None): - after: ClassVar[tuple[type[PlanPhase], ...]] = () -- task: ClassVar[PhaseTask] = UnspecifiedPhaseTask() -+ @classmethod -+ def task(cls) -> PhaseTask: -+ return UnspecifiedPhaseTask() - - @classmethod - def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: -@@ -38,7 +40,7 @@ - - @classmethod - def done(cls, todos: Iterable[Todo]) -> bool: -- return cls.task.done(cls.serving(todos)) -+ return cls.task().done(cls.serving(todos)) - - @classmethod - def ordered(cls, todos: Iterable[Todo]) -> bool: -@@ -80,6 +82,36 @@ - class RecordPlanePhase(PlanPhase): - after = (RetainedStorePhase,) - -+ @classmethod -+ def task(cls) -> PhaseTask: -+ import shlex -+ import sys -+ from pathlib import Path -+ from .change_report import FileLine -+ from .goals import OrderCheck -+ from .recorded_time import RecordedEventTime -+ from .todos import ApplicationOrderRun, PendingProblemOrder -+ from .work_dispatch import CheckedPhaseTask -+ -+ checkout = Path(__file__).resolve().parents[2] -+ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", -+ "-m", "pytest", "-o", "addopts=", "-n", "0", -+ "tests/test_commander_problem_orders.py", "tests/test_commander_run_rules.py")) -+ return CheckedPhaseTask( -+ problem="P900003", repo="OpenHCSDev/agent-comms", -+ text="Complete and verify the full native commander record plane.", -+ deadline_seconds=7200, -+ order=PendingProblemOrder( -+ id="record-plane-full-task", owner=FileLine(str(Path(__file__).with_name("todos.py")), 1066), -+ check=OrderCheck(str(checkout), check), -+ edits=("Finish native verdict, strike, transfer, delivery and build-bound closing through the existing Todo and Goal owners.",), -+ banned=("No private verdict or strike files; no fixture pass claimed as native application acceptance.",), -+ report="Report the delivered commit and the copied-root native record-plane observations with their build revisions.", -+ run=ApplicationOrderRun("On a copied root, a real send-back, missed deadline and false completion each produce one record; a second strike fires the unit once; missing Works and wrong-build evidence are refused; a delivered checked order cannot transfer; renaming a deletion target cannot pass; only real installed evidence closes the whole task."), -+ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), -+ ), -+ ) -+ - - class ReviewQueuePhase(MergedPhase): - after = (RecordPlanePhase,) -@@ -88,6 +120,37 @@ - - class ControlPlanePhase(PlanPhase): - after = (RecordPlanePhase,) -+ -+ @classmethod -+ def task(cls) -> PhaseTask: -+ import shlex -+ import sys -+ from pathlib import Path -+ from .change_report import FileLine -+ from .goals import OrderCheck -+ from .recorded_time import RecordedEventTime -+ from .todos import ApplicationOrderRun, PendingProblemOrder -+ from .work_dispatch import CheckedPhaseTask -+ -+ checkout = Path(__file__).resolve().parents[2] -+ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", -+ "-m", "pytest", "-o", "addopts=", "-n", "0", -+ "tests/test_work_dispatch.py", "tests/test_standing_rules.py", -+ "tests/test_storage_rules.py", "tests/test_turn_digests.py")) -+ return CheckedPhaseTask( -+ problem="P900007", repo="OpenHCSDev/agent-comms", -+ text="Complete and verify the full native commander control plane.", -+ deadline_seconds=7200, -+ order=PendingProblemOrder( -+ id="control-plane-full-task", owner=FileLine(str(Path(__file__).with_name("standing_rules.py")), 25), -+ check=OrderCheck(str(checkout), check), -+ edits=("Move the minute wake into Core scheduling; dispatch checked ready work through native order and lifecycle owners; derive standing rules, load, growth, storage and time from their original owners; delete the replaced script and private-state decisions.",), -+ banned=("No live-bus proof writes, phase-name dispatch map, second codec, private heartbeat files or fixture pass claimed as native application acceptance.",), -+ report="Name the started scratch unit, retained order, first reply and build; report native session parity, duplicate decisions, time totals and every removed competing path.", -+ run=ApplicationOrderRun("Scratch rotation starts a real Pi unit for a declared ready task and receives its first reply; a second rotation duplicates neither unit nor order; a full native commander session uses no script process; replay of a preserved hour produces each required decision once and UnitTime classes sum to elapsed time; stopping the commander ends its wake."), -+ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), -+ ), -+ ) - - - class CutoverPhase(PlanPhase): From 5fd74d9ea800d097023c1280e9913504517e529f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:56:51 -0400 Subject: [PATCH 12/21] P256: derive merged task completion from the original app proof MergedProblemOrder must not erase verified application work or invent it from an installed merge. Make completion a read-only order capability and let the retained original supply it through codec reconstruction. Phase identity remains with pi-plan-claims on P244. --- src/agent_comms/todos.py | 10 ++++++++-- tests/test_work_dispatch.py | 6 +++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index d94af58112..6d75b7ec2e 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1313,7 +1313,6 @@ def instructions(self) -> tuple[str, ...]: class ProblemOrder(DeclaredFamily, affix="ProblemOrder"): """A problem's order state; an unassigned finding is not a fabricated order.""" pending: ClassVar[bool] = False - accepted: ClassVar[bool] = False line: ClassVar[str] = "No order" pull_request_label: ClassVar[str] = "Agent-Comms-Order:" finding: ProblemFinding = field(default_factory=UnlocatedProblemFinding, @@ -1338,6 +1337,10 @@ def phase(self) -> type[OrderPhase]: def for_phase(self, phase: type[OrderPhase]) -> ProblemOrder: raise ValueError("Only a checked order can declare a plan phase.") + @property + def accepted(self) -> bool: + return False + def require_transfer(self, todo: Todo) -> None: raise ValueError("This problem cannot receive another order.") @@ -1634,9 +1637,12 @@ def waiting_on_install(self, todo: Todo, read: dict[str, OrderProgress]) -> tupl class InstalledProblemOrder(DeliveredProblemOrder): works: str artifact: str - accepted: ClassVar[bool] = True line: ClassVar[str] = "Installed" + @property + def accepted(self) -> bool: + return True + def __post_init__(self): super().__post_init__() if not self.works.strip() or not self.artifact.strip(): diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py index 6c776046aa..06298deebc 100644 --- a/tests/test_work_dispatch.py +++ b/tests/test_work_dispatch.py @@ -4,10 +4,11 @@ import pytest +from agent_comms.field_codec import FieldCodec from agent_comms.goal_actions import CreateProblemGoalAction from agent_comms.plan_phases import RecordPlanePhase from agent_comms.recorded_time import RecordedEventTime -from agent_comms.todos import DoneTodoState, OperationDelivery, StrikeReason +from agent_comms.todos import DoneTodoState, OperationDelivery, ProblemOrder, StrikeReason from agent_comms.phase_tasks import UnspecifiedPhaseTask from agent_comms.work_dispatch import CheckedPhaseTask, WorkDispatch @@ -27,6 +28,9 @@ def test_phase_task_accepts_only_its_full_problem_with_installed_evidence(comman assert not task.done((replace(full, order=accepted),)) assert task.done((replace(full, state=DoneTodoState, order=accepted),)) assert not UnspecifiedPhaseTask().done((replace(full, state=DoneTodoState, order=accepted),)) + for original, expected in ((full.order, False), (accepted, True)): + merged = FieldCodec.decode(ProblemOrder, FieldCodec.encode(original.merged("a" * 40))) + assert task.done((replace(full, state=DoneTodoState, order=merged),)) is expected @pytest.mark.parametrize("interval", [0, -1, float("nan"), float("inf")]) From 13ced645e0f93c14d8aa5df15b010262950d4a20 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:27:34 -0400 Subject: [PATCH 13/21] Checkpoint P256 rebased assignment reconciliation as handed over Preserve the current source and tests unchanged for the next holder. Last recorded sanity batch: 42 passed, 2 failed; no tests rerun for this checkpoint. --- docs/refactor/phase7-dispatch.md | 50 +++++++++------ src/agent_comms/commander/standing.py | 87 +++++++++++++-------------- src/agent_comms/work_dispatch.py | 48 ++++++++------- tests/test_stall_watcher_strike.py | 5 +- tests/test_standing_rules.py | 7 ++- tests/test_work_dispatch.py | 28 ++++++++- 6 files changed, 129 insertions(+), 96 deletions(-) diff --git a/docs/refactor/phase7-dispatch.md b/docs/refactor/phase7-dispatch.md index b16864dc6d..56bedeae88 100644 --- a/docs/refactor/phase7-dispatch.md +++ b/docs/refactor/phase7-dispatch.md @@ -1,7 +1,7 @@ # Phase 7: dispatch checked, ready work -Owner: pi-cn-phase7. Integration base: origin/main `6a0f5e931`. -Shared task capability repair: pi-plan-claims #1928, checkpoint `1220a914f`. +Owner: pi-cn-phase7. Rebased onto main `65f58db93` under the direct conflict instruction. +P270's merged-order and installed-proof integration belongs to pi-connector (#2036). ## Implemented ownership @@ -11,12 +11,19 @@ existing `PendingProblemOrder`, including its source, check, edits and brief. Production full-phase task briefs are still separate work; the dispatch proof is not their completion. -`PlanIdle.enforce` calls that task. `UnownedWork` replaces the old -`UnownedWheelSpin` path, so checked released work does not require an already -idle unit. Both use `WorkDispatch`: storage preflight, isolated checkout, -stopped native declaration, canonical goal/order publication, then existing -`OwnerLifecycle.start`. Publication precedes execution. Failed or uncertain -startup state remains retained, never automatically replayed or adopted. +`PlanIdle.enforce` calls that task. The existing `UnownedWheelSpin` is the sole +owner selecting unowned rows: pair eligible idle units first, then allocate a +fresh destination only for the remaining unowned rows. Its stall watcher still +uses idle pairing only, so no spare leaves the old holder and strikes untouched. +`UnownedWork` and `WorkDispatch.reissue` are deleted. + +`WorkDispatch.publish` owns assignment publication for both destination lifetimes. +Fresh dispatch preflights storage, allocates its checkout and stopped native +registration, publishes, then calls `OwnerLifecycle.start`. `ReusedWorkDispatch` +publishes to an existing unit without allocating or starting another process. +The existing `WheelSpin` base builds the reissued action and runs both rule kinds; +the watcher retains its own assigner identity and revision-checked strike/release. +Failed or uncertain startup remains retained, never replayed or adopted. The task's exact problem identity and accepted installed order determine its completion. Other closed subtasks, withdrawn orders and merely delivered @@ -34,10 +41,16 @@ that limitation was not treated as a successful NRA scan. `StandingRule.broken/enforce`; no second rotation entrypoint was added. - `PlanPhase` and `PlanIdle` consume the dependency-light `PhaseTask` owner. - `CheckedPhaseTask` stays with dispatch; no compatibility re-export exists. -- `UnownedWheelSpin` has no remaining declaration or consumer. -- `UnownedWork` belongs to the existing commander rule discovery package. - Importing dispatch in generic `standing_rules` instead created the real - storage/rule/thread import cycle; moving the one member removed that cycle. +- `UnownedWheelSpin.watch/after_stall` remain the connector's entrypoints. + `tests/test_stall_watcher_strike.py` remains a consumer, including refusal, + blocked orders, no spare and root-release behavior. +- Only `UnownedWheelSpin.violations` selects unowned rows. There is no second + rule or generic standing-rule import of dispatch; that import would recreate + the storage/rule/thread cycle. +- The current source census is `P256-stall-owner-before.txt`: both retained and + main implementations, tests, tools and Toad were parsed through `Package.load`. + The first Toad path was absent; the corrected owned Toad checkout was appended. + Dynamic receiver resolution is not a behavioral proof. - `ProblemOrder` owns refusal of unsupported reissue operations. - `Registration` and `OwnerLifecycle` retain identity and process ownership; no launch codec, second task store or copied fork history was introduced. @@ -66,15 +79,12 @@ the read-only reply check also returned 1. This establishes source dispatch through the configured native process, not candidate installation or full production-phase completion. No new provider input is needed to retain it. -## Source-line allocation +## Source checks -Both roots' structural findings were repaired before allocating feature lines. -Against the integration base the new dispatch/task implementation adds 238 and -removes 37 production lines (net 201). Its real native proof adds 287 tool lines. -The batch therefore declares `Src-Budget: 488`, the exact combined new-feature -allocation, through the ratchet's existing commit-trailer mechanism. Neither -the checker, baseline, thresholds nor structural findings are changed. This is -the same allocation mechanism used by the installed install-gate feature. +Source growth alone is not a defect under the owner's corrected ruling. No new +budget-only or formatting-only commit is used for this reconciliation. Both +production roots still need their structural checks; the checker and baselines +are not modified here. ## Retained evidence and limits diff --git a/src/agent_comms/commander/standing.py b/src/agent_comms/commander/standing.py index 6509effb45..8b6b85eb00 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -3,6 +3,7 @@ from __future__ import annotations import time +from abc import abstractmethod from collections.abc import Callable, Iterable, Iterator from contextlib import AbstractContextManager from dataclasses import dataclass, field @@ -22,29 +23,10 @@ from ..threads import Thread from ..todos import FlatTrend, OrderProgress, PullRequests, Todo from ..turn_digests import OrderEditCountRead, TurnDigestRead, TurnsSince -from ..work_dispatch import WorkDispatch +from ..work_dispatch import ReusedWorkDispatch, WorkDispatch from .config import CommanderConfig -@dataclass(frozen=True) -class UnownedWork(UndatedStandingRule): - """An unowned checked row starts a unit, even when the idle fleet is empty.""" - - todo: Todo - - @classmethod - def violations(cls, comms: Comms, commander: str) -> tuple[UnownedWork, ...]: - return tuple(cls(todo) for todo in comms.goals.todos.for_commander( - comms.registry.require(commander).incarnation) if todo.pending and not todo.holder) - - def enforce(self, comms: Comms, commander: str) -> None: - WorkDispatch.reissue(comms, comms.registry.require(commander), self.todo) - - def line(self) -> str: - return (f"UNOWNED WORK: {self.todo.id} has a checked order and no unit" - " -> Core starts its unit in an isolated checkout") - - @dataclass(frozen=True) class Detached(UndatedStandingRule): unit: str @@ -103,16 +85,22 @@ def idle(cls, comms: Comms, commander: str) -> list[Thread]: and TurnDigestRead(comms.root, thread, bound).read().turns), key=lambda thread: thread.name) - def assign(self, comms: Comms, commander: str, unit: Thread, - assigner: ThreadIncarnation) -> None: - """The order sent again to ``unit`` through its Goal, with the order's own text.""" - todo = self.todo - order = todo.order.reissued(time.time()) - comms.goals.update_goal(unit.name, OrderGoalAction( - problem=todo.id, expected_revision=todo.revision, order=order, - request=todo.goal.request(), deadline=todo.deadline, commander=commander, - assigner=assigner, traced=todo.order.owner)) - comms.messaging.send(commander, unit.name, f"{self.line()}\n{order.instructions(todo)}") + @staticmethod + def order_for(todo: Todo, commander: str, assigner: ThreadIncarnation) -> OrderGoalAction: + return OrderGoalAction( + problem=todo.id, expected_revision=todo.revision, + order=todo.order.reissued(time.time()), request=todo.goal.request(), + deadline=todo.deadline, commander=commander, assigner=assigner, + traced=todo.order.owner) + + @abstractmethod + def dispatches(self, comms: Comms, commander: str) -> Iterable[WorkDispatch]: ... + + def enforce(self, comms: Comms, commander: str) -> None: + for dispatch in self.dispatches(comms, commander): + unit = dispatch.start(comms) + comms.messaging.send(commander, unit.name, + f"{self.line()}\n{dispatch.action.order.instructions(self.todo)}") @dataclass(frozen=True) @@ -141,33 +129,44 @@ def line(self) -> str: " to done did not fall across two readings -> Core strikes it and gives the order" " to an idle unit") - def enforce(self, comms: Comms, commander: str) -> None: + def dispatches(self, comms: Comms, commander: str) -> Iterable[WorkDispatch]: for unit in self.idle(comms, commander)[:1]: - self.assign(comms, commander, unit, StallWatcherIncarnation.watcher()) + yield ReusedWorkDispatch(self.order_for( + self.todo, commander, StallWatcherIncarnation.watcher()), self.todo.revision, unit) @dataclass(frozen=True) class UnownedWheelSpin(WheelSpin, UndatedStandingRule): """A unit holds no next order while the board has unowned rows.""" - unit: str + dispatch: WorkDispatch + + @property + def unit(self) -> str: + return self.dispatch.name @classmethod def violations(cls, comms: Comms, commander: str) -> tuple[UnownedWheelSpin, ...]: - return cls.paired(comms, commander, (todo for todo in - comms.goals.todos.for_commander(comms.registry.require(commander).incarnation) - if not todo.holder)) + actor = comms.registry.require(commander).incarnation + rows = tuple(todo for todo in comms.goals.todos.for_commander(actor) + if todo.pending and not todo.holder) + paired = cls.paired(comms, commander, rows, actor) + assigned = {rule.todo.id for rule in paired} + return paired + tuple(cls(todo, WorkDispatch( + cls.order_for(todo, commander, actor), todo.revision)) + for todo in rows if todo.id not in assigned) @classmethod - def paired(cls, comms: Comms, commander: str, - todos: Iterable[Todo]) -> tuple[UnownedWheelSpin, ...]: + def paired(cls, comms: Comms, commander: str, todos: Iterable[Todo], + assigner: ThreadIncarnation) -> tuple[UnownedWheelSpin, ...]: """Each pending row with the first idle unit not struck on it.""" idle, found = cls.idle(comms, commander), [] for todo in (todo for todo in todos if todo.pending): for unit in idle: if not todo.live_strikes(unit.name): idle.remove(unit) - found.append(cls(todo, unit.name)) + found.append(cls(todo, ReusedWorkDispatch( + cls.order_for(todo, commander, assigner), todo.revision, unit))) break return tuple(found) @@ -175,9 +174,8 @@ def line(self) -> str: return (f"WHEEL SPIN: {self.unit} holds no order while {self.todo.id} is unowned" f" -> Core gives it to {self.unit}") - def enforce(self, comms: Comms, commander: str) -> None: - self.assign(comms, commander, comms.registry.require(self.unit), - comms.registry.require(commander).incarnation) + def dispatches(self, comms: Comms, commander: str) -> Iterable[WorkDispatch]: + return (self.dispatch,) @classmethod def after_stall(cls, comms: Comms) -> None: @@ -185,9 +183,8 @@ def after_stall(cls, comms: Comms) -> None: stalled = comms.goals.todos.stalled(comms.registry.storage_rule) for commander in {todo.creator_identity.name for todo in stalled}: mine = (todo for todo in stalled if todo.creator_identity.name == commander) - for rule in cls.paired(comms, commander, mine): - rule.assign(comms, commander, comms.registry.require(rule.unit), - StallWatcherIncarnation.watcher()) + for rule in cls.paired(comms, commander, mine, StallWatcherIncarnation.watcher()): + rule.enforce(comms, commander) @classmethod def watch_pass(cls, opened_wire: Callable[[], AbstractContextManager[Comms]]) -> None: diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index 2f10f5df81..579efa41a0 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -17,7 +17,6 @@ from .goal_actions import ( CreateProblemGoalAction, GoalActionContext, - OrderGoalAction, ProblemOrderGoalAction, ) from .native_settings import NativeThreadDefaults @@ -40,11 +39,18 @@ class WorkDispatch: action: ProblemOrderGoalAction revision: int + @property + def name(self) -> str: + return f"unit-{self.action.problem.lower()}-r{self.revision}" + + def publish(self, comms: Comms, child: Thread) -> Thread: + comms.goals.update_goal(child.name, self.action) + return comms.registry.require(child.name) + def start(self, comms: Comms) -> Thread: defaults = NativeThreadDefaults() - # A concurrent rotation of this same row contends on one git checkout, - # not on two newly generated unit names. Never adopt a partial attempt. - name = f"unit-{self.action.problem.lower()}-r{self.revision}" + # A concurrent rotation contends on one checkout. Never adopt a partial attempt. + name = self.name storage = comms.registry.storage_rule checkout = storage.order_checkout(name, self.action.problem) candidate = comms.registry.new_thread( @@ -88,27 +94,23 @@ def start(self, comms: Comms) -> Thread: ) # A failed publication leaves a stopped unit and its checkout, not an # untracked running task. A failed/uncertain start never releases its order. - comms.goals.update_goal(child.name, self.action) + self.publish(comms, child) comms.owners.start(child.name) return comms.registry.require(child.name) - @classmethod - def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: - """Retained checked work needs no already-idle unit.""" - current = comms.goals.todos.get(todo.id) - current.require_revision(todo.revision) - if current.holder or not current.pending: - return - action = OrderGoalAction( - problem=current.id, - expected_revision=current.revision, - order=current.order.reissued(time.time()), - request=current.goal.request(), - deadline=current.deadline, - commander=commander.name, - assigner=commander.incarnation, - ) - cls(action, revision=current.revision).start(comms) + +@dataclass(frozen=True) +class ReusedWorkDispatch(WorkDispatch): + """Publish to an existing unit without allocating or starting another process.""" + + unit: Thread + + @property + def name(self) -> str: + return self.unit.name + + def start(self, comms: Comms) -> Thread: + return self.publish(comms, comms.registry.require(self.name)) @dataclass(frozen=True) @@ -130,7 +132,7 @@ def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None history = comms.goals.todos.history_for_commander(commander.incarnation) if not phase.ready(history) or phase.ordered(history): return - # This task's existing row belongs to UnownedWork. Other phase subtasks + # This task's existing row belongs to UnownedWheelSpin. Other phase subtasks # neither supply this task's check nor replace its explicit identity. if any(todo.id == self.problem for todo in history): return diff --git a/tests/test_stall_watcher_strike.py b/tests/test_stall_watcher_strike.py index 5fdf42d87d..19813c61c7 100644 --- a/tests/test_stall_watcher_strike.py +++ b/tests/test_stall_watcher_strike.py @@ -109,8 +109,9 @@ def test_blocked_held_orders_are_not_struck_or_released(commander, commander_scr comms = Comms(copied) standing.UnownedWheelSpin.after_stall(comms) with pytest.raises(TodoConflict): - standing.UnownedWheelSpin(blocked, 'spare').assign( - comms, 'lead', comms.registry.require('spare'), StallWatcherIncarnation.watcher()) + action = standing.WheelSpin.order_for(blocked, 'lead', StallWatcherIncarnation.watcher()) + dispatch = standing.ReusedWorkDispatch(action, blocked.revision, comms.registry.require('spare')) + standing.UnownedWheelSpin(blocked, dispatch).enforce(comms, 'lead') assert comms.goals.todos.get('P1') == blocked == original.goals.todos.get('P1') diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 1d92e9c184..742a0e85e6 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -242,11 +242,12 @@ def progress(): taken = StrikeReason("The unit was taken off P2 so the test leaves it unowned.") comms.goals.fire("worker", taken, "lead") assert comms.goals.todos.get("P2").holder is None - [unowned] = module.UnownedWork.violations(comms, "lead") + [unowned] = module.UnownedWheelSpin.violations(comms, "lead") assert unowned.todo.id == "P2" + assert type(unowned.dispatch) is module.WorkDispatch assert unowned.line() == ( - "UNOWNED WORK: P2 has a checked order and no unit" - " -> Core starts its unit in an isolated checkout") + f"WHEEL SPIN: {unowned.unit} holds no order while P2 is unowned" + f" -> Core gives it to {unowned.unit}") # Actual unit launch/first reply is a separate native application proof. assert comms.goals.todos.get("P2").holder is None diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py index 06298deebc..f98ecbc7ad 100644 --- a/tests/test_work_dispatch.py +++ b/tests/test_work_dispatch.py @@ -4,15 +4,17 @@ import pytest +from agent_comms.commander.standing import UnownedWheelSpin from agent_comms.field_codec import FieldCodec from agent_comms.goal_actions import CreateProblemGoalAction from agent_comms.plan_phases import RecordPlanePhase from agent_comms.recorded_time import RecordedEventTime from agent_comms.todos import DoneTodoState, OperationDelivery, ProblemOrder, StrikeReason from agent_comms.phase_tasks import UnspecifiedPhaseTask -from agent_comms.work_dispatch import CheckedPhaseTask, WorkDispatch +from agent_comms.work_dispatch import CheckedPhaseTask, ReusedWorkDispatch, WorkDispatch from test_commander_problem_orders import create, order +from test_standing_rules import working def test_phase_task_accepts_only_its_full_problem_with_installed_evidence(commander): @@ -68,6 +70,26 @@ def test_invalid_request_refuses_dispatch_before_allocating_anything(commander): assert comms.registry.all_threads() == before +def test_one_rule_pairs_idle_units_before_allocating_unowned_fallbacks(commander): + comms, _ = commander + create(commander, "P1") + create(commander, "P2") + comms.goals.fire("worker", StrikeReason("Release both orders for reassignment."), "lead") + spare = comms.registry.declare(comms.registry.new_thread( + "spare", frozenset({"comms"}), str(comms.root), created_at=3.0)) + working(comms, spare.name, spare.created_at) + rules = UnownedWheelSpin.violations(comms, "lead") + assert {rule.todo.id for rule in rules} == {"P1", "P2"} + assert len({rule.unit for rule in rules}) == 2 + reused = [rule for rule in rules if isinstance(rule.dispatch, ReusedWorkDispatch)] + fresh = [rule for rule in rules if type(rule.dispatch) is WorkDispatch] + assert len(reused) == len(fresh) == 1 + assert reused[0].unit == spare.name + assert not fresh[0].todo.holder + # Selection must not allocate or launch either destination. + assert set(comms.registry.all_threads()) == {"lead", "worker", "spare"} + + def test_released_order_preserves_the_two_owner_trace_requirement(commander): comms, _ = commander lead, worker = (comms.registry.require(name) for name in ("lead", "worker")) @@ -80,7 +102,7 @@ def test_released_order_preserves_the_two_owner_trace_requirement(commander): comms.goals.todos.fire(worker.name, lead.incarnation, reason) released = comms.goals.todos.get(todo.id) assigned = comms.goals.todos.assign( - todo.id, expected_revision=released.revision, owner=second, parent=lead, + todo.id, expected_revision=released.revision, owner=second, assigner=lead.incarnation, generation="second-generation", goal=todo.goal, deadline=todo.deadline, order=todo.order.reissued(200), ) @@ -89,7 +111,7 @@ def test_released_order_preserves_the_two_owner_trace_requirement(commander): released = comms.goals.todos.get(todo.id) with pytest.raises(ValueError, match="pass --traced"): comms.goals.todos.assign( - todo.id, expected_revision=released.revision, owner=third, parent=lead, + todo.id, expected_revision=released.revision, owner=third, assigner=lead.incarnation, generation="third-generation", goal=todo.goal, deadline=todo.deadline, order=todo.order.reissued(300), ) From cecca9878b82524bdd33e6e19b6d2167df230164 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:31:33 -0400 Subject: [PATCH 14/21] P256: reconcile release fixtures with idle-unit reuse --- src/agent_comms/work_dispatch.py | 5 +++-- tests/test_standing_rules.py | 5 ++++- tests/test_work_dispatch.py | 2 +- tools/prove_commander_dispatch.py | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index 579efa41a0..cdcac546fb 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -1,4 +1,4 @@ -"""Checked work starts through Core's existing order and native lifecycle owners. +"""Core assigns checked work to its unit, then starts it through the native lifecycle. A phase declares an existing PendingProblemOrder, not a second task format. Work without that declaration stays visible; readiness is not permission to invent an @@ -44,6 +44,7 @@ def name(self) -> str: return f"unit-{self.action.problem.lower()}-r{self.revision}" def publish(self, comms: Comms, child: Thread) -> Thread: + """Assign the native order before its unit can begin work.""" comms.goals.update_goal(child.name, self.action) return comms.registry.require(child.name) @@ -122,7 +123,7 @@ class CheckedPhaseTask(PhaseTask): text: str order: PendingProblemOrder deadline_seconds: float - instruction = "Core starts the declared checked task in its own unit checkout" + instruction = "Core assigns the declared checked task to a unit and starts it" def __post_init__(self) -> None: if not math.isfinite(self.deadline_seconds) or self.deadline_seconds <= 0: diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 742a0e85e6..4053c77e2d 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -244,7 +244,10 @@ def progress(): assert comms.goals.todos.get("P2").holder is None [unowned] = module.UnownedWheelSpin.violations(comms, "lead") assert unowned.todo.id == "P2" - assert type(unowned.dispatch) is module.WorkDispatch + # Firing releases P2 without striking its recently active worker on that order. + # Reuse that eligible idle unit instead of allocating a second process. + assert type(unowned.dispatch) is module.ReusedWorkDispatch + assert unowned.unit == "worker" assert unowned.line() == ( f"WHEEL SPIN: {unowned.unit} holds no order while P2 is unowned" f" -> Core gives it to {unowned.unit}") diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py index f98ecbc7ad..460b55d64e 100644 --- a/tests/test_work_dispatch.py +++ b/tests/test_work_dispatch.py @@ -74,7 +74,7 @@ def test_one_rule_pairs_idle_units_before_allocating_unowned_fallbacks(commander comms, _ = commander create(commander, "P1") create(commander, "P2") - comms.goals.fire("worker", StrikeReason("Release both orders for reassignment."), "lead") + comms.goals.fire("worker", StrikeReason("The unit stopped work on both orders and left their changes unfinished."), "lead") spare = comms.registry.declare(comms.registry.new_thread( "spare", frozenset({"comms"}), str(comms.root), created_at=3.0)) working(comms, spare.name, spare.created_at) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index f82e4a1e19..3b7b34ed8d 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -33,6 +33,7 @@ from agent_comms.plan_phases import RecordPlanePhase from agent_comms.progress_claims import WorkingClaim from agent_comms.recorded_time import RecordedEventTime +from agent_comms.storage_rules import StorageRule from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_status import StoppedThreadStatus from agent_comms.todos import MechanicalOrderRun, OperationDelivery, PendingProblemOrder @@ -129,7 +130,7 @@ class RunProofCommand(ProofCommand): def apply(self, project: Path) -> int: root = self.root.expanduser().resolve() - allowed = Path.home() / ".cache/agent-scratch/pi-cn-phase7" + allowed = StorageRule().scratch_base if not root.is_relative_to(allowed) or root.exists(): raise ValueError("Proof needs a NEW root under the owner's agent-scratch directory") home = root.parent / "home" From bd74909dae5c35709e3af4b5ff933fcf5e63eee5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:38:01 -0400 Subject: [PATCH 15/21] P256: import repository identity from its moved owner --- tools/prove_commander_dispatch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 3b7b34ed8d..c3915ffdc7 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -23,7 +23,7 @@ from agent_comms.change_report import FileLine from agent_comms.cli_commands import DeclaredOptions, option from agent_comms.commander import rotate -from agent_comms.commander.config import CommanderConfig, Repo +from agent_comms.commander.config import CommanderConfig from agent_comms.comms import Comms from agent_comms.declared_family import DeclaredFamily from agent_comms.field_codec import FieldCodec, PathText @@ -33,6 +33,7 @@ from agent_comms.plan_phases import RecordPlanePhase from agent_comms.progress_claims import WorkingClaim from agent_comms.recorded_time import RecordedEventTime +from agent_comms.repo import Repo from agent_comms.storage_rules import StorageRule from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_status import StoppedThreadStatus From 1c414771b2c247f68304c2bf293c277adf5cfac1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:44:41 -0400 Subject: [PATCH 16/21] =?UTF-8?q?P256:=20use=20current=20main=E2=80=99s=20?= =?UTF-8?q?declared=20CLI=20decoding=20owner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/prove_commander_dispatch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index c3915ffdc7..063f004046 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -21,12 +21,12 @@ from uuid import uuid4 from agent_comms.change_report import FileLine -from agent_comms.cli_commands import DeclaredOptions, option +from agent_comms.cli_bridges.shared import DeclaredOptions from agent_comms.commander import rotate from agent_comms.commander.config import CommanderConfig from agent_comms.comms import Comms from agent_comms.declared_family import DeclaredFamily -from agent_comms.field_codec import FieldCodec, PathText +from agent_comms.field_codec import FieldCodec, PathText, option from agent_comms.goal_actions import SetGoalAction from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder from agent_comms.messages import Message @@ -280,7 +280,7 @@ def main() -> int: arguments = parser.parse_args() return ( ProofCommand.member_named(arguments.command) - .from_namespace(arguments) + .from_arguments(arguments) .apply(Path(__file__).resolve().parents[1]) ) From 541590a8896083d69c15e9bfd16f39378d59c9db Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:52:41 -0400 Subject: [PATCH 17/21] =?UTF-8?q?P256:=20compose=20installed=20evidence=20?= =?UTF-8?q?with=20main=E2=80=99s=20flat=20merged=20order?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent_comms/todos.py | 8 ++++++++ tests/test_work_dispatch.py | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 6d75b7ec2e..675bdecef0 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1653,6 +1653,9 @@ def installed(self, works, artifact): raise ValueError("The order already records different installed evidence.") return self + def merged(self, sha: str) -> ProblemOrder: + return self.copy_as(InstalledMergedProblemOrder, sha=sha) + def require_close(self): pass @@ -1660,6 +1663,11 @@ def waiting_on_install(self, todo: Todo, read: dict[str, OrderProgress]) -> tupl return () +@dataclass(frozen=True, kw_only=True) +class InstalledMergedProblemOrder(MergedProblemOrder, InstalledProblemOrder): + """An installed merge retains the application's existing evidence, without a wrapper.""" + + @dataclass(frozen=True, kw_only=True) class WithdrawnProblemOrder(CheckedProblemOrder): """An order the commander sent in error and retired before delivery; it strikes no one.""" diff --git a/tests/test_work_dispatch.py b/tests/test_work_dispatch.py index 460b55d64e..33008f549e 100644 --- a/tests/test_work_dispatch.py +++ b/tests/test_work_dispatch.py @@ -31,7 +31,12 @@ def test_phase_task_accepts_only_its_full_problem_with_installed_evidence(comman assert task.done((replace(full, state=DoneTodoState, order=accepted),)) assert not UnspecifiedPhaseTask().done((replace(full, state=DoneTodoState, order=accepted),)) for original, expected in ((full.order, False), (accepted, True)): - merged = FieldCodec.decode(ProblemOrder, FieldCodec.encode(original.merged("a" * 40))) + encoded = FieldCodec.encode(original.merged("a" * 40)) + assert "original" not in encoded + if expected: + assert encoded["works"] == original.works + assert encoded["artifact"] == original.artifact + merged = FieldCodec.decode(ProblemOrder, encoded) assert task.done((replace(full, state=DoneTodoState, order=merged),)) is expected From 96b80e8bda02c0f22ba897137f19347e03840377 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 22:12:48 -0400 Subject: [PATCH 18/21] Use the selected commander context in native dispatch proof --- tools/prove_commander_dispatch.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 063f004046..185223b0ad 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -24,6 +24,7 @@ from agent_comms.cli_bridges.shared import DeclaredOptions from agent_comms.commander import rotate from agent_comms.commander.config import CommanderConfig +from agent_comms.commander.order import CommanderContext from agent_comms.comms import Comms from agent_comms.declared_family import DeclaredFamily from agent_comms.field_codec import FieldCodec, PathText, option @@ -186,11 +187,9 @@ class ObserveProofCommand(ProofCommand): def apply(self, project: Path) -> int: root = self.root.resolve() - config = CommanderConfig.installed() - if config.root != root: - raise ValueError("Observe requires the separately configured proof commander") - comms = Comms(root) - commander = comms.registry.require(config.commander) + context = CommanderContext.read_root(root) + comms = context.comms + commander = comms.registry.require(context.commander) problem = f"P{time.time_ns()}" unit = f"unit-{problem.lower()}-r0" token = f"ROTATION_REPLY_{uuid4().hex}" @@ -235,7 +234,7 @@ def apply(self, project: Path) -> int: ("git", "-C", str(project), "rev-parse", "HEAD"), text=True ) print(f"SOURCE {source.strip()}", flush=True) - for line in rotate.broken_rules(): + for line in rotate.broken_rules(context): print(line, flush=True) assigned = comms.goals.todos.get(problem) received_order = FieldCodec.decode(PendingProblemOrder, FieldCodec.encode(assigned.order)) @@ -249,7 +248,7 @@ def apply(self, project: Path) -> int: ) (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2)) before = comms.registry.all_threads() - for line in rotate.broken_rules(): + for line in rotate.broken_rules(context): print(line, flush=True) assert comms.registry.all_threads().keys() == before.keys(), "duplicate unit" after = FieldCodec.decode( From b6ffe72d3b9eb896f52b37c567c384dce6ab3d3b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 22:25:08 -0400 Subject: [PATCH 19/21] Use the native standing-rule tool without the retired rotate wrapper --- tools/prove_commander_dispatch.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/prove_commander_dispatch.py b/tools/prove_commander_dispatch.py index 185223b0ad..6b91de9b82 100644 --- a/tools/prove_commander_dispatch.py +++ b/tools/prove_commander_dispatch.py @@ -1,4 +1,4 @@ -"""Run real rotation/native dispatch on a new scratch bus and retain its reply. +"""Run real checked-order native dispatch on a new scratch bus and retain its reply. No fake transport, provider or history is used. Failed attempts remain retained. """ @@ -22,7 +22,6 @@ from agent_comms.change_report import FileLine from agent_comms.cli_bridges.shared import DeclaredOptions -from agent_comms.commander import rotate from agent_comms.commander.config import CommanderConfig from agent_comms.commander.order import CommanderContext from agent_comms.comms import Comms @@ -40,7 +39,7 @@ from agent_comms.thread_status import StoppedThreadStatus from agent_comms.todos import MechanicalOrderRun, OperationDelivery, PendingProblemOrder from agent_comms.tool_caller import ThreadCaller -from agent_comms.tools import CommsHistoryTool, CommsOrderDoneTool +from agent_comms.tools import CommsHistoryTool, CommsOrderDoneTool, CommsStandingRulesTool from agent_comms.wire_watch import WatchSource, WireWatch from agent_comms.work_dispatch import CheckedPhaseTask @@ -139,8 +138,8 @@ def apply(self, project: Path) -> int: home.mkdir(parents=True) comms = Comms(root) comms.messaging.initialize_private_initial_protocol() - # A fresh interpreter acquires the scratch configuration before importing - # rotation. Keep the actual storage declaration across its isolated HOME. + # A fresh interpreter acquires the scratch commander configuration. + # Keep the actual storage declaration across its isolated HOME. (root / "storage.json").write_text( json.dumps(FieldCodec.encode(comms.registry.storage_rule)) ) @@ -234,7 +233,7 @@ def apply(self, project: Path) -> int: ("git", "-C", str(project), "rev-parse", "HEAD"), text=True ) print(f"SOURCE {source.strip()}", flush=True) - for line in rotate.broken_rules(context): + for line in context.call(CommsStandingRulesTool()).broken: print(line, flush=True) assigned = comms.goals.todos.get(problem) received_order = FieldCodec.decode(PendingProblemOrder, FieldCodec.encode(assigned.order)) @@ -248,7 +247,7 @@ def apply(self, project: Path) -> int: ) (root.parent / "order.json").write_text(json.dumps(FieldCodec.encode(assigned), indent=2)) before = comms.registry.all_threads() - for line in rotate.broken_rules(context): + for line in context.call(CommsStandingRulesTool()).broken: print(line, flush=True) assert comms.registry.all_threads().keys() == before.keys(), "duplicate unit" after = FieldCodec.decode( From 183be2f636ee224168cbc0aebf7b708a98d1b56d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 22:32:31 -0400 Subject: [PATCH 20/21] Derive folded phase attribution without inheriting acceptance --- src/agent_comms/todos.py | 4 ++++ tests/test_plan_phases.py | 27 ++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index ea58133091..28abc2b70d 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1394,6 +1394,10 @@ class FoldedProblemOrder(ProblemOrder): why: str line: ClassVar[str] = "Folded" + @property + def phase(self) -> type[OrderPhase]: + return self.original.phase + def withdrawals(self, todo: Todo) -> tuple[str, ...]: return (f"{todo.id} folded into {self.into}: {self.why}",) diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index 82dfc41aeb..785a3e98eb 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -124,7 +124,32 @@ def test_merged_phase_is_inherited_and_round_trips_without_a_wrapper(commander): assert original.phase is RecordPlanePhase assert not order.accepted folded = FoldedProblemOrder(original=original, into="P2", why="Keep one owner") - assert folded.phase is UnplannedPhase + assert folded.phase is RecordPlanePhase + assert not folded.accepted + + +@pytest.mark.parametrize("phase", (CutoverPhase, CommanderPagePhase)) +@pytest.mark.parametrize("installed", (False, True)) +def test_folded_provenance_retains_phase_without_acceptance( + commander, monkeypatch, phase, installed): + todo = create(commander, "P1") + declared = replace(todo, order=todo.order.for_phase(phase)) + monkeypatch.setattr(phase, "task", task(declared)) + original = accepted(declared).order if installed else declared.order + folded = original.folded("P2", "Keep one owner") + nested = folded.folded("P3", "Keep the retained provenance") + for order in (folded, nested): + restored = FieldCodec.decode(ProblemOrder, FieldCodec.encode(order)) + assert restored == order + assert restored.phase is phase + assert not restored.accepted + saved = replace(todo, order=restored, state=DoneTodoState, assignment=None) + assert phase.serving((saved,)) == (saved,) + assert not phase.done((saved,)) + with pytest.raises(ValueError, match="Only a checked order"): + restored.for_phase(UnplannedPhase) + assert original.phase is phase + assert original.accepted is installed def test_closed_related_subtask_does_not_accept_a_whole_phase(commander, monkeypatch): From 9e4a20bd73d8c2377d83c5ec0f879242b7a0fbf7 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 22:34:22 -0400 Subject: [PATCH 21/21] Exercise provenance through admissible merged and installed states --- tests/test_plan_phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index 785a3e98eb..65c52d9601 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -135,7 +135,7 @@ def test_folded_provenance_retains_phase_without_acceptance( todo = create(commander, "P1") declared = replace(todo, order=todo.order.for_phase(phase)) monkeypatch.setattr(phase, "task", task(declared)) - original = accepted(declared).order if installed else declared.order + original = accepted(declared).order if installed else declared.order.merged("a" * 40) folded = original.folded("P2", "Keep one owner") nested = folded.folded("P3", "Keep the retained provenance") for order in (folded, nested):