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

SubcommandPurpose
run-planRun an AgentTaskPlan through extension-declared executor providers.
run <run-id>Execute one exact durable run id, bypassing older queued work.
run-nextClaim and execute the oldest eligible queued durable run, reporting skipped quarantined work.
submitPersist 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|removeManage 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.

bash
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-1

submit-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.

sh
homeboy agent-task cook --prompt @task.md --to-worktree project@fix-gate 
  --verify-file ./quality-gate.sh 
  --private-verify-file ./private-gate.sh

For example, quality-gate.sh can contain this program without shell-argument interpolation:

sh
for file in src/*.rs; do
  cargo fmt --check -- "$file"
done

Relative 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.

bash
# 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).

bash
# 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:

bash
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"
}' --json

Per-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.

bash
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"
}' --json

Metrics are things any host can observe about any provider, so supervision stays runtime-neutral:

MetricMeaning
elapsed_secondsWall-clock time since provider execution began.
rss_mibResident memory across the provider’s whole process tree. Summing double-counts shared pages, so it is an upper bound.
child_processesProcesses observed under the cook.
no_progress_secondsTime 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 warnnudgestop. 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.

SubcommandPurpose
cookRun 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-planNormalize, inspect, or run a batch of independent cooks, each with its own worktree/branch/PR.
fanout submit-batch|status|artifactsSubmit 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-prFinalize 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-feedbackConvert 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:

bash
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:

bash
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.

bash
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.txt

Use 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.

bash
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/6454

For 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.

json
{
  "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:

bash
homeboy agent-task fanout run-plan --input @batch-cook-plan.json

Prompt 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:

bash
homeboy --runner homeboy-lab --detach-after-handoff agent-task cook 
  --to-worktree homeboy@fix-issue-6453 --verify 'cargo test --lib' --prompt @task.txt

Wait for the completed Cook when the caller owns a synchronous workflow:

bash
homeboy --runner homeboy-lab agent-task cook 
  --to-worktree homeboy@fix-issue-6453 --verify 'cargo test --lib' --prompt @task.txt

Waiting 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:

bash
homeboy agent-task status <run-id>
homeboy agent-task evidence <run-id> --full

Provider 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:

FieldMeaning
files_changedUncommitted 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_writtenCommits 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_secondsThe 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_unavailablePresent 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_secondsTime since provider execution began.
observed_atWhen 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

SubcommandPurpose
providersList extension-declared executor providers and optional secret/backend readiness.
contractExport Homeboy’s machine-readable agent-task core contract metadata.
authConfigure 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.

bash
homeboy agent-task prompts save issue-123 --input @prompt.md
homeboy agent-task prompts list
homeboy agent-task cook --repo homeboy --prompt prompt:issue-123

Cook 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

SubcommandPurpose
compile-loopCompile a declarative loop definition into an agent-task plan.
loop define|status|resume|stopOperate durable defined loops with explicit on/off, revolutions, continuation policy, and handoffs.
controllerCreate, 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.

bash
homeboy agent-task controller run-from-spec @controller.json 
  --inputs @run-inputs.json 
  --policy-result @policy-result.json 
  --max-actions 5 
  --dispatch-backend fixture

The 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.

bash
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.json

Use 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.

bash
homeboy agent-task controller materialize 
  @.github/homeboy/controllers/site-loop.json 
  --inputs @run-inputs.json

Domains that evaluate their own policies can pass deterministic policy decisions without teaching Homeboy the policy semantics:

bash
homeboy agent-task controller materialize 
  @.github/homeboy/controllers/site-loop.json 
  --policy-result @policy-result.json

The generic policy result envelope is:

json
{
  "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.

bash
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.json

Loop 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:

json
{
  "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.

bash
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-loop

Use 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.

bash
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.txt

Homeboy 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:

json
{
  "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:

sh
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.

bash
homeboy agent-task fanout submit 
  --input @batch-cooks.json 
  --fanout-id audit-batch-2026-06-21 
  --backend codex 
  --selector openai-codex

fanout 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.

bash
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-flow

agent-task review returns homeboy/agent-task-review/v1 with:

  • record: the durable run record from status.
  • 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: generated homeboy agent-task promote <run-id> command arrays for apply candidates, completed with --to-worktree when 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:

json
{
  "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:

json
{ "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:

bash
alias homeboy='cargo run --features test-support --quiet --'

Run it from a disposable Homeboy worktree:

bash
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-run

The gate passes when:

  • submit returns a durable run_id immediately with state: "queued".
  • Pre-run status and logs show the queued fixture cell.
  • run exits successfully and writes the aggregate lifecycle record.
  • Post-run status shows state: "succeeded".
  • artifacts lists a patch artifact, an agent result artifact, and a transcript evidence ref.
  • review returns a homeboy/agent-task-review/v1 envelope with transport.chat_state_required: false, aggregate reconciliation, and promotion candidates.
  • promote <run-id> --dry-run resolves the aggregate from the durable run id and reports the selected non-empty patch plus changed files without requiring the operator to look up aggregate_path manually.

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:

bash
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.diff

The 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:

bash
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:

json
{
  "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:

bash
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.txt

runner 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:

bash
homeboy agent-task cook 
  --repo homeboy 
  --cwd /path/to/homeboy@fix-issue 
  --to-worktree homeboy@fix-issue 
  --verify "homeboy review test homeboy" 
  --prompt @task.txt

Use --workspace <ID_OR_PATH> for a Homeboy-managed task worktree ID or an existing workspace path:

bash
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.txt

External 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:

json
{
  "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_requirements and runner_readiness describe required secret env names and redacted readiness probes without exposing values.
  • workspace_materialization describes the checkout shape a provider needs; it does not name any workspace manager or product runtime.
  • timeout_artifact_discovery extends timeout evidence recovery with declared paths, request metadata/config path keys, and typed filename/extension patterns. Discovered files are normalized into AgentTaskArtifact entries with generic kind, 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:

bash
homeboy agent-task controller init transformer-loop 
  --phase generate 
  --config-version transformer-v1

homeboy agent-task controller status transformer-loop
homeboy agent-task controller list

Apply external events, such as CI completion, PR review, human merge, scheduled wakeups, or artifact availability:

bash
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.json

The 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:

bash
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.patch as a non-empty unified diff.
  • agent-result.json as a structured homeboy/agent-task-outcome/v1 artifact.
  • transcript.log as 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 to success; set to empty_patch or empty_runtime_bundle for 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:

json
{
  "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:

  • instructions
  • inputs
  • executor.config
  • workspace.materialization
  • metadata
  • expected_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:

json
{
  "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:

CaseDiagnostic/classification
no-op or empty patchagent_task.fixture_empty_patch plus promotion rejecting promotion refuses an empty patch artifact
provider timeoutagent_task.provider_timeout, failure_classification: "timeout"
missing secrets/preflightagent_task.secret_env_missing, failure_classification: "invalid_input"
empty runtime bundleagent_task.fixture_empty_runtime_bundle
stale/non-terminal statusstatus 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_classificationDerived next actions
providerfailure evidence for the task, agent-task providers, the runner readiness chain when a runner owns the run, then retry
transientretry first (documented as safe to retry), then failure evidence
timeoutfailure evidence, agent-task review (a timeout can still leave a candidate patch), then retry
stalledagent-task reconcile --dry-run, runner status/runner doctor --repair for the owning runner, failure evidence, then retry
rate_limitedfailure evidence (retry-after hint), agent-task providers to rotate to, then retry
policy_deniedfailure evidence and the full run record. No retry: an identical request is denied identically
capability_missingfailure evidence, agent-task providers --full, and agent-task doctor --runner <id> with its --repair form
invalid_inputfailure evidence and agent-task replay-provider-boundary for the rejected input. No retry: the same input fails the same way
execution_failedfailure evidence for the failing step (gate/verify, harvest, required typed artifacts), agent-task review, agent-task artifacts --full, then retry
unknownno 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.