nextv1.4.0
Configuration

Policy

Every field in policy.yaml: caps, budget, archiving, defaults, maxima, and triggers.

policy.yaml sets caps, budget, archiving, and defaults for every work item.

It lives at $KRAFT_HOME/templates/policy.yaml.

kraft admin reload rereads this file with the same validation as startup. If the file does not validate, Kraft keeps the running policy and reload exits 1 naming why. New loop caps apply to loops that start after the reload.

For how caps, maxima and layers combine, see Caps and budgets.

The shipped file sets these keys:

loops: {}
default: { attempts: 3, wall_clock_s: 3600 }

findings:
  loop_severities: [critical, important]

budget:
  work_item_usd: 10
  daily_usd: 50

rate_limit_retries: 5
forge_cli_timeout_s: 120

archive:
  after_days: 30

The shipped file also holds triggers:, defaults: and maxima: as comments. Kraft ships them commented out because an uncommented value is a real ceiling. This example is illustrative. Uncomment and edit it to use it:

# triggers:
#   - cron: "0 9 * * 1,2,3,4,5"
#     repo: /path/to/repo
#     chain: default
#     title: "Weekday dependency check"
#
# defaults:
#   timeout_minutes: 60
#   max_attempts: 3
#   allowed_harnesses: [codex, claude]
#   escalation_harness: claude
#   work_item: { time_cap_minutes: 480, total_time_cap_minutes: 2880, budget_usd: 20 }
#   nodes:     { time_cap_minutes: 180 }
#   steps:     { time_cap_minutes: 120 }
#   tasks:     { time_cap_minutes: 90 }
# maxima:
#   timeout_minutes: 180
#   allowed_harnesses: [codex, claude]
#   work_item: { time_cap_minutes: 1440, token_budget: 2000000, budget_usd: 25 }
#   tasks:     { time_cap_minutes: 240, total_time_cap_minutes: 10080 }

Do not set maxima.allowed_tools unless every harness you use can run under it. A safety field set only in maxima binds every task. gemini refuses to launch under any tool list, and codex and cursor refuse a list that leaves out the web tools their permission hook never sees.

Top-level keys

KeyDefaultEffect
loops.<name>noneSets attempts and wall_clock_s for one named loop; a key that names no live loop is silently unused.
default(required); the shipped file sets {attempts: 3, wall_clock_s: 3600}The attempts and wall_clock_s ceiling for every fix loop not named in loops; a max_attempts on the loop itself wins. Without it policy.yaml does not load, and Kraft refuses new work.
max_concurrent3How many work items may be active at once across all repos, however they were started.
auto_escalate_stucktrueWhether a stuck stop auto-dispatches an escalation turn on a node that declares no escalation of its own.
auto_escalate_stuck_cap3How many times one stuck run is auto-escalated before it is left for a human.
auto_escalate_delay_s0Seconds to wait after the triggering event before an auto-escalation fires, so a human about to look is not preempted.
auto_review_attempts1How many automated-review attempts one pending gate may spend before it is left to a human.
forge_cli_timeout_s120Seconds one forge CLI call (gh, glab, git) may run before Kraft kills it and reports a forge error; read at startup.
forge_poll_s300Seconds between the MR-closed poller's ticks, which ask the forge about every item parked at a merge-request node; minimum 30.
findings.loop_severities[critical, important]Which review-finding severities burn a fix cycle; lower ones are recorded and shown at the human-review gate.
budget.work_item_usdoff (null); shipped file sets 10Dollar cap per work item across all its loops; remove the key to turn it off.
budget.daily_usdoff (null); shipped file sets 50Dollar cap for every work item on this instance since local midnight; remove the key to turn it off.
rate_limit_retries5How many times Kraft relaunches a work item after a rejected API rate limit before stopping for a human.
archive.after_daysoff if the key is absent; shipped file sets 30Completed and abandoned items older than this auto-archive; keep it in sync with the number the board's Done header shows.
triggersnoneCron-fired chain starts; see Inbound triggers.
defaultsunsetStarting points for timeout_minutes, max_attempts, allowed_harnesses, escalation_harness, escalation_grants and the per-level caps; any scope may move them within maxima.
maximaunset (no bound)The administrator ceiling on policy overrides, with the same keys as defaults plus allowed_tools.

