feat(mcp): coordinator agent with task visibility, pilot/co-pilot control, and expanded options#100
feat(mcp): coordinator agent with task visibility, pilot/co-pilot control, and expanded options#100brooksc wants to merge 29 commits intojohannesjo:mainfrom
Conversation
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>
When main has new commits but no conflicts, highlight "Rebase onto main" as the primary button. Only promote "Rebase with AI" to primary when conflicts are actually detected. 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>
2e523b0 to
b436f19
Compare
|
@cledoux95 as you initiated this I'd love to get your thoughts on my building further on your work. Here's what I'm trying to solve for.
What I'm hoping to get to:
1-3 is working here (although the take control/return control may need some work) I'm thinking I need some webhook or way for agentN to notify the coordinator when it's done (hook...?) Anyway... this may take some time to polish. @johannesjo What do you think about adding a "Beta" section in settings, when you're ready to integrate it - it can live in Beta for a little while so others can opt-in and shake it out if they want. I think it'll take some time to live with this to continue to refine it. I'm using it myself going forward. |
Add OrchestratedTask lifecycle flags, PendingNotification, CoordinatorState to electron/mcp/types.ts and StagedNotification + Task.stagedNotification to src/store/types.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add coordinator registry (registerCoordinator/deregisterCoordinator), maybeQueueReviewNotification hooked into PTY exit and idle transitions, stageBatch/formatNotificationText helpers, and ackNotification/markPromptDelivered public methods to the Orchestrator class. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8 tests covering startup-idle guard, first-idle notification, duplicate dedup, idle→exited upgrade, ack semantics, idempotent ack, shortened delay for non-zero exit, and orphaned sub-task notification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire MCP_CoordinatorRegistered, MCP_CoordinatorDeregistered, MCP_CoordinatedTaskPromptDelivered, and MCP_CoordinatorNotificationAck handlers to orchestrator methods in register.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tification listeners - Register coordinator after StartMCPServer succeeds in createTask - Deregister coordinator in closeTask's coordinatorMode block - Listen for MCP_CoordinatorNotificationStaged and update Task.stagedNotification - Export clearStagedNotification and setStagedNotificationUserEdited helpers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vered signal - Add coordinatedBy and stagedNotification props - Auto-populate textarea with staged text; start 1s interval to auto-fire when autoFireAt elapses and coordinator PTY shows prompt marker - Send MCP_CoordinatedTaskPromptDelivered after initial prompt auto-send for coordinated sub-tasks - Track userEdited when user types content different from staged text - Ack and clear staged notification on manual exact-match send - Show hidden completion count badge when userEdited Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l to PromptInput Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mcpConfigPath was stored on the task but never injected into the agent's args, so the coordinator's Claude started without --mcp-config. Tools were only discoverable via .mcp.json auto-discovery, which requires explicit approval in Claude Code unless skip-permissions is enabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…spawns agent TerminalView kills the existing PTY (clearing all subscribers) when it mounts for an orchestrated sub-task, then spawns a new one with the same agentId. This lost the orchestrator's outputCb permanently, so it could never detect when the sub-task became idle and therefore never staged a coordinator notification. Fix by listening for the 'spawn' PTY event and re-subscribing when a managed agentId is respawned. Also clear the stale tail buffer and reset task.status from 'exited' → 'running' so idle detection works correctly in the new session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add signal_done MCP tool + --task-id CLI arg to MCP server/client - Add POST /api/tasks/:taskId/done HTTP endpoint - Orchestrator: signalDone(), setMCPServerInfo(), per-task MCP config, SUB_TASK_PREAMBLE prepended to sub-task prompts, 5s delay override - register.ts: call setMCPServerInfo after remote server starts - Store: persist mcpConfigPath on sub-tasks for --mcp-config passthrough - Tests: two new signalDone unit tests (10 tests total, all pass) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thank you very much @brooksc (and @cledoux95 !) !! This is impressive work, and the integration fixes on top of #31 are exactly the kind of stuff that's painful to discover from the outside. I want to ship this, but I agree with you that it's not ready as a default-on feature, and I think your "Beta in Settings" instinct is the right framing. Let me make that concrete. Yes to beta-gating, with a real flag. Specifically:
Three things I'd want addressed before I merge, even into beta:
On your unsolved step #4 (coordinator noticing "agent done"): I think Claude Code's Graduation criteria I'd want to hit before flipping the flag on by default:
If you're up for the gating work I'm happy to keep this open and help where useful. And to be clear: I do want to ship this – the pilot/co-pilot pattern is the right shape for human-in-the-loop orchestration, and you've already shaved off a lot of the rakes. |
… log viewer - Add wait_for_signal_done MCP tool — coordinator blocks until sub-task explicitly signals done (more reliable than PTY-idle detection) - Track signalDoneAt on OrchestratedTask; expose in list_tasks/get_task_status - Sub-task chips in coordinator panel turn green with checkmark when signal_done received - MCP logs button always visible in coordinator sub-task strip; opens modal with timestamped log entries and refresh button - Auto-delete per-task tmp MCP config file on sub-task close - MCP_TaskStateSync listener in renderer store updates signalDoneReceived flag Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndoff improvements - Rename Orchestrator class and all files/identifiers to Coordinator - Fix spawn settings (agentCommand/agentArgs/skipPermissions) threading through MCP_TaskCreated so sub-agents inherit --dangerously-skip-permissions correctly - Fix per-task projectRoot snapshot to prevent singleton mutation across coordinators - Fix waiter resolver leak on timeout (indexOf wrappedResolve not resolve) - Gate MCP logs button on Verbose Logging setting - Notify coordinator when control returns after a blocked send_prompt - Notify coordinator when sub-task exits before prompt delivery (user closed early) - Expand unit tests to 379 covering all new paths - Add KNOWN-TODOS.md for unimplemented items - Remove design docs and test plan (superseded) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Keep coordinator mode toggle in correct position after AgentSelector - Take upstream steps-tracking checkbox attributes (stepsEnabled) - Fix task possibly-undefined TypeScript error in setTaskControl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@johannesjo — thanks for the detailed review, this is exactly the kind of feedback that makes it worth sharing early. Lots to respond to. Where things stand: working e2eSince the original PR I've built considerably more on top. Here's what's confirmed working end-to-end: Coordinator creation & MCP tooling
Sub-task lifecycle
Coordinator ↔ sub-agent notification — and why we didn't use Stop hooks This is where we diverge from your suggestion, and I think for good reason. You suggested Instead, sub-agents call the Additional notification paths:
Pilot / co-pilot control handoff — how it works and two open items Each sub-task panel shows a banner indicating who's driving. When the coordinator has control the banner reads "Coordinator driving" in grey. Clicking "Take Control" blocks the coordinator's Two things to note as known gaps: first, the button labels ("Take Control" / "Return to Orchestrator") overstate what's happening — the coordinator agent keeps running, only its ability to write to that terminal is paused. "Pause coordinator" / "Resume coordinator" would be more accurate. Second, the sub-task's PromptInput and raw xterm terminal are not currently gated on control state, so a user could type simultaneously with a coordinator Unit test coverage What isn't done yetBeta gating — you're right that zero-footprint opt-in is the right approach. We haven't done the Settings dialog placement — with coordinator mode, Docker, verbose logging, and more to come, Settings is getting long. A tabbed settings dialog (General / Experimental) would be a natural home for the beta flag, but that's its own PR and should land independently of this one. Docker + coordinator — currently mutually exclusive. Sub-tasks spawned by a coordinator run as native host processes, defeating Docker isolation. Two approaches documented in Post-restart MCP path integration test — fair ask. The port/token rotation path is the right thing to test. Will add.
Minor items (in KNOWN-TODOS.md): orphaned sub-task badge UI, re-stage notification after user manually sends an edited prompt, configurable notification delay, control handoff input gating and button renaming. Happy to tackle the beta gating as the next chunk. Does the |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4057d03 to
0be08ca
Compare
|
Thanks @brooksc — the depth of the response (and the 379 tests!) tells me where this is heading, and I'm on board with the shape. A few replies and a few new things I noticed reading the diff today.
Beta gating shape: skipPermissions guardrail — the propagation checkbox is the right shape. Good refinement: a "propagate skip-permissions to sub-tasks" checkbox in the New Task dialog, defaulting off even when the coordinator itself was launched with skip-permissions. Don't auto-inherit silently. The 40-tasks-at-a-time workflow is real and worth optimizing for. A few things I caught on a code pass that haven't come up yet:
Item #1 is the only one that affects whether the test plan currently passes; the rest can ride along with the beta-gating PR. Excited about this — the pilot/co-pilot framing has aged well over the discussion. |
Overview
This PR builds on the initial coordinating agent implementation by @cledoux95 (PR #31) with substantial fixes and new capabilities.
The core motivation: I wanted to use a coordinator agent to drive parallel workstreams, but still be able to monitor each sub-task, answer questions when an agent gets stuck, and take over a task when I want to make a decision myself — then hand it back. Think of it like a pilot/co-pilot handoff: explicit, visible, and safe.
Credit
The foundation of this PR is @cledoux95's work in #31, which introduced the coordinating agent concept, the MCP server/client, and the
create_task/send_prompt/wait_for_idle/merge_tasktools. This PR would not exist without that work.What's new / changed
Sub-task visibility (addresses the core UX gap)
In #31, sub-tasks created by the coordinator were not visible in the sidebar. The coordinator agent was essentially working in the dark. Now each sub-task spawned via
create_taskappears as its own sidebar panel, giving you full terminal visibility into what every agent is doing — exactly like a manually created task.Pilot/co-pilot control handoff
Each coordinated sub-task shows a banner indicating who is driving:
You can click "Take Control" at any time to pause the coordinator for that task, interact with the agent yourself, then click "Return to Orchestrator" when done.
Expanded
create_taskoptionsThe coordinator agent can now specify:
skipPermissions: true— passes--dangerously-skip-permissionsto the sub-agent so it runs fully autonomously without tool-approval interruptionsgitIsolation: "worktree" | "direct" | "none"— controls git isolation mode (defaults to"worktree"as before)Fixes to #31
Several bugs were found and fixed while integrating and testing:
findFreePort(7777, 7800)tries ports sequentially until one is free--mcp-configarg — the coordinator agent was spawned without the MCP config flag, so it had no MCP tools and fell back to bash orchestrationTaskAITerminalnow passes--mcp-config <path>whentask.mcpConfigPathis setspawnAgentdirectly AND the renderer'sTerminalViewalso called it, killing the backend spawn and losing the output subscriptionspawnAgentcall; orchestrator now usesonPtyEvent('spawn', ...)to subscribe to output each time the renderer spawns the agent — survives restarts toofetch failedon all MCP tool callsApp.tsxnow awaitsStartMCPServerfor each persisted coordinator task before the agents are spawned, refreshing the config file with the new port/tokendeleteTaskwrong call signature — the orchestrator calleddeleteTaskwith positional args but the function now takes an options objectdeleteTask({ agentIds, branchName, deleteBranch, projectRoot })400for invalidname,prompt,projectId,skipPermissions,gitIsolationwaitForIdlehangs when human takes control —setTaskControl('human')left pending waiters stuck until timeoutsetTaskControlnow unblocks pendingwaitForIdlecallers on any control changemcp_control_changedmissing from preload allowlistALLOWED_CHANNELSinpreload.cjsfetch failedgave no actionable infoMCPClientnow wraps network errors: "Cannot reach Parallel Code at http://127.0.0.1:PORT. Is the app running?"Tests
Added 25 new tests across two files:
electron/mcp/prompt-detect.test.ts—stripAnsiandchunkContainsAgentPrompt(the core ofwaitForIdleidle detection)electron/mcp/orchestrator.test.ts— control handoff logic:sendPromptblocked/unblocked,waitForIdlebehavior under human control, PTY exit propagationThe tests caught the
waitForIdlehang bug described above before it shipped.Test plan
fetch failed)create_taskwithskipPermissions: truespawns sub-agent without permission promptsnpm run check && npm test— all pass🤖 Generated with Claude Code