Skip to content

fix(orchestrate): a coordinator ends its turn only with every unblocked unit started or parked - #75

Open
adryanev wants to merge 1 commit into
chhoumann:mainfrom
adryanev:fix/orchestrate-unassigned-units
Open

adryanev wants to merge 1 commit into
chhoumann:mainfrom
adryanev:fix/orchestrate-unassigned-units

Conversation

@adryanev

@adryanev adryanev commented Nov 10, 2026 •

Copy link
Copy Markdown

Why

A coordinator running Orchestrate on T3 must keep the program moving between human turns. Only a child finishing and a PR watch wake its thread. A coordinator that closes a round and ends its turn with unblocked units unassigned, and nothing running or watched, stays idle until a person writes. #74 logs this three times in one day, once for 5 hours.

What changed

One anchored insert in tools/substitutions.json, regenerated into playbooks/orchestrate.md:

 Call no scripts, run no polling loops, and never relay messages or status between agents.
+Only a child finishing and a PR watch wake this thread, so before you end a turn, start a
+worker for every unit that is not blocked, or write in `notes.md` why it waits and what will
+wake it. A turn that ends with work left and nothing running or watched stops the program.
+Report it to the user as a stop, never as a finished round.

Evidence

The incident in #74, from the coordinator thread's message log

A standing program on main: one coordinator on claude-opus-5-5, six owner threads (one per repository), 33 units, run in correction rounds over several days.

Time (UTC, 2026-11-09) What happened
18:09 The coordinator's reply opens "Correction round 1 finished". The same reply lists engineering work still open, with no blocker and no owner. Its state file records that no resource work is assigned. Every owner had received a closing message and finished its turn, so nothing was running or watched.
18:09 to 23:13 Nothing happens on any thread.
23:13 The operator writes that every thread has stopped. The coordinator replies that the threads had not crashed: its last run closed the round and assigned no new work. It says the open engineering item "can run right away without you" and asks whether it should continue.

This is the shape the rule targets: a round closed, unblocked units unassigned, a turn ended with nothing running, and the stop reported as a finished round. Under the new text, the unblocked item gets a worker, or a written reason to wait. Here none existed, because the coordinator itself called it unblocked. The reply would then report a stop. This is inferred from the text and was not replayed, because the program is in a private repository.

A/B on synthetic programs

Each arm is a fresh root coordinator thread on claude-opus-5-5 (effort high), reading the playbook from /tmp/pstack-old (main) or /tmp/pstack-new (this branch). The fixture is a textkit repo plus a tracker with two corrections (R1a, R1b), two unblocked engineering items (E1, E2) and one item blocked on the operator (Q1). There are 2 runs per arm. Transcripts show every coordinator read only its assigned playbook, with no reads of an installed copy.

Run 1. The operator says "Run correction round 1 (R1a and R1b)."

old-1 old-2 new-1 new-2
R1a, R1b landed, verified at head yes yes yes yes
E1, E2 started no no no no
notes.md says why E1/E2 wait and what wakes them no no yes yes
Reply reports that nothing is running no, asks "Should I start E1 and E2 next?" no yes, "paused with nothing running" yes, "Nothing is running"
Q1 untouched yes yes yes yes

Run 2. The same fixtures are reset. The operator says "Work the tracker. Run correction round 1 (R1a and R1b) first."

old-1 old-2 new-1 new-2
R1a, R1b, E1, E2 landed, verified at head yes yes yes yes
Q1 untouched yes yes yes yes

What this shows, and what it does not:

  • The stall from Orchestrate: coordinator ends its turn with unblocked units unassigned #74 did not reproduce in these synthetic runs. When E1/E2 were in scope, the old playbook also carried them through. The main arm never stalled, so this A/B does not show that the rule prevents the stall.
  • The measured difference is the parking clause. In run 1 the new arm wrote a wait reason and a wake condition into notes.md 2/2 times, and the old arm 0/2. In the new arm the reply also reported the idle state.
  • The "never as a finished round" clause did not change the reply's opening line. All four run-1 replies open with "Correction round 1 is done". Rewording it is left for a follow-up once there is a fixture that reproduces the stall.
  • bun tools/sync.mjs … --dry-run reports no FAIL. bun tools/generate.mjs --check passes. GIT_CONFIG_GLOBAL=/dev/null bun test tests/ --timeout 30000 gives 561 pass, 0 fail.

Merge Danger

Door: two-way. It is one prose rule in one playbook, and a revert restores it.

Blast Radius: small. Only Orchestrate coordinators read it. The risk is a coordinator that starts in-scope work more eagerly, which run 2 shows the old text already does.

Refs #74

…ed unit started or parked

Only a child finishing and a PR watch wake an Orchestrate coordinator on T3.
A coordinator that closed a round and ended its turn with unblocked units
unassigned and nothing running stayed idle until a person wrote: three times
in one day, once for 5 h.

The anchored rule after "Call no scripts, ..." tells the coordinator to start
a worker for every unit that is not blocked, or to record in notes.md why it
waits and what will wake it, and to report a turn that leaves work with
nothing running as a stop.

Fixes chhoumann#74
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant