Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
e73e261
P265: preserve source-prefix clocks and require recorded times throug…
trissim Oct 19, 2026
91ce696
Merge remote-tracking branch 'origin/main' into fix/p265-recorded-eve…
trissim Oct 19, 2026
e95aeb9
Date the external events saved before event time became required
trissim Oct 19, 2026
db384b2
Carry reads tool blocks through Claude's decoders and dates all rows …
trissim Oct 19, 2026
bfa9807
Wrap the carry's SQL and look up block decoders by membership
trissim Oct 19, 2026
4534a64
Decode each transcript line with the bridge's record decoder
trissim Oct 19, 2026
4038452
P265: retain malformed shell input after integrating the source-backe…
trissim Oct 20, 2026
47a5a1e
P265: verify the source-backed carry on a snapshot of the actual even…
trissim Oct 20, 2026
d9c3c5d
P281: bind maintenance holds to detached process identities
trissim Oct 20, 2026
81d831b
P281: migrate maintenance consumers and cover real holder death
trissim Oct 20, 2026
c2b8cb8
P281: publish recovery proof and wire installed harness refresh
trissim Oct 20, 2026
fdb5e9e
Verify declaration-driven hook publication with installed programs
trissim Oct 20, 2026
554988a
Keep publication process fixture independent of host harnesses
trissim Oct 20, 2026
7e2651d
Use canonical publication module and typed carry schema at the boundary
trissim Oct 20, 2026
d02b1fe
Preserve standalone installer bootstrap with canonical module execution
trissim Oct 20, 2026
11d359f
Compile the stored maintenance choice as its runtime union
trissim Oct 20, 2026
8366bd7
Use one installer module identity and compose the writer contexts
trissim Oct 20, 2026
92d14fa
Migrate installer test consumers to their canonical tool modules
trissim Oct 20, 2026
0c298b3
Use the typed codec boundary and execute the declared publication source
trissim Oct 20, 2026
2abb43c
Preserve the compiled union result at its validated type boundary
trissim Oct 20, 2026
93c2b1e
Assert native pause refusal before recovering its retired holder
trissim Oct 20, 2026
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
9cf5460
Place checked-commit worktrees under the configured checkout owner
trissim Oct 20, 2026
0509388
Exercise current and native delivered-commit outcomes on the copied bus
trissim Oct 20, 2026
44081cc
Keep every copied delivery order failing until its actual run
trissim Oct 20, 2026
fc5358e
State the checked main commit and migrate the explicit check proof
trissim Oct 20, 2026
15758fd
Compare real command process exits without swallowing native refusals
trissim Oct 20, 2026
b3100a8
Keep child timeout and retirement outcomes in the delivery comparison
trissim Oct 20, 2026
36a29c2
Use the lifecycle-owned candidate process launcher for delivered-comm…
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
fe8006b
P311 bind delivered proof to the installed Core and Toad pair
trissim Oct 20, 2026
d3d2c91
Model missing proof builds explicitly and exercise copied-bus close
trissim Oct 20, 2026
e3a325e
Retain proof identity through checked commit delivery
trissim Oct 20, 2026
10a5163
Freeze installation selection and inspect retained proof identities w…
trissim Oct 20, 2026
70aea2a
Observe the installed build before entering the Todo write transaction
trissim Oct 20, 2026
a55d56f
Type the installed-proof preparation boundary
trissim Oct 20, 2026
25709d5
Use the canonical event-time clock after rebasing onto main
trissim Oct 20, 2026
8c08709
Remove the deleted commander CLI field from lifecycle construction
trissim Oct 20, 2026
3db34a5
P302: bound load comparison and observe actual CLI returns without fr…
trissim Oct 20, 2026
7152883
P302: let BoundedRun own native comparison collection and retirement
trissim Oct 20, 2026
bab521f
P302: derive comparison interpreter from the selected runtime
trissim Oct 20, 2026
a0c7a61
P302: observe the CLI-selected root context return
trissim Oct 20, 2026
958f0a3
Merge #2124
trissim Oct 20, 2026
071656f
Merge #2127
trissim Oct 20, 2026
435fbd7
Merge #2025
trissim Oct 20, 2026
14772e3
Keep completion times on finished items; delete EventTime.stamp
trissim Oct 20, 2026
d711e5a
Assert #2025's malformed-command case at the parse owner
trissim Oct 20, 2026
7ed46a2
Merge #2095
trissim Oct 20, 2026
3913330
Merge #2115
trissim Oct 20, 2026
92a5f17
Merge #2118
trissim Oct 20, 2026
b562ad9
Forward lifecycle process options to BoundedRun instead of restating …
trissim Oct 20, 2026
6ab2e8a
Merge origin/main
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
60 changes: 60 additions & 0 deletions docs/maintenance-holder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Process-owned maintenance (P281)

