Skip to content

Drone agent — remote task execution (v4 phase 10) #96

Description

@andresharpe

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

  • Missed heartbeat (> 2× heartbeat interval) marks assignment as ailed and returns it to queue
  • drone.failed event triggers queue return with incremented retry count
  • Max retries respected — dead_letter state after limit reached
  • Dead-letter assignments visible in fleet dashboard with failure reason
  • Retry count and history stored on assignment record

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions