foreman — thin router
What this skill touches (trust boundary)
Farming work out to another agent necessarily means handing something a credential and your code, so here is exactly what that involves:
- Sends repository content to the worker backends you configured in
~/.handoff/config.yaml(or, for the caged worker, to the endpoint inforeman-cworker.sh). The skill's own scripts send nothing else — no telemetry, no phone-home. What a worker agent does with network access is up to that agent: this skill does not restrict it (see the caged-worker note below). - Writes
.foreman/inside your repo (task and batch state; it must be gitignored), worktrees under~/foreman-worktrees/(override withFOREMAN_WORKTREE_ROOT), and a backend health table under~/.local/state/foreman/(override withFOREMAN_STATE_DIR). - Reads
~/.handoff/— the dispatcher's own task directory — to locate what a worker delivered. - Probes backends before dispatch (
backend-health.sh, all probes listed in its header):claude auth statusandcursor-agent statusread local login state only; thedeepseekprobe sends$DEEPSEEK_API_KEYto DeepSeek's own balance endpoint; thecodexprobe makes one minimal request. Probes you define yourself withFOREMAN_PROBE_<NAME>run as you wrote them. foreman-cworker.shstarts a Docker container, passes$DEEPSEEK_API_KEYinto it as that worker's own API credential, and runs the worker with--dangerously-skip-permissions— inside the container only. That is the entire point of the caged worker: it receives one key and one mounted worktree, and nothing else — not your home directory, not your other repos, not your primary agent login. The key is read from your environment at call time, is never stored by this skill, is visible only to processes inside the cage, and is gone when that container exits. The container's network is not restricted, so anything running inside it could reach other hosts with that key: use a dedicated key with a low spending limit. The script refuses to start unless the mount is a git-registered task worktree underFOREMAN_WORKTREE_ROOTand the image is pinned by an immutable id (FOREMAN_WORKER_IMAGE), and it runs the container with all capabilities dropped,no-new-privileges, and a read-only root filesystem.- Runs the verification commands you wrote in each work order. This skill never invents commands; what you put in a work order is what runs.
Entry decision (settle this first, then read the matching chapter)
- Step 0 — is the foreman on a strong enough model? The foreman is the judge: it writes work orders, runs the intake checks, decides on rework, and merges. If the current session is on a small or fast tier, tell the user to switch to their strongest general model before continuing. Don't push genuinely hard reasoning out to workers either — that kind of task shouldn't be farmed out at all.
- Default (there is work to farm out): read
dispatch.mdin this directory and run the dispatch chapter. Once every delivery has passed intake, automatically readverify.mdand enter the acceptance chapter — do not wait to be asked again. - User says "just dispatch, skip acceptance for now": run
dispatch.mdonly and stop at intake. /foreman verify, or acceptance only (including code somebody else handed you): readverify.mddirectly.
Global invariants (both chapters, ahead of any playbook)
- Probe before dispatch.
backend-health.sh pickchooses a usable backend; if the whole pool is down, stop dispatching and report — never force it. Infrastructure failures (auth/quota/config/net) do not count as rework rounds: they say nothing about the quality of the work, and mixing them into the rework count misreads "the channel is down" as "the builder can't do the job". - State goes to disk. Always under
.foreman/— one<task_id>.jsonper task, onebatch.jsonper batch. First action on every entry into foreman: scan.foreman/for overdue and unsettled tasks and reconcile them before starting anything new. - One shared escalation budget. The same
error_fingerprintshowing up a second time → escalate immediately. Dispatch reworks plus acceptance repairs reaching 4 rounds combined → circuit-break and report to the user. - The measuring instruments are protected. If a builder's diff touches tests, assertions, or CI config → reject the delivery. No exceptions. This is the only mechanism in the entire scheme that prevents a consistent false green: a builder that can edit the ruler can always make the work measure up.
- Judge on data, not exit codes. Acceptance tooling routinely exits 0 no matter what it found. Read only the manifest/findings that this run newly generated.
- The main session only judges and merges. Executable work gets farmed out to a separate process, picked from the worker pool (
dispatch.md§3). Prefer backends paid for by a subscription you already have, chosen by which one still has quota, not by which IDE the foreman happens to run in; when one is exhausted or its login fails, move down the pool, and end on a pay-as-you-go backend that doesn't run out. Farming out buys two things: an isolated context, and a builder that can't reach the ruler.