The barrier stores the holder's PID and kernel birth time through the existing
`ProcessIdentity`/`FieldCodec` implementation. Names are not identities. READY
records describe completed work and do not depend on the old operator staying
alive.

A non-READY read checks that exact process. Recovery compares the observed
record under the existing registry lock, writes `dead_holder_released` with the
dead PID/birth time, and refuses the detecting input. The following input uses
READY behavior. The asynchronous ingress borrows the asynchronous registry
lock; neither path upgrades an admitted shared wire descriptor.

Live installs run in a separate session with private file-backed output, so
losing the calling session does not kill the installer or break its output
pipe. The worker records its own identity. Rehearsals remain synchronous.

`TodoStore.writer_connection` admits all todo writers before opening SQLite,
including commander first-call, hunt, overdue and reconciliation writes.

One registered carry converts version-1 READY witnesses. An old active record
contains only a name: its holder's death cannot be established. The carry
refuses that record rather than fabricating a PID or retaining a name fallback.
The runtime reads only version 2.

## Delivery checklist

- [x] Implementation checkpoint pushed on `fix/p281-maintenance-holder`.
- [x] Production AST inventory: 381 Core modules and 39 tools; no parse omissions.
- [x] Migrate all discovered producer and test callers off string operators.
- [x] Eleven focused real-process/ingress/todo/carry checks passed.
- [ ] Tools ratchet and src ratchet against `origin/main`.
- [x] Copied-bus live-phase proof with existing Python, without a build,
environment creation or `uv sync`.
- [x] Calling-session death left the worker alive; SIGKILL of its job mid-drain
gave a named `held` refusal, then an accepted and retained second message.
The release record retained the dead PID/birth time; original bus unchanged.
- [x] Draft PR #2072 opened through the commander's canonical PR command.

## Installation wiring follow-up

Normal and unchanged live installs now use the selected interpreter to refresh
`RuntimePublication`. That existing owner publishes console/skill links and
calls the declared `Harness.configure` implementations; repeated refreshes are
safe. Consumers no longer skip refresh merely because the pointer is current.

Actual installed programs, with a private HOME and candidate publication code,
verified every declared hook on PATH/configuration, including P275 SessionStart
and P285 UserPromptSubmit. SessionStart exited zero with all 45 packaged files;
repeated refresh preserved user settings without duplicate hooks. Production
configuration and services were not changed.

This does not yet fence external bridge watches or session hooks across mutable
checkout/schema updates. The reported transitional-reader failure remains
unfixed; publication success is not acceptance of that separate lifecycle gap.

The inventory includes declarations, inheritance, imports, writes, decisions
and references throughout both production roots. Generated test child programs
are additionally located by source search; AST does not resolve their runtime
imports or prove behavior.
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
2 changes: 1 addition & 1 deletion src/agent_comms/cli_bridges/codex_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ def publication(self) -> RuntimeUpdate:
if self.completedAtMs is None:
raise ValueError(f'Item {self.item.id} has not finished')
event_time = self.started_at.continued_from(self.completedAtMs / 1000)
return self.item.completed(self.turnId, self.item.events(event_time.transcript_timestamp()))
return self.item.completed(self.turnId, self.item.events(event_time.recorded_at('A Codex item')))


