You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on May 24, 2026. It is now read-only.
The OrchestratorReflect mode relies on the orchestrator producing @worker:name text blocks, which PolyPilot's reflect loop parses via regex and dispatches to worker sessions. However, the orchestrator is a full Copilot SDK session with built-in tools (task, grep, edit, etc.), and the model strongly prefers using the SDK's task sub-agent tool over producing text-based @worker: blocks.
This causes:
The orchestrator does all work itself via SDK sub-agents instead of delegating to PolyPilot worker sessions
Worker sessions sit idle with zero events
The reflect loop's ParseTaskAssignments finds no @worker: blocks and stalls
Sub-agent work is invisible in the PolyPilot UI — no progress tracking, no session history
Rewrote the orchestrator's planning prompt to forcefully say "You are a DISPATCHER ONLY, you do NOT have tools" and removed tool-like language from the RoutingContext. This works but fights against the model's natural behavior.
Proposed Approach: Intercept SDK task Tool
Instead of fighting the model, embrace its preference for the task tool and intercept it to route through PolyPilot's worker sessions.
Architecture
Disable the SDK's built-in task tool via ExcludedTools = new List<string> { "task" } on the orchestrator's SessionConfig
Register a custom AIFunction (e.g., delegate_to_worker) that:
Receives the prompt and agent_type from the model
Maps to the appropriate PolyPilot worker session (or creates one dynamically)
Calls ExecuteWorkerAsync(workerName, task, originalPrompt, ct) — the same code path the reflect loop already uses
Returns the worker's response back to the SDK as the tool result
Workers become visible sessions — each sub-agent delegation shows up as a real PolyPilot session with:
Full event stream (tool calls, edits, file changes)
tool.execution_start for task contains the full prompt, agent_type, and description — all the data needed to route to a worker
The model naturally names its sub-agent calls descriptively (e.g., "Implement steering messages", "Challenge steering implementation") — these could become session display names
Open Questions
Does ExcludedTools work for the built-in task tool?
Can a custom AIFunction be named task to transparently replace the built-in, or does it need a different name?
Should dynamic worker sessions be created on-the-fly, or pre-allocated from the preset?
How to handle the explore agent type — map to a lightweight read-only worker?
What happens to general-purpose sub-agents that the orchestrator spawns for complex multi-step work?
Benefits
Stop fighting the model — let it use tools naturally
Full visibility — every sub-agent becomes a trackable session
Worktree isolation — workers can have their own git worktrees
Persistence — sub-agent work survives app restarts
Simplify the reflect loop — no more @worker: text parsing, ParseTaskAssignments regex, or prompt engineering to force text dispatch
Problem
The OrchestratorReflect mode relies on the orchestrator producing
@worker:nametext blocks, which PolyPilot's reflect loop parses via regex and dispatches to worker sessions. However, the orchestrator is a full Copilot SDK session with built-in tools (task,grep,edit, etc.), and the model strongly prefers using the SDK'stasksub-agent tool over producing text-based@worker:blocks.This causes:
ParseTaskAssignmentsfinds no@worker:blocks and stallsCurrent workaround (PR #225)
Rewrote the orchestrator's planning prompt to forcefully say "You are a DISPATCHER ONLY, you do NOT have tools" and removed tool-like language from the RoutingContext. This works but fights against the model's natural behavior.
Proposed Approach: Intercept SDK
taskToolInstead of fighting the model, embrace its preference for the
tasktool and intercept it to route through PolyPilot's worker sessions.Architecture
Disable the SDK's built-in
tasktool viaExcludedTools = new List<string> { "task" }on the orchestrator'sSessionConfigRegister a custom
AIFunction(e.g.,delegate_to_worker) that:ExecuteWorkerAsync(workerName, task, originalPrompt, ct)— the same code path the reflect loop already usesWorkers become visible sessions — each sub-agent delegation shows up as a real PolyPilot session with:
Key Research Findings
SessionConfig.ToolsacceptsList<AIFunction>— custom tools added alongside built-in ones (seeShowImageToolpattern)SessionConfig.ExcludedToolsandSessionConfig.AvailableToolsexist in SDK 0.1.26 but aren't used by PolyPilot yetsubagent.startedevents containtoolCallId,agentName,agentDisplayNametool.execution_startfortaskcontains the fullprompt,agent_type, anddescription— all the data needed to route to a workerOpen Questions
ExcludedToolswork for the built-intasktool?AIFunctionbe namedtaskto transparently replace the built-in, or does it need a different name?exploreagent type — map to a lightweight read-only worker?general-purposesub-agents that the orchestrator spawns for complex multi-step work?Benefits
@worker:text parsing,ParseTaskAssignmentsregex, or prompt engineering to force text dispatchFiles Involved
PolyPilot/Services/CopilotService.cs—SessionConfig.Tools,SessionConfig.ExcludedToolsPolyPilot/Services/CopilotService.Organization.cs—ExecuteWorkerAsync,SendViaOrchestratorReflectAsyncPolyPilot/Services/ShowImageTool.cs— existing customAIFunctionpattern to followPolyPilot/Models/ModelCapabilities.cs— preset definitions, RoutingContext