Skip to content

feat(mcp): pilot/co-pilot control handoff for coordinated tasks#99

Closed
brooksc wants to merge 7 commits intojohannesjo:mainfrom
brooksc:feature/orchestrator-control-v2
Closed

feat(mcp): pilot/co-pilot control handoff for coordinated tasks#99
brooksc wants to merge 7 commits intojohannesjo:mainfrom
brooksc:feature/orchestrator-control-v2

Conversation

@brooksc
Copy link
Copy Markdown
Contributor

@brooksc brooksc commented May 4, 2026

Summary

Builds on the coordinating agent concept from PR #31 to add explicit human/orchestrator control handoff — a pilot/co-pilot model where control is always unambiguously held by one party.

Note: This PR includes the coordinating agent commits from PR #31 to be self-contained. The new feature is the final commit; everything below it is from PR #31.

The problem

Without this feature, a coordinating agent can send prompts to sub-tasks at any time. If the user wants to intervene in a sub-task — correct a mistake, redirect the agent, or just inspect what's happening — there's no mechanism to stop the orchestrator from sending competing instructions.

How it works

Each sub-task created by a coordinator has a runtime field controlledBy: 'orchestrator' | 'human'. The task panel shows a persistent banner so the user always knows who has the stick:

  • Orchestrator driving — subtle bar + "Take Control" button
  • Human in control — amber warning banner: "You have control — orchestrator is paused" + "Return to Orchestrator" button

When the human holds control, the orchestrator's MCP tools are blocked for that specific task:

  • send_prompt throws immediately so the coordinator agent knows it cannot proceed and must wait or work on other tasks
  • wait_for_idle resolves immediately (non-blocking) so the coordinator isn't left hanging

Control returns to the orchestrator only via explicit user action — never automatically. Returning control fires any queued waitForIdle resolvers so the coordinator resumes cleanly.

Changes (control handoff only)

  • src/store/types.tscontrolledBy?: 'orchestrator' | 'human' on Task (runtime, not persisted)
  • electron/ipc/channels.tsMCP_ControlChanged channel
  • src/store/tasks.ts + src/store/store.tssetTaskControl(taskId, who) action
  • electron/mcp/orchestrator.ts — enforce control in sendPrompt and waitForIdle
  • electron/ipc/register.ts — handle MCP_ControlChanged IPC
  • src/components/TaskPanel.tsx — control banner UI

Test plan

  • Create a coordinator task; verify sub-tasks show "Orchestrator driving" bar
  • Click "Take Control"; verify banner turns amber
  • While in human control, verify coordinator's send_prompt MCP call returns an error
  • Click "Return to Orchestrator"; verify banner reverts and coordinator resumes
  • Verify non-coordinated tasks show no banner

🤖 Generated with Claude Code

brooksc and others added 7 commits May 2, 2026 16:10
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enable a Claude Code agent to programmatically create and orchestrate
other tasks in Parallel Code. Adds an MCP server with 9 tools
(create_task, send_prompt, wait_for_idle, get_task_diff, merge_task,
etc.), a main-process orchestrator, REST API endpoints, and UI
support including coordinator mode toggle, sub-task status strip,
and "via Coordinator" sidebar labels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-picked from cledoux95/task/coordinating-agent (commit 6650b05).
Fixed console.log → console.warn to satisfy eslint no-console rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address review issues from PR johannesjo#31:
- Try ports 7777-7800 when starting MCP remote server instead of
  hardcoding 7777, preventing silent failure when port is in use
- Validate REST body fields (name, prompt, timeoutMs, squash, message,
  cleanup) in orchestrator API routes, returning 400 on bad input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds explicit control handoff between the orchestrating agent and the
human user, modelled on a pilot/co-pilot system where control is always
unambiguously held by one party.

Each sub-task created by a coordinator has a new runtime field
`controlledBy: 'orchestrator' | 'human'`. The task panel shows a
persistent banner so the user always knows who has the stick:

- Orchestrator driving: subtle bar + "Take Control" button
- Human in control: amber warning banner "You have control —
  orchestrator is paused" + "Return to Orchestrator" button

When the human holds control, the orchestrator's MCP tools are blocked:
- `send_prompt` throws immediately so the coordinator agent knows it
  cannot proceed and must wait or work on other tasks
- `wait_for_idle` resolves immediately so the coordinator is not left
  hanging; the status signals human control

Control returns to the orchestrator only via explicit user action —
never automatically. Returning control also fires any queued
`waitForIdle` resolvers so the coordinator can resume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The mcpConfigPath was stored on the task but never forwarded as a
CLI argument, so the coordinator agent had no MCP tools available
and fell back to bash-based orchestration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@brooksc brooksc force-pushed the feature/orchestrator-control-v2 branch from a1949f8 to 0cb8d99 Compare May 4, 2026 01:29
@brooksc brooksc closed this May 4, 2026
@brooksc brooksc deleted the feature/orchestrator-control-v2 branch May 4, 2026 01:49
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