mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
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:
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user