nextv2.0.0
Run the server

Upgrade from 1.4

What 2.0 changes from 1.4 and what to do about each change, and the steps to update from 1.4 without losing the database.

You will be on 2.0, coming from 1.4, and you will know which of its changes need something from you. An upgrade between 2.x releases needs only Upgrade Kraft. A release candidate, a 0.x home or an install from before the kraft-sdlc rename follows Upgrade from 0.x or a release candidate.

Before you start

  • You know how Kraft was installed: with uv or install.sh, with Homebrew, or with pipx. Step 4 differs for each.
  • On uv or pipx, you know the Python Kraft runs on and whether you use vector search. "$(uv tool dir)/kraft-sdlc/bin/python" -V prints the Python. The embeddings line of kraft admin health says unavailable (the 'vector' extra is not installed when you don't have the extra.
  • sqlite3 or python3 is on your PATH, for the backup in step 1.
  • You can pick a moment when no agent runs, or you will pause the running items first (step 3).

Update from 1.4

Do not update with 1.4's own kraft admin update. That command runs in the release you have installed, and 1.4's mishandles a uv install and a pipx install, as step 4 says. Use your package manager:

  1. Back up the database, as step 1 of Upgrade says. 1.4 cannot open the database 2.0 migrates. Keep the file name that step prints: a rollback to 1.4 needs it, and a later upgrade's backup never replaces it.
  2. Do what has to come first. Each item names its change below:
    • On a network bind, add each name you use to allowed_hosts (Network access).
    • Behind a tunnel or reverse proxy on a loopback bind, make it send a loopback Host (Network access).
    • On a repo entry with test_command: "", set a real test command or enabled: false (Items and repos).
    • On a repo whose sandbox: has no network:, add one, or set unrestricted_network: true (Items and repos).
    • On a schedule with day of week 7, change or remove it unless you want it to fire on Sundays (Config directory and files).
  3. Pause the running items, or wait until none run. See A restart ends running agents.
  4. Install 2.0 with the tool you installed Kraft with:
    • uv or install.sh: run uv tool install --force --python 3.X "kraft-sdlc[vector]==<version>", with the Python you installed Kraft with for 3.X and the release for <version>: the number without the v, as the newest tag on the releases page shows it (tag v2.0.0 is 2.0.0). For example, uv tool install --force --python 3.13 "kraft-sdlc[vector]==2.0.0". Leave out [vector] if you don't use vector search.
      1.4's update installs the new release without the [vector] extra and on uv's default Python, and leaves uv tool upgrade pointing at a file it has deleted.
    • Homebrew: run brew update, then brew info kraft. It shows the version the tap offers, and brew info alone does not refresh the tap.
      • If it still shows 1.4, the tap has no 2.0 yet, and brew upgrade kraft changes nothing: wait, or run brew uninstall kraft and install 2.0 with uv as above.
      • Otherwise run brew upgrade kraft. 1.4's kraft admin update runs the same command, and loses nothing: the formula has no [vector] extra.
    • pipx: run pipx install --force --python python3.X "kraft-sdlc[vector]==<version>", with <version> as above. 1.4's kraft admin update doesn't update a pipx install: it puts a second Kraft beside it with uv.
  5. Restart the server with kraft admin restart. That restart is 2.0's: it lists the running items and asks before it ends their agents.

Check it worked

  • kraft --version and kraft admin health print the release you installed, as step 5 of Upgrade shows.
  • kraft admin doctor has a keys, moved keys, tests and config row. Each names what 2.0 found in your files: What changed says what to do about it.

If you used 1.4's update anyway

On a uv install, an update run by 1.4 drops the vector extra and moves Kraft to uv's default Python. Restart right after it, and do not act on the board before:

  1. Run kraft admin restart as soon as the update finishes.
  2. Reinstall [vector] with the uv command above, if you use vector search. Without it, vector search stops working: see Enable vector search.
  3. If you reinstalled the extra, run kraft admin restart once more, to be safe. The reinstall replaced files under the server you just restarted, as any update does, and a restarted server reads them fresh. The embeddings line of kraft admin health then says available.

Before the restart you may see:

  • Items under the wrong heading, and a footer that names the new release. 1.4's server keeps running and serves 2.0's interface, which misreads its board.
  • A banner over the board saying so, from 2.0.0. A 1.5.0rc14 or earlier interface had none. kraft admin doctor's restart row warns that the server runs a release older than the one installed.
  • Internal Server Error instead of the board, if Kraft ran on a Python other than uv's default. 1.4's update moved Kraft to another Python and deleted the files the running server serves, though its API still answers.

If you ran kraft admin update --restart (or restarted a service), 1.4 restarted the server without asking, which ended the agents of running items. Pause them first next time.

What changed

2.0 changes some 1.4 behavior that the stability policy covers, such as --json fields and exit codes, which is why it is a major release. Apart from the changes listed here, your chains, library and other config files carry over as they are.

Each change below says what is true now, what 1.4 did, and what you do about it. "No action" means nothing to change.

Network access

  • The Host check on a network bind. On a network bind, 2.0 checks the address every browser uses to reach the board. 1.4 skipped that check over plain http, so your board may have worked at a name that isn't in allowed_hosts. Such a browser now gets a page saying the name isn't allowed.
    • Do: before you upgrade, add each exact name you use (no wildcards) on Settings › Access, or to allowed_hosts in access.yaml.
    • kraft admin doctor warns when the bind is off loopback and the list is empty.
  • A tunnel or reverse proxy in front of a loopback-bound Kraft must send a loopback Host. On a server bound to 127.0.0.1 (the default), 2.0 answers 403 to every request whose Host header isn't localhost, 127.0.0.1 or [::1], webhooks to POST /api/triggers and GET /api/health included. 1.4 checked only requests from a browser, and allowed_hosts doesn't apply on a loopback bind. See Who may call for the rule.
    • Do, for the board: bind Kraft beyond loopback, with a password, and list the name in allowed_hosts. That is the one way the board works through cloudflared, ngrok, Caddy or nginx. What a proxy you run must pass is in Behind a reverse proxy you run.
    • Do, for webhooks and scripts alone: have the proxy rewrite Host to localhost. Read Put a proxy in front of Kraft first, because it also changes who has to sign in.

Items and repos

  • A sandbox with no network: is refused. Every agent launch into it stops the item as a configuration error, whatever the harness. 1.4 already refused a Claude task there, and a Codex or Cursor task with a tool policy. It ran every other one, with the worker's network unrestricted and no channel to Kraft, so gates were not enforced. A repo that runs only Claude needs nothing done.
    Do: add a network: policy to the sandbox, or set unrestricted_network: true on it to run as 1.4 did, knowing that. The two keys together are refused when repos.yaml loads. kraft admin doctor fails the egress row of each such repo.
  • Cancel keeps the branch. Cancel in the web interface keeps the item's branch and worktree. 1.4 deleted them. Do: to delete them, run kraft item abandon ID --yes.
  • A merge request closed without merging stops its item. In 1.4 the item kept waiting. Do: reopen the merge request from the item's page, retry, or cancel.
  • A repo with open items can't be disconnected. kraft repo disconnect (and DELETE /api/repos) is refused with a 409 until each of the repo's items is done, cancelled or abandoned, paused and never-started ones included. In 1.4 it went through and left those items stranded. Do: finish, cancel or abandon the items first.
  • test_command: "" means the repo has no tests. Its items pass verify without running a test. In 1.4 such an entry stopped every item at verify. Do: before you upgrade, set a real test command on any such entry, or enabled: false to keep it from running. After the upgrade, the tests row of kraft admin doctor names each such entry.
  • kraft repo connect on a repo that is already connected changes it. In 1.4 it did nothing. Now it probes the repo again and saves what its entry left undecided: a test command where it has none and no test scopes, and a setup command where none is declared.
    • That can enable a repo saved disabled for want of a test command. In a terminal it asks first; with -y, --json or no terminal it saves without asking.
    • Do: a script that re-runs connect to check a repo should run kraft repo list instead.
  • kraft repo connect refuses a repo with no commit yet. 1.4 connected it. Do: commit its files first. kraft admin doctor names each entry 1.4 left without a test or setup command where 2.0 finds one, with the connect command that saves it.

Scripts, JSON output and the API

  • agent_overrides is an object, not JSON text, wherever it appears: in kraft view show --json, in kraft item approve --json and every other --json answer that carries the item, in the MCP tools' results, and in GET /api/work-items/{id}.
    • In the answers to an action, such as kraft item approve --json, node_overrides and policy_override are objects too, as kraft view show --json already had them.
    • Do: if a script runs json.loads on one of them, read it as an object instead.
    • Setting the override works as in 1.4: kraft item set-overrides and PATCH /api/work-items/{id} replace the whole override.
  • budget_cap.cap_usd in kraft view show --json is the cap that stops the item first: the lower of its own cap and its policy's item-wide budget_usd. Do: read budget_cap.key to tell which one it is.
  • kraft view events -f --json prints one JSON object per line from the first line on. 1.4 printed the backlog as an indented array. Do: read it line by line.
  • Action commands print one line. kraft item approve, reject, pause, resume, retry, raise-budget, skip, complete, cancel, escalate and set-chain, kraft item resolve and reopen (one line naming the thread), and kraft item review approve or request-changes at a pending gate, print what they did, not the item's fields (or, for a thread, its comments). Do: a script that read those fields should use --json, which is unchanged.
  • kraft admin restart with no server running exits 1. 1.4 exited 0. update --restart is unchanged. Do: a script that runs it under set -e stops there, so start the server first.
  • kraft admin restart and update --restart list the running items first. In a terminal they ask before going on (-y skips the question); a script with no terminal only prints the list. 1.4 restarted without asking. No action.
  • A review comment names a file inside the repository, and a suggestion goes on new-side lines. kraft item comment, the MCP tool add_review_comment and POST /api/work-items/{id}/threads refuse an absolute --file path, or one with .., with a 422, and kraft item comment --suggest with --side old exits 2 (the API answers 422). 1.4 stored both. Do: use a repository-relative path, and --side new for a suggestion.
  • A model override that is not a model id is refused. kraft item set-overrides, kraft item set-node-override, PATCH /api/work-items/{id} and a retry that changes a task's model (task_config.model) answer one, such as --model "my model", with a 422. 1.4 stored any text. One 1.4 already stored now stops its item at the next launch instead of reaching the agent, and the reason names the command that clears it. Do: run that command.
  • Escalating an item a spend cap stopped is refused, and so is raising a cap to what the item has already spent.
    • kraft item escalate (and POST /api/work-items/{id}/escalate) on an item any spend cap stopped, the daily cap included, answers 409 and points to the cap to raise. 1.4 started an escalation that the same cap then stopped.
    • kraft item raise-budget with a cap at or below what the item has spent answers 422. 1.4 saved it and the item stopped again at once.
    • Do: raise the cap above the spend.
  • A work item title is one line of plain text, and not blank. kraft item create, the MCP tool create_work_item, POST /api/triggers and PATCH /api/work-items/{id} refuse a title that is empty, only whitespace, holds a line break (a trailing one included), or holds a tab, an escape or another control character, with a 422. 1.4 filed or saved it (only a blank PATCH title was refused).
    • Do: if a webhook or script builds the title from text that can be empty or run over several lines, give it a fallback and send its first line, with the rest in description.
    • A schedule's title (a policy.yaml cron trigger in 1.4), and Duplicate of an item 1.4 filed with one, is cleaned up instead of refused: the first line is the title and the rest goes to the top of the description, a tab becomes a space, other control characters are dropped, and a blank title takes the description's first line (a schedule with neither is skipped).
    • kraft view list shows the first line of a title 1.4 stored with a line break, without its control characters.

Chains are named chain, not template, in commands, tools and the API.

BeforeNowWhat to do
kraft item set-chain --template--chain; --template still worksNothing.
The MCP tool set_chain_templateset_chain, with chain for template; the old tool name no longer existsChange an agent prompt or skill that names set_chain_template.
chain_template in the MCP tool create_work_itemchainUse chain.
chain_template in POST /api/triggerschain; chain_template is still readNothing.
default_chain_template in POST and PATCH /api/reposdefault_chain; default_chain_template is still readNothing.
default_chain_template in the answers of kraft repo list --json, kraft repo connect --json, the MCP tool ensure_repo and GET /api/reposdefault_chainRead default_chain in a script that uses the key.

Config directory and files

  • The config directory. The config directory is $KRAFT_HOME/config/, not templates/. Do: nothing for a default home; the first start renames it. The rest of this item says what that touches.
    • The first start renames yours, every file in it as it was, once it holds the server's lock. Started while the 1.4 server is still up, it exits "already running" and touches nothing. templates is left as a link to config, so a 1.4 process still running (an MCP server under an agent) and a path in a script keep working.
    • KRAFT_CONFIG_DIR names it. KRAFT_TEMPLATES_DIR is still read when the new one is unset, and one pointing at the old default is renamed with it, so a service unit or a shell written for 1.4 keeps working. Change it when you next touch that unit.
    • A config/ you made before the first start, such as the harness guide's config/harnesses, gets what it lacks moved in (an empty directory in templates/ is removed). Whatever both hold stays in templates/, and kraft admin doctor's config row names both until you merge them.
    • If a service unit or a shell sets KRAFT_TEMPLATES_DIR to the old default and you also made a config/, 2.0 refuses to start until you merge templates/ into config/ and remove it.
    • A directory you point at anywhere else is never renamed. A templates that is a link to another directory (a dotfiles repo) is not renamed or merged either, and its files are not read, except a harnesses/ directory in it, which stays read until a config/harnesses exists. The start prints a warning naming the target, and the config row warns the same. Do: set KRAFT_CONFIG_DIR to that target.
    • kraft admin doctor --json names the row config, not templates.
  • A schedule's day of week 7 is Sunday, as in cron. 1.4 accepted 7 and never fired it. Ranges (1-5) and steps (*/15) work since 2.0. Do: a 0 9 * * 7 trigger that never ran in 1.4 fires every Sunday after the upgrade, so change or remove it first if that is not what you want.
  • A steering/ directory is no longer read. 0.x read a directory of .md files, and 1.x folded it into library.yaml at the first start. Every 1.x start already folded it, so a directory left over is only files. Do: on a 0.x home updated straight to 2.0, copy those files' text into library.yaml's steering: by hand, because 2.0 loses them as steering (Upgrade from 0.x has the update).
  • Agent profiles in harnesses.yaml are written as providers: (per provider, {model, effort}), the shape Settings › Harnesses edits. The older model: map with one effort still loads. No action.

Three keys moved. Every old place is still read, so a hand-written file stops nothing, except max_concurrent in intake.yaml: the server ignores it, with a warning, until the next start moves it to policy.yaml. kraft admin doctor's moved keys row names each old place you still have.

BeforeNowWhat to do
triggers: in policy.yamlschedules: in intake.yaml, edited on Settings › Auto-intakeNothing: the first start moves it.
max_concurrent in intake.yamlmax_concurrent in policy.yaml, on Settings › Policy › HousekeepingNothing: the first start moves it, unless policy.yaml already sets one.
default_chain_template in a repo entrydefault_chainNothing: the old key is still read.
board.group_by: template in theme.yamlboard.group_by: chainNothing: the old value reads as chain.

Unless the notes below apply, there is nothing to do:

  • The first start carries the first two, in the renamed home or in one you point at by hand: policy.yaml's triggers: become schedules: in intake.yaml, and an intake.yaml max_concurrent moves to policy.yaml unless it already sets one.
  • Each file a key moves between is rewritten, readable by you alone (0600); the rest of it keeps its comments and layout.
  • A moved trigger takes its comments with it: the lines above it and the ones on its own lines. A comment line above triggers: itself stays in policy.yaml, even once every trigger has moved, when it sits over the next key: delete it by hand.
  • A comment on the max_concurrent line is dropped. If keeping a file's comments would leave it unreadable, the start writes it without them, and its log says so.
  • A trigger 2.0's schedule refuses (1.4 ignored an extra key such as enabled: false) stays in policy.yaml, still read and fired, and the rest move. Doctor's moved keys row names the key that keeps each one there (the start's log names only the first).
  • A trigger whose cron names a value outside its field (0 24 * * *, 0 9 * * 8), which 1.4 loaded and never ran, or a number in digits other than 0-9 (², which 1.4 never ran, or ٣, which it ran as 3), stays in policy.yaml too and is skipped, with a warning, rather than refusing the file.

Four repos.yaml keys an older Kraft wrote are not read. A repo still carrying one loads, the server log names it once, and kraft admin doctor's keys row warns, naming what replaced it. gitlab_project is still read as forge: gitlab and project:.

BeforeNowWhat to do
default_modelA model per harness profile under models:Replace the key.
submodulesEach submodule as an entry of its ownReplace the key.
default_root_merge_policyA workspace's root_pointer_defaultReplace the key.
allow_cross_repoNothingRemove it.

Permission hooks

  • Cursor's hook entry runs Kraft's Python with -I. 1.4 wrote the entry in a worktree's .cursor/hooks.json without -I. The server adds -I to each existing worktree's entry when it starts, so a worktree that already exists gets it too. No action. See Permission gate.

The interface

  • 2.0 replaces the interface, and some pages moved. Old addresses still open the right page. No action: see Where the pages moved for where each one went.
  • The theme file changes once. The first start rewrites theme.yaml so the look you had stays. No action: see Your theme if it looks wrong.
  • The phone layout shows on a screen narrower than 768 pixels, with the same pages behind a bottom bar. No action: see Kraft on a phone.
  • The 2.0 VS Code extension needs Kraft 2.0 or a later 2.x. VS Code updates the extension on its own. Against a 1.4 server it shows the board but disables every action, and a 1.4 extension is read-only against a 2.0 server. Do: update Kraft and the extension together; the messages are in VS Code.

Where the pages moved

Kraft 2.0 replaced the interface. The old pages moved, and the old addresses still open the right page.

BeforeNow
Settings › Chains, and the old Templates pageTemplates › Chains (/templates/chains)
Settings › Library and SteeringTemplates › Library (/templates/library); steering profiles are library components
Settings › Repos and Settings › HarnessesStill under Settings (/settings/repos, /settings/harnesses); the /templates/... addresses the 2.0 release candidates gave them open the same pages
Settings › Plugins (/settings/plugins, a redirect to Settings › Chains already in 1.4)Templates › Chains
Settings › Notifications (/settings/notify)Settings › Notifications (/settings/notifications)
Settings › Auto-intake (/settings/intake)Settings › Auto-intake (/settings/auto-intake)
Settings (the front page)Settings › Policy (/settings/policy)
The Search page (/search)The board, with the search box on ⌘K (Ctrl+K) from any page

Repos, Harnesses, Policy, Access and Appearance stayed under Settings. About is still at /settings/about: on the desktop it opens from the version line at the foot of the sidebar, and on a phone from More.

A bookmark to an old address opens the page that replaced it, and keeps its query string. Two older kinds of address:

  • An address from the preview of the new interface, which began with /ng, drops that prefix and opens the same page.
  • A bookmark to an item's old tab address (/work-items/ID#tab=changes) opens the item's review page, and one to a node (#node=NAME) opens that node.

The Current UI link and the Try the new UI banner are gone: there is one interface. On a phone, a screen with no desktop page of its own opens the nearest one: see Kraft on a phone.

Your theme

The 2.0 interface chooses color with surface, accent and colour_amount, not palette. The first start after the upgrade rewrites theme.yaml once, so the look you had stays: your old palette is replaced by the three keys that draw it, and the original file is kept as theme.yaml.pre-2.0 beside it. A home a 1.5.0 release candidate already converted keeps that candidate's copy as its one copy, and gets no second one: theme.yaml.pre-1.5, or theme.yaml.pre-ux2 from 1.5.0rc5 to rc9.

Which surface and accent each old palette becomes, and what happens to a palette that reappears, is in palette is legacy.

If it still looks wrong:

  • Open Settings › Appearance and pick the surface, accent and amount of color you want. That is the supported way to change them.
  • To go back to the file as it was before the upgrade, stop Kraft, copy ~/.kraft/config/theme.yaml.pre-2.0 (or the candidate's copy named above) over theme.yaml, and start it again. The next start converts its palette once more. The saved copy is never overwritten.
  • A theme.yaml that does not parse, or names a value Kraft does not know, is left as it was and the interface falls back to its defaults. kraft admin doctor has no row for this file: fix the key by hand (theme.yaml lists them), or delete the file to start from the defaults.
  • mode: system follows your operating system's light or dark setting.

Next steps

Copyright © 2026