Skip to content

Documentation for v0.36.0

External executors: adapters and their limits

This page says what each adapter can technically do, how to check it on this machine, and what you still do with its output. Seven runners ship with the kit; delegation-run drives codex and claude-code itself. Each runner is the sole enforcement authority for its own transport, credentials, sandbox, tools and refusals; nothing in the configuration, the router or a skill can widen what it allows. Which model runs is your choice (user configuration); where keys are read from is in keys and environment.

Adapters

adapterrunnertransportkey or login
claude-zaidelegation-glmClaude Code CLI against the Z.AI endpointZAI_API_KEY
kimi-code-clidelegation-kimiKimi Code CLI in a macOS sandbox-exec profileCLI login
muse-code-clidelegation-museMuse Code CLI in a macOS sandbox-exec profile, on the subscriptionCLI login (muse login)
grok-build-clidelegation-grokGrok Build CLI in its attested custom sandboxCLI login
token-plan-openaidelegation-qwenone chat-completions requestQWEN_TOKEN_PLAN_API_KEY, or credential_env
deepseek-apidelegation-deepseekone chat-completions requestDEEPSEEK_API_KEY, or credential_env
openai-compatibledelegation-openai-compatibleone chat-completions request (HTTPS or loopback)credential_env, or none
agydelegation-geminiAntigravity CLI, prompt-only, empty isolated workspaceCLI login
codexdelegation-runcodex exec --sandbox read-only in an empty temp dirCLI login
claude-codedelegation-runclaude -p, no tools or MCP, in an empty temp dirCLI login
adapterroles → permission classefforts (default)
claude-zaiclerk, scout, reviewer, policy-annotation: read-only · builder: worktree-editmax
kimi-code-cliclerk, scout, policy-annotation: read-only · builder, frontend-builder: worktree-editmax
muse-code-cliclerk, scout, reviewer, policy-annotation: read-only · builder, frontend-builder: worktree-editminimal … max (high)
grok-build-clipolicy-annotation: read-only · builder, frontend-builder: worktree-edithigh
token-plan-openaitext roles: text-patchminimal … max (xhigh)
deepseek-apitext roles: text-patchminimal … max (max)
openai-compatibletext roles: text-patchminimal … max, sent only when declared (none)
agyscout, builder, frontend-builder, reviewer, judgement: read-onlymedium, high
codextext roles plus routine-review, material-review, security: read-onlynone … max (high)
claude-codetext roles plus routine-review, material-review, security: read-onlylow … max (high)

delegation-route and decide --lane report each adapter’s capabilities, the same for every role: read-only and text-patch (HTTP adapters, agy), sandboxed-worktree (kimi-code-cli, muse-code-cli, grok-build-cli), native-tool-policy (claude-zai), native-profile (codex, claude-code).

Text roles are builder, clerk, scout, reviewer, senior, judgement and policy-annotation. Parameters: every adapter takes timeout; deepseek-api, token-plan-openai and openai-compatible add effort and max_tokens, muse-code-cli adds effort and contributor_mode, the rest add effort. openai-compatible sends the effort as reasoning_effort only when the profile declares one, so an endpoint that does not know the field never sees it. The Claude Code and Codex host profiles the kit installs keep their own sandbox; only the delegation-run entry is read-only and prompt-only.

<runner> check --json is the authority on this machine. It reports model, adapter, roles, efforts, selected_backend and backends.{available, reason}; native runners add their sandbox, process allowlist, OAuth mode and isolation. Run it just before a dispatch: a runner that reports selected_backend: "none" refuses with exit 69.

Permission classes

Exactly three, mutually exclusive, fixed per adapter and role:

classworktree writesproduct
read-onlynoanalysis text
text-patchno (no filesystem)patch text; the lead applies and verifies it
worktree-editscoped to the delegated worktreeedits in place

A builder role on a text adapter grants no filesystem: its output is a unified diff, and the trust boundary is where the lead applies it.

Muse Code: subscription or API key

Meta’s Muse models ship two ways, and you choose by profile:

profilesadapterbilledneeds
muse-code-wonder-full, muse-code-wonder, muse-code-sparkmuse-code-cliyour Muse Code subscriptionmuse login (browser) and an active plan; Wonder Full needs the Power or Maximum plan
muse-wonder-1.0, muse-spark-1.3openai-compatibleper token, on the Meta Model APIMETA_API_KEY
  • The CLI lane never sees META_API_KEY. Muse Code prefers an API key over the browser sign-in, so a key in its environment would move the run to per-token billing without saying so. A sign-in made with an API key (muse auth set) is refused (check says why); use the HTTP profiles for that. The receipt records billing: subscription and whether the response carried the plan’s allowance.
  • Contributor mode decides whether Meta may train on what a run sends: the profile’s contributor_mode, on or off, off when unset. Every run passes it explicitly, so the setting saved in Muse Code does not apply, and the receipt records it.
  • Isolation. The runner calls the release binary the muse launcher selected (no launcher, no self-update during a run) from an isolated home that holds minimal settings, a copy of auth.json and a link to the login keychain, where the token lives; the kit never reads the token. No user or project rules, skills, hooks, MCP servers or memory load. The shell is off in every role, as are web tools, reminders, questions to the user and approvals; read-only roles also have writes off. Under sandbox-exec only the Muse binary may execute, the ambient home is unreadable except the keychain, and writes go only to runner scratch, the keychain, and the worktree for builders.
  • One dispatch at a time. A kit lock serializes Muse runs, because each refreshes the same sign-in; a refreshed auth.json goes back to the ambient file unless that file changed meanwhile (exit 75, oauth_conflict).
  • Muse Wonder Full exists only on the subscription, so it has no API price: the catalog ranks it at Muse Wonder’s ($4/$20), never as free.
  • Known limits. Muse Code’s own telemetry follows its defaults: the kit sets no option it does not document. Its bundled skills still load.

Checking a text patch

delegation-patch-verify enforces the versioned policy in config/external-patch-policy.json. It never applies a patch: it parses it, checks it against the policy, fixes the strip level from the header shape, runs git apply --check at that one level, and prints a receipt with no patch content.

# 1. verify — read-only; nothing is applied and nothing is written
delegation-patch-verify check --patch "$patch" --workdir "$repo" --json >receipt.json

# 2. inspect the receipt: verdict, paths, operations, and the strip level
jq '{verdict, file_count, operations, strip, files: [.files[].path], violations}' receipt.json

# 3. the LEAD applies it, with the strip level the receipt recorded
git -C "$repo" apply -p"$(jq -r '.strip.chosen' receipt.json)" -- "$patch"

# 4. the LEAD runs the tests — provider output is never proof that a change works
( cd "$repo" && ./run-tests.sh )

The default policy is fail-closed:

  • Format: unified diff text only.
  • Paths: no absolute, traversing or non-representable paths.
  • Denied paths: .git, hooks, CI, .env*, shell startup files, keys, credential stores, this kit’s provider auth directories.
  • Modes: new files only as 100644; no symlinks, submodules or mode changes.
  • Operations: add and modify; delete only with --allow-delete; rename and copy refused, with no override.
  • Limits: bounded file count, bytes, lines, hunks and added lines.

The strip level is fixed by the header shape, never guessed. The verifier digests the patch, worktree, index and git control directory before and after its run, and reports attestation-failed if anything changed. Its own exit codes: 0 accepted, 64 invalid input, 65 rejected by policy, 66 patch or worktree unreadable, 69 missing dependency, 70 attestation failed. delegation-patch-verify policy --json prints the installed policy; tests/external-patch-verify.sh is its regression suite.

Model identity

Every run records the requested model apart from what the provider reported:

fieldmeaning
requested_modelwhat the profile asked for, never read from a response
provider_reported_modelthe model the provider named in its response, or null
model_identity_sourceprovider-reported when a response named the requested model or a configured alias; requested-only when the provider named nothing

A response naming a different model fails with exit 70 (provider_identity_mismatch) unless the name is in the profile’s aliases. A provider report certifies nothing. requested-only is not a defect: some transports never name a model, and Claude Code’s JSON result never does, so a claude-code profile is always requested-only. Grok’s usage_model (grok-4.6-build) is the billing participant, not the content identity.

delegation-router serve discloses a mismatch on an HTTP pool profile (mismatch: true, provider_reported_model, X-Router-Model-Mismatch: 1) instead of failing; a native pool profile goes through delegation-run and fails as 502 provider-identity-mismatch.

Exit codes

coderetryablemeaning
64noinvalid input: unknown argument, missing or colliding path, refused flag combination
65nodelegation-run: invalid configuration or paths, unsupported role, missing CLI, malformed native response
69nounavailable: runtime, login, entitlement, key or quota; also a credential_env variable that is unset (missing_credentials)
70nodispatch, sandbox, identity, extraction or publication failure
75yestemporary: rate limit, overload, 5xx, timeout, credential lock
78nothe adapter does not support the requested role or effort
130nothe caller interrupted (Kimi, Muse Code); the runner stopped the child and released its locks

A failure writes a sanitized <output>.error.json (phase, reason, sizes, exit codes) and publishes no output or metrics. Raw provider responses are kept only under an explicit --debug-dir, mode 700.

Timeouts

No runner has a time limit of its own. Without the profile’s timeout, a hung run waits until you interrupt it, and Kimi, Muse Code and Grok hold their OAuth lock the whole time. With it (whole seconds, 1–86400), delegation-run exports it as DELEGATION_TIMEOUT and the run is bounded:

adaptermechanismon expiry
kimi-code-cli, muse-code-cli, grok-build-cli, claude-zaitimeout -k 10 (or gtimeout) around the CLI; without either on PATH the runner refuses with exit 69exit 75, reason timeout (operational_timeout on Kimi and Muse Code)
deepseek-api, token-plan-openai, openai-compatiblecurl --max-timeexit 75, reason deadline_exceeded
codex, claude-codesubprocess deadline in delegation-runexit 75, reason timeout
agyagy --print-timeout (24h when unset)the CLI’s failure: exit 70, reason process_exit

A run that hits the deadline publishes nothing. macOS ships neither timeout nor gtimeout; Homebrew’s coreutils provides gtimeout. Two guards are not task limits: a 20 s connect timeout on the HTTP adapters, and the short probes check runs against a CLI. On Grok, check --json reports timeout_seconds (the configured value or null).