Harness files
A harness is a YAML file that maps Kraft's capabilities onto one CLI's flags. A file in $KRAFT_HOME/templates/harnesses/ with
the same name as a shipped harness overrides it, and a file with a new name
adds a harness. That directory is not seeded on first run, so it exists only
once you put a file in it. To write one, see
Add or override a harness.
Required fields
| Field | Meaning |
|---|---|
id | Must match the file name's stem. |
kind | cli. |
command | The argv prefix, such as [mytool]. |
capabilities.prompt | Required. A cli: argv fragment. |
capabilities.context | Required. channel: prompt, or channel: system_prompt with a cli: fragment. |
capabilities.usage | Required. source: result_file, or source: envelope with the name of a reader:. |
Every other capability (model, effort, permission_mode, deny_tools,
allowed_tools, restrict_tools, approval_channel, resume, autocompact,
structured_log, rate_limit_signal, writable_dirs, mcp_config) is optional. A binding
that names a capability the file omits is rejected at load, pointing at the
file.
Optional top-level keys
| Key | Meaning |
|---|---|
command_resume | The argv prefix for a resume, when it differs from command (see below). |
permission_hook | The translator (cursor or codex) for a capability bound via: permission_hook. |
tool_names | Maps the CLI's tool names to Kraft's (Shell: Bash). |
unhooked_tools | Kraft tool names that never reach the hook ([WebFetch, WebSearch]). A launch whose policy would have to deny one is refused. |
permission_rules | The renderer (opencode or amp) for a capability bound via: permission_rules. |
config_dir | The CLI's config-directory variable (env:) and the JSON files Kraft writes into that directory before each launch (files:). |
network.requires | The hosts the CLI itself reaches ([api.anthropic.com]), added to the runtime allow list of a sandbox with a network policy. Checked as network-policy hosts when the file loads. |
proxy_aware | false for a CLI that ignores HTTP(S)_PROXY. Under a network policy its only route is Kraft's proxy, so such a harness is refused before launch. Default true. |
credentials | How Kraft's egress proxy can hold each credential the CLI reads: env, service, the sentinel the container sees instead (shaped like a real key where the CLI may check), and inject, a list of domain, header and optional format (Bearer %s). Each domain must be one of network.requires. phase and source are a repository's to set, and refused here. Used only for a variable a repository lists under sandbox.credentials. The shipped claude (ANTHROPIC_API_KEY, Claude Code 2.1.284), codex (CODEX_API_KEY, codex-cli 0.158.0) and gemini (GEMINI_API_KEY, Gemini CLI 0.61.0) declarations are verified by e2e(<cli>) in tests/worker/test_credentials_docker.py (KRAFT_E2E=1); claude's CLAUDE_CODE_OAUTH_TOKEN is not. |
min_version | The oldest release (N.N.N) of the CLI this file's argv works with. Each launch runs <command> --version (in the image, when sandboxed) and refuses an older one by name; an answer with no version in it lets the launch go ahead. The shipped opencode needs 2.0.0: npm's opencode-ai 1.x refuses --standalone and --log-level error, so install 2.x from opencode.ai or Homebrew's anomalyco/tap/opencode-v2. |
container_permission_mode | The permission_mode a launch gets under Kraft's docker sandbox when nothing chose a mode other than the capability's always:, for a CLI whose own sandbox cannot start inside a container (codex: danger-full-access). Checked against permission_mode's values: when the file loads. |
The reader: in a usage or rate_limit_signal capability names one of
Kraft's log readers: claude-stream-json, codex-json, cursor-stream-json,
opencode-json or amp-stream-json.
Argv fragments and placeholders
A capability needs a cli: argv fragment unless it is usage or
rate_limit_signal (read back out, not invoked) or context with
channel: prompt (folded into the prompt text). {value} and {csv} are the
whole placeholder language: {value} is one substituted string and {csv} a
comma-joined list.
Constraints and alternate bindings
values:is a list ofre.fullmatchpatterns. A value outside them fails at load, not at launch.always:is the value Kraft uses when a binding supplies none. For a list capability, it is merged with what a binding supplies. It is checked againstvalues:too.resumecan bindvia: command_resumeinstead ofcli:, when a resume needs its own command prefix.codex.yamlusescommand_resume: [codex, exec, resume, "{value}"].deny_toolsandallowed_toolscan bindvia: permission_hook: no flag, the tool lists enforced by Kraft's pre-tool hook. This needs a top-levelpermission_hook:naming the translator (cursororcodex), andtool_names:mapping the CLI's tool names to Kraft's (Shell: Bash).- The same two capabilities can bind
via: permission_rules, for a CLI with no hook. A top-levelpermission_rules:names the renderer (opencodeoramp) that writes the tool lists into the CLI's own permission config at launch. A policy name that no CLI tool maps to refuses the launch.
Allowlists
A launch whose policy sets allowed_tools (an empty list included) must not
let a tool outside the list run. A harness meets that in one of three ways:
- It declares
restrict_tools(the CLI's own flag for which built-in tools exist, such as Claude's--tools) and apermission_modewith anunder_allowlist:mode that asks the approval channel about everything else (Claude'smanual). A harness missing either refuses to launch under an allowlist, and so does a launch whose ownpermission_modediffers from that mode. - It has a
permission_hook, which denies every tool the list doesn't name, fail-closed. - It has
permission_rules, which deny every tool the list doesn't name.
Checking harness files
kraft admin doctor runs one PATH check per harness profile that the live
library's chains select, not every declared one. It adds a failure row for any
harness file that failed to load.