class ItemsPage(WireModel):
Expand Down
4 changes: 2 additions & 2 deletions src/agent_comms/cli_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .maintenance_barrier import MaintenanceHeldError
from .messages import Message
from .field_codec import TimestampText
from .recorded_time import MissingTranscriptTimestampError
from .recorded_time import UnrecordedTimeError
from .external_transcript import RequestedEarlierPage

if TYPE_CHECKING:
Expand Down Expand Up @@ -45,7 +45,7 @@ class TimestampedWire(WireModel):
@classmethod
def require_source_time(cls, value):
if isinstance(value, dict) and value.get('timestamp') is None:
raise MissingTranscriptTimestampError(f'{cls.__name__} has no recorded timestamp')
raise UnrecordedTimeError(f'{cls.__name__} has no recorded timestamp')
return value

@field_validator('timestamp', mode='before')
Expand Down
9 changes: 6 additions & 3 deletions src/agent_comms/commander/capacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ class Load(ToolResult):
def of(cls, comms: Comms, commander: str) -> Load:
snapshot, todos, now = comms.registry.snapshot(), comms.goals.todos, time.time()
owner = snapshot.require(commander)
owed = tuple(t for t in todos.held_for_commander(owner.incarnation) if t.pending)
held = todos.held_for_commander(owner.incarnation)
owed = tuple(t for t in held if t.pending)
forks = tuple(sorted(
thread.name for thread in snapshot.threads.values()
if thread.parent == owner.name and snapshot.status(thread.name).receiving
and todos.held_for_commander(thread.incarnation)
))
return cls(len(owed), sum(t.overdue(now) for t in owed),
OrderEditCountRead.count(comms, owner.name), forks)
unjudged = sum(
read.read() for read in OrderEditCountRead.for_orders(comms.root, snapshot, held)
)
return cls(len(owed), sum(t.overdue(now) for t in owed), unjudged, forks)

@projected(view="wire")
def total(self) -> float:
Expand Down
4 changes: 2 additions & 2 deletions src/agent_comms/commander/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,8 +742,8 @@ def due(self, pid: str, at: str, why: str) -> None:
moved = RecordedEventTime(time.time()).at_clock(at)
self.call(CommsChangeProblemTool(problem=pid, operation=AssignTodo(
todo.revision, todo.assignment, moved, todo.order, todo.goal)))
self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} due '
f'{todo.deadline.clock()} moved to {moved.clock()}): {why}', 'Deadline',
self.on_record(f'{time.strftime("%Y-%m-%d %H:%M")} {todo.holder} ({pid} order {todo.order.id} '
f'deadline changed): {why}', 'Deadline',
VerdictAudience.of(todo.held().owner, todo.goal))
print(f'{pid}: due {moved.clock()}')

Expand Down
261 changes: 261 additions & 0 deletions src/agent_comms/commander_parity/deletion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
"""Deletion intent parity through real orders, pushed commits and native delivery checks."""

from __future__ import annotations

import contextlib
import io
import json
import shlex
import sys
import tempfile
from abc import ABC, abstractmethod
from dataclasses import dataclass, replace
from pathlib import Path

from ..change_report import FileLine
from ..child_process import CountPrinted, ExitedOutcome
from ..commander.order import OrderFile, deadline
from ..field_codec import FieldCodec
from ..goal_actions import CreateProblemGoalAction
from ..goals import CountCheckResult, OrderCheck
from ..plan_phases import RecordPlanePhase
from ..progress_claims import MergedClaim
from ..repo import Repo
from ..todos import (
CheckedCommitDelivery,
CommitDelivery,
DeliveredProblemOrder,
FamilyDeletion,
MechanicalOrderRun,
Todo,
)
from ..tool_caller import ThreadCaller
from ..tools import CommsOrderDoneTool, CommsTodoCreateTool, ProblemOrdered
from . import ParityComparison, ParityRow
from .lifecycle import LifecycleObservation, LifecycleRun, MeasuredCommander


@dataclass(frozen=True)
class DeletionComparison(ParityComparison):
current: LifecycleObservation
native: LifecycleObservation

@property
def passed(self) -> bool:
return (
self.current.passed and self.native.passed and self.current.checks == self.native.checks
)

def describe(self) -> str:
return f"current: {self.current.describe()}; native: {self.native.describe()}"

@classmethod
def read(cls) -> DeletionComparison:
return cls(CurrentDeletionPath.read(), NativeDeletionPath.read())


class DeletionPath(ABC):
"""Both adapters exercise the same scenario on independent LifecycleRun copies."""

@classmethod
@abstractmethod
def send(cls, run: LifecycleRun, document: OrderFile) -> Todo: ...

@classmethod
@abstractmethod
def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]: ...

@staticmethod
def commit(repository: Path, text: str, message: str) -> str:
(repository / "family.py").write_text(text)
Repo.git(repository, "add", "family.py")
Repo.git(repository, "commit", "-qm", message)
Repo.git(repository, "push", "-q", "origin", "main")
return Repo.git(repository, "rev-parse", "HEAD").strip()

@classmethod
def read(cls) -> LifecycleObservation:
with LifecycleRun.open() as original:
# Keep source checkouts out of the copied worker's storage accounting.
# Limits remain unchanged; all detached checks use this persistent owned base.
rule = original.commander.comms.registry.storage_rule
with tempfile.TemporaryDirectory(
prefix="parity-deletion-", dir=rule.checkout_base
) as checkouts:
root = original.commander.root
(root / "storage.json").write_text(
json.dumps(FieldCodec.encode(replace(rule, checkout_base=Path(checkouts))))
)
(root / "commander.json").write_text(original.commander.model_dump_json())
run = replace(original, commander=MeasuredCommander.read_root(root))
return cls.exercise(run)

@classmethod
def exercise(cls, run: LifecycleRun) -> LifecycleObservation:
repository = Path(run.commander.comms.registry.require(run.worker).worktree)
remote = repository.parent / "deletion-origin.git"
Repo.git(repository, "init", "-q", "-b", "main")
Repo.git(repository, "config", "user.name", "Deletion parity")
Repo.git(repository, "config", "user.email", "deletion-parity@localhost")
Repo.git(repository, "init", "--bare", "-q", "--initial-branch=main", str(remote))
Repo.git(repository, "remote", "add", "origin", str(remote))
family = "class RetiredDecision:\n pass\n\n"
cls.commit(
repository,
family + "def decisions():\n return [RetiredDecision()]\n",
"Declare the obsolete decision family and its caller",
)
count = OrderCheck(
str(repository),
shlex.join(
(
sys.executable,
"-c",
"import ast;from pathlib import Path;"
"print(sum(isinstance(node,ast.ClassDef) for node in "
"ast.walk(ast.parse(Path('family.py').read_text()))))",
)
),
CountCheckResult(0),
)
deletion = FamilyDeletion(
"obsolete decision family", FileLine(str(repository / "family.py"), 1), count
)
initial = OrderFile.read(run.document.read_text())
order = replace(
initial.order,
check=OrderCheck(
str(repository),
shlex.join((sys.executable, "-c", "import family;assert not family.decisions()")),
),
run=MechanicalOrderRun(),
deletes=deletion,
)
document = replace(initial, order=order)
run.document.write_text(json.dumps(FieldCodec.encode(document)))
output = io.StringIO()
with contextlib.redirect_stdout(output):
before = cls.send(run, document)
partial = cls.commit(
repository,
family + "def decisions():\n return []\n",
"Repair the caller but leave one obsolete family site",
)
refused = run.invoke_command(
cls.delivery_command(run, before, partial), env=run.python_environment()
)
after_refusal = run.commander.comms.goals.todos.get(before.id)
remaining = count.recorded(partial, str(repository))
complete = cls.commit(
repository,
"def decisions():\n return []\n",
"Delete the last obsolete family declaration",
)
accepted = run.invoke_command(
cls.delivery_command(run, before, complete), env=run.python_environment()
)
after_acceptance = run.commander.comms.goals.todos.get(before.id)
zero = count.recorded(complete, str(repository))
primary = order.check.recorded(partial, str(repository))
delivered = after_acceptance.order
checks = (
("native order retained deletion intent", before.order.checked().deletes == deletion),
(
"partial commit passed primary check",
bool(primary) and order.check.result.distance(primary[0]) == CountPrinted(0),
),
(
"partial commit retained one family site",
bool(remaining) and count.result.distance(remaining[0]) == CountPrinted(1),
),
("nonzero delivery refused", refused.outcome == ExitedOutcome(1)),
("refusal preserved pending order", before == after_refusal and after_refusal.pending),
(
"zero count checked on delivered commit",
bool(zero) and count.result.distance(zero[0]) == CountPrinted(0),
),
(
"zero delivery accepted",
accepted.outcome == ExitedOutcome(0) and not after_acceptance.pending,
),
(
"accepted delivery names the zero-site commit",
isinstance(delivered, DeliveredProblemOrder)
and isinstance(delivered.delivery, CheckedCommitDelivery)
and delivered.delivery.delivered_commit == complete,
),
("accepted delivery retained deletion intent", delivered.checked().deletes == deletion),
)
return LifecycleObservation(
CommsOrderDoneTool.__qualname__,
len((refused, accepted)),
checks,
output.getvalue() + f"partial={partial}; zero={complete}; "
f"refused={refused.outcome.summary}: "
f"{refused.stdout.decode(errors='replace')}{refused.stderr.decode(errors='replace')}; "
f"accepted={accepted.outcome.summary}: "
f"{accepted.stdout.decode(errors='replace')}{accepted.stderr.decode(errors='replace')}",
)


class CurrentDeletionPath(DeletionPath):
@classmethod
def send(cls, run: LifecycleRun, document: OrderFile) -> Todo:
return run.create()

@classmethod
def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]:
return (
sys.executable,
"-c",
"import sys;from agent_comms.commander.order import Commander,DoneVerb;"
"DoneVerb(unit=sys.argv[2],message=sys.argv[3],sha=sys.argv[4]).run("
"Commander.read_root(sys.argv[1]))",
str(run.commander.root),
run.worker,
todo.order.checked().id,
commit,
)


class NativeDeletionPath(DeletionPath):
@classmethod
def send(cls, run: LifecycleRun, document: OrderFile) -> Todo:
result = run.commander.call(
CommsTodoCreateTool(
owner=run.worker,
order=CreateProblemGoalAction(
problem="P1",
repo=Repo.of(Path(document.order.check.directory)).github,
text=document.defect,
order=document.order,
request=document.order.request(
run.commander.comms.registry.require(run.commander.commander), ("P1",)
),
deadline=deadline(run.due),
commander=run.commander.commander,
),
)
)
if not isinstance(result, ProblemOrdered):
raise TypeError("Native creation returned no ordered problem")
return result.todo

@classmethod
def delivery_command(cls, run: LifecycleRun, todo: Todo, commit: str) -> tuple[str, ...]:
request = CommsOrderDoneTool(
problem=todo.id,
order_id=todo.order.checked().id,
delivery=CommitDelivery(commit=commit, delivered_commit=commit),
claims=(MergedClaim(commit),),
)
return request.cli_command(ThreadCaller(run.commander.commander)).argv(run.commander.root)


class DeletionTargetParityRow(ParityRow):
number = 31
capability = "An order states its intent as a deletion target"
current = "Current order command with a declared FamilyDeletion"
native = "CommsTodoCreateTool and CommsOrderDoneTool check the delivered family count"
phases = (RecordPlanePhase,)
comparison = DeletionComparison.read
Loading
Loading