Phase 10: Drone Agent
⬅ Back to Roadmap
Concept
A Drone is a headless dotbot worker that polls the Mothership for work, clones repos, executes tasks, and reports results. Drones reuse the existing Runtime, MCP Server, and ProviderCLI — the only new code is the supervisor agent and its lifecycle management.
Drone Agent script
Path: scripts/drone-agent.ps1
The Drone Agent is a long-running PowerShell script that:
# drone-agent.ps1 — Headless autonomous worker
param(
[string]$ConfigPath = "./drone-config.yaml" # Drone configuration
)
# 1. Load config (providers, capabilities, mothership URL)
# 2. Register with Mothership (POST /api/fleet/register with instance_type=drone)
# 3. Enter main loop:
# a. Poll Mothership for work (GET /api/fleet/work-queue/poll)
# b. Poll for commands (GET /api/fleet/{drone_id}/commands/pending) — alongside heartbeat
# c. If assignment received:
# - Clone repo to workspace_dir
# - Run dotbot init with required stacks
# - Launch process (analysis, execution, or workflow)
# - Stream events to Mothership via event bus
# - On completion: push commits, create PR, report results
# - Cleanup workspace
# d. If no work: heartbeat + sleep(poll_interval)
# 4. On shutdown: deregister, cleanup active workspaces
DroneAgent.psm1
Path: profiles/default/systems/runtime/modules/DroneAgent.psm1
Functions:
Initialize-Drone -Config <hashtable> — load config, validate providers
Register-Drone -MothershipUrl <string> -ApiKey <string> -Capabilities <hashtable> — register with mothership
Get-DroneAssignment -MothershipUrl <string> -DroneId <string> — poll work queue
Get-DroneCommands -MothershipUrl <string> -DroneId <string> — poll pending commands
Invoke-DroneAssignment -Assignment <hashtable> — clone, init, execute, report
Send-DroneHeartbeat -MothershipUrl <string> -DroneId <string> -Status <hashtable> — periodic heartbeat
Complete-DroneAssignment -AssignmentId <string> -Result <hashtable> — report completion
Remove-DroneWorkspace -WorkspacePath <string> — cleanup after assignment
Drone configuration format
Path: defaults/drone-config.example.yaml
name: "drone-prod-01"
mothership:
url: "https://mothership.example.com"
api_key: "..."
poll_interval_seconds: 10
heartbeat_interval_seconds: 30
providers:
- name: claude
env_key: ANTHROPIC_API_KEY
models: [opus, sonnet]
default_model: opus
- name: codex
env_key: OPENAI_API_KEY
models: [gpt-5.2-codex]
- name: gemini
env_key: GEMINI_API_KEY
models: [gemini-2.5-pro]
capabilities:
max_concurrent: 3
stacks: [dotnet, dotnet-blazor, dotnet-ef]
workspace_dir: /var/dotbot/workspaces
cleanup_on_complete: true
git:
credential_helper: "store"
user_name: "dotbot-drone"
user_email: "drone@dotbot.dev"
logging:
level: Info
forward_to_mothership: true
Provider selection
When the Mothership dispatches work to a Drone:
- Assignment specifies
preferred_provider and preferred_model
- Drone matches against its configured providers
- If preferred not available, falls back to any available provider
- The existing
ProviderCLI.psm1 handles the actual invocation — Drone just sets the provider config
Credential management
Drone Command Channel
Outposts have developer "whisper" steering via JSONL files. Drones are headless so they get Mothership commands instead. The Drone polls for pending commands on every heartbeat cycle.
API
POST /api/fleet/{drone_id}/command
{
"type": "stop|pause|resume|reassign",
"payload": { ... } // optional, e.g. new assignment_id for reassign
}
GET /api/fleet/{drone_id}/commands/pending
# Returns list of unacknowledged commands
POST /api/fleet/{drone_id}/commands/{command_id}/ack
# Drone acknowledges receipt and execution of command
Command types
| Command |
Behaviour |
stop |
Graceful shutdown — finish current task step, push progress, deregister, exit |
pause |
Suspend polling for new assignments; complete current assignment if in-flight |
resume |
Resume polling after a pause |
reassign |
Abandon current assignment and accept a replacement (payload includes new assignment_id) |
Implementation
Commands map to the same internal stop-signal mechanism (Test-ProcessStopSignal) already used by the local runner. Get-DroneCommands is called in the main loop alongside heartbeat. On receipt of a command the Drone acknowledges it immediately, then executes the action at the next safe point (end of current task step, not mid-commit).
Outpost command channel (future — not in scope here)
Outposts do not yet have an equivalent remote command channel. A Mothership admin cannot remotely pause or stop a workflow running on an outpost. This gap is noted for a future issue — the pattern established here for drones should be extended to outposts in a follow-on issue.
Docker support
Path: docker/Dockerfile.drone
FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04
RUN apt-get update && apt-get install -y git
# Install provider CLIs (claude, codex, gemini)
COPY . /opt/dotbot
RUN pwsh /opt/dotbot/install.ps1
ENTRYPOINT ["pwsh", "/opt/dotbot/scripts/drone-agent.ps1"]
Docker Compose for drone fleet:
services:
drone-1:
build: { context: ., dockerfile: docker/Dockerfile.drone }
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
volumes:
- ./drone-config-1.yaml:/config/drone-config.yaml
- drone-workspaces-1:/var/dotbot/workspaces
command: ["-ConfigPath", "/config/drone-config.yaml"]
Events
drone.registered — Drone connects to Mothership
drone.assigned — Drone receives work assignment
drone.working — Drone starts task execution
drone.completed — Drone finishes assignment successfully
drone.failed — Drone assignment failed
drone.idle — Drone has no work (heartbeat)
drone.command_received — Drone acknowledges a Mothership command
drone.stopped — Drone deregisters on graceful shutdown
Files
- Create:
scripts/drone-agent.ps1 — main entry point
- Create:
profiles/default/systems/runtime/modules/DroneAgent.psm1 — Drone lifecycle functions
- Create:
defaults/drone-config.example.yaml — example configuration
- Create:
docker/Dockerfile.drone — containerized Drone
- Create:
docker/docker-compose.drone.yaml — multi-drone deployment
- Server: Extend
FleetController with work queue + command endpoints
- Server: Create
WorkQueueService.cs, DroneSchedulerService.cs, DroneCommandService.cs
Dependencies
Status Assessment
- Status: NOT STARTED
- Date: 2026-04-01
- Evidence: No remote execution, distributed agent, SSH-based spawning, Docker containerization, or Kubernetes pod support. All execution is local only.
GitHub Issue
Failure handling and retry
When a drone assignment fails (crash, timeout, provider error), the task must not be silently lost.
Failure modes
| Failure |
Behaviour |
| Drone process crash |
Mothership detects missed heartbeat → marks assignment as ailed → returns to queue |
| Provider error (Claude timeout) |
Drone emits drone.failed event with reason → Mothership returns task to queue with retry count incremented |
| Task-level error (bad output, test failure) |
Drone reports drone.completed with status: failed → Mothership routes to needs-input for human review |
| Drone deregistered mid-task |
Same as crash — heartbeat timeout triggers queue return |
Retry policy
- Max 3 automatic retries per assignment (configurable in drone-config.yaml)
- On retry: Mothership may reassign to a different drone if the original is unhealthy
- After max retries: assignment moves to dead_letter state — visible in fleet dashboard, requires manual intervention
- Retry count and failure reason stored on the assignment record
Acceptance criteria
Phase 10: Drone Agent
⬅ Back to Roadmap
Concept
A Drone is a headless dotbot worker that polls the Mothership for work, clones repos, executes tasks, and reports results. Drones reuse the existing Runtime, MCP Server, and ProviderCLI — the only new code is the supervisor agent and its lifecycle management.
Drone Agent script
Path:
scripts/drone-agent.ps1The Drone Agent is a long-running PowerShell script that:
DroneAgent.psm1
Path:
profiles/default/systems/runtime/modules/DroneAgent.psm1Functions:
Initialize-Drone -Config <hashtable>— load config, validate providersRegister-Drone -MothershipUrl <string> -ApiKey <string> -Capabilities <hashtable>— register with mothershipGet-DroneAssignment -MothershipUrl <string> -DroneId <string>— poll work queueGet-DroneCommands -MothershipUrl <string> -DroneId <string>— poll pending commandsInvoke-DroneAssignment -Assignment <hashtable>— clone, init, execute, reportSend-DroneHeartbeat -MothershipUrl <string> -DroneId <string> -Status <hashtable>— periodic heartbeatComplete-DroneAssignment -AssignmentId <string> -Result <hashtable>— report completionRemove-DroneWorkspace -WorkspacePath <string>— cleanup after assignmentDrone configuration format
Path:
defaults/drone-config.example.yamlProvider selection
When the Mothership dispatches work to a Drone:
preferred_providerandpreferred_modelProviderCLI.psm1handles the actual invocation — Drone just sets the provider configCredential management
credentials_refin assignment maps to the credential store on the Drone (see Drone credential store — secure credentials_ref resolution for repo cloning #574)Drone Command Channel
Outposts have developer "whisper" steering via JSONL files. Drones are headless so they get Mothership commands instead. The Drone polls for pending commands on every heartbeat cycle.
API
Command types
stoppauseresumepausereassignassignment_id)Implementation
Commands map to the same internal stop-signal mechanism (
Test-ProcessStopSignal) already used by the local runner.Get-DroneCommandsis called in the main loop alongside heartbeat. On receipt of a command the Drone acknowledges it immediately, then executes the action at the next safe point (end of current task step, not mid-commit).Outpost command channel (future — not in scope here)
Outposts do not yet have an equivalent remote command channel. A Mothership admin cannot remotely pause or stop a workflow running on an outpost. This gap is noted for a future issue — the pattern established here for drones should be extended to outposts in a follow-on issue.
Docker support
Path:
docker/Dockerfile.droneDocker Compose for drone fleet:
Events
drone.registered— Drone connects to Mothershipdrone.assigned— Drone receives work assignmentdrone.working— Drone starts task executiondrone.completed— Drone finishes assignment successfullydrone.failed— Drone assignment faileddrone.idle— Drone has no work (heartbeat)drone.command_received— Drone acknowledges a Mothership commanddrone.stopped— Drone deregisters on graceful shutdownFiles
scripts/drone-agent.ps1— main entry pointprofiles/default/systems/runtime/modules/DroneAgent.psm1— Drone lifecycle functionsdefaults/drone-config.example.yaml— example configurationdocker/Dockerfile.drone— containerized Dronedocker/docker-compose.drone.yaml— multi-drone deploymentFleetControllerwith work queue + command endpointsWorkQueueService.cs,DroneSchedulerService.cs,DroneCommandService.csDependencies
Status Assessment
GitHub Issue
Failure handling and retry
When a drone assignment fails (crash, timeout, provider error), the task must not be silently lost.
Failure modes
Retry policy
Acceptance criteria