Harness definition files
A harness file maps Kraft's capabilities onto one CLI's flags. This page lists the fields of that file and the sixteen capabilities it can declare.
id: mytool
kind: cli
command: [mytool]
capabilities:
prompt: { cli: ["-p", "{value}"] }
context: { channel: prompt }
usage: { source: result_file }
model: { cli: ["--model", "{value}"] }
effort: { cli: ["--effort", "{value}"], values: [low, high] }
A file in $KRAFT_HOME/config/harnesses/ with the same name as a shipped harness overrides it. 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, follow Add or override a harness.
Required fields
| Field | Type | Default | Meaning |
|---|---|---|---|
id | string | required | Must match the file name without .yaml. |
kind | string | required | cli. |
command | string or list of strings | required | The argv prefix, such as [mytool]. |
capabilities.prompt | mapping | required | A cli: argv fragment. |
capabilities.context | mapping | required | channel: prompt, or channel: system_prompt with a cli: fragment. |
capabilities.usage | mapping | required | source: result_file, or source: envelope with the name of a reader:. |
Capabilities
Every capability other than prompt, context and usage is optional. A task or a harnesses.yaml harness profile that sets an option whose capability the file omits is rejected when the chain or harnesses.yaml loads (kraft admin templates lint shows it). A repository's models: entry that does is refused at launch, naming this file.
A task's YAML never names a CLI flag. It asks for a capability, and the harness file maps it to whatever the CLI calls it.
| Capability | Kind | Meaning | Shipped on |
|---|---|---|---|
prompt | Required, sent | The task's instruction. | all seven |
context | Required, sent | Kraft's context for the session. channel: system_prompt is out of band; channel: prompt folds it into the prompt text. | all seven; system_prompt on claude and codex |
usage | Required, read back | The session's tokens and cost, from the result file or from the log. | all seven |
structured_log | Sent | The flag that makes the CLI write the log a reader: parses. | all seven |
model | Sent | The model. | all but amp |
effort | Sent | The reasoning effort. On amp, its mode (-m). | claude, codex, antigravity, amp |
permission_mode | Sent | The CLI's approval or sandbox mode: --permission-mode on Claude, -c sandbox_mode= on Codex, --approval-mode on Gemini, --auto-review or --force on Cursor. | all but amp |
deny_tools, allowed_tools | Sent, or enforced by a hook or rules | The task's tool lists. See Constraints and alternate bindings. | all but gemini and antigravity |
restrict_tools | Sent | The CLI's own flag for which built-in tools exist. See Allowlists. | claude |
approval_channel | Sent | Hands the CLI's permission asks to Kraft's MCP permission tool. | claude |
resume | Sent | Continues an earlier session by id. | all but gemini |
autocompact | Filled by Kraft | The CLI's own context auto-compaction. Kraft sets it on an escalation turn. | claude |
rate_limit_signal | Read back | Tells Kraft a launch was rate-limited. See Fallback and escalation. | claude, codex, opencode, antigravity |
writable_dirs | Filled by Kraft | Directories outside the worktree that the worker must write. See writable_dirs. | codex |
mcp_config | Filled by Kraft | The CLI's MCP servers for a sandboxed launch with network:. See mcp_config. | claude |
Two capabilities need no argv: usage and rate_limit_signal describe what Kraft reads back, so they are legal with no cli: binding.
writable_dirs
Kraft fills writable_dirs for a launch, and a task never sets it. It lists the directories outside the worktree that a worker must write:
- the directory holding the launch's
$KRAFT_RESULT_PATH($KRAFT_HOME/run/results); - the worktree's git common dir, where every commit writes. For a linked worktree that is the main checkout's
.git. Kraft asks git for it (git rev-parse --git-common-dir) and leaves it out when git has none.
{value} is one JSON array of absolute paths, such as ["/home/me/.kraft/run/results","/home/me/src/app/.git"]. A harness that does not declare writable_dirs gets nothing extra.
Codex binds it to -c sandbox_workspace_write.writable_roots={value}. Its workspace-write sandbox writes only the workspace and /tmp, so without the grant a Codex worker on a default install (~/.kraft) could write neither its result file nor a commit. Kraft grants both directories outright, because Codex's automatic reviewer is not relied on for either one.
mcp_config
Kraft fills mcp_config only for a sandboxed launch with network:. It is the CLI's MCP servers as one JSON object:
{"mcpServers": {"kraft": {"type": "http", "url": "http://kraft/mcp"}}}
That is Kraft's own server for the session, reached through the sandbox's route out, because the MCP server registered on your machine is out of the container's reach. Claude binds it to --strict-mcp-config --mcp-config {value}, so that server is its only one and its approval_channel tool is answered there.
Capability keys
These keys sit under a capability.
| Key | Type | Default | Applies to | Meaning |
|---|---|---|---|---|
cli | list of strings | [] | any invocable capability | The argv fragment. See Argv fragments and placeholders. |
via | string | unset | resume, deny_tools, allowed_tools | Binds the capability to something other than a flag: command_resume, permission_hook or permission_rules. See Constraints and alternate bindings. |
values | list of strings | [] | any capability with a value | A closed vocabulary the CLI itself would reject, as re.fullmatch patterns. Codex's effort is minimal, low, medium, high, xhigh; Claude's is low, medium, high, xhigh, max. |
always | string or list of strings | unset | any capability | The value Kraft uses when nothing else is supplied. Gemini's permission_mode is yolo, since a headless worker has nobody to answer an approval prompt. |
under_allowlist | string | unset | permission_mode | The mode a launch runs in under allowed_tools. See Allowlists. |
channel | string | unset | context | prompt or system_prompt. |
source | string | unset | usage | envelope or result_file. |
reader | string | unset | usage, rate_limit_signal | The name of one of Kraft's log readers. |
Optional top-level keys
| Key | Type | Default | Meaning |
|---|---|---|---|
command_resume | string or list of strings | [] | The argv prefix for a resume, when it differs from command (see below). |
permission_hook | string | unset | The translator (cursor or codex) for a capability bound via: permission_hook. |
tool_names | mapping | {} | Maps the CLI's tool names to Kraft's (Shell: Bash). |
unhooked_tools | list of strings | [] | Kraft tool names that never reach the hook ([WebFetch, WebSearch]). A launch whose policy would have to deny one is refused. |
permission_rules | string | unset | The renderer (opencode or amp) for a capability bound via: permission_rules. |
config_dir | mapping of env and files | unset | The CLI's config-directory variable (env:) and the JSON files Kraft writes into that directory before each launch (files:). |
network.requires | list of strings | [] | The hosts the CLI itself reaches ([api.anthropic.com]). Kraft adds them to the runtime allow list of a sandbox with a network policy, but not to a Kit, which lists its own. Checked as network-policy hosts when the file loads. |
proxy_aware | boolean | true | false for a CLI that ignores HTTP(S)_PROXY. Under a network policy its only route is Kraft's proxy, so Kraft refuses such a harness before launch. |
credentials | list of mappings | [] | How Kraft's egress proxy can hold each credential the CLI reads. See credentials. |
min_version | string | unset | The oldest release (N.N.N) of the CLI this file's argv works with. See min_version. |
container_permission_mode | string | unset | The permission_mode a launch gets under Kraft's docker sandbox, unless it chose a mode other than the capability's always:. For a CLI whose own sandbox cannot start in a container (codex: danger-full-access). Checked against permission_mode's values: at load. |
credentials
The shipped claude, codex and gemini files declare credentials:
| Harness | Variable | Header | Host |
|---|---|---|---|
| Claude | ANTHROPIC_API_KEY | x-api-key | api.anthropic.com |
| Claude | CLAUDE_CODE_OAUTH_TOKEN | Authorization: Bearer | api.anthropic.com |
| Codex | CODEX_API_KEY | Authorization: Bearer | api.openai.com |
| Gemini | GEMINI_API_KEY | x-goog-api-key | generativelanguage.googleapis.com |
Codex's variable is CODEX_API_KEY because codex exec does not send an OPENAI_API_KEY. The ANTHROPIC_API_KEY, CODEX_API_KEY and GEMINI_API_KEY pairings work in a Docker and a Podman sandbox. Kraft has not confirmed the CLAUDE_CODE_OAUTH_TOKEN pairing.
Each credential declares:
envservicesentinel: the value the container sees instead (shaped like a real key where the CLI may check).inject: a list ofdomain,headerand optionalformat(Bearer %s).
The rules:
- Each
domainmust be one ofnetwork.requires. phaseandsourceare a repository's to set, and refused here.- Kraft uses a declaration only for a variable a repository lists under
sandbox.credentials.
min_version
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.
Log readers
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, amp-stream-json or antigravity-stream-json. A reader reads the log, so the file must also declare structured_log.
Argv fragments and placeholders
A capability needs a cli: argv fragment unless it is usage or rate_limit_signal, or it is context with channel: prompt, which Kraft folds into the prompt text. {value} and {csv} are the whole placeholder language: {value} is one substituted string and {csv} a comma-joined list.
Kraft builds the command line in the order the capabilities are declared. A capability whose fragment is a bare {value} (a positional argument) must be declared last.
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, Kraft merges it with what a binding supplies. Kraft checks it 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, and Kraft's pre-tool hook enforces the lists. 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 does not name, fail-closed. - It has
permission_rules, which deny every tool the list does not 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.
Related
- Agent harnesses: the seven shipped harnesses side by side.
- Add or override a harness: write a file step by step.
harnesses.yaml: the harness profiles inharnesses.yamlthat select a harness.