Cross-harness delegation
Hand a task to a different agent harness and treat its output as a claim to be verified, not a result to be trusted.
When this is the right tool
Delegate when a different model or provider is the point:
- A second opinion where the value is in the disagreement
- Offloading bulk work to preserve Claude quota
- Running several harnesses in parallel across separate quota pools
- A capability only another harness has (e.g. enforced output schemas)
Otherwise use an in-process subagent. A subagent shares Claude's tool layer, so its reports are backed by the same machinery that performed the work. A delegate shares only a filesystem and an exit code. Delegation buys model diversity and pays for it in verification burden — do not pay that price for work a subagent handles fine.
Operating principles
- Exit code 0 means the process finished, not that the work is done. This is the crux of the whole skill. Never report success from an exit status.
- The diff is evidence; the summary is a claim. Read
diff.patch. The delegate's own account of what it did is unverified testimony. - Never relay a delegate's claim as fact. Check cited
path:linereferences against the real file before passing a finding to the user. - Read-only means enforced by the tool, not requested in the prompt.
crushandaiderhave no read-only mode and are hard-excluded from review.opencodesits in between — its restriction is agent config, not a sandbox, sodelegate.shallows it but prints a warning. Prefercodex,gemini, orpifor review whenever the choice is free. - Confirm before spending someone else's quota. Build and fan-out runs are billed to another provider that this session cannot see or account for.
- Never merge without showing the diff. Integration is the user's decision.
Modes
review |
build |
|
|---|---|---|
| Runs in | current working directory | throwaway git worktree |
| Writes | blocked by the harness's own sandbox | allowed, bounded by the worktree |
| Sees uncommitted work | yes | no — starts from a ref |
| Produces | a text/JSON verdict | a branch plus a diff |
| Dispatch | automatic | confirm first |
Review runs in place because a worktree is created at a commit and therefore cannot contain the uncommitted work a review is usually about. Containment for review comes from the sandbox flag instead.
Workflow
1. Classify
Review or build? If genuinely ambiguous, ask — the modes have different blast radii and different billing profiles.
2. Route
"$CLAUDE_PLUGIN_ROOT"/skills/cross-harness-delegation/scripts/delegate.sh list
The skill runs inside the user's target repo, not inside this plugin, so
always invoke the script by its absolute plugin path. A relative
./scripts/delegate.sh will not resolve. If $CLAUDE_PLUGIN_ROOT is unset,
locate the script under the installed plugin directory and use that path. The
snippets below abbreviate it as $DELEGATE:
DELEGATE="$CLAUDE_PLUGIN_ROOT/skills/cross-harness-delegation/scripts/delegate.sh"
Filter to installed × mode-eligible, then choose on capability
(see references/harnesses.md):
- Need an enforced output shape →
codex(--output-schemais the only mechanism that guarantees rather than requests a format) - Need the strongest read-only guarantee →
codexorgemini - Need fine-grained tool control →
pi(-tallowlist /-xtdenylist) - Need model diversity above all → pick a different provider family than the one that produced the code under review
State the choice and the reason. If nothing is eligible, say so — never silently substitute a harness the user did not expect.
3. Gate
- A single review dispatches without asking: read-only, cheap, reversible.
- Any build, or any fan-out, prints the plan and waits:
Plan:
codex → build → worktree .delegates/worktrees/<id> → branch delegate/<id>
gemini → build → worktree .delegates/worktrees/<id2> → branch delegate/<id2>
2 delegates, write mode, billed to OpenAI + Google
Proceed?
4. Brief
Write prompt.md following references/prompt-contract.md. All six sections.
The delegate has no conversational context — under-briefing is the most
common cause of a useless result.
5. Dispatch
# review — in place, read-only
"$DELEGATE" run --harness codex --mode review --prompt-file prompt.md
# build — worktree isolated
"$DELEGATE" run --harness codex --mode build \
--prompt-file prompt.md --task-id refactor-auth
For parallel fan-out, run each in the background with a distinct --task-id.
Each gets its own worktree and branch, so there is no shared state and no
coordination protocol is needed.
6. Verify — mandatory
"$DELEGATE" collect <task-id>
collect is mandatory before clean, and it is what makes the work durable.
The prompt contract tells delegates not to commit, so a build worktree normally
holds uncommitted output while the branch still points at base. collect stages
and commits that onto delegate/<task-id>. Skipping it and running clean would
delete the only copy — so clean refuses when the worktree is dirty.
Then, without exception:
- Read
diff.patch. Notresult.txt. The summary is a claim. - Run the tests yourself in the worktree. Never accept "tests pass".
- Scope check. Flag any file in the diff outside the expected set.
- Commit-discipline check. Verify the actual commit state matches what the brief demanded.
- Citation check (review mode). Open each cited
path:line. Findings that cannot be verified against a real line are discarded, not relayed.
A delegate reporting success with an empty diff has done nothing. This is a real
observed failure, not a hypothetical — see the aider self-update case in
references/harnesses.md.
7. Integrate or discard
Present the verified diff and let the user decide. Then:
"$DELEGATE" collect <task-id> # commits the work onto the branch
"$DELEGATE" clean <task-id> # removes worktree, KEEPS the branch
git branch -D delegate/<task-id> # separate, explicit discard
Cleanup never destroys delegate output — provided collect ran first, which
clean enforces by refusing on a dirty worktree. clean <id> --force overrides
that and does discard uncommitted work; use it only to throw a run away.
Discarding the branch is always a distinct, explicit step.
Artifacts
Everything lands in .delegates/ at the repo root (add to .gitignore):
.delegates/
├── worktrees/<task-id>/ # build mode; branch delegate/<task-id>
└── runs/<task-id>/
├── prompt.md # exact brief sent (auditable)
├── meta.json # harness, mode, model, base_sha, exit_code
├── stdout.log / stderr.log
├── result.txt # the delegate's claim
└── diff.patch # the evidence
Exit codes
0— harness ran to completion. Not a success signal.1— harness exited non-zero (auth failure, crash). Checkstderr.log.2— precondition failed (unknown or uninstalled harness, ineligible mode, duplicate task-id, not a git repo).
Failure modes
- Hangs with no output → almost always stdin.
delegate.shredirects</dev/nullfor this reason; never remove it. - Installed but not authenticated → exit 1 with the reason in
stderr.log. Distinguish from task failure before retrying. - Exit 0, empty diff → the harness did nothing. Read
references/harnesses.md. - Diff full of unrelated files → the harness wrote its own config, history, or gitignore entries. Tighten the flags rather than accepting the noise.
- Delegate committed when told not to → check every time; defaults vary.
References
references/harnesses.md— verified per-harness flags, enforcement tiers, and the gotchas found by running themreferences/prompt-contract.md— how to brief an agent with no context