v1.4.0next
Agent harnesses

Harness files

The YAML file that describes one harness: required fields, argv fragments, constraints, and allowlists.

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

FieldMeaning
idMust match the file name's stem.
kindcli.
commandThe argv prefix, such as [mytool].
capabilities.promptRequired. A cli: argv fragment.
capabilities.contextRequired. channel: prompt, or channel: system_prompt with a cli: fragment.
capabilities.usageRequired. 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

KeyMeaning
command_resumeThe argv prefix for a resume, when it differs from command (see below).
permission_hookThe translator (cursor or codex) for a capability bound via: permission_hook.
tool_namesMaps the CLI's tool names to Kraft's (Shell: Bash).
unhooked_toolsKraft tool names that never reach the hook ([WebFetch, WebSearch]). A launch whose policy would have to deny one is refused.
permission_rulesThe renderer (opencode or amp) for a capability bound via: permission_rules.
config_dirThe CLI's config-directory variable (env:) and the JSON files Kraft writes into that directory before each launch (files:).
network.requiresThe 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_awarefalse 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.
credentialsHow 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_versionThe 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_modeThe 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 of re.fullmatch patterns. 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 against values: too.
  • resume can bind via: command_resume instead of cli:, when a resume needs its own command prefix. codex.yaml uses command_resume: [codex, exec, resume, "{value}"].
  • deny_tools and allowed_tools can bind via: permission_hook: no flag, the tool lists enforced by Kraft's pre-tool hook. This needs a top-level permission_hook: naming the translator (cursor or codex), and tool_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-level permission_rules: names the renderer (opencode or amp) 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 a permission_mode with an under_allowlist: mode that asks the approval channel about everything else (Claude's manual). A harness missing either refuses to launch under an allowlist, and so does a launch whose own permission_mode differs 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.

Copyright © 2026