Skip to content

Getting Started — Fleet Dispatch

Technical track. Plain version: docs/plain/getting-started.md.

Prerequisites

  • .NET 10.0 SDK (all three projects target net10.0).
  • Windows (the listener, availability signals, and RealJobExecutor are Windows-specific — registry reads, HttpListener, Process.GetProcessesByName).
  • Git, on PATH, for the repo-registry scanner.
  • Optional per harness kind you plan to dispatch to: the claude CLI, the ChatGPT (Codex) CLI, the Antigravity (agy) CLI, the grok CLI, ollama, and/or PowerShell 7 (pwsh) — a box without pwsh falls back to Windows PowerShell for kind=pwsh jobs automatically.

Build and test

dotnet restore FleetDispatch.sln
dotnet build FleetDispatch.sln --no-restore -warnaserror
dotnet test tests/FleetDispatch.Tests/FleetDispatch.Tests.csproj --no-build

-warnaserror matches CI — a local build that only warns will fail in ci.yml.

First-time setup: the fleet key

Every signed job (HMAC-SHA256) and every peer bearer token derives from one shared 32-byte key at %USERPROFILE%\.claude\fleet-dispatch.key (override with FLEET_DISPATCH_KEY_FILE).

dotnet run --project src/FleetDispatch.Cli -- init-key

This creates the key (idempotent — a second run just prints its location), restricts its ACL to the current user, and prints the reminder: copy this file to every other PC's ~/.claude via the SSH mesh (scp), never through OneDrive. OneDrive is the job transport; the key must never ride the same channel a compromised OneDrive account could read.

Running the listener locally

dotnet run --project src/FleetDispatch.Cli -- serve --port 47600 --poll 30

serve binds the HTTP listener, starts the queue-drain loop, the availability presence loop, and the repo-scan loop, then self-registers this PC in the peer registry — but only after the bind is confirmed, never before. Useful flags:

  • --host <ip> / FLEET_DISPATCH_HOST — pin the bind/advertise address on a dual-homed box instead of relying on auto-pick (Tailscale address preferred, else the gatewayed LAN NIC).
  • --no-queue — HTTP-only, no queue-drain loop.
  • --no-repo-scan / --repo-scan-poll-hours <n> — disable or retune the repo-registry scan timer (default 24h).
  • --check — probe a running listener's own /health and exit 0/non-zero; use this in a deploy script to verify the box actually came up, rather than grepping a log line.

For the real per-box deployment (Scheduled Task, URL ACL reservation, firewall rules), see README.md § Persistent Listener — it is a two-script, two-step process (setup-urlacl.ps1 unelevated, then repair-fleet-listener.ps1 elevated) and explicitly not a Windows Service.

Dispatching your first job

# Async, OneDrive-queued (works even if the target is offline right now):
dotnet run --project src/FleetDispatch.Cli -- enqueue --targets ANDYGREATROOMPC --kind pwsh --lane WT-0001 --payload "Get-Process pwsh"

# Synchronous, direct LAN peer (target must be online and reachable):
dotnet run --project src/FleetDispatch.Cli -- send --target ANDYGREATROOMPC --kind claude --lane WT-0001 --payload-file job.md --wait

# Check results:
dotnet run --project src/FleetDispatch.Cli -- results --target ANDYGREATROOMPC
dotnet run --project src/FleetDispatch.Cli -- status

--lane is required on every job — it is a WT-xxxx worktree/lane label, not optional metadata. --targets all expands to the live fleet in TargetResolver.DefaultPcs (ANDREWBEDROOMTV, ANDYGREATROOMPC, DESKTOP-D249KD4, DESKTOP-SCDT9HI) — a folder existing under the dispatch root does not make a name dispatchable (see README § Targets and FLT-68).

Optionally tag the job's --job-class (control|trivial|heavy_cpu|heavy_gpu|focus_risk) so the availability gate knows how disruptive it is; omitting it defaults to heavy_cpu (blocked while the target is gaming/streaming).

Repo-registry scan (on demand)

dotnet run --project src/FleetDispatch.Cli -- repo-scan

Scans the approved roots (%USERPROFILE%\source\repos plus any FLEET_REPO_SCAN_EXTRA_ROOTS), signs the result, and writes it to {RepoInventoryDir}\{ComputerName}.json. A serve-running box already does this at boot and on a daily timer — this is for an immediate, out-of-band run.

Next steps