Skip to content

feat(orchestration): add Close Idle Orchestration Agents - #962

Merged
Juliusolsson05 merged 5 commits into
mainfrom
feat/close-idle-orchestration-agents
Sep 13, 2026
Merged

Juliusolsson05 merged 5 commits into
mainfrom
feat/close-idle-orchestration-agents

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

Problem

Orchestration runs leave their workers behind. A parent agent spawns children with orchestration_create_agent; when they finish, they stay in Dispatch as idle rows until the parent calls orchestration_close_run, which often never happens (the parent moved on, was closed, or the user wanted to read the output first). The only cleanup tools were closing rows one by one, or Close Old Agents, which is driven by an inactivity threshold and cannot tell a worker from an agent the user opened by hand.

What this adds

A palette command, Close Idle Orchestration Agents:

  • Targets every orchestration child in the window (a session with orchestrationParentId, grandchildren included) placed in a project as a grid pane or Dispatch row.
  • Idle means finished and quiet: the child's orchestration lifecycle is completed — the same renderer-derived state orchestration_list_agents reports to the parent — and its evidence is settled (not bootstrapping, spawning, or loading its transcript). Working, starting, never-answered, exited and failed children stay open.
  • Coordinators (children with children) close only when every worker below them closes too; at the kill boundary a coordinator with any worker left is refused.
  • Always confirms with the exact list through the existing close confirmation dialog, even for one target. Nothing idle → a toast, no dialog.
  • Reports closed / skipped / kept / failed in one toast. No Undo Close entries, matching close_run and Close Old Agents.

Design decisions

  • One bulk-close loop. The bug(workspace): Close Old Agents cascades into unselected and running sessions #886 approved-close loop moves out of CloseOldAgentsModal into workspace/bulkClose.ts (closeGrantedSessions); Close Old Agents and the new command both call it. A second copy of destructive safety code would drift with nothing comparing them.
    • Kills are ordered along both ownership edges (linkedParentId ?? orchestrationParentId), deepest first. For Close Old Agents this only reorders kills among sessions it was already closing.
    • The modal's pre-call revalidation is dropped. The onlyIf at the kill boundary re-runs the same predicate against the action's live refs and lands a dropped target in the same skipped bucket. narrowGrantToCurrent lost its only caller and is deleted with its tests.
  • "Idle" reuses the orchestration lifecycle. orchestrationChildLifecycle is exported from orchestrationMcp.ts and buildAgentRecord uses it, so the user's cleanup and a parent's wait_agents cannot disagree about whether a worker is done. Requiring output (not merely "not live") is what protects a just-spawned worker whose prompt has not been delivered yet.
  • Scope is every project tab in the window, like Close Old Agents' default; the dialog lists each target with its folder.
  • Default picker tier, not advanced: advanced hides a command from the palette, and this is the everyday end of an orchestration run.
  • when is metadata-only (any orchestration child exists) because the palette evaluates it on every render and idleness scans transcripts.
  • Wired as workspace.closeIdleOrchestrationAgents in useWorkspace, because the flow needs the global toast and the action's live refs, which a command context does not have (same as Close Tab and Switch Agents).

Plan: docs/superpowers/plans/2026-09-12-close-idle-orchestration-agents.md (first commit).

Fixes #960

Tests and verification

New tests:

  • workspace/idleOrchestrationAgents.test.ts (unit): the eligibility contract. A finished worker is listed; the lead agent, an agent the user opened and a terminal child are not. Each working signal excludes, and so does each not-done or unsettled state (no output yet, no runtime, exited, failed, spawning, replaying history, loading transcript). A buried worker is ignored. A coordinator stays out while any worker below it is not a target, and joins once they all are. The per-kill predicate refuses a coordinator with any worker left.
  • workspace/idleOrchestrationAgents.renderer.test.tsx (renderer, real close executor via mountPaneActions): the dialog lists exactly the finished workers, and workers close before their coordinator with no undo entries. A worker that starts working while the dialog is open is skipped and its coordinator kept. Declining closes nothing. With nothing idle there is a toast and no dialog.

