agent-task
Run provider-neutral task plans through Homeboy’s durable agent-task lifecycle.
Homeboy owns durable orchestration and provider-neutral outcomes. Runtime
providers own backend-specific execution. For the provider fanout ownership seam,
see docs/architecture/provider-fanout-boundary.md.
Boundaries
agent-task is split into four operator-facing seams:
- Lifecycle: durable run submission, execution, inspection, cancellation, and retry.
- Cook/review: workspace task conveniences that compose lifecycle runs with promotion, gates, and PR finalization.
- Provider: executor discovery, machine-readable contracts, and redacted auth readiness.
- Prompt store: Homeboy-owned markdown prompts for reusable cook/controller input.
- Loop/controller: durable multi-agent loop state with on/off, revolutions, handoffs, continuation policy, and resume/stop controls.
Subcommands
Lifecycle
| Subcommand | Purpose |
|---|---|
run-plan | Run an AgentTaskPlan through extension-declared executor providers. |
run <run-id> | Execute one exact durable run id, bypassing older queued work. |
run-next | Claim and execute the oldest eligible queued durable run, reporting skipped quarantined work. |
submit | Persist an agent-task plan and return a durable run id without executing it. |
status <run-id> [--exact] | Read durable run status; --exact bypasses Cook alias resolution to inspect that concrete lifecycle record. |
list [--limit <n>] | List durable runs, newest first. |
active [--limit <n>] [--cursor <n>] [--reconcile [--dry-run|--apply]] | List queued and running durable runs, newest first, or preview/reconcile the explicit fleet mutation set. |
reconcile <run-id> [--dry-run|--apply] | Preview or reconcile one durable run after refreshing its authoritative provider state. |
latest [--limit <n>] | Show the latest durable run. |
logs <run-id> [--raw] | Read the canonical durable event stream; --raw adds transport frames for diagnostics. |
artifacts <run-id> | List artifacts and evidence refs recorded for a completed run. |
replay-provider-boundary <run-id> [--task <task-id>] | Hydrate the latest raw executor input and print provider-boundary fields without relaunching a provider. |
cancel <run-id> | Mark a queued or stale-running durable run as cancelled. |
quarantine <run-id> --reason <text> | Exclude one exact queued run while retaining its nonterminal lifecycle evidence. |
rearm <run-id> | Return one exact quarantined queued run to eligibility after repair. |
resume <run-id> | Resume a queued or stale-running durable run. |
retry <run-id> | Submit a fresh durable run from an existing run’s plan. |
prompts save|list|show|remove | Manage markdown prompts in Homeboy-owned storage. |
agent-task list, agent-task active, and agent-task latest accept --limit <n> to cap discovery output. list and active default to 20 newest rows and return next_cursor when another page exists; continue active discovery with agent-task active --limit <n> --cursor <next_cursor>. Active discovery emits at most eight prioritized next_actions: fleet reconciliation when stale records exist, page continuation when truncated, and focused per-run inspection actions. list --full and active --full return every matching row. Filter list discovery by --task-url, --repo, --worktree, --submitted-after, --state, --run-placement, or --parent-id. agent-task reconcile <run-id> is the recovery path emitted by status and activity: it previews only that run by default, refreshes runner/provider state before classification, and requires --apply to mutate it. If ownership or provider state changes before apply, it reports a no-op. agent-task active --reconcile is an explicit fleet operation: it previews every candidate by default and requires --apply to reconcile the fleet-wide candidate set. Its --limit, --cursor, and --full selectors are rejected so discovery pagination cannot imply a reconciliation scope.
Resource Behavior
Resource admission follows command capability rather than command names. Bounded
controller metadata reads (status, logs, metadata-only artifacts, list,
active, latest, and their fanout/loop/controller equivalents) run locally by
default and produce no hot-machine or Lab-routing guidance. This keeps recovery
inspection available when a runner is disconnected, stale, or reports conflicting
readiness. An explicit --runner retains runner-resident polling for status,
logs, artifacts, list, active, latest, and fanout state reads when the durable
state lives there. Provider execution, deterministic gates, explicit evidence or
provider-boundary hydration, and review remain subject to their admission and
routing contracts. Reconciliation is an explicit workload even when it begins
with an inspection.
Machine-readable cook, resume, adopt, and status responses default to bounded summaries: stable ids, states, totals, timestamps when present, artifact/evidence references, and actionable failure reasons are retained while nested provider evidence is projected away. Use cook --full, resume --full, adopt --full, or the emitted full_command; use the emitted evidence_command (with --task or --kind) to retrieve selected durable evidence.
agent-task replay-provider-boundary <run-id> is a focused inspect/replay path for
provider-boundary debugging. It loads saved executor-input evidence, projects the
normalized runtime_task, provider config, runtime_component_paths,
runtime_env, artifact declarations, and package descriptor, then persists the
inspection as provider-boundary-replay evidence. Use --task <task-id> for
multi-task runs.
Durable Fanout Batches
Use agent-task fanout submit-batch when a caller has many independent tasks and
needs durable lifecycle records for every child run. Homeboy persists one parent
batch record plus one queued agent-task run per packet/task, so callers can
drive execution with agent-task run-next or existing runner/lab queue loops and
later reconcile status/artifacts without in-process Promise fanout or manual
collation.
homeboy agent-task fanout submit-batch --input @packets.json --batch-id audit-wave-1
homeboy agent-task run-next
homeboy agent-task fanout status audit-wave-1
homeboy agent-task fanout artifacts audit-wave-1submit-batch is intentionally provider-neutral: packets still carry ordinary
AgentTaskRequest executor contracts, and child runs use the existing
agent-task lifecycle. Dependent workflow plans are rejected because their
ordering and output bindings belong in the existing single-run fanout submit /
run-plan scheduler path.
Cook/Review
File-backed verification gates
Use --verify-file <path> or --private-verify-file <path> for shell programs
with loops, quotes, newlines, or $variables. Each option is repeatable. Homeboy
reads the regular UTF-8 file from the controller invocation directory before it
provisions a worktree or dispatches a provider, then executes and persists that
exact snapshot. Changing the source file afterwards cannot change the Cook.
homeboy agent-task cook --prompt @task.md --to-worktree project@fix-gate
--verify-file ./quality-gate.sh
--private-verify-file ./private-gate.shFor example, quality-gate.sh can contain this program without shell-argument
interpolation:
for file in src/*.rs; do
cargo fmt --check -- "$file"
doneRelative paths resolve from the directory where the homeboy controller command
starts, not from the destination worktree. Quote inline gates with single quotes
when they are concise, such as --verify 'cargo fmt --check'; use a file for
complex programs. Files must be readable, non-empty, regular files no larger than
1 MiB. Durable gate policy records source_kind, SHA-256 digest, byte size, and
redaction policy. Public file paths are retained as provenance; private file paths
and private program text are redacted from that provenance. Existing inline
--verify and --private-verify commands remain supported.
Private gate programs, whether inline or file-backed, use the existing trusted
durable Cook recipe boundary so retries and adoption can replay the declared gate.
They are not protected as secret material at rest. Homeboy excludes private gate
text and paths from public Cook-batch plans, preflight summaries, reviewer-facing
evidence, and agent feedback according to --private-gate-reveal; operators who
need encrypted secret storage should supply a non-secret command that retrieves
credentials at execution time rather than place credentials in a gate program.
For a Cook-batch with private gates, Homeboy atomically retains the exact plan in
$HOMEBOY_DATA_DIR/agent-task/private-batch-plans/<fanout-id>.json before it
returns its public result. The returned run-plan --input @<absolute-path> command
uses that controller-owned artifact; its private contents are never included in
public JSON. Its SHA-256 checksum detects accidental corruption in trusted local
state; it is not adversarial authentication. On Unix, the private artifact
directory is mode 0700 and temporary/final artifacts are created mode 0600
before private bytes are written; rename preserves that mode. Other platforms use
their platform-default owner-private file semantics where exposed by the runtime.
Artifacts are retained with Homeboy controller data until data-root cleanup. On
platforms without Unix device/inode identity APIs, Homeboy retains
the portable descriptor-bound contract: it opens once, validates the opened
descriptor as a regular bounded file, and reads that descriptor; Unix additionally
uses no-follow, nonblocking open flags and verifies device/inode identity.
fanout plan --input @<private-artifact> may inspect this trusted local artifact,
but it returns only the same public projection used by Cook-batch output: private
gate commands and paths remain redacted. fanout run-plan is the execution path
that loads the private gate bytes.
Provider Execution Budgets
Every agent-task plan serializes one execution_budget per task: total provider
executions, same-provider retries, and cross-provider rotations. The total cap is
always authoritative across both retry paths.
# Exactly one provider process: no retry and no rotation.
homeboy agent-task cook --prompt @task.md --max-provider-executions 1
# One retry on the same provider, with at most two executions total.
homeboy agent-task cook --prompt @task.md --max-provider-executions 2 --max-same-provider-retries 1
# Rotate once after a provider failure, with no same-provider retry.
homeboy agent-task cook --prompt @task.md --max-provider-executions 2 --max-provider-rotations 1--attempts N remains accepted as a legacy alias for --max-provider-executions N.
Retry and rotation ceilings remain explicit, so the alias never grants either
category an independent budget. It cannot be combined with the canonical total
execution flag. Plan and
agent-task status output show the resolved defaults before provider execution;
an exhausted run records which budget stopped further execution.
When these flags are omitted entirely, the budget is derived from the configured
agent_task.rotation policy: the rotation chain funds its own reachability, so
N configured entries resolve to N rotations and N + 1 total executions
(bounded by the policy’s own max_attempts). Without a configured rotation the
derived budget is still one execution and no rotation. Same-provider retries are
never derived — they fund gate and required review-form remediation on the same
provider identity, which a rotation chain says nothing about. Any explicitly
passed flag, including an explicit 0, always wins over the derived value.
Cook states the effective rotation on submission, e.g.
cook: rotation: 2 fallback provider(s), up to 3 provider execution(s) or
cook: rotation: disabled (1 provider execution(s)).
Command Policy
An execution budget bounds how long a provider may run. A command policy bounds what it may run. On a resource-constrained host the two are not interchangeable: an agent that spends its whole budget compiling produces zero edits, and prompt text prohibiting the compile is a request the agent is free to ignore — it has been, twice, escalating (#11481).
# Refuse the heavy commands for this cook, and say why.
homeboy agent-task cook --prompt @task.md
--deny-command 'cargo test'
--deny-command 'cargo build'
--command-policy-reason 'this host routes builds to CI; make your edits and push'
# Allow-list mode: only these patterns may run.
homeboy agent-task cook --prompt @task.md
--allow-command 'cargo fmt' --allow-command 'git *'Set it once for the whole machine so every cook inherits it without a flag:
homeboy config set /agent_task/command_policy '{
"deny": [
{ "pattern": "cargo test", "reason": "this host routes builds to CI" },
{ "pattern": "cargo build" }
],
"reason": "shared host; heavy compiles OOM the box"
}' --jsonPer-cook flags extend the host policy rather than replacing it, so a host-level refusal cannot be dropped by forgetting a flag.
Pattern matching. A pattern is a token sequence matched anywhere in the
command line, so cargo test also refuses
timeout 1200 cargo test -q -p homeboy-agents. Shell operators are token
separators, so make deps && cargo build still matches cargo build. * globs
within a token (cargo *); ** spans whole tokens (cargo ** test matches
cargo --quiet test). In deny-list mode an --allow-command is an explicit
exemption that beats a deny rule; in allow-list mode a deny rule wins.
What a refusal looks like. The agent receives an
homeboy/agent-tool-result/v1 with status denied, an
agent_tool.command_denied diagnostic carrying the matched pattern, the
operator’s reason, and the alternative to take instead. The denial is also
recorded on the homeboy/agent-tool-dispatch-evidence/v1 record, so "the agent
tried to compile and was refused" is visible in run evidence afterwards rather
than being invisible or indistinguishable from a command that silently failed.
Where it is enforced — read this before trusting it. Homeboy structurally
enforces the policy at the boundary it owns: every request reaching
homeboy agent-task tool dispatch (and the in-process control-plane dispatcher)
is evaluated and refused before it runs, including for tools whose policy
execution location is runner. The policy travels to the provider inside
request.policy.tools.commands and via HOMEBOY_AGENT_TOOL_POLICY_JSON, and its
homeboy/agent-command-policy/v1 schema is advertised in the core contract.
A provider runtime that executes shell commands inside its own process never
crosses that boundary. For those runtimes — which is most coding-agent runtimes
today — this is a declaration the runtime is expected to honour, plus a hard
constraint restated in the provider prompt. It is not containment. That gap is
deliberate and stated here rather than papered over; closing it requires the
runtime to route its shell tool through the dispatch command Homeboy already
hands it in HOMEBOY_AGENT_TOOL_DISPATCH_COMMAND.
Supervision policy: how much a provider session may consume
The command policy bounds what an agent may run. It cannot see an agent that stays entirely inside its permitted commands and still grows to nine gigabytes across forty child processes, and neither can an execution budget that counts attempts. Homeboy used to learn the cost of a session only after the process boundary closed.
agent_task.supervision_policy is the resource half. It is host config, and it
is opt-in: with nothing declared, a cook behaves exactly as it did before.
homeboy config set /agent_task/supervision_policy '{
"budgets": [
{ "metric": "rss_mib", "limit": 6144, "action": "warn" },
{ "metric": "rss_mib", "limit": 10240, "action": "stop",
"reason": "15Gi box with four agents on it" },
{ "metric": "no_progress_seconds", "limit": 1800, "action": "stop" }
],
"backends": {
"bench": []
},
"reason": "shared host"
}' --jsonMetrics are things any host can observe about any provider, so supervision stays runtime-neutral:
| Metric | Meaning |
|---|---|
elapsed_seconds | Wall-clock time since provider execution began. |
rss_mib | Resident memory across the provider’s whole process tree. Summing double-counts shared pages, so it is an upper bound. |
child_processes | Processes observed under the cook. |
no_progress_seconds | Time since the destination worktree last changed. This is the stall detector: a cook burning memory while writing files is working, one that has written nothing for twenty minutes is not. |
The ladder is warn → nudge → stop. Several budgets on one metric are
how you say "tell me at 6 GiB, stop it at 10". A breach is announced once and
again only when it climbs a rung, so the heartbeat channel stays worth reading.
A stop terminates the provider’s process tree (SIGTERM, then SIGKILL for
survivors) and leaves the controller alive to record why.
nudge currently records and surfaces an escalation between warn and stop;
it does not yet inject feedback into the running session, because Homeboy
has no channel into a live provider’s reasoning (#7451, #7530).
A backends entry replaces the global budgets for that backend rather than
extending them — unlike the command policy, where extension is the safe default.
An empty list opts a backend out entirely.
Unobserved is never zero. Every metric is optional at the sample, and an
absent metric never breaches a budget. A host with no ps has no memory or
process reading; a cook with no destination worktree has no progress reading.
Reporting those as 0 would make every budget fire immediately on exactly the
hosts that can least afford a spurious kill.
Evidence. Each heartbeat appends to cook_resource_timeline (a rolling
window of samples) and every decision appends to cook_supervision_events,
which also records whether an ordered termination actually succeeded. The two
are separate arrays so an hour of quiet samples cannot evict the decision that
explains a stop. agent-task status projects both under
liveness.supervision.
| Subcommand | Purpose |
|---|---|
cook | Run one workspace task through the patch-artifact handoff workflow. |
fanout cook-batch <issue-url>... --repo <repo> | One-command multi-issue cook setup: derive prompts, create/reuse DMC worktrees, generate PR metadata, and return status/resume commands. |
fanout plan|submit|run-plan | Normalize, inspect, or run a batch of independent cooks, each with its own worktree/branch/PR. |
fanout submit-batch|status|artifacts | Submit and inspect durable batches of independent AgentTaskPlan tasks. |
review <run-id> | Build a durable aggregate review envelope from run state, logs, artifacts, and promotion hints. |
promote <source> | Promote a completed generic patch artifact into a managed worktree. |
adopt <run-or-cook-id> --candidate-ref <sha> | Adopt an immutable commit candidate through the recorded cook gates and finalization policy. |
finalize-pr | Finalize a green run, or recover publication from a durable Cook record. |
accept <run-id> --verdict accepted|rejected --token <token> --evidence-ref <ref> | Record an independently verified acceptance decision for an applied candidate. |
gate-feedback | Convert deterministic gate results into a cook retry or stop decision. |
adopt accepts only immutable commits from the recorded cook source workspace.
Use promote for patch-artifact candidates; it remains the controller-owned
patch-artifact ingestion and gate path.
finalize-pr is the core-owned publication boundary for external runtimes. Its
homeboy/agent-task-pr-finalization/v1 report keeps the legacy top-level
status, pr_action, pr_number, and pr_url fields, and also emits explicit
structured contracts:
publication_intent:homeboy/agent-task-publication-intent/v1, describing the requested review publication target, source refs, artifact refs, changed files, and Homeboy proof.publication_proof:homeboy/agent-task-publication-proof/v1, describing the adapter action/ref that satisfied the intent.finalization_outcome:homeboy/agent-task-pr-finalization-outcome/v1, tying the terminal finalization status to publication status/action, branch refs, PR refs, changed files, and commit/push/publish booleans.
Extensions and lab runners should delegate PR creation/update and proof assembly to this command instead of recreating GitHub publication logic locally.
Independent Acceptance
cook --require-acceptance --acceptance-authority <authority> --acceptance-policy <policy>
adds an independent acceptance boundary. Homeboy creates its durable pending
record only after an applied promotion has recorded the candidate and verified
base, following successful deterministic gates. Provider output and review forms
remain evidence but cannot satisfy this decision.
The runtime must register an AgentTaskAcceptanceVerifier. Its attestation must
match the declared authority and policy and include an actor, timestamp, provider
reference, and opaque verifier/configuration provenance. The CLI token is passed
only to that verifier and is never persisted. accept requires at least one
evidence reference. Replaying an identical decision is idempotent. A changed
candidate or verified base archives the old decision and creates a new pending
record. A rejection preserves its evidence and permits one repair continuation.
Finalization reports and persisted Cook finalization evidence project the same
authoritative acceptance record.
Recovery hydrates the run, worktree, base snapshot, candidate, changed files,
gates, source references, model/tool disclosure, and accepted review form from
the durable Cook recipe and applied promotion. It fails closed when either
record is absent, malformed, belongs to another attempt, or no longer matches
the candidate. --preflight performs the same hydration and complete validation
without commit, push, or pull-request mutation:
homeboy agent-task finalize-pr --recover cook-9750 --preflight
homeboy agent-task finalize-pr --recover cook-9750
--review-override 'compatibility=No public compatibility impact.@reviewed issue #9750'Strict repeated-value shapes are --gate-result NAME=STATUS[:DETAIL],
--test-step COMMAND=>EXPECTED, --changed-public-contract ID=>SUMMARY, and
--review-override TARGET=VALUE@PROVENANCE. Issue references accept #NUMBER,
OWNER/REPO#NUMBER, or an HTTPS github.com/OWNER/REPO/issues/NUMBER URL.
Declaring a changed public contract requires one complete evidence bundle:
homeboy agent-task finalize-pr ...
--changed-public-contract 'cli.finalize-pr=>Adds durable recovery input'
--compatibility-impact 'Additive CLI input; existing manual mode remains supported.'
--external-consumer-impact 'External runtimes may replace reconstructed commands.'
--external-usage-status completed
--external-usage-source 'repository call-site search and CLI integration tests'
--external-usage-limitations 'No consumers outside indexed repositories were inspected.'
--external-usage-url 'https://github.com/Extra-Chill/homeboy/issues/9750'Review Dossier Migration
finalize-pr accepts typed reviewer fields without breaking existing invocations.
Explicit --summary, repeated --what-changed, --test-step COMMAND=>EXPECTED,
and --compatibility take precedence. When omitted, Homeboy derives the dossier
from the legacy fields: --title supplies Summary, --attempt-summary supplies
What changed, each --targeted-check-run supplies a COMMAND=>passes test step,
and --manual-reviewer-check supplies a reviewer command with its expected
observation. Compatibility becomes an explicit legacy disclosure. --closes and
--relates-to are the only issue-relationship inputs; source URLs never imply
closure. Use --review-override TARGET=VALUE@PROVENANCE only for summary,
what_changed, or compatibility; override provenance remains in JSON output.
Normal finalization hydrates --run-id; --manual-finalization is the explicit
migration mode for a manually prepared candidate with no durable run.
Single-Issue Cook
Use agent-task cook for one issue or one workspace task. It is the non-fanout
path: create or reuse one task worktree, dispatch one provider run, promote the
patch artifact into --to-worktree, run deterministic gates, retry red gates up
to --max-attempts, and finalize the green result into a PR unless
--no-finalize is set.
homeboy worktree create homeboy
--branch fix/issue-6453
--from origin/main
--task-url https://github.com/Extra-Chill/homeboy/issues/6453
homeboy agent-task cook
--repo homeboy
--task-url https://github.com/Extra-Chill/homeboy/issues/6453
--workspace homeboy@fix-issue-6453
--to-worktree homeboy@fix-issue-6453
--verify 'cargo test --lib'
--backend sandbox
--selector wordpress.sandbox-agent-task-executor
--prompt @task.txtUse agent-task fanout cook-batch only when there are multiple independent
issues that should each get separate worktree materialization, branch/PR
metadata, and fanout status collation.
Multi-Issue Cook Batch
Use agent-task fanout cook-batch when an operator has a set of GitHub issues
that should each get an isolated branch, worktree, cook run, deterministic gates,
and PR finalization defaults. The command accepts issue URLs directly, derives
the batch-cook plan, queues DMC worktree creation from origin/main, and returns
one structured status envelope with the generated plan plus resume commands.
Every child needs at least one deterministic gate after shared and profile gates resolve; this is checked before any worktree is created.
homeboy agent-task fanout cook-batch
--repo homeboy
--verify 'cargo test --lib'
--backend sandbox
--selector wordpress.sandbox-agent-task-executor
https://github.com/Extra-Chill/homeboy/issues/6453
https://github.com/Extra-Chill/homeboy/issues/6454For mixed stacks, keep shared gates on --verify and assign named additions or
replacements with one JSON declaration. --verification-profiles accepts inline
JSON or @file.json. An assignment selector is an exact issue URL, GitHub issue
key (OWNER/REPO#NUMBER), or generated task selector (issue-NUMBER). Every
assignment must match one child; unmatched selectors return the typed
selector_unmatched validation error before worktree creation.
{
"profiles": {
"php": { "mode": "append", "verify": ["composer audit --format=json"] },
"node": { "mode": "replace", "verify": ["npm audit --omit=dev"] },
"rust": { "verify": ["cargo fmt --check", "cargo test -p homeboy-cli"] }
},
"assignments": [
{ "selector": "Extra-Chill/homeboy#6453", "profile": "php" },
{ "selector": "issue-6454", "profile": "node" },
{ "selector": "https://github.com/Extra-Chill/homeboy/issues/6455", "profile": "rust" }
]
}append is the default mode and adds profile gates after shared gates; replace
uses only that profile’s gates. The generated dry-run plan.cooks and
preflight.deterministic_gates show every child’s effective profile and exact
commands. Those command arrays are persisted with each cook recipe and carried
unchanged to Lab provider handoff.
Add --dry-run to inspect the derived branch/worktree names and batch-cook spec
without creating worktrees. Add --run-plan after reviewing provider readiness
to execute the generated batch immediately. When DMC worktree creation is blocked
by an active lock or another queue issue, the output reports status: blocked,
lists the exact worktree rows and retry commands, and exits non-zero before any
provider process starts.
Completed cook batches use one aggregate outcome across the result payload, the
homeboy/command-result/v3 envelope, and the shell exit code: succeeded exits
zero; partial_failure means at least one child succeeded and at least one failed,
and exits non-zero; failed means every child failed and exits non-zero. Child
results and durable evidence remain available for fanout status, artifacts,
and resume. This changes prior behavior where a failed cook-batch --run-plan
could report a zero exit code; callers must treat nonzero as an unsuccessful batch.
fanout status and fanout resume use the same aggregate state and exit policy.
Active child cooks remain queued or running (including detached in_flight
handoffs) with exit zero; a resume retains active children until their durable
lifecycle reaches a terminal state.
The generated plan uses the existing
homeboy/agent-task-batch-cook-fanout-plan/v1 contract. That means operators can
save the returned plan object and resume with:
homeboy agent-task fanout run-plan --input @batch-cook-plan.jsonPrompt templates can be customized with --prompt-template; placeholders are
{issue_url}, {issue_ref}, {repo}, {branch}, and {worktree}. PR titles,
commit messages, source refs, and AI disclosure defaults are derived per issue
unless the generated plan is edited before fanout run-plan.
Lab Guardrails
Use global --placement lab with long-running or
patch-producing agent-task cook waves that must not execute
provider processes on the controller. If Lab routing cannot select or prepare a
runner, Homeboy fails before local execution instead of falling back.
cook and the batch fanout coordinators (fanout run-plan, fanout cook-batch --run-plan) use split placement: the coordinator always stays on the
controller — it owns target resolution, the durable record, artifact ingestion,
promotion, gates, and finalization — while each provider attempt is dispatched
to the selected Lab runner. --placement lab and --runner <runner-id> select
the runner for those attempts; neither offloads the coordinator itself. When no
Lab runner is ready, these commands report the runner readiness verdict and its
remediation commands rather than claiming Lab placement is unsupported. Use
--placement lab-or-local to authorize controller execution as a fallback.
Concurrent single-cook submissions do not race: controller-generation admission is a FIFO queue, so parallel cooks wait their turn and re-running an identical command after a contended attempt queues rather than collides. Admission diagnostics always name the current holder (PID, verified liveness, and the owning admission request) or the actionable lock state.
Use global --detach-after-handoff with --runner <runner-id> when the Lab job is
expected to outlive the local shell. Homeboy returns after the runner daemon
accepts the job and prints follow/cancel commands instead of waiting for remote
provider completion.
Lab Cook has two observation modes: waiting by default, and detaching on request. Submit and return when an interruptible client should hand the provider attempt to the Lab controller:
homeboy --runner homeboy-lab --detach-after-handoff agent-task cook
--to-worktree homeboy@fix-issue-6453 --verify 'cargo test --lib' --prompt @task.txtWait for the completed Cook when the caller owns a synchronous workflow:
homeboy --runner homeboy-lab agent-task cook
--to-worktree homeboy@fix-issue-6453 --verify 'cargo test --lib' --prompt @task.txtWaiting is the default, so a synchronous caller passes no observation flag at
all; interruptible clients specify --detach-after-handoff. Both modes print
bounded phase heartbeats with the durable run id. Reconnect and retrieve durable
state with:
homeboy agent-task status <run-id>
homeboy agent-task evidence <run-id> --fullProvider activity
A locally executed Cook samples what the provider is actually doing and carries
it on both the heartbeat line and agent-task status, under
liveness.provider_activity:
| Field | Meaning |
|---|---|
files_changed | Uncommitted files in the destination worktree, untracked included. 0 after several minutes is the clearest sign a cook is not producing work. Homeboy’s own .homeboy/ run state is excluded, so this counts provider edits only. |
commits_written | Commits made in that worktree since the provider started. A provider that commits leaves a clean tree, so this is what distinguishes "finished" from "did nothing". |
command / command_elapsed_seconds | The longest-running command the provider is running, and its age — cargo test -p homeboy-agents, six minutes in. Homeboy’s own processes are never reported here: not the cook controller, not the nested controller a --placement local cook re-enters through, not a gate, and not a homeboy … call the agent made as a tool. |
command_unavailable | Present instead of command when the process tree was sampled and no provider process was found. Homeboy states the absence rather than naming its own command, which is what made this signal misleading in #11598. |
elapsed_seconds | Time since provider execution began. |
observed_at | When the sample was taken. A retained sample keeps its own observation time, so a stale reading is visible as stale. |
Each field is absent when it could not be sampled; an unsampled cook reports no activity rather than a zero that would read as a measurement. Lab-offloaded attempts run the provider on the runner and therefore report no local activity.
--placement local is safe only when local execution on this
controller is intentional. For agent-task waves with concurrency greater than 1
or multiple tasks, Homeboy prints HOMEBOY_LOCAL_FANOUT_WARNING before provider
processes start. Compact agent-task status includes execution_location as
local or runner:<id>.
Provider
| Subcommand | Purpose |
|---|---|
providers | List extension-declared executor providers and optional secret/backend readiness. |
contract | Export Homeboy’s machine-readable agent-task core contract metadata. |
auth | Configure and inspect provider authentication secrets. |
agent-task contract --format=json includes agent_runtime_handshake, the
Homeboy-owned extension-facing protocol registry for runtime capability
manifests, readiness checks, resolved execution contracts, materialization plans,
secret env plans, and result/artifact declarations. The registry is generic by
design: extensions provide runtime-specific declarations and results, while
Homeboy owns schema ids, required wire fields, redaction boundaries, and resolved
handoff vocabulary. See
docs/architecture/agent-runtime-contract-handshake.md.
Prompt Store
agent-task prompts stores markdown prompt files under Homeboy’s data directory,
not under the current repo/worktree. Save prompt content with inline text,
@file, or - for stdin, then reference it from cook or controller specs
with prompt:<id> anywhere a prompt string is accepted.
homeboy agent-task prompts save issue-123 --input @prompt.md
homeboy agent-task prompts list
homeboy agent-task cook --repo homeboy --prompt prompt:issue-123Cook accepts one source through --prompt: --prompt @file, --prompt -, inline
prompt text, and prompt:<id> references all use the same resolution behavior.
Use agent-task fanout cook-batch for independent task waves.
Controller
| Subcommand | Purpose |
|---|---|
compile-loop | Compile a declarative loop definition into an agent-task plan. |
loop define|status|resume|stop | Operate durable defined loops with explicit on/off, revolutions, continuation policy, and handoffs. |
controller | Create, inspect, and resume durable multi-agent loop controller state. |
agent-task controller run-from-spec <SPEC> --max-actions <N> is the stable
bounded loop primitive for headless callers. It materializes an optional spec
generator or repo-authored spec, applies --inputs and repeated
--policy-result envelopes, initializes durable controller state, executes up to
N pending controller actions, and returns one persisted status envelope with the
materialized spec, controller initialization report, per-action results, final
controller status, and artifact/status lineage recorded by the normal agent-task
lifecycle.
homeboy agent-task controller run-from-spec @controller.json
--inputs @run-inputs.json
--policy-result @policy-result.json
--max-actions 5
--dispatch-backend fixtureThe command stops when no executable action remains, a terminal controller state
is reached, an action fails, or --max-actions is reached. --max-iterations is
accepted as an alias for --max-actions for loop-oriented callers. Execution
remains provider-neutral: controller actions use their declared generic request
shape, and --dispatch-backend, --dispatch-selector, --dispatch-model, and
--dispatch-provider-config only provide defaults when an action omits them.
Controller spec materialization commands are portable Lab commands:
controller from-spec --resume, controller run-from-spec, and
controller materialize auto-select the configured default Lab runner when
global --runner is omitted. Use --runner <id> to choose a specific runner, or
--placement local only when controller-machine execution is
intentional.
Internal Bridge
agent-task tool is a hidden provider-runtime bridge. It remains parseable for
runtime adapters that dispatch tool requests through Homeboy, but it is omitted
from homeboy agent-task --help and the visible command surface because it is
not an operator-facing workflow.
Controller Events
agent-task controller events is the stable generic primitive for applying an
external event to a durable controller. It accepts the same provider-neutral event
shape as agent-task controller apply-event and returns
homeboy/agent-task-loop-controller-event-result/v1 with the updated controller
record and any actions created by event policy evaluation.
homeboy agent-task controller events "$loop_id"
--event-type task.completed
--event-id task-123-completed
--event-key task#123
--entity-id entity-123
--payload @event.jsonUse events for downstream integrations that need a stable generic controller
event contract. apply-event remains the explicit event-application spelling and
uses the same request and response contract.
Controller Spec Materialization
agent-task controller materialize <SPEC> --inputs <JSON> is the generic seam for
repos that have a loop spec plus per-run inputs, but should not carry repo-local
build-homeboy-controller-run-spec scripts. It returns
homeboy/agent-task-loop-spec-materialization/v1 with a cloned materialized spec;
it does not initialize durable controller state or mutate the source spec file.
The inputs payload may contain inputs and metadata objects. inputs are
merged into each workflow’s inputs, and metadata is merged into top-level spec
metadata. Explicit values override same-named workflow input or metadata keys.
homeboy agent-task controller materialize
@.github/homeboy/controllers/site-loop.json
--inputs @run-inputs.jsonDomains that evaluate their own policies can pass deterministic policy decisions without teaching Homeboy the policy semantics:
homeboy agent-task controller materialize
@.github/homeboy/controllers/site-loop.json
--policy-result @policy-result.jsonThe generic policy result envelope is:
{
"policy_id": "example-policy",
"policy_inputs": { "requested_tier": "foundation" },
"policy_results": { "selected_tier": "foundation", "decision": "hold" },
"provenance": { "source": "policy-evaluator", "sha256": "..." }
}policy_id is required and must be unique per materialization. The other fields
are optional JSON objects. Homeboy projects policy_inputs and policy_results
under the same keys in every workflow’s inputs, keyed by policy_id, and records
the full envelope under top-level metadata.policy_materialization. Homeboy does
not evaluate expressions, choose tiers, generate random seeds, or interpret the
policy result; repo-owned evaluators supply the envelope.
agent-task controller from-spec keeps its existing behavior: it reads a complete
repo-authored controller spec, applies dispatch defaults for the spec checkout,
and initializes or resumes durable controller state. Use materialize first when
the spec needs deterministic run-input expansion before from-spec.
agent-task controller validate-proof <JSON> validates a proof, materialized spec,
or controller record without writing controller state. It returns
homeboy/proof-validation/v1 and exits non-zero when the input is not ready for a
deterministic reviewer handoff.
The validator is provider-neutral. It checks Homeboy proof envelopes for declared
artifact references, reviewer-visible evidence refs, completed gates, and unresolved
proof gaps. For controller materialize output, it reuses the generic loop compiler
diagnostics so unsupported controller-only joins, gates, or graph shapes are reported
instead of being silently accepted. For controller records, completed records must
include a terminal outcome and must not retain pending actions.
homeboy agent-task controller materialize @controller.json --inputs @run.json
--output-file materialized.json
homeboy agent-task controller validate-proof @materialized.json
homeboy agent-task controller validate-proof @proof.jsonLoop Spec Compilation
agent-task compile-loop --definition <SPEC> compiles a declarative loop spec into
an executable homeboy/agent-task-plan/v1 without submitting or running it. It
accepts Homeboy’s native homeboy/agent-task-loop-definition/v1 shape and the
repo-authored workflow-oriented loop spec shape used by WPSG-style controllers.
Repo-style compilation is intentionally deterministic: workflow ids become task
ids, artifact producers are wired to consumers through output_dependencies, and
declared emitted artifacts become artifact_outputs. Controller-only sections
such as transition policies, phases, arbitrary actions, initial events, and
entity fan-out are rejected with explicit diagnostics instead of being ignored.
Repo-style specs may also declare an artifact_graph edge list. The narrow
compiler support is deliberately limited to direct one-producer, one-consumer
artifact flow:
{
"artifact_graph": {
"edges": [
{
"artifact_id": "site_plan",
"from_workflow_id": "plan-site",
"to_workflow_id": "build-site",
"required": true
}
]
}
}compile-loop validates graph edges against declared artifacts and workflow
emits/consumes, then materializes supported edges as output_dependencies
and artifact_outputs. The controller path exposes the same edge records in
workflow client_context.artifact_graph_edges and includes graph producers in
artifact_dependencies.producer_workflow_ids. Fan-out graph edges, joins, gates,
and retry policy remain controller-only follow-ups and produce deterministic
diagnostics instead of partial compilation.
Durable Loops
agent-task loop is reserved for defined, durable multi-agent loops. A loop is
not a one-shot PR cook. It persists controller state, tracks whether it is on or
off, counts revolutions, records continuation policy, and resumes or stops
handoffs explicitly.
homeboy agent-task loop define @.github/homeboy/controllers/site-loop.json
--on
--revolution-limit 5
homeboy agent-task loop status site-loop
homeboy agent-task loop resume site-loop
homeboy agent-task loop stop site-loopUse loop define --off to register or update loop state without executing
handoffs. Use loop define --on --resume when the operator wants to initialize
the controller and immediately run pending handoffs. loop resume refuses to
run off loops and stops once the persisted or supplied revolution limit is
reached.
Cook
agent-task cook is the one-shot end-to-end PR workflow. It dispatches an agent,
promotes the selected patch into the target worktree, runs deterministic gates,
retries red gates within the configured budget, then commits, pushes, and opens or
updates a PR.
Use --draft-pr to retain that verified commit/push/PR workflow while creating a
draft PR. A retry updates an existing PR but preserves its observed draft or ready
state; after acceptance evidence is attached, use homeboy git pr ready <component> --number <number> for the explicit ready-for-review transition.
--goal is one-line Cook framing metadata, recorded on the durable plan and its
single provider task. Pair explicit work with --prompt; --goal never creates
an additional task. A goal without explicit work supplies the one provider task.
homeboy agent-task cook
--repo sample-plugin
--cwd /path/to/worktree
--to-worktree sample-plugin@fix-issue
--provider-config @provider-config.json
--client-context @client-context.json
--verify "npm test"
--prompt @task.txtHomeboy core treats --client-context as an optional opaque JSON object. Client
adapters may include whatever correlation data they need to reconcile their own
notifications or UI state, but Homeboy does not interpret transport-specific
identifiers in core lifecycle state. Provider-specific execution settings belong
in --provider-config; durable lifecycle commands remain headless and can be
inspected later with agent-task status, agent-task logs, or agent-task review.
--backend selects the generic executor backend, --dispatch-provider-id (also
accepted as --selector) selects a specific provider id for that backend, and
--model is only a provider-owned model override. Provider ids come from
homeboy agent-task providers; they are not model names or provider families.
Fanout/Reconcile
agent-task fanout means batch cook: many independent one-shot cooks launched
from one plan. Each cook declares its own target worktree and optional head
branch, runs through the same cook-loop path as a single PR cook, and finalizes
its own pull request when deterministic gates pass.
The public input shape is homeboy/agent-task-batch-cook-fanout-plan/v1:
{
"schema": "homeboy/agent-task-batch-cook-fanout-plan/v1",
"fanout_id": "audit-batch-2026-06-21",
"cooks": [
{
"cook_id": "finding-a",
"prompt": "Fix finding A",
"repo": "homeboy",
"to_worktree": "homeboy@fix-finding-a",
"head": "fix/finding-a",
"verify": ["homeboy review test homeboy"]
}
]
}Generic task fanout is not a public operator contract. Existing
homeboy/agent-task-plan/v1, homeboy/agent-task-fanout-plan/v1, packet arrays,
and tasks/packets objects are rejected by agent-task fanout; internal
schedulers may still use provider-neutral fanout machinery behind a clearer
batch-cook surface.
Cook entries accept dispatch fields such as prompt, tasks, repo, cwd,
workspace, task_url, backend, selector, model, secret_env,
provider_config, and client_context. Review fields include to_worktree,
provider_command, verify, private_verify, max_attempts, base, head,
title, commit_message, protected_branches, ai_tool, and ai_used_for.
Gate Command Contracts
Before Cook provisions a worktree or dispatches a provider, Homeboy validates
each exact simple homeboy ... deterministic gate against the installed Clap
contract, including global flags and required arguments. Compound shell gates
remain external and unvalidated during admission. Gate validation is recorded as
gate_contract_validation in the Cook plan; gate execution remains separate
promotion evidence.
Repository homeboy.json entries under scripts.lint and scripts.test are
capability identities, not top-level CLI verbs. Use their canonical gate forms:
homeboy review lint --path .
homeboy review test --path .Other shell gates remain unvalidated during admission. Declare a --gate-toolchain
when Homeboy must probe an external executable before provider dispatch.
Each cook must declare at least one deterministic verify or private_verify
gate so PR finalization is reviewer-ready.
homeboy agent-task fanout submit
--input @batch-cooks.json
--fanout-id audit-batch-2026-06-21
--backend codex
--selector openai-codexfanout submit prints the exact per-cook commands for runner or operator
execution. fanout run-plan executes each cook through the cook-loop service and
returns a batch summary with each child cook result; successful child cooks open
or update their own PRs.
Headless Fleet-Cooking Review
The authoritative non-chat workflow is the durable agent-task lifecycle. Chat
clients, Discord threads, GitHub Actions, cron, and terminal operators can all
submit the same run id, inspect it later, and promote selected artifacts without
depending on transport-local state.
run_id="homeboy-3357-$(date +%s)"
homeboy agent-task cook
--repo homeboy
--cwd /path/to/homeboy@fix-issue
--to-worktree homeboy@fix-issue-3357-agent-task-non-chat-flow
--task-url https://github.com/Extra-Chill/homeboy/issues/3357
--concurrency 4
--attempts 2
--verify "homeboy review test homeboy"
--run-id "$run_id"
--prompt @task.txt
# One review envelope contains lifecycle state, logs, artifacts, aggregate
# reconciliation, promotion candidates, and next actions.
homeboy agent-task review "$run_id"
--to-worktree homeboy@fix-issue-3357-agent-task-non-chat-flowagent-task review returns homeboy/agent-task-review/v1 with:
record: the durable run record fromstatus.logs: scheduler events from queued or completed lifecycle state.artifacts: artifacts and evidence refs from the completed aggregate.aggregate_review: apply/retry/issue-report/review candidate reconciliation.promotion_candidates: generatedhomeboy agent-task promote <run-id>command arrays for apply candidates, completed with--to-worktreewhen supplied.transport.chat_state_required: false, making Homeboy the source of truth.
This is the terminal/daemon-owned review surface for fleet cooking. Kimaki or any other chat UI should submit, poll, render, and call these commands rather than owning scheduling, state, artifacts, reconciliation, or promotion.
Provider Contracts
agent-task providers returns capability_contract with Homeboy-owned schema ids
for executor provider manifests, requests, and outcomes. Extensions should read
that metadata, or import the matching homeboy::core::agent_tasks::provider
constants, instead of copying schema strings into downstream code. Provider
manifests may omit schema, request_schema, and outcome_schema; Homeboy
defaults them to the current core contract ids.
Provider discovery is scope-sensitive
Controller and runner carry different extensions, runtime defaults, secrets, and
provider readiness, so a provider catalog only means something alongside where
it was read. agent-task providers therefore reports an additive
observed_scope object naming the observed location (controller or lab plus
runner_id), the Homeboy build identity, the extension/runtime sources behind
the catalog, and the observation timestamp.
Unscoped homeboy agent-task providers is always a controller-local read: it is
never relocated to a Lab runner because the controller is under load, since that
would silently answer a different question. Ask for a runner’s catalog
explicitly with homeboy agent-task providers --runner <runner-id> (or
--placement lab). The explicit runner probe is runner-resident — it
materializes no workload workspace — and its dispatch is bounded
(HOMEBOY_LAB_PROVIDER_DISCOVERY_DISPATCH_TIMEOUT_SECS, default 90s) so it
returns a labelled timeout with durable runner/job identity instead of hanging.
Per-runtime revision probes (git rev-parse HEAD) are bounded too. A wedged
runtime checkout yields a partial catalog plus an
agent_runtime_manifest.revision_probe_timeout diagnostic naming the runtime,
rather than an unbounded discovery.
Use homeboy agent-task providers --backend <backend> --validate-readiness to
fail fast when the selected backend is registered but its declared runner
readiness is not usable in the current environment. Lab offload runs this check
on the selected runner before agent-task cook dispatches work internally, so a
missing provider executable/config blocks the run before a multi-cell task wave
is queued.
Repo-Local Gate Tasks
Use execution_kind: repo_local_gate for deterministic, repo-local gate
evaluation that should run inside the task workspace without selecting an AI
runtime. The gate executor runs direct argv or a relative script path without
a shell, rejects paths that escape workspace.root, materializes JSON inputs as
<INPUT_KEY>_PATH files, materializes declared JSON outputs as
<OUTPUT_KEY>_PATH files, and returns homeboy/agent-task-outcome/v1 with
typed artifacts and outputs.<key> payloads.
Minimal task config:
{
"execution_kind": "repo_local_gate",
"script": ".github/scripts/evaluate-publish-gate.mjs",
"inputs": {
"import_validation_result": "{{outputs.import_validation_result}}",
"visual_parity_artifact": "{{outputs.visual_parity_artifact}}"
},
"artifact_outputs": {
"static_site_publish_gate": {
"schema": "example/StaticSitePublishGate/v1",
"type": "StaticSitePublishGate"
}
}
}Gate scripts should read JSON from the generated input path env vars and write
JSON to the generated output path env vars. For example,
IMPORT_VALIDATION_RESULT_PATH, VISUAL_PARITY_ARTIFACT_PATH, and
STATIC_SITE_PUBLISH_GATE_PATH. node_script is accepted only as a legacy alias
for existing plans; new plans should use repo_local_gate so the contract stays
portable beyond Node.
agent-task contract --format=json returns homeboy/agent-task-core-contract/v1,
the machine-readable Homeboy-owned contract export for downstream integrations.
It includes schema ids, provider capability metadata, status/failure enum values,
and default redaction policy metadata without naming or depending on any specific
executor provider.
agent-task status, logs, and artifacts are read-only durable lifecycle
inspection commands. They do not start workloads and are not gated by warm-machine
resource policy; use homeboy runner exec <runner> -- homeboy agent-task status <run-id> when the durable state lives on a Lab runner host. agent-task review
hydrates aggregate evidence and remains resource-managed.
Controller-local status never requires the runner
agent-task status always reports a runner_probe object describing whether the
read reconciled against a runner:
{ "performed": false, "skipped_reason": "controller_local_record", "controller_local": true, "note": "..." }A controller-local run (no runner id, no runner job id, no Lab handoff) is
answered entirely from durable controller state and never contacts a runner, so
inspection stays available while a Lab runner is wedged. Pass
--no-runner-probe to extend that to a runner-backed run: the answer is
returned immediately from controller state and labelled
skipped_reason: "caller_opted_out", meaning runner-side job state may be
stale. Without the flag, a runner-backed running record still reconciles
against its runner, but every remote probe on that path is bounded (see
docs/commands/runner.md).
Deterministic Smoke Gate
Issue #3392 is covered by a no-secret fixture plan at
tests/fixtures/agent_task_smoke_plan.json. It exercises the operator path
without provider credentials, chat state, or long-running external services.
The plan selects "backend": "fixture". That backend is a test double, not an
agent runtime: it has no provider manifest under _extensions/agent-runtimes/
and is compiled only behind the test-support feature, so a released homeboy
binary rejects it with the normal "no selectable provider found for backend"
diagnostic. Run the gate from a source checkout with the feature enabled:
alias homeboy='cargo run --features test-support --quiet --'Run it from a disposable Homeboy worktree:
run_id="agent-task-smoke-$(date +%s)"
target_worktree="homeboy@fix-3392-agent-task-smoke"
homeboy agent-task submit
--plan @tests/fixtures/agent_task_smoke_plan.json
--run-id "$run_id"
homeboy agent-task status "$run_id"
homeboy agent-task logs "$run_id"
homeboy agent-task run "$run_id"
# Or let a generic worker claim the oldest queued durable run:
# homeboy agent-task run-next
homeboy agent-task status "$run_id"
homeboy agent-task artifacts "$run_id"
homeboy agent-task review "$run_id"
--to-worktree "$target_worktree"
homeboy agent-task promote "$run_id"
--to-worktree "$target_worktree"
--dry-runThe gate passes when:
submitreturns a durablerun_idimmediately withstate: "queued".- Pre-run
statusandlogsshow the queued fixture cell. runexits successfully and writes the aggregate lifecycle record.- Post-run
statusshowsstate: "succeeded". artifactslists a patch artifact, an agent result artifact, and a transcript evidence ref.reviewreturns ahomeboy/agent-task-review/v1envelope withtransport.chat_state_required: false, aggregate reconciliation, and promotion candidates.promote <run-id> --dry-runresolves the aggregate from the durable run id and reports the selected non-empty patch plus changed files without requiring the operator to look upaggregate_pathmanually.
When promote <run-id> applies a patch, Homeboy records metadata.latest_promotion
on the durable run. That status event includes the source run id, source task id,
patch artifact id/path, target worktree, discovered target branch/head when
available, changed files, and an operator notification. agent-task status <run-id> surfaces the latest promotion so callers can tell whether promotion
completed or is blocked without spelunking Lab artifact paths.
When promotion runs without --dry-run, each --verify <command> is treated as
a visible deterministic gate in the promoted worktree. Gates run in declaration
order and, by default, a failure records every remaining gate as skipped with
its blocking gate ID without invoking its command; use
--gate-execution-policy continue-all for independent or exhaustive suites.
Promotion reports gate results as deterministic_gates[] using
homeboy/agent-task-gate-report/v3. Failed visible gates set promotion
status: "gate_failed", exit nonzero, and include
failure_evidence.agent_feedback plus stdout/stderr tails so the next cook
agent task can receive exact failure context instead of a generic shell error.
Gate stdout and stderr retain only their final 64 KiB. Each report’s capture
contains per-stream bytes_seen, bytes_retained, bytes_truncated,
byte_limit, truncated, and full-stream sha256, so reviewers can identify
the complete command output without materializing it in the run record. The
report keeps the exact shell argv, termination (completed, timed_out,
no_progress, or cancelled), and failure tail as resolvable evidence.
Supervised gates persist gate_output_tail, gate_elapsed_ms, and the age of
their most recent structured progress update in the active adoption record. A
portable gate may emit this ordinary output line on stdout or stderr to mark
forward progress: HOMEBOY_PROGRESS {"phase":"test","current":"case name"}.
phase is required and current is optional. The configured no-progress
deadline applies from launch until the first marker and between later markers;
ordinary output remains evidence but cannot mask a stalled test case. Markers
add durable phase/current context to that liveness signal. It is separate from
the wall-clock gate timeout. A nonzero command exit remains
termination: "completed", while controller deadline outcomes have distinct
termination values.
Use --private-verify <command> for orchestrator-only completion gates that
should decide completion without exposing hidden evaluator details to the next
agent attempt. Private gate reports still appear in the promotion report for
human/orchestrator evidence, but agent-task gate-feedback applies
--private-gate-reveal <policy> before building the follow-up request. Supported
policies are summary-only (default), redacted, no-detail, and
full-evidence. Visible gate failures continue to provide full deterministic
evidence to the agent.
agent-task gate-feedback converts a promotion report and the original
AgentTaskRequest into a provider-neutral cook feedback decision:
homeboy agent-task gate-feedback
--promotion @promotion.json
--source-task @source-task.json
--source-run-id "$run_id"
--attempt 1
--max-attempts 3
--current-diff @current.diffThe command returns homeboy/agent-task-cook-feedback-report/v1. Red gates with
remaining budget produce status: "retry_requested" and a complete
follow_up_request containing the failed command, exit status, log tails,
changed files, patch artifact ref, current diff context, and source run/task
refs. When a gate producer supplies versioned diagnostic records in its
declared versioned sidecar evidence, Homeboy carries their stable identities, bounded
summaries, opaque locations/actions, and resolvable evidence refs without
parsing raw command output. Follow-up attempts retain and compare compact
failure sets, prioritizing new failures and shared unchanged failures without
expanding default output. Red gates with exhausted budget return
status: "retries_exhausted".
Green promotion returns status: "green_completed" and no follow-up task.
Queued runs that should not execute can be cancelled without chat/session state:
homeboy agent-task cancel "$run_id" --reason "not selected by controller"cancel marks queued runs and stale-running records as cancelled in the
durable lifecycle store. It refuses to claim live provider cancellation for an
active runner process until a provider-owned cancellation channel is available.
Component Contracts
Agent-task plans may declare generic top-level component_contracts. Homeboy
preserves these objects as executor request inputs and does not attach product,
provider, or sandbox-specific semantics to them:
{
"schema": "homeboy/agent-task-plan/v1",
"plan_id": "site-generation-loop",
"component_contracts": [
{
"slug": "domain-component",
"path": "/workspace/domain-component",
"loadAs": "plugin",
"activate": true
}
],
"tasks": []
}When a plan is Lab-offloaded, controller-local component_contracts[].path
values are discovered, synced, and remapped with the same local-to-remote
workspace mapping used for provider configs, runtime component paths, provider
plugin paths, workspace roots, and path-valued settings. Lab offload evidence
records the original and remapped paths in workspace_mapping.workspaces using
the component_contract role.
When the intended checkout already exists on a Lab runner, cook from that
runner-side checkout through runner exec instead of forcing a controller-local
hot run:
homeboy runner exec --cwd /srv/homeboy/checkouts/homeboy homeboy-lab
-- homeboy agent-task cook
--repo homeboy
--cwd /srv/homeboy/checkouts/homeboy
--to-worktree homeboy@remote-cook
--verify "homeboy review test homeboy"
--prompt @task.txtrunner exec marks non-local jobs as runner-hosted, so nested agent-task cook
commands pass the non-interactive resource preflight without a public placement override.
Cook Workspaces
agent-task cook accepts generic Homeboy workspace inputs and does not
resolve product-specific workspace handles itself.
Use --cwd <PATH> when the caller already knows the checkout or worktree path:
homeboy agent-task cook
--repo homeboy
--cwd /path/to/homeboy@fix-issue
--to-worktree homeboy@fix-issue
--verify "homeboy review test homeboy"
--prompt @task.txtUse --workspace <ID_OR_PATH> for a Homeboy-managed task worktree ID or an
existing workspace path:
homeboy worktree create homeboy --branch fix/issue-123
homeboy agent-task cook
--workspace homeboy@fix-issue-123
--to-worktree homeboy@fix-issue-123
--verify "homeboy review test homeboy"
--prompt @task.txtExternal workspace managers should resolve their own handles to local paths and
call cook with --cwd <resolved-path>.
When agent-task cook is Lab-offloaded with a
patch-producing provider, --cwd must point at a clean git checkout with
remote.origin.url configured. Homeboy uses that contract to materialize a real
runner-side git checkout/worktree before provider dispatch so generated files can
come back as patch artifacts. Non-git directories, dirty worktrees, and checkouts
without origin fail on the controller before offload with a supported-path
diagnostic; use a Homeboy worktree or another clean checkout
for write-capable agent tasks.
Provider Runtime Contracts
Agent runtime manifests may declare portable provider contracts that Homeboy uses
before and after execution without learning provider-specific APIs. These fields
belong on each agent_task_executors[] entry:
{
"schema": "homeboy/agent-task-executor-provider/v1",
"id": "example.default",
"backend": "example",
"invocation": {
"argv": ["example-provider"]
},
"request_schema": "homeboy/agent-task-request/v1",
"outcome_schema": "homeboy/agent-task-outcome/v1",
"secret_env_requirements": [
{
"env": ["EXAMPLE_API_TOKEN"],
"secret_env_sources": {
"EXAMPLE_API_TOKEN": { "kind": "env", "name": "EXAMPLE_API_TOKEN" }
}
}
],
"runner_readiness": [
{
"id": "example-auth",
"label": "Example provider auth",
"secret_env": ["EXAMPLE_API_TOKEN"],
"remediation": "Configure EXAMPLE_API_TOKEN with homeboy agent-task auth."
}
],
"workspace_materialization": {
"cwd": "git_checkout",
"requires_git": true,
"write_scope": "workspace",
"artifact_paths": ["artifacts"]
},
"timeout_artifact_discovery": {
"config_path_keys": ["provider_artifact_root"],
"paths": ["/var/tmp/example-provider/latest"],
"artifact_patterns": [
{
"kind": "metrics",
"filename_patterns": ["*-metrics.ndjson"],
"mime": "application/x-ndjson",
"metadata": { "role": "telemetry" }
}
]
}
}Homeboy treats these declarations as generic contracts:
secret_env_requirementsandrunner_readinessdescribe required secret env names and redacted readiness probes without exposing values.workspace_materializationdescribes the checkout shape a provider needs; it does not name any workspace manager or product runtime.timeout_artifact_discoveryextends timeout evidence recovery with declared paths, request metadata/config path keys, and typed filename/extension patterns. Discovered files are normalized intoAgentTaskArtifactentries with generickind,mime, and opaque metadata.- Provider-specific sessions, APIs, artifact namespaces, and backend payloads stay outside Homeboy core and are represented only as artifacts, evidence refs, diagnostics, workflow steps, or opaque metadata.
Durable Loop Controllers
agent-task controller stores domain-agnostic controller state for multi-day
multi-agent loops. The controller record lives outside any single agent-task run
and can reference runs, artifacts, gates, reviews, waits, and human-ready work by
stable ids instead of copying every payload inline.
Create and inspect a controller:
homeboy agent-task controller init transformer-loop
--phase generate
--config-version transformer-v1
homeboy agent-task controller status transformer-loop
homeboy agent-task controller listApply external events, such as CI completion, PR review, human merge, scheduled wakeups, or artifact availability:
homeboy agent-task controller apply-event transformer-loop
--event-type github.pr.merged
--event-key Extra-Chill/homeboy#123
--entity-id pr:123
--payload @event.jsonThe payload may include a policy object using
homeboy/agent-task-loop-controller/v1 action names such as spawn_task,
fan_out, spawn_controller, spawn_subloop, wait_for_controller, join,
retry, request_changes, run_gates, wait_for_event, mark_human_ready,
complete, abandon, and escalate. Actions with deterministic dedupe_key
values are recorded once, so replaying a resumed controller does not duplicate
already-open tasks, child controllers, or PR work.
Nested controller actions are first-class state primitives. spawn_controller
and its spawn_subloop alias record a parent-visible child controller ref with
the parent loop id, spawning action id, optional entity id, request payload, and
dedupe key. Controller records also include optional parent_loop_id,
parent_action_id, and parent_entity_id fields so spawned child records can
carry their parent provenance directly. wait_for_controller puts the parent in
waiting state and records a wait that is satisfied when controller status
observes the child controller in a terminal state (completed, failed,
human_ready, abandoned, or escalated by default). Autonomous execution of
pending spawn/wait actions is still owned by #3905; until that lands, these
primitives define the durable schema, idempotency, and status visibility that
the runner will execute.
Mark work as explicitly ready for a human handoff:
homeboy agent-task controller mark-human-ready transformer-loop
--entity-id pr:123
--reason "gates passed and review approved"Gate bundles are represented as structured checks and results. Existing
--verify command gates are compatible as the simplest command check type;
long-running loops can reuse named bundles across repos and persist normalized
passed, failed, or warn results against a loop, entity, PR, finding, or
run.
retry and request_changes are executable generic controller actions.
retry queues a new durable agent-task run from the target run’s original plan
and records parent/child run lineage on the controller. request_changes records
a normalized feedback artifact with status: "changes_requested" against the
target run so downstream agents and reviewers can consume the same controller
state without product-specific glue.
Fixture Backend
The built-in fixture backend is intentionally narrow. It exists for smoke
proofs and unit tests, not production task execution. A successful fixture cell
writes:
changes.patchas a non-empty unified diff.agent-result.jsonas a structuredhomeboy/agent-task-outcome/v1artifact.transcript.logas transcript evidence.
Useful fixture executor.config fields:
artifact_root: directory where fixture artifacts are written.changed_file: diff path recorded in the generated patch.metadata: optional JSON object copied into the fixture outcome metadata.mode: omit or set tosuccess; set toempty_patchorempty_runtime_bundlefor classification checks.
Output-Driven DAG Phases
agent-task run-plan supports backend-neutral output dependencies with a
plan-level output_dependencies map keyed by downstream task id. A task with
bindings waits until every declared upstream task has a terminal outcome, selects
values from prior homeboy/agent-task-outcome/v1 payloads with JSON Pointer,
renders {{outputs.<name>}} placeholders into the downstream request, then
dispatches the generated task.
Example:
{
"schema": "homeboy/agent-task-plan/v1",
"plan_id": "site-generator-static-fanout",
"tasks": [
{
"schema": "homeboy/agent-task-request/v1",
"task_id": "idea",
"executor": { "backend": "provider" },
"instructions": "Create the GitHub issue for this site idea."
},
{
"schema": "homeboy/agent-task-request/v1",
"task_id": "design",
"executor": {
"backend": "provider",
"config": {
"github_issue": "{{outputs.issue_number}}"
}
},
"instructions": "Build the design for GitHub issue #{{outputs.issue_number}}."
}
],
"output_dependencies": {
"design": {
"bindings": {
"issue_number": {
"task_id": "idea",
"path": "/metadata/github/issue_number",
"required": true
}
}
}
}
}Supported rendering targets:
instructionsinputsexecutor.configworkspace.materializationmetadataexpected_artifacts
If a field is exactly {{outputs.<name>}}, Homeboy preserves the selected JSON
value type. Inline placeholders render as strings. If a required binding is
missing, the downstream task is not sent to the provider; the aggregate records a
skipped scheduler event, increments totals.skipped, and writes a no-op
outcome with diagnostic class output_dependency_missing.
Use depends_on for ordering-only edges that do not bind values:
{
"output_dependencies": {
"static-build": {
"depends_on": ["design"],
"bindings": {
"issue_number": {
"task_id": "idea",
"path": "/metadata/github/issue_number"
}
}
}
}
}Failure Classifications
The deterministic smoke and existing provider path expose these failure classes:
| Case | Diagnostic/classification |
|---|---|
| no-op or empty patch | agent_task.fixture_empty_patch plus promotion rejecting promotion refuses an empty patch artifact |
| provider timeout | agent_task.provider_timeout, failure_classification: "timeout" |
| missing secrets/preflight | agent_task.secret_env_missing, failure_classification: "invalid_input" |
| empty runtime bundle | agent_task.fixture_empty_runtime_bundle |
| stale/non-terminal status | status annotates running records with metadata.stale_running and metadata.stale_running_reason |
Diagnose derives its next actions from the classification
agent-task diagnose computes a root cause, a causal chain, the recorded
failure classification, and the declared artifacts that were not produced. It
projects that diagnosis into the shared _homeboy_actionable envelope (run,
refs, next_actions, artifacts, evidence) instead of returning only
prose. The existing next_commands field is unchanged.
next_action_basis reports how next_actions was produced: diagnosis when a
classification or a concrete missing-artifact set mapped to specific commands,
generic_fallback when nothing in the diagnosis was specific enough to act on.
failure_classification | Derived next actions |
|---|---|
provider | failure evidence for the task, agent-task providers, the runner readiness chain when a runner owns the run, then retry |
transient | retry first (documented as safe to retry), then failure evidence |
timeout | failure evidence, agent-task review (a timeout can still leave a candidate patch), then retry |
stalled | agent-task reconcile --dry-run, runner status/runner doctor --repair for the owning runner, failure evidence, then retry |
rate_limited | failure evidence (retry-after hint), agent-task providers to rotate to, then retry |
policy_denied | failure evidence and the full run record. No retry: an identical request is denied identically |
capability_missing | failure evidence, agent-task providers --full, and agent-task doctor --runner <id> with its --repair form |
invalid_input | failure evidence and agent-task replay-provider-boundary for the rejected input. No retry: the same input fails the same way |
execution_failed | failure evidence for the failing step (gate/verify, harvest, required typed artifacts), agent-task review, agent-task artifacts --full, then retry |
unknown | no specific step is substantiable; the generic fallback set is emitted |
Declared-but-missing artifacts add their own actions regardless of
classification: agent-task artifacts --full, plus a task-scoped
agent-task replay-provider-boundary and failure-evidence command naming the
artifacts that were not produced. Repair-class actions carry
kind: "repair"; runner-scoped actions are emitted only when the run records a
runner id.