Skip to content

Configuration reference

Everything here lives under $KRAFT_HOME/templates/ (default ~/.kraft/templates/), seeded from the packaged defaults on first run and never overwritten after — an upgrade cannot clobber an edited policy. The Settings screens in the UI edit these same files; hand-editing is equally supported (kraft admin doctor reports anything that doesn't parse, and kraft admin reload picks up an on-disk edit without a restart).

Every file in templates/ is validated when it is loaded, against a model that describes both its shape and its references — a chain node may only name a hook the registry defines, and a reject_to may only name a node at or before the one declaring it. A file that does not load is reported with the offending key named, and the rest of the configuration keeps working: one broken chain template does not stop the server.

Chain templates (*.yaml)

Any YAML file in templates/ whose top level is id: + nodes: is a chain template, selectable by that id when creating a work item. See Concepts for the node schema (tasks/steps, gate_after, fix_loop, on_failure, rebase_bounce_to, reject_to, auto_escalate, auto_escalate_stuck, auto_escalate_delay_s) with the shipped default and quick-task templates as worked examples, and Concepts → Composing a template for building a custom one with extends/remove/insert_before/insert_after instead of restating a whole node list.

registry.yaml — hook point bindings

Maps every hook point a chain can name to the adapter that runs it:

defaults:
  agent: { steering: [never-signal-processes-you-didnt-start] }
hooks:
  on.env.prepare:          { kind: builtin,    handler: env_setup }
  on.spec.requested:       { kind: agent,      harness: claude, skill: spec, artifact: spec }
  on.test.run:             { kind: subprocess, command: [uv, run, pytest, -q] }
  on.mr.open:              { kind: forge,      handler: open_mr, backend: auto }
  on.ci.poll:              { kind: forge,      handler: ci_poll, backend: auto, on_failure: [on.ci.repair] }
Key Applies to Means
kind every entry agent, subprocess, builtin, or forge — see Concepts.
harness agent Which agent runtime to launch — claude (the default), codex, gemini, or an operator-added one. See Agent harnesses.
command subprocess The argv to run, e.g. on.test.run's [uv, run, pytest, -q].
skill agent A named skill the agent is launched with (spec, plan, code-review, mr-metadata, ...).
artifact agent What the hook is expected to produce — a spec, a plan, a review brief — surfaced on the gate that follows it.
model / effort agent Per-hook overrides of the agent's model and effort, where the default isn't right for that step.
handler builtin, forge Which Python function or forge operation runs.
backend forge auto resolves per repo from the forge field on that repo's repos.yaml entry — never pin a forge here, or every repo on the install is forced onto one.
on_failure every entry Hook points to run when this task fails, before the task is re-dispatched on its own. The repair travels with the binding, so every chain that runs the task gets it — on.ci.poll ships with on_failure: [on.ci.repair]. A repair is believed only when the task passes on the re-dispatch, never on the repair's own say-so. One layer deep: a repair hook's own on_failure is never dispatched, and a hook may not name itself.
defaults.agent.* top level Applied to every kind: agent hook that doesn't set its own value. Any of harness, profile, model, escalate_model, effort, permission_mode, skill, artifact, plus three that merge as a list instead of binding-wins — steering (from templates/steering/), deny_tools, allowed_tools (default's items first, then the binding's own, deduped).

Rebinding a hook — say, pointing on.test.run at a different command, or on.review.local.run at a different skill — is an edit here, not a chain template change. on.review.security.run ships registered but in no default chain; a plan touching auth/sessions/tokens/secrets adds it to verify dynamically.

policy.yaml — caps, budget, archiving

loops:
  verify_fix_loop:   { attempts: 3, wall_clock_s: 3600 }
  ci_fix_loop:       { attempts: 3, wall_clock_s: 3600 }
  ci_wait:           { attempts: 60, wall_clock_s: 1800 }
  rebase_bounce:     { attempts: 2, wall_clock_s: 3600 }
  rebase_conflict:   { attempts: 3, wall_clock_s: 3600 }
default:             { attempts: 3, wall_clock_s: 3600 }

max_concurrent: 3
auto_escalate_stuck: true
auto_escalate_stuck_cap: 3
auto_escalate_delay_s: 0
forge_cli_timeout_s: 120

findings:
  loop_severities: [critical, important]

budget:
  work_item_usd: 10     # per work item, across every loop in its chain
  daily_usd: 50         # every work item on this instance, since local midnight

rate_limit_retries: 5

archive:
  after_days: 30

triggers:
  - cron: "0 9 * * 1,2,3,4,5"
    repo: /path/to/repo
    chain: default
    title: "Nightly dependency check"
Key Means
loops.<name> attempts and wall_clock_s ceiling for a named fix loop, referenced by a node's fix_loop. default covers anything not named explicitly. rebase_conflict bounds the conflict-resolving agent /resume, /retry, or any node's rebase can dispatch.
max_concurrent How many work items may be active at once, across every repo, however they were started (resume, retry, or auto-intake). Moved here from intake.yaml — that file's copy is now a legacy fallback load_policy reads only when this key is absent.
auto_escalate_stuck Whether a needs_human stop for a reason other than a pending gate (e.g. a stuck fix loop) auto-dispatches an escalation turn. Independent of a node's own auto_escalate (gate review) — different mechanism, different trigger. Defaults on.
auto_escalate_stuck_cap Attempts one needs_human run may be auto-escalated by auto_escalate_stuck before leaving it for a human — the stuck-escalation equivalent of a fix loop's attempts.
auto_escalate_delay_s Seconds to wait after the triggering event before auto_escalate or auto_escalate_stuck fires, so a human already about to look isn't preempted by the agent. 0 (the default) fires immediately.
forge_cli_timeout_s Seconds one forge CLI call (gh, glab, git) may run before it is killed and reported as a forge error. Bounds a single call, not a pipeline wait (that is loops.ci_wait). Default 120. Read at startup.
findings.loop_severities Which review-finding severities burn a fix cycle. Anything below that bar is recorded and shown at the human-review gate instead of silently discarded.
budget.work_item_usd / budget.daily_usd Spend caps in dollars, both off by default (null). A cap refuses to start the next agent task — it cannot interrupt one already running, since cost is only known when a session exits, so overshoot is bounded by one task's cost.
rate_limit_retries How many times Kraft auto-relaunches a work item after a rejected API rate limit before stopping for a human. Counts attempts, not wall-clock time — a rate-limit wait can run for hours.
archive.after_days Completed/abandoned items older than this auto-archive. The board's Done group header states this number — keep them in sync if you change it. Defaults to 30 in the shipped template, but disables auto-archiving entirely (None/absent) if you remove the key rather than edit it.
triggers Optional list of cron-fired chain starts. See Inbound triggers.

repos.yaml — connected repos

repos:
  - path: /home/you/code/my-service
    name: my-service
    default_chain_template: default
    forge: github
    test_command: null
    setup_command: "uv sync"
    env: {}
    env_passthrough: []
    local_files: []
    default_root_merge_policy: bump
    deny_tools: []
    steering: []
    sandbox: null
Field Default Means
path (required) Absolute path to the repo. The only field with no default.
name Display name; set at connect time, not otherwise validated.
managed true Keeps a human-connected repo out of Settings' "Detected" section; auto-connected submodules are written with managed: false.
default_chain_template Which chain template a work item on this repo uses when none is named explicitly.
forge null github or gitlab, which forge adapter backend: auto resolves to for this repo. null at load time — kraft repo connect is what actually resolves it, from the repo's remote.
project null The GitLab project path, when forge: gitlab. Renamed from the legacy gitlab_project key, which a hand-edited file may still carry — read transparently, never rewritten out from under you.
default_model null Overrides the agent model for every hook on this repo, where set.
test_command null (falls back to the registry's on.test.run) The command CI actually runs for this repo — lets verify's local test run and CI's differ deliberately, rather than drift apart by accident.
test_scopes null A monorepo's per-directory test commands: a list of {paths: [...], command: "..."} mappings, each paths non-empty and each command a non-empty string. Not synthesized from test_command — the two stay independently editable.
setup_command (required — no fallback) Run in every new worktree before any node starts. "" means "deliberately nothing"; an absent value stops the repo's next work item rather than guessing.
env {} Literal environment variables every worker for this repo gets, layered onto the worker baseline allowlist.
env_passthrough [] Names of variables to carry over from the daemon's own environment, for what the baseline allowlist doesn't cover.
local_files [] Relative paths (no globs, no directories) to copy into every new worktree — for files git worktree add can't carry, like an untracked .python-version.
default_root_merge_policy bump How a submodule bump at this repo's root is handled by default.
deny_tools [] Tool names withheld from every agent hook on this repo.
steering [] Steering docs (from templates/steering/) attached to every agent hook on this repo, layered under the registry's own defaults.
sandbox null Sandbox policy for this repo's worker processes, if set.

kraft repo connect probes a setup_command from the repo's markers; check it before trusting it, and kraft admin doctor reports any connected repo still missing one.

access.yaml — bind, password, remote access

bind: 127.0.0.1
port: 8765
password_hash: null
session_expiry_days: 7
allowed_hosts: []
Field Default Means
bind 127.0.0.1 The address kraft admin start binds. A non-loopback value is refused unless password_hash is set.
port 8765 Flag (--port) overrides this, which overrides the env var, which overrides this file.
password_hash null Set from Settings → Access while still on 127.0.0.1. Required for any non-loopback bind.
session_expiry_days 7 How long a browser session cookie stays valid after logging in.
allowed_hosts [] Host header allowlist checked on a non-loopback bind — a password alone does not authorize an arbitrary hostname. See Remote access.

intake.yaml — autonomous pickup

Off by default. When enabled, Kraft polls bd ready and files matching beads as paused work items without anyone typing kraft item create — same "lands paused, no gate skipped" guarantee as every other intake path.

enabled: false
interval_s: 300
repos: []              # empty means every enabled repo in repos.yaml
priority_ceiling: 2    # only P2 and below start unattended

max_concurrent used to live here; it's read from policy.yaml now, since auto-intake was never the only door onto a running item. A value still set here is a legacy fallback, used only when policy.yaml doesn't have the key.

An auto-started work item passes no gate automatically — it runs to its first gate and stops for a human exactly as a typed-in one does. Auto-intake removes the typing, not the judgement. Editable here or from Settings → Auto-intake, which applies a change without a restart.