nextv2.0.0
Agent harnesses

Harness definition files

The YAML file that describes one harness: the capabilities it declares, argv fragments, constraints, and allowlists.

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.

config/harnesses/mytool.yaml
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

FieldTypeDefaultMeaning
idstringrequiredMust match the file name without .yaml.
kindstringrequiredcli.
commandstring or list of stringsrequiredThe argv prefix, such as [mytool].
capabilities.promptmappingrequiredA cli: argv fragment.
capabilities.contextmappingrequiredchannel: prompt, or channel: system_prompt with a cli: fragment.
capabilities.usagemappingrequiredsource: 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.

CapabilityKindMeaningShipped on
promptRequired, sentThe task's instruction.all seven
contextRequired, sentKraft'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
usageRequired, read backThe session's tokens and cost, from the result file or from the log.all seven
structured_logSentThe flag that makes the CLI write the log a reader: parses.all seven
modelSentThe model.all but amp
effortSentThe reasoning effort. On amp, its mode (-m).claude, codex, antigravity, amp
permission_modeSentThe 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_toolsSent, or enforced by a hook or rulesThe task's tool lists. See Constraints and alternate bindings.all but gemini and antigravity
restrict_toolsSentThe CLI's own flag for which built-in tools exist. See Allowlists.claude
approval_channelSentHands the CLI's permission asks to Kraft's MCP permission tool.claude
resumeSentContinues an earlier session by id.all but gemini
autocompactFilled by KraftThe CLI's own context auto-compaction. Kraft sets it on an escalation turn.claude
rate_limit_signalRead backTells Kraft a launch was rate-limited. See Fallback and escalation.claude, codex, opencode, antigravity
writable_dirsFilled by KraftDirectories outside the worktree that the worker must write. See writable_dirs.codex
mcp_configFilled by KraftThe 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.

KeyTypeDefaultApplies toMeaning
clilist of strings[]any invocable capabilityThe argv fragment. See Argv fragments and placeholders.
viastringunsetresume, deny_tools, allowed_toolsBinds the capability to something other than a flag: command_resume, permission_hook or permission_rules. See Constraints and alternate bindings.
valueslist of strings[]any capability with a valueA 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.
alwaysstring or list of stringsunsetany capabilityThe 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_allowliststringunsetpermission_modeThe mode a launch runs in under allowed_tools. See Allowlists.
channelstringunsetcontextprompt or system_prompt.
sourcestringunsetusageenvelope or result_file.
readerstringunsetusage, rate_limit_signalThe name of one of Kraft's log readers.

Optional top-level keys

KeyTypeDefaultMeaning
command_resumestring or list of strings[]The argv prefix for a resume, when it differs from command (see below).
permission_hookstringunsetThe translator (cursor or codex) for a capability bound via: permission_hook.
tool_namesmapping{}Maps the CLI's tool names to Kraft's (Shell: Bash).
unhooked_toolslist of strings[]Kraft tool names that never reach the hook ([WebFetch, WebSearch]). A launch whose policy would have to deny one is refused.
permission_rulesstringunsetThe renderer (opencode or amp) for a capability bound via: permission_rules.
config_dirmapping of env and filesunsetThe CLI's config-directory variable (env:) and the JSON files Kraft writes into that directory before each launch (files:).
network.requireslist 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_awarebooleantruefalse 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.
credentialslist of mappings[]How Kraft's egress proxy can hold each credential the CLI reads. See credentials.
min_versionstringunsetThe oldest release (N.N.N) of the CLI this file's argv works with. See min_version.
container_permission_modestringunsetThe 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:

HarnessVariableHeaderHost
ClaudeANTHROPIC_API_KEYx-api-keyapi.anthropic.com
ClaudeCLAUDE_CODE_OAUTH_TOKENAuthorization: Bearerapi.anthropic.com
CodexCODEX_API_KEYAuthorization: Bearerapi.openai.com
GeminiGEMINI_API_KEYx-goog-api-keygenerativelanguage.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:

  • env
  • service
  • sentinel: the value the container sees instead (shaped like a real key where the CLI may check).
  • inject: a list of domain, header and optional format (Bearer %s).

The rules:

  • Each domain must be one of network.requires.
  • phase and source are 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 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, Kraft merges it with what a binding supplies. Kraft checks it 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, and Kraft's pre-tool hook enforces the lists. 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 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.

Copyright © 2026