12 KiB
Agent Fleet
Agent Fleet is the local-first control plane for durable multi-worker runs. The initial CLI surface is:
codewhale fleet init
codewhale fleet run tasks.json --max-workers 4
codewhale fleet status
codewhale fleet inspect <worker-id>
codewhale fleet logs <worker-id>
codewhale fleet artifacts <worker-id>
codewhale fleet interrupt <worker-id>
codewhale fleet restart <worker-id>
codewhale fleet stop --all
Fleet state is stored under the workspace in .codewhale/fleet.jsonl. Worker
logs and adapter logs are stored under .codewhale/fleet/ and
.codewhale/fleet-host/.
Task Spec
codewhale fleet run accepts JSON or TOML. A minimal JSON spec:
{
"name": "local smoke",
"tasks": [
{
"id": "lint",
"name": "Lint",
"instructions": "Run the lint check and report failures.",
"expected_artifacts": ["log"]
}
]
}
Workers are optional. If omitted, CodeWhale creates local worker slots up to
--max-workers.
Task specs are typed in Rust and keep verification data separate from worker transcripts. A task can declare:
id,name,description,objective, andinstructionsworkerrole, tool profile, tools, and required capabilitiesworkspaceroot, required files, writable paths, and environment allowlistinput_files, extracontext,budget,timeout_seconds, andretry_policyexpected_artifacts,scorer,tags, and free-formmetadata
Workers write bounded artifact files under .codewhale/fleet/ and ledger only
the artifact refs: kind, path, checksum, MIME type, and size. Receipts record
pass, fail, partial, skip, or timeout; failed receipts may also mark
the source as transport, task, or verifier. codewhale fleet status
surfaces those failure-source counts separately.
Deterministic built-in scorers are exit_code, file_exists, regex_match,
and json_path. Specs may also declare command,
code_whale_verifier_prompt, or manual; those record a partial receipt until
an explicit verifier pass completes.
Release Triage Example
{
"name": "v0.8.60 release triage",
"labels": {
"milestone": "v0.8.60"
},
"tasks": [
{
"id": "release-issue-sweep",
"name": "Release issue sweep",
"objective": "Find open v0.8.60 blockers and credit-sensitive PRs.",
"instructions": "Review the v0.8.60 milestone, linked PRs, changelog entries, and contributor-credit requirements. Write a concise blocker report.",
"worker": {
"role": "release-triage",
"tool_profile": "read-only",
"tools": ["gh", "git"],
"capabilities": ["github", "release"]
},
"workspace": {
"required_files": ["Cargo.toml", "CHANGELOG.md", ".github/AUTHOR_MAP"],
"writable_paths": [".codewhale/fleet"],
"environment": {
"required": ["PATH"]
}
},
"input_files": ["CHANGELOG.md", ".github/AUTHOR_MAP"],
"context": ["Treat community PRs as maintainer evidence."],
"budget": {
"max_tokens": 12000,
"max_tool_calls": 24,
"max_seconds": 900
},
"timeout_seconds": 900,
"expected_artifacts": ["log", "report", "receipt"],
"scorer": {
"kind": "exit_code"
},
"retry_policy": {
"max_attempts": 2,
"initial_backoff_seconds": 10,
"max_backoff_seconds": 60,
"backoff_multiplier": 2
},
"tags": ["release", "triage"],
"metadata": {
"class": "release"
}
}
]
}
Code Review Swarm Example
{
"name": "code review swarm",
"tasks": [
{
"id": "protocol-review",
"name": "Protocol review",
"objective": "Review fleet protocol changes for compatibility and sparse JSON behavior.",
"instructions": "Inspect crates/protocol/src/fleet.rs and report behavior regressions, missing serde defaults, or unsafe wire changes.",
"worker": {
"role": "reviewer",
"tool_profile": "read-only",
"tools": ["git", "rg", "cargo"],
"capabilities": ["rust"]
},
"input_files": ["crates/protocol/src/fleet.rs"],
"budget": {
"max_tokens": 8000,
"max_tool_calls": 16,
"max_seconds": 600
},
"expected_artifacts": ["log", "report", "receipt"],
"scorer": {
"kind": "code_whale_verifier_prompt",
"prompt": "Verify the review includes at least one concrete file:line finding or explicitly says no issues were found."
},
"tags": ["review", "protocol"],
"metadata": {
"class": "code-review"
}
},
{
"id": "tui-review",
"name": "TUI review",
"objective": "Review fleet CLI and manager behavior for operator-visible regressions.",
"instructions": "Inspect crates/tui/src/fleet and crates/tui/src/main.rs. Focus on status output, receipt recording, and failure classification.",
"worker": {
"role": "reviewer",
"tool_profile": "read-only",
"tools": ["git", "rg", "cargo"],
"capabilities": ["rust", "cli"]
},
"input_files": ["crates/tui/src/fleet", "crates/tui/src/main.rs"],
"budget": {
"max_tokens": 10000,
"max_tool_calls": 20,
"max_seconds": 600
},
"expected_artifacts": ["log", "report", "receipt"],
"scorer": {
"kind": "manual"
},
"tags": ["review", "tui"],
"metadata": {
"class": "code-review"
}
}
]
}
Alerts
Fleet alerting is disabled by default. A caller must supply an enabled alert config before anything is sent. Routes match typed fleet event classes, not log strings:
stalerestart_exhaustedneeds_humanbudget_exceededverifier_failedrun_completed
Adapter config stores environment variable names, not secret values. Send-time
code resolves those names from the environment or a future secrets provider.
Ledger records store only audit labels such as slack, webhook, or
pagerduty; task specs persisted in the ledger redact webhook URLs and routing
keys.
Example alert config shape:
{
"enabled": true,
"dry_run": true,
"routes": [
{
"events": ["stale", "restart_exhausted", "verifier_failed"],
"adapter": "ops-slack"
},
{
"events": ["restart_exhausted"],
"adapter": "pager"
}
],
"adapters": {
"ops-slack": {
"kind": "slack",
"webhook_env": "CODEWHALE_FLEET_SLACK_WEBHOOK",
"channel": "#codewhale-fleet"
},
"pager": {
"kind": "pager_duty",
"routing_key_env": "CODEWHALE_FLEET_PAGERDUTY_ROUTING_KEY",
"severity": "critical"
}
}
}
Use dry-run to inspect a redacted adapter payload without sending:
codewhale fleet alert-dry-run \
--event stale \
--run-id fleet-demo \
--worker-id fleet-demo-local-1 \
--task-id release-triage \
--reason "worker heartbeat stale since 2026-06-13T02:00:00Z" \
--adapter slack
The payload includes the run id, worker id, task id, status, short reason, and
safe inspection commands such as codewhale fleet status and
codewhale fleet inspect <worker-id>. Endpoints, webhook secrets, and
PagerDuty routing keys are shown as <redacted:env:...>.
Status Surfaces
codewhale fleet status shows compact counts for queued, running, completed,
partial, failed, restarted, escalated, cancelled, stale, and verifier/transport
failure sources. inspect shows the worker state plus the current task
objective, role, host, heartbeat, latest event, artifact refs, latest error, and
alert state. logs prints bounded log artifact contents, and artifacts lists
artifact refs without embedding large payloads.
The Runtime API exposes the same ledger-backed projection behind the existing runtime auth middleware:
GET /v1/fleet/runs
GET /v1/fleet/runs/{run_id}
GET /v1/fleet/runs/{run_id}/workers
GET /v1/fleet/workers/{worker_id}
POST /v1/fleet/workers/{worker_id}/interrupt
POST /v1/fleet/workers/{worker_id}/restart
POST /v1/fleet/runs/{run_id}/stop
Action endpoints call the same manager controls as the CLI and record their decisions in the fleet ledger.
Manager-Agent Runbook
Manager agents should treat Fleet operations as typed, ledgered control-plane
work. Start with codewhale fleet status, then inspect one run or worker with
codewhale fleet inspect <worker-id>, logs, and artifacts. Use direct
reads of .codewhale/fleet.jsonl, host logs, or remote files only when the
typed CLI/API surface cannot provide the required evidence.
Classify the worker before taking action:
transient failure: stale heartbeat, host timeout, interrupted transport, retryable provider/network error, or an adapter status that can plausibly recover without changing the task.task failure: the worker completed but produced an incorrect result, domain failure, missing required artifact, or explicit task-level error.verifier failure: the worker result exists, but the scorer/verifier failed, timed out, or disagrees with the receipt.needs-human: missing authority, secret request, destructive operation, repeated restart exhaustion, ambiguous product decision, or conflicting evidence that the manager cannot resolve from typed artifacts.
Choose one typed action:
- Restart a worker only when the failure is transient, retry budget remains,
the task is idempotent or retry-safe, and no permission or secret boundary is
involved:
codewhale fleet restart <worker-id>. - Interrupt or stop only when the current task is unsafe to continue or the
operator explicitly asks for cancellation:
codewhale fleet interrupt <worker-id>orcodewhale fleet stop --all. - Do not restart pure task failures by default; preserve artifacts and hand the receipt to the task owner unless the task spec says retrying can produce new evidence.
- For verifier failures, inspect scorer inputs and artifact refs first. If the verifier cannot be corrected through typed fleet actions, escalate for human review.
- For
needs-human, draft an escalation instead of sending it unless alert config explicitly authorizes sending.
Safe Slack or PagerDuty draft:
CodeWhale fleet needs attention
Run: <run-id>
Worker: <worker-id>
Task: <task-id or unknown>
Classification: <transient failure | task failure | verifier failure | needs-human>
Reason: <one sentence, no secrets>
Latest typed evidence: codewhale fleet inspect <worker-id>; codewhale fleet artifacts <worker-id>
Safe log excerpt: <3 lines max or "see artifact <ref>">
Requested decision: <restart approval | verifier review | task owner review | permission decision>
Post-run summaries should include the run id, workers checked, classification, typed action taken or drafted, expected ledger effect, artifact refs reviewed, and next owner. Keep summaries bounded; link artifact refs instead of copying full logs or transcripts.
The bundled fleet-manager skill mirrors this runbook for manager agents. It
is a first-party system skill and should be discoverable through the normal
skill registry after system skills are installed or refreshed.
Host Adapters
The host adapter boundary supports local child processes and explicit SSH workers. Adapters expose the same operations: start, read status, read bounded logs, interrupt, restart, stop, and cleanup.
Local workers run as child processes with stdin closed and stdout/stderr written
to bounded fleet host logs. They inherit only a small safe base environment
such as PATH and explicitly allowlisted variables.
SSH workers run through the system ssh client with BatchMode=yes and a
bounded connect timeout. Remote environment variables are sent with OpenSSH
SendEnv; values are not embedded in the local ssh argv or fleet logs.
Example SSH worker spec:
{
"id": "builder-1",
"name": "Builder 1",
"host": {
"kind": "ssh",
"host": "builder.example.com",
"user": "codewhale",
"port": 22,
"identity": "~/.ssh/codewhale_fleet",
"working_directory": "/srv/codewhale/work",
"env_allowlist": ["CODEWHALE_PROFILE"],
"codewhale_binary": "/usr/local/bin/codewhale"
},
"capabilities": ["local", "linux", "tests"],
"max_concurrent_tasks": 1
}
Defaults are intentionally conservative:
- no hosted control plane or cloud provisioning is enabled;
- SSH requires an explicit host, working directory, and CodeWhale binary path;
- secret-like environment names such as
TOKEN,SECRET,PASSWORD,API_KEY, andPRIVATE_KEYare rejected from adapter allowlists; - secrets should remain in CodeWhale config providers or remote host config, not in task instructions, argv, or fleet logs.