11.5.26 `motir auto` reads the agent's SIGNAL — in review to continue, a finding to submit the re-plan and stop; the PR is always last
The loop half of 11.5.25. The agent now signals its outcome; this card makes motir auto read it, and defines what happens when the signal is "this card is wrong".
Today the loop cannot tell the difference. An agent that stops on a broken card exits non-zero like any other failure, the item is added to the exclusion list as if the CODE were the problem, and the run either halts with a generic message or — with --keep-going — carries on dispatching against a plan it now knows to be wrong.
Completion is BOTH signals
exit 0 and the card at in_review. Neither alone is enough: a process can exit clean having done nothing, and a card can be moved by something other than this run. The exit is the primary trigger — precise, instant, already awaited — and the card read is the confirmation.
A poll exists only as a LIVENESS net: if the agent has neither exited nor moved the card after a bound, the run is wedged and must say so rather than hang. Pace it against how long a card takes (minutes to tens of minutes), not against how responsive the loop feels — nothing downstream waits on the loop noticing a second sooner, and a one-second tick is ~1800 wasted requests on a 30-minute card.
The defect signal, and how the loop sees it
The AGENT submits the re-plan (11.5.25) — it holds the findings and motir plan --detach <KEY> "…" is one call. The loop's job is to NOTICE.
PlanSession carries lastJobId and lastSubmittedAt. Read the thread before dispatching an item and again once the item is finished: a moved lastSubmittedAt means the agent submitted. Authoritative, no stdout parsing, no exit-code convention, one cheap read per dispatched item.
⚠️ The agent anchors its submission at the card's key, and a scope-addressed read for a DIFFERENT anchor set is a different thread. Verify which read sees an anchored submission before building on either — a loop watching the wrong thread never fires, and never fires silently.
On seeing it: stop with replanned, dispatch nothing further — the ready set stopped being a truthful work list the moment the agent said the plan was wrong about one card.
⚠️ The PR is the LAST thing, on EVERY path — which means a finally
motir auto runs every card onto one session branch and opens one pull request at the end. That close-out must be structurally last, not last-on-the-happy-path: a stop path that bypasses it strands N integrated commits on an unpushed branch, invisible until someone goes looking for work that was never delivered.
That is MOTIR-1836 exactly — the close-out sat where a throw could jump over it, and a run that had already integrated work opened NO pull request. replanned is one more exit path, and the fix is that no exit path can be written that skips it.
It also makes stopping cheap rather than lossy: the run delivers everything it finished, then ends.
Scope BOUNDARY
motir auto's loop, its stop reasons and its summary. It does NOT add a flag: stopping on a re-plan is the behaviour, not an opt-in. It does NOT submit the plan — the agent does. It does NOT approve, poll or wait on the plan. It does NOT change the prompt (11.5.25's) or any endpoint. It does NOT change motir batch, which opens one pull request per item from a snapshot frozen before any agent runs.
Acceptance criteria
- An item is treated as finished only when the agent exited 0 AND the card reads
in_review; either alone is a failure, asserted both ways. - A run whose agent submits a re-plan stops with
replannedand dispatches nothing further. - The close-out runs on the
replannedpath — branch pushed, one pull request opened for the cards that landed. Asserted on the git commands. - And it runs when the loop THROWS. Asserted by making a step fail after an item has integrated: the pull request is still opened. This is the MOTIR-1836 regression test for the new exit path, and the one a "call it at the end" implementation fails.
- The re-planned item gets NO exclusion-list entry, asserted by reading the store: a card awaiting a plan must not be held out of a future run that the approved plan makes valid.
- The summary names both next actions — the plan (id, job id, review URL) and the pull request — asserted on the printed text.
- An exit code distinct from an agent failure, so a wrapper can tell "needs planning approval" from "something broke".
- A run whose agent fails NORMALLY is unaffected: same exclusion, same
halted, same exit code as today. - A run that drains the ready set still reports
drained; the two stop conditions do not collapse into one. - A wedged agent — neither exited nor card moved within the bound — ends the run with a named reason rather than hanging.
- The plan-session read happens once per DISPATCHED item, not per candidate — asserted on the request count.
motir auto's existing output is byte-identical on every path that is not a re-plan.
Context refs
packages/cli/src/commands/auto.ts— the loop,dispatchOne,closeOutRepos, the exclusion handling.packages/cli/src/autoLoop.ts—StopReasonand the summary renderer.- MOTIR-1836 — the close-out escaping on a throw; the precedent this card must not repeat, and the shape of its regression test.
packages/cli/src/mcpClient.ts—openPlanSession(returnslastJobId/lastSubmittedAt), ported onto/api/v1by MOTIR-2341.packages/cli/src/plan.ts—planReviewUrl, so the summary names the plan the waymotir plandoes.- 11.5.25 — the prompt half; nothing to read until the agent is told to signal.
- Story: MOTIR-1855.