Local runs on Node 24:

  • npm run typecheck (control SDK, workflow-mcp build, tsc -b): pass.
  • Targeted vitest, unit: idleOrchestrationAgents, closeConfirmation, orchestrationClose, command catalog and taxonomy, 113 passed. Renderer: idleOrchestrationAgents.renderer, CloseOldAgentsModal close and rows, 23 passed. The Close Old Agents tests are unchanged and guard the shared-loop refactor.
  • Mutation checks, each reverted byte-identical afterwards:
    • dropping the coordinator kill-boundary rule fails 1 renderer and 1 unit test;
    • closing in listed order instead of deepest first fails 1 renderer test;
    • treating any non-running worker as idle fails 3 unit tests.
  • npm run test:contract: pass.
  • Full renderer project: 182 files, 928 tests passed. Full unit project: 2663 of 2664 passed; the one failure is imageAttachment.test.ts "every fixture is traceable to a census row and a real session", which needs a local Claude transcript that is no longer on this machine and fails identically on origin/main (unrelated to this change; CI does not depend on it). npm run check:keybindings: pass.

Not run locally: the app itself (the flow is exercised through the real close executor instead), and the system project (this change is renderer-only; CI runs it). CI quality-gate is the merge gate.

Known limitations

  • Eligibility reads renderer state only. Main's prompt_sent overlay (OrchestrationBridge.lifecycleWithPromptDelivery) is not visible there, so a prompt a parent sends to a finished worker in the moment before the user confirms can still be in flight. The kill-boundary check catches it once the provider reports activity; close_run and Close Old Agents share this window.
  • A child whose orchestration metadata was stripped by reload, provider switch, resume or rewind (bug(orchestration): reload, switch provider, resume and rewind strip a child's orchestration metadata, so wait_agents reports a false done #879) is not recognized.
  • A hibernated worker whose history is not hydrated reads as waiting and is left open.
  • No keybinding; no per-project or per-run scoping.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FmkiA3y12dq41bYLVZcuZj

Juliusolsson05 and others added 5 commits September 12, 2026 19:32
Orchestration runs leave finished workers behind as idle Dispatch rows
whenever the parent never calls close_run. The plan records what "idle"
means (the completed lifecycle a parent agent already sees), why
coordinators stay open while a worker below them is open, and why the
bulk-close loop moves out of Close Old Agents so both surfaces share one
copy of the destructive safety rules.

Refs #960

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmkiA3y12dq41bYLVZcuZj
…up surfaces

Close Old Agents carried the #886 approved-close loop inline: preConfirmed
per session, onlyIf revalidation at the kill boundary, linked children
before parents, sequential mutation and per-session outcome buckets. A
second bulk cleanup surface is coming, and a second copy of destructive
safety code would drift with nothing comparing the two, so the loop moves
to workspace/bulkClose.ts as closeGrantedSessions.

Kills are ordered along both ownership edges (linkedParentId, then
orchestrationParentId), deepest first. For Close Old Agents this only
reorders kills among sessions it was already closing.

The modal's pre-call revalidation is dropped: onlyIf re-runs the same
predicate synchronously against the action's live refs, and a target
either check drops lands in the same skipped bucket. That leaves
narrowGrantToCurrent without a caller, so it is deleted with its tests.
The existing CloseOldAgentsModal close tests pass unchanged.

Refs #960

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmkiA3y12dq41bYLVZcuZj
Orchestration runs leave finished workers behind as idle Dispatch rows
whenever the parent never calls close_run. The new command closes every
orchestration child in the window that has finished and is quiet, after
one confirmation listing exactly those agents.

"Idle" is the completed lifecycle a parent agent already sees through
list_agents (exported as orchestrationChildLifecycle so there is one
derivation), plus settled evidence. Requiring output rather than only
the absence of work is what spares a just-spawned worker whose prompt has
not been delivered. A coordinator closes only when every worker below it
does, and is refused at its kill boundary while any worker is left, so a
working worker is never orphaned.

The flow is wired as workspace.closeIdleOrchestrationAgents because it
needs the global toast and the action's live refs. It confirms even a
single target: unlike ⌘W on a visible pane, it reaches rows the user may
not see and records no undo entry.

Fixes #960

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmkiA3y12dq41bYLVZcuZj
… its PR

Refs #960

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmkiA3y12dq41bYLVZcuZj
…stration-agents

# Conflicts:
#	src/renderer/src/features/command-palette/catalog.test.ts
@Juliusolsson05
Juliusolsson05 merged commit d12cd34 into main Sep 13, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/close-idle-orchestration-agents branch September 13, 2026 03:36
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.

feat(orchestration): add a Close Idle Orchestration Agents command

1 participant