Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d85b061
P301 checkpoint: observe live native order progress without claiming …
trissim Oct 20, 2026
524cd61
Exercise copied native creation assignment evidence and strikes; repa…
trissim Oct 20, 2026
356e332
Own candidate worker retirement and measure native calls through Comm…
trissim Oct 20, 2026
9a74e86
Let parity declarations own their comparison callable
trissim Oct 20, 2026
049e801
Declare comparisons once and keep lifecycle observations typed at the…
trissim Oct 20, 2026
d4e77bd
Select lifecycle units from the caller or canonical held orders, not …
trissim Oct 20, 2026
f7b3284
Route native closing through the existing commander invocation owner
trissim Oct 20, 2026
2c7ccc8
Let the existing lifecycle copier include real receiver threads for t…
trissim Oct 20, 2026
58280c3
Read commander authority through its existing configured context inst…
trissim Oct 20, 2026
8de42d1
Accept read and write request capabilities in lifecycle comparison de…
trissim Oct 20, 2026
5adf2ed
Compare real current and native refusals to hand delivered checked wo…
trissim Oct 20, 2026
c474d9f
Admit both read and thread-only tools in lifecycle observations
trissim Oct 20, 2026
47ac405
Let BoundedRun own native comparison process outcomes and retirement …
trissim Oct 20, 2026
80c5348
Preserve caller-owned cwd and sanitized environments in the bounded p…
trissim Oct 20, 2026
c140e10
Compare deletion-intent refusal and acceptance through both real deli…
trissim Oct 20, 2026
9aca553
Place checked-commit worktrees under the configured checkout owner
trissim Oct 20, 2026
afbb68f
Exercise current command and native entrypoints with exact delivered-…
trissim Oct 20, 2026
e9f6345
Let declared requests compose argument projection and let the CLI own…
trissim Oct 20, 2026
eca51be
Use caller-owned identity and declared native CLI projection in delet…
trissim Oct 20, 2026
928cfa5
Bind the native CLI module at its ordinary module import boundary
trissim Oct 20, 2026
4ca8aab
Delete the other hand-built native tool argv and envelope projection
trissim Oct 20, 2026
dde7370
Use the shared bounded lifecycle producer and typed delivery outcomes
trissim Oct 20, 2026
472b1f2
Remove codec imports made obsolete by native declaration ownership
trissim Oct 20, 2026
f6679cf
Pass candidate Python environment through the shared process owner ex…
trissim Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plans/commander-native-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
| 28 | Measured load and self-report | `capacity.py` unit count | `commander.capacity.Load.of` reports orders, overdue, edits and forks; self-report comparison unverified | `control_plane` |
| 29 | No handoff of finished, checked work | none; scratch transfer moved P507 after its fix was pushed and checked | Transfer refuses delivered checked work; that owner merges; check is reused | `record_plane` |
| 30 | A proof is bound to the build it ran on | none; screenshots and logs are bare paths | Build run records runtime and Core/Toad revisions; close requires delivered commit | `record_plane` |
| 31 | An order states its intent as a deletion target | none; P520 renamed a decision rather than deleting it | Order owns deletion family/site/count; delivery refuses nonzero or renamed decisions | `record_plane` |
| 31 | An order states its intent as a deletion target | Current order command with a declared FamilyDeletion | CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count | `record_plane` |
| 32 | Where units' time goes, classified | none; activity.log lacks durations and turn boundaries | `UnitTime`: model, build/check/test/shell, dependency/commander waits and idle; classify large waits | `commander_page`, `control_plane` |
| 33 | Order progress beside its deadline; the unit never sees a due time | `order.py board`, `overdue`, `due`, `send`; `rotate.py` | Derived `OrderProgress`; merged work draws no strike; first commit 10 min, later progress 15 min STALL; units see no due time | `one_order_owner`, `control_plane` |
| 34 | Several commanders on one machine and bus, one per project | none; a second commander.json install overwrites the first | Registry commander/project ownership; caller from --from; scoped orders, strikes, load, board, alarms and forks; raw-send guard; duplicate project commander refused | `one_order_owner`, `cutover` |
Expand Down
120 changes: 62 additions & 58 deletions src/agent_comms/candidate_maintenance.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
"""Best-effort indexing of successful appends after the outer locks release.

Losing a hint or crashing the daemon leaves the optional index unavailable;
canonical bus and sealed SQL still drive original delivery. Byte positions
include observations as well as messages, without inventing message sequences.
"""
"""Index successful appends after outer locks release, within the owning root's lifetime."""

