nextv2.0.0
Run the server

Upgrade your configuration

Pick up new shipped chains, library tasks and policy after a Kraft upgrade, without losing your own edits.

See what a Kraft upgrade shipped that your config lacks, and take what you want without losing your edits. Kraft seeds $KRAFT_HOME/config/ from its shipped defaults on first run and never overwrites it after, so an upgrade cannot clobber your edits. It also never brings you new shipped nodes, tasks or policy keys.

Before you start

  • You have upgraded Kraft (kraft admin update). See Upgrade Kraft.
  • Your config directory is ~/.kraft/config/, or $KRAFT_HOME/config/ if you set KRAFT_HOME. The directory KRAFT_CONFIG_DIR names wins over both, and so does a 1.x KRAFT_TEMPLATES_DIR when KRAFT_CONFIG_DIR is unset. The commands below follow the same order.
  • git is installed.

Keep your config in git

Do this once. If you have not yet, do it now, before you merge anything, so that git diff shows your merge. A git history lets you see your own edits apart from Kraft's, and undo a bad merge.

Skip this block if the config directory is already in git: with config set as in its first line, git -C "$config" status answers instead of failing.

Each command runs only if the one before it worked, and every git command names the directory with -C. So a config directory that does not exist stops the block before it touches anything, and nothing runs in the folder you are in. The block adds access.yaml and notify.yaml to a .gitignore that is already there, and leaves its other lines alone:

config="${KRAFT_CONFIG_DIR:-${KRAFT_TEMPLATES_DIR:-${KRAFT_HOME:-$HOME/.kraft}/config}}"
git -C "$config" init &&
  for f in access.yaml notify.yaml; do
    grep -qxs "$f" "$config/.gitignore" || printf '\n%s\n' "$f" >> "$config/.gitignore"
  done &&
  git -C "$config" add -A &&
  git -C "$config" commit -m "My Kraft config"

Leave access.yaml and notify.yaml out. One holds your password hash, the other a webhook URL that usually carries a token. Keep the repository on this machine: repos.yaml can hold secrets in a repo's env:.

See what is new

Run the checks that know what shipped:

kraft admin doctor

Two rows say whether your config has fallen behind. When it has not, they read:

ok    capabilities     seeded at 2.0.0; up to date
ok    chain_templates  2 template(s), no missing nodes
  • The capabilities row lists what this Kraft can do that your config, seeded earlier, does not use yet, each with the edit that adopts it. Your config records the version it was seeded from in config/.seeded-version.
  • The chain_templates row names nodes a shipped chain has that your copy lacks. It compares node ids only, so a clean row does not prove the chains match.

With the Claude Code plugin, /kraft:check runs these checks and templates lint, and explains each row. It also flags a plugin:skill in your chains that your agent does not have. It only reports until you ask it to change something.

Diff against the shipped defaults

The shipped defaults sit inside the installed Kraft. The next command finds them through the Python that runs your kraft command. It works for a uv, script or Homebrew install:

KRAFT_PY="$(head -1 "$(command -v kraft)" | cut -c3-)"
SHIPPED="$("$KRAFT_PY" -c 'import kraft.paths; print(kraft.paths.BUNDLED / "config")')"
echo "$SHIPPED"

echo prints a path that ends in kraft/_bundled/config. If it prints an empty line, the line above it failed and the shell printed that error: the first line of your kraft command is not an interpreter path. Use the repo's copy instead: the config/ directory at the tag for your version, such as v2.0.0, not main. For a tag before 2.0, the directory is templates/.

Download it, and set SHIPPED to that folder with SHIPPED=/path/to/config. The next two blocks stop with a message while it is unset.

Then compare it with yours:

config="${KRAFT_CONFIG_DIR:-${KRAFT_TEMPLATES_DIR:-${KRAFT_HOME:-$HOME/.kraft}/config}}"
diff -ru "${SHIPPED:?set SHIPPED first, as the block above does}" "$config"

A line starting - is in the shipped file and not in yours. That is either something new or something you removed on purpose. Your git log tells the two apart. Only in lines name whole files, such as a chain you added.

A task that ships and that your library.yaml lacks, such as mr_rebase, shows like the hunk below. A real diff also prints a file header, an @@ line, the six comment lines above mr_rebase in the shipped file (as - lines), and a few lines of context on each side. They are left out here:

-  mr_rebase:
-    kind: builtin
-    ref: kraft.mr_rebase
-
   open_draft_mr:
     kind: forge
     target: mr.open_draft

Merge what you want

Copy changes into your files by hand. Do not copy the shipped files over yours: your library holds your own model, effort and harness choices, and a copy destroys them.

  • A new library task or node. Copy its entry into your library.yaml. For the hunk above, add the three mr_rebase lines, without the -, under tasks:.
  • A new node in a shipped chain. Add it to your copy of that chain in chains/. If your chain is a copy of a shipped one with your own nodes added, as in Write your own chain, add the new node there too.
  • A new policy.yaml key. Add it only if you want to change its default. A key your file leaves out takes the default. See policy.yaml.

Then check and load the result:

kraft admin templates lint
kraft admin reload

lint checks every chain against your library and exits 1 on any error. reload makes the running server read the files, with no restart. It exits 1 and names any chain that does not resolve. A pass prints these lines (the counts are your own):

3 chain(s), no errors
reloaded 3 template(s) and policy.yaml and intake.yaml

To check the shipped set on its own, without a server, with SHIPPED set as above:

kraft admin templates lint --dir "${SHIPPED:?set SHIPPED first, as the block above does}"

Check it worked

config="${KRAFT_CONFIG_DIR:-${KRAFT_TEMPLATES_DIR:-${KRAFT_HOME:-$HOME/.kraft}/config}}"
kraft admin templates show default --resolved
kraft admin doctor
git -C "$config" diff
  1. show --resolved prints the chain as it will run, with each library entry expanded. Check that the new node or task is there.
  2. In doctor, the capabilities row no longer lists a capability you adopted. The row clears a capability once your files contain the key its edit adds: the agent profiles (profiles:) in harnesses.yaml, or the never-signal-processes-you-didnt-startsteering profile in library.yaml. It does not check that a chain uses it, so mr_rebase clears as soon as library.yaml defines it.
  3. git diff shows exactly what the merge changed. Commit it.

Next steps

Copyright © 2026