Wire delegation to A12's trigger, and implement A3's resume rule
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m8s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m8s
Two gaps found by auditing spec against code, the second created by 7b landing the harness without its boot-time half. **The harness was not connected to anything.** Only a live test constructed a Delegation; A12's failure-count trigger still gated to the human, which A13 describes as the fallback rather than the path. Consecutive command failures now hand the sub-task to the delegate, with the failures in the brief so it need not rediscover what the local model learned the hard way. A usable handback lets the local loop carry on; anything else escalates, because a cap breach leaves a half-edited tree. **A3 step 5 was unimplemented.** A crash mid-delegation failed safe — boot gated on "restarted mid-EXECUTING" — but always gated, even on the clean cut the spec says should resume, and left the branch orphaned because _abandon only runs while the process lives. - A delegation now records an intent before the branch exists, and settles it whichever way the delegation ends; an aborted delegation is a finished one, and leaving it pending would make the next boot believe work was still in flight - delegation_verdict implements the clean-cut test as an observable check, never model self-assessment: settled if a completion follows the start, absent if the branch was never created, gate on a tool_call with no recorded result, resume otherwise - recover() sweeps at boot, announcing a resume with a notice rather than a question, since a clean cut needs no decision from the operator, and leaving an unclean one pending so the generic sweep gates on it - resume() continues the same delegation rather than starting a second: no new branch, no second delegation_started, because A10 defines a session as the delegation, not the process lifetime Honest limit, stated in the code: "the branch sits at the expected commit" is approximated by the branch existing and carrying the work, since per-turn shas are not recorded in the log, so a branch rewritten underneath us would go undetected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
608a3a5bfb
commit
ce436262f4
5 files changed
+414
-5
No files matched your search
@@ -163,6 +163,7 @@ class Orchestrator:
|
||||
publisher: "MilestonePublisher | None" = None,
|
||||
gitea_operators: Iterable[str] = (),
|
||||
board: "PlanBoard | None" = None,
|
||||
delegate: Any = None,
|
||||
) -> None:
|
||||
self._store = store
|
||||
self._transport = transport
|
||||
@@ -176,6 +177,7 @@ class Orchestrator:
|
||||
# the other.
|
||||
self._gitea_operators = set(gitea_operators)
|
||||
self._board = board
|
||||
self._delegate = delegate
|
||||
# Transport and the comment poller deliver on separate threads;
|
||||
# state transitions must not interleave.
|
||||
self._lock = threading.RLock()
|
||||
@@ -184,6 +186,11 @@ class Orchestrator:
|
||||
transport.on_message(self._handle)
|
||||
|
||||
def start(self, reconcilers: Mapping[str, Reconciler] | None = None) -> None:
|
||||
# A3 step 5 runs before the generic sweep: an interrupted delegation is
|
||||
# resumed or discarded by inspecting the branch and the log, and only a
|
||||
# genuinely unclean cut is left for the sweep below to gate on.
|
||||
if self._delegate is not None:
|
||||
self._delegate.recover(self._transport.send_message)
|
||||
# The publisher owns the external-truth query for its own intents, so
|
||||
# a configured publisher always contributes its reconciler (A3).
|
||||
registered: dict[str, Reconciler] = dict(reconcilers or {})
|
||||
|
||||
Reference in new issue
Block a user