A budget cap refuses to start the next agent task and cannot interrupt one that is running. See Caps and budgets.

defaults and maxima

KeyEffect
timeout_minutes, max_attempts, allowed_harnessesOperational starting points that any layer may move in either direction, bounded only by maxima.
escalation_harnessThe harnesses.yaml profile an escalation turn runs on, or item; unset is claude.
escalation_grantsThe grants every escalation turn holds; unset is git-commit, git-rebase and git-push, and [] is none. Set it in defaults only.
work_item, nodes, steps, tasksPer-level values for time_cap_minutes, total_time_cap_minutes, token_budget and budget_usd; a gate is a node. Unset means unbounded.
maxima.allowed_toolsA safety field that starts at its maximum and can only be narrowed.

A narrower level's cap may not exceed a broader one's (tasks <= steps <= nodes <= work_item). A defaults entry may not exceed its level's maximum. Kraft refuses either when it reads the file, naming both. A cap written flat (defaults: time_cap_minutes: 30) is refused too, with a message naming the level form to use.

Policy fields

FieldRule down the layersEffect
allowed_toolsOnly narrows.Lists tool names the agent may use; the permission gate answers from it, and unset allows every tool while [] allows none.
deny_toolsOnly accumulates.Tool names the permission gate denies, on top of allowed_tools.
grantsOnly accumulates; an item override can drop one, never add one.Named git operations a task is guaranteed: git-commit, git-rebase, git-push. See Grants.
sandboxSet once, never changed or removed, its resources included.Runs every process of the whole work item in docker run, within its resource limits; two scopes with different sandboxes are refused. A tool policy that needs Kraft's permission hook (Cursor, Codex) is refused under it. See Sandboxed workers.
token_budgetWithin its level's maxima; a child's never above its parent's.Tokens (input plus output) the scope's launches may spend before the next launch is refused.
budget_usdAs token_budget.The same cap in dollars, under budget.work_item_usd and budget.daily_usd.
allowed_harnessesWithin maxima.Profiles an agent task may select; Kraft refuses others at intake and again at launch.
escalation_harnessAny value, execution node or broader.The profile an escalation turn runs on; a name harnesses.yaml does not define is refused.
max_attempts, timeout_minutesWithin maxima; execution node or broader.Bound the node's fix loop; a step, task or gate refuses them.
time_cap_minutesAs token_budget.Running time of the scope; a launch past it is refused and a running process is killed at it.
total_time_cap_minutesAs time_cap_minutes.Wall-clock time of the scope, including waits and gates, less only a manual pause; for a wait it is the wait's timeout.

allowed_tools and deny_tools hold tool names, never permission rules. Use a bare tool (Bash, Read) or one exact MCP tool (mcp__kraft__report_progress). Kraft refuses a scoped rule (Bash(git *)), a glob (mcp__github__*) or a whole server (mcp__github) wherever the list is read, and the message names the field and the name to write instead.

A harness with no tool-list capability (gemini) refuses to launch under an allowed_tools list rather than run unrestricted. codex and cursor enforce the list through Kraft's permission hook, but their web tools never reach the hook, so they refuse a list that leaves out WebSearch (codex) or WebFetch and WebSearch (cursor). Allowing Bash allows its read-only use without a gate ask. Claude runs a read-only shell command (cat, ls, git status) itself, so it can read any file the worktree holds, gitignored ones included.

wait_timeout_minutes is retired. Kraft refuses it and names total_time_cap_minutes.

Copyright © 2026