from __future__ import annotations

import logging
import sqlite3
import threading
from contextlib import ExitStack
from pathlib import Path

from .bus_route_counts import BusRouteCounts
from .coordination_errors import CoordinationError
from .routing import DeliveryMessage
from .wire_log import CurrentSnapshotOpening, WireLog
from .wake_candidate_index import FirstBuild, ProjectionUnavailableError, WakeCandidateIndex
from .wire_log import CurrentSnapshotOpening, WireLog

_LOG = logging.getLogger(__name__)
_MAX_ACTIVE_ROOTS = 32
_guard = threading.Lock()
_pending: dict[Path, tuple[WireLog, int]] = {}
_pending: dict[Path, CandidateMaintenance] = {}


class CandidateMaintenanceFailed(RuntimeError): # noqa: N818 - named worker failure
Expand All @@ -33,72 +27,82 @@ def __init__(self, root: Path) -> None:
super().__init__(f"Candidate maintenance failed for {root}")


def schedule_candidate_catchup(log: WireLog, through_bytes: int) -> None:
"""Prepare writer indexes, then signal their existing maintenance worker."""
if through_bytes <= 0:
raise ValueError("candidate notification needs an actual committed byte position")
BusRouteCounts(log.path).prepare()
root = log.path.parent
with _guard:
current = _pending.get(root)
if current is not None:
_pending[root] = (current[0], max(current[1], through_bytes))
return
if len(_pending) >= _MAX_ACTIVE_ROOTS:
raise CandidateMaintenanceFailed(root)
_pending[root] = (log, through_bytes)
try:
threading.Thread(
target=_drain_candidate,
args=(root,),
name="agent-comms-candidate-maintenance",
daemon=True,
).start()
except RuntimeError as error:
_pending.pop(root, None)
raise CandidateMaintenanceFailed(root) from error


def _retire_candidate(root: Path) -> None:
with _guard:
_pending.pop(root, None)
class CandidateMaintenance(threading.Thread):
"""One root's coalesced cursor and actual worker; retirement joins this owner."""

def __init__(self, log: WireLog, through_bytes: int) -> None:
super().__init__(name="agent-comms-candidate-maintenance", daemon=True)
self.log = log
self.through_bytes = through_bytes

def _drain_candidate(root: Path) -> None:
with ExitStack() as cleanup:
cleanup.callback(_retire_candidate, root)
def run(self) -> None:
root = self.log.path.parent
try:
with _guard:
log, _bytes = _pending[root]
# Source ownership is checked off the producer path. Maintenance cannot
# create the protocol marker or authorize delivery.
with log.checked_read() as source:
with self.log.checked_read() as source:
metadata = source.marker
root_id = metadata.root_id
index = WakeCandidateIndex(log)
routes = BusRouteCounts(log.path)
index = WakeCandidateIndex(self.log)
routes = BusRouteCounts(self.log.path)
while True:
with _guard:
target = _pending[root][1]
target = self.through_bytes
hint = index.notify_committed_append(root_id=root_id, through_bytes=target)
result = index.catch_up_committed_append(
hint, start=FirstBuild()
)
with log.opened_wire_snapshot(opening=CurrentSnapshotOpening()) as snapshot:
result = index.catch_up_committed_append(hint, start=FirstBuild())
with self.log.opened_wire_snapshot(opening=CurrentSnapshotOpening()) as snapshot:
route_result = routes.sync(
snapshot,
lambda record: DeliveryMessage.from_wire(record, root_id=snapshot.metadata.root_id),
lambda record: DeliveryMessage.from_wire(
record, root_id=snapshot.metadata.root_id
),
)
if result.caught_up and route_result.complete:
with _guard:
if _pending[root][1] <= result.checkpoint_bytes:
# Disarm cleanup before a successor can own this root.
cleanup.pop_all()
if self.through_bytes <= result.checkpoint_bytes:
_pending.pop(root)
return
except (
OSError, ValueError, sqlite3.Error, CoordinationError, ProjectionUnavailableError,
OSError,
ValueError,
sqlite3.Error,
CoordinationError,
ProjectionUnavailableError,
) as error:
failure = CandidateMaintenanceFailed(root)
_LOG.exception("%s", failure)
raise failure from error
finally:
with _guard:
if root in _pending and _pending[root] is self:
_pending.pop(root)


