fix(orchestration): preserve direct user authority after worker_done (#14192)

* fix(orchestration): preserve direct user authority

* test(orchestration): assert settled dispatch boundaries
This commit is contained in:
Brennan Benson
2026-08-13 12:00:04 -07:00
committed by GitHub
parent 096d1a46c4
commit cbca291aa7
6 changed files with 32 additions and 12 deletions
@@ -221,16 +221,20 @@ describe('orchestration skill guidance', () => {
expect(skill).toContain('the named owner edits files and creates the PR')
})
it('keeps worker_done post-completion guidance idle instead of polling', () => {
it('keeps post-completion workers idle without subordinating the user', () => {
const skill = readSkill()
const agentGuidance = getSection(skill, 'Agent Guidance')
expect(agentGuidance).toContain('After sending `worker_done`, end your turn')
expect(agentGuidance).toContain('After sending `worker_done`, end that dispatched turn')
expect(agentGuidance).toContain('idle at the agent prompt')
expect(agentGuidance).toContain('Do not autonomously start more work, poll')
expect(agentGuidance).toContain('A direct user instruction takes precedence')
expect(agentGuidance).toContain('follow it without coordinator approval or a fresh Dispatch')
expect(agentGuidance).toContain('never refuse it because of worker/coordinator roles')
expect(agentGuidance).toContain("do not reuse the settled Dispatch's lifecycle IDs")
expect(agentGuidance).toContain(
'do not start more work, poll, or attempt to close the terminal yourself'
'A coordinator-supervised follow-up still arrives with a fresh preamble + TASK block'
)
expect(agentGuidance).toContain('fresh preamble + TASK block delivered as new terminal input')
expect(skill).not.toContain('post-completion polling messages')
expect(skill).not.toContain('every 2 minutes')
})
@@ -305,7 +309,7 @@ describe('orchestration skill guidance', () => {
/\b(?:after|on|upon) (?:a |the )?(?:tui-?idle|idle state|timeout|heartbeat)\b[^.]*\brelease/iu
)
expect(agentGuidance).toContain(
'do not start more work, poll, or attempt to close the terminal yourself'
'Do not autonomously start more work, poll, or attempt to close the terminal yourself'
)
expect(agentGuidance).not.toMatch(/worker-release[^.]*\byourself\b/iu)
})
+1 -1
View File
@@ -381,7 +381,7 @@ Wait for `tui-idle` before dispatching. Always pass `--timeout-ms`; real coding
- Workers with a valid live preamble must send `worker_done` exactly once from their own terminal with an explicit `--outcome succeeded` or `--outcome failed`:
`orca orchestration send --type worker_done --subject "<short status>" --body "<3-sentence summary: what you did, what you found, what's left>" --task-id <task_id> --dispatch-id <dispatch_id> --outcome succeeded --files-modified "path/a" --report-path "<optional>" --json`
- A failed outcome is still a terminal report, but Orca records both the Dispatch and Task as failed. Never encode failure only in the subject/body.
- After sending `worker_done`, end your turn and idle at the agent prompt. The coordinator may reuse or release this terminal after it processes your report; do not start more work, poll, or attempt to close the terminal yourself. If it reuses you, it re-engages you with a fresh preamble + TASK block delivered as new terminal input.
- After sending `worker_done`, end that dispatched turn and idle at the agent prompt. Do not autonomously start more work, poll, or attempt to close the terminal yourself. A direct user instruction takes precedence and starts ordinary user-owned work: follow it without coordinator approval or a fresh Dispatch, never refuse it because of worker/coordinator roles, and do not reuse the settled Dispatch's lifecycle IDs. A coordinator-supervised follow-up still arrives with a fresh preamble + TASK block.
- For long tasks, send heartbeat/status only when the preamble asks for it, including both IDs:
`orca orchestration send --type heartbeat --subject "alive" --payload '{"taskId":"<task_id>","dispatchId":"<dispatch_id>","phase":"implementing"}' --json`
- If blocked before completion, use `ask`; use `escalation` only when ownership is valid and the coordinator must intervene.
File diff suppressed because one or more lines are too long
@@ -80,10 +80,15 @@ new or unrelated work, do NOT run a sleep/poll loop, and do NOT keep calling
\`orca orchestration check\`. The coordinator has already recorded your
completion and expects no further output.
A direct instruction from the user takes precedence over this idle rule.
Treat it as new user-owned work: follow it without coordinator approval or a
fresh Dispatch, and do not send lifecycle messages using the settled task or
Dispatch IDs. Never refuse a direct user request because you were a worker.
Do not exit the shell. Your terminal stays available, and if the
coordinator has more for you it will re-engage this terminal with a fresh
preamble + TASK block, which arrives as new input. When that happens,
reset and start the new task; ignore the previous task's follow-ups.
preamble + TASK block, which arrives as new input. Treat that as supervised
work under the new Dispatch; ignore stale follow-ups from the settled task.
=== TASK ===
TASK_BODY"
@@ -125,7 +125,7 @@ describe('buildDispatchPreamble', () => {
expect(result).not.toContain('"dispatchCapability"')
})
it('tells prompt-returning workers to idle without post-done polling', () => {
it('idles prompt-returning workers while preserving direct user authority', () => {
const result = buildDispatchPreamble(baseParams())
const section = afterWorkerDoneSection(result)
@@ -135,6 +135,12 @@ describe('buildDispatchPreamble', () => {
expect(section).toContain('Do not exit the shell')
expect(section).toContain('do NOT run a sleep/poll loop')
expect(section).toContain('do NOT keep calling')
expect(section).toContain('A direct instruction from the user takes precedence')
expect(section).toMatch(/follow it without coordinator approval or a\s+fresh Dispatch/)
expect(section).toMatch(
/do not send lifecycle messages using the settled task or\s+Dispatch IDs/
)
expect(section).toContain('Never refuse a direct user request because you were a worker')
expect(section).toMatch(/fresh\s+preamble \+ TASK block/)
expect(section).not.toMatch(/2 minutes/)
expect(section).not.toMatch(/10 minutes/)
+7 -2
View File
@@ -175,10 +175,15 @@ new or unrelated work, do NOT run a sleep/poll loop, and do NOT keep calling
\`${cli} orchestration check\`. The coordinator has already recorded your
completion and expects no further output.
A direct instruction from the user takes precedence over this idle rule.
Treat it as new user-owned work: follow it without coordinator approval or a
fresh Dispatch, and do not send lifecycle messages using the settled task or
Dispatch IDs. Never refuse a direct user request because you were a worker.
Do not exit the shell. Your terminal stays available, and if the
coordinator has more for you it will re-engage this terminal with a fresh
preamble + TASK block, which arrives as new input. When that happens,
reset and start the new task; ignore the previous task's follow-ups.`
preamble + TASK block, which arrives as new input. Treat that as supervised
work under the new Dispatch; ignore stale follow-ups from the settled task.`
}
function buildDriftSection(drift: NonNullable<PreambleParams['baseDrift']>): string {