def schedule_candidate_catchup(log: WireLog, through_bytes: int) -> None:
"""Prepare writer indexes, then coalesce into the root's actual worker."""
if through_bytes <= 0:
raise ValueError("candidate notification needs an actual committed byte position")
BusRouteCounts(log.path).prepare()
root = log.path.parent
with _guard:
if root in _pending:
worker = _pending[root]
worker.through_bytes = max(worker.through_bytes, through_bytes)
return
if len(_pending) >= _MAX_ACTIVE_ROOTS:
raise CandidateMaintenanceFailed(root)
worker = CandidateMaintenance(log, through_bytes)
_pending[root] = worker
try:
worker.start()
except RuntimeError as error:
_pending.pop(root)
raise CandidateMaintenanceFailed(root) from error


def drain_candidate_catchup(root: Path) -> None:
"""A stopped root cannot be renamed or removed while its worker still writes."""
while True:
with _guard:
if root not in _pending:
return
worker = _pending[root]
worker.join()
2 changes: 2 additions & 0 deletions src/agent_comms/check_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing import ClassVar

from .attempt_recovery import RecoveryMonitorCapability
from .candidate_maintenance import drain_candidate_catchup
from .child_process import NoProcess
from .cli_result import CliResult
from .comms import Comms
Expand Down Expand Up @@ -74,6 +75,7 @@ def open(self) -> Iterator[Comms]:
):
copied.owners.stop(thread.name)
finally:
drain_candidate_catchup(root)
RecoveryMonitorCapability.retire_check_root(root)

def _populate(self, copied: Comms) -> None:
Expand Down
7 changes: 3 additions & 4 deletions src/agent_comms/commander/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,7 @@ def problem(self, order_file, unit, due, where, disk=False,
def close(self, pid, works, artifact=''):
"""Core owns closing: the installed evidence, the artifact and the no-open-order rule live on the Todo."""
evidence = artifact or self.comms.goals.todos.get(pid).order.closing_artifact()
ThreadCaller(self.commander).invoke(CommsProblemCloseTool(problem=pid,
works=works, artifact=evidence), self.comms)
self.call(CommsProblemCloseTool(problem=pid, works=works, artifact=evidence))
self.write_board()
print(f'{pid} closed')

Expand Down Expand Up @@ -729,8 +728,8 @@ def due(self, pid: str, at: str, why: str) -> None:
moved = deadline(at)
self.call(CommsChangeProblemTool(problem=pid, operation=AssignTodo(
todo.revision, todo.assignment, moved, todo.order, todo.goal)))
self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} due '
f'{hhmm(todo.deadline)} moved to {hhmm(moved)}): {why}', 'Deadline',
self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} '
f'deadline changed): {why}', 'Deadline',
VerdictAudience.of(todo.held().owner, todo.goal))
print(f'{pid}: due {hhmm(moved)}')

Expand Down
5 changes: 3 additions & 2 deletions src/agent_comms/commander_parity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Iterable
from collections.abc import Callable, Iterable
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, ClassVar
Expand Down Expand Up @@ -43,6 +43,7 @@ class ParityRow(
current: ClassVar[str]
native: ClassVar[str]
phases: ClassVar[tuple[type[PlanPhase], ...]]
comparison: ClassVar[Callable[[], ParityComparison]] = UnverifiedParity

@classmethod
def compare(cls, todos: Iterable[Todo]) -> ParityComparison:
Expand All @@ -51,7 +52,7 @@ def compare(cls, todos: Iterable[Todo]) -> ParityComparison:
@classmethod
def verify(cls) -> ParityComparison:
"""Run this declared workflow now; a run is not durable cutover authority."""
return UnverifiedParity()
return cls.comparison()

@classmethod
def rows(cls) -> tuple[type[ParityRow], ...]:
Expand Down
Loading