Upgrade from 1.4
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.
- On 1.4: follow Update from 1.4, then read What changed for the changes that need something from you.
- Already ran 1.4's own update: go to If you used 1.4's update anyway.
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" -Vprints the Python. Theembeddingsline ofkraft admin healthsaysunavailable (the 'vector' extra is not installedwhen you don't have the extra. sqlite3orpython3is on yourPATH, 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:
- 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.
- 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 orenabled: false(Items and repos). - On a repo whose
sandbox:has nonetwork:, add one, or setunrestricted_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).
- On a network bind, add each name you use to
- Pause the running items, or wait until none run. See A restart ends running agents.
- Install 2.0 with the tool you installed Kraft with:
- uv or
install.sh: runuv tool install --force --python 3.X "kraft-sdlc[vector]==<version>", with the Python you installed Kraft with for3.Xand the release for<version>: the number without thev, as the newest tag on the releases page shows it (tagv2.0.0is2.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 leavesuv tool upgradepointing at a file it has deleted. - Homebrew: run
brew update, thenbrew info kraft. It shows the version the tap offers, andbrew infoalone does not refresh the tap.- If it still shows 1.4, the tap has no 2.0 yet, and
brew upgrade kraftchanges nothing: wait, or runbrew uninstall kraftand install 2.0 with uv as above. - Otherwise run
brew upgrade kraft. 1.4'skraft admin updateruns the same command, and loses nothing: the formula has no[vector]extra.
- If it still shows 1.4, the tap has no 2.0 yet, and
- pipx: run
pipx install --force --python python3.X "kraft-sdlc[vector]==<version>", with<version>as above. 1.4'skraft admin updatedoesn't update a pipx install: it puts a second Kraft beside it with uv.
- uv or
- 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 --versionandkraft admin healthprint the release you installed, as step 5 of Upgrade shows.kraft admin doctorhas akeys,moved keys,testsandconfigrow. 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:
- Run
kraft admin restartas soon as the update finishes. - Reinstall
[vector]with the uv command above, if you use vector search. Without it, vector search stops working: see Enable vector search. - If you reinstalled the extra, run
kraft admin restartonce 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. Theembeddingsline ofkraft admin healththen saysavailable.
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'srestartrow warns that the server runs a release older than the one installed. Internal Server Errorinstead 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_hostsinaccess.yaml. kraft admin doctorwarns when the bind is off loopback and the list is empty.
- Do: before you upgrade, add each exact name you use (no wildcards) on
Settings › Access, or to
- A tunnel or reverse proxy in front of a loopback-bound Kraft must send a
loopback
Host. On a server bound to127.0.0.1(the default), 2.0 answers 403 to every request whoseHostheader isn'tlocalhost,127.0.0.1or[::1], webhooks toPOST /api/triggersandGET /api/healthincluded. 1.4 checked only requests from a browser, andallowed_hostsdoesn'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
Hosttolocalhost. Read Put a proxy in front of Kraft first, because it also changes who has to sign in.
- Do, for the board: bind Kraft beyond loopback, with a password, and list the
name 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 anetwork:policy to the sandbox, or setunrestricted_network: trueon it to run as 1.4 did, knowing that. The two keys together are refused whenrepos.yamlloads.kraft admin doctorfails theegressrow 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(andDELETE /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 passverifywithout running a test. In 1.4 such an entry stopped every item atverify. Do: before you upgrade, set a real test command on any such entry, orenabled: falseto keep it from running. After the upgrade, thetestsrow ofkraft admin doctornames each such entry.kraft repo connecton 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,--jsonor no terminal it saves without asking. - Do: a script that re-runs connect to check a repo should run
kraft repo listinstead.
- That can enable a repo saved disabled for want of a test command. In a
terminal it asks first; with
kraft repo connectrefuses a repo with no commit yet. 1.4 connected it. Do: commit its files first.kraft admin doctornames 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_overridesis an object, not JSON text, wherever it appears: inkraft view show --json, inkraft item approve --jsonand every other--jsonanswer that carries the item, in the MCP tools' results, and inGET /api/work-items/{id}.- In the answers to an action, such as
kraft item approve --json,node_overridesandpolicy_overrideare objects too, askraft view show --jsonalready had them. - Do: if a script runs
json.loadson one of them, read it as an object instead. - Setting the override works as in 1.4:
kraft item set-overridesandPATCH /api/work-items/{id}replace the whole override.
- In the answers to an action, such as
budget_cap.cap_usdinkraft view show --jsonis the cap that stops the item first: the lower of its own cap and its policy's item-widebudget_usd. Do: readbudget_cap.keyto tell which one it is.kraft view events -f --jsonprints 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,escalateandset-chain,kraft item resolveandreopen(one line naming the thread), andkraft item review approveorrequest-changesat 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 restartwith no server running exits 1. 1.4 exited 0.update --restartis unchanged. Do: a script that runs it underset -estops there, so start the server first.kraft admin restartandupdate --restartlist the running items first. In a terminal they ask before going on (-yskips 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 tooladd_review_commentandPOST /api/work-items/{id}/threadsrefuse an absolute--filepath, or one with.., with a422, andkraft item comment --suggestwith--side oldexits 2 (the API answers422). 1.4 stored both. Do: use a repository-relative path, and--side newfor 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 a422. 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(andPOST /api/work-items/{id}/escalate) on an item any spend cap stopped, the daily cap included, answers409and points to the cap to raise. 1.4 started an escalation that the same cap then stopped.kraft item raise-budgetwith a cap at or below what the item has spent answers422. 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 toolcreate_work_item,POST /api/triggersandPATCH /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 a422. 1.4 filed or saved it (only a blankPATCHtitle 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.yamlcron 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 listshows the first line of a title 1.4 stored with a line break, without its control characters.
- 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
Chains are named chain, not template, in commands, tools and the API.
| Before | Now | What to do |
|---|---|---|
kraft item set-chain --template | --chain; --template still works | Nothing. |
The MCP tool set_chain_template | set_chain, with chain for template; the old tool name no longer exists | Change an agent prompt or skill that names set_chain_template. |
chain_template in the MCP tool create_work_item | chain | Use chain. |
chain_template in POST /api/triggers | chain; chain_template is still read | Nothing. |
default_chain_template in POST and PATCH /api/repos | default_chain; default_chain_template is still read | Nothing. |
default_chain_template in the answers of kraft repo list --json, kraft repo connect --json, the MCP tool ensure_repo and GET /api/repos | default_chain | Read default_chain in a script that uses the key. |
Config directory and files
- The config directory. The config directory is
$KRAFT_HOME/config/, nottemplates/. 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.
templatesis left as a link toconfig, so a 1.4 process still running (an MCP server under an agent) and a path in a script keep working. KRAFT_CONFIG_DIRnames it.KRAFT_TEMPLATES_DIRis 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'sconfig/harnesses, gets what it lacks moved in (an empty directory intemplates/is removed). Whatever both hold stays intemplates/, andkraft admin doctor'sconfigrow names both until you merge them. - If a service unit or a shell sets
KRAFT_TEMPLATES_DIRto the old default and you also made aconfig/, 2.0 refuses to start until you mergetemplates/intoconfig/and remove it. - A directory you point at anywhere else is never renamed. A
templatesthat is a link to another directory (a dotfiles repo) is not renamed or merged either, and its files are not read, except aharnesses/directory in it, which stays read until aconfig/harnessesexists. The start prints a warning naming the target, and theconfigrow warns the same. Do: setKRAFT_CONFIG_DIRto that target. kraft admin doctor --jsonnames the rowconfig, nottemplates.
- 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.
- A schedule's day of week
7is Sunday, as in cron. 1.4 accepted7and never fired it. Ranges (1-5) and steps (*/15) work since 2.0. Do: a0 9 * * 7trigger 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.mdfiles, and 1.x folded it intolibrary.yamlat 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 intolibrary.yaml'ssteering:by hand, because 2.0 loses them as steering (Upgrade from 0.x has the update). - Agent profiles in
harnesses.yamlare written asproviders:(per provider,{model, effort}), the shape Settings › Harnesses edits. The oldermodel:map with oneeffortstill 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.
| Before | Now | What to do |
|---|---|---|
triggers: in policy.yaml | schedules: in intake.yaml, edited on Settings › Auto-intake | Nothing: the first start moves it. |
max_concurrent in intake.yaml | max_concurrent in policy.yaml, on Settings › Policy › Housekeeping | Nothing: the first start moves it, unless policy.yaml already sets one. |
default_chain_template in a repo entry | default_chain | Nothing: the old key is still read. |
board.group_by: template in theme.yaml | board.group_by: chain | Nothing: 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'striggers:becomeschedules:inintake.yaml, and anintake.yamlmax_concurrentmoves topolicy.yamlunless 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 inpolicy.yaml, even once every trigger has moved, when it sits over the next key: delete it by hand. - A comment on the
max_concurrentline 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 inpolicy.yaml, still read and fired, and the rest move. Doctor'smoved keysrow names the key that keeps each one there (the start's log names only the first). - A trigger whose
cronnames 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 inpolicy.yamltoo 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:.
| Before | Now | What to do |
|---|---|---|
default_model | A model per harness profile under models: | Replace the key. |
submodules | Each submodule as an entry of its own | Replace the key. |
default_root_merge_policy | A workspace's root_pointer_default | Replace the key. |
allow_cross_repo | Nothing | Remove it. |
Permission hooks
- Cursor's hook entry runs Kraft's Python with
-I. 1.4 wrote the entry in a worktree's.cursor/hooks.jsonwithout-I. The server adds-Ito 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.yamlso 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.
| Before | Now |
|---|---|
| Settings › Chains, and the old Templates page | Templates › Chains (/templates/chains) |
| Settings › Library and Steering | Templates › Library (/templates/library); steering profiles are library components |
| Settings › Repos and Settings › Harnesses | Still 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) overtheme.yaml, and start it again. The next start converts itspaletteonce more. The saved copy is never overwritten. - A
theme.yamlthat 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 doctorhas no row for this file: fix the key by hand (theme.yaml lists them), or delete the file to start from the defaults. mode: systemfollows your operating system's light or dark setting.
Next steps
- Upgrade Kraft: the backup, the update and the check that every upgrade uses
- Roll back to 1.4: go back if the upgrade went wrong
- Back up and restore the database: take the backup 1.4 needs and find the copy to roll back to
- What's new in 2.0: what you get for these changes
Upgrade your configuration
Pick up new shipped chains, library tasks and policy after a Kraft upgrade, without losing your own edits.
Upgrade from 0.x or a release candidate
Upgrade to 2.0 from a 1.5.0 release candidate, from an install made before the kraft-sdlc rename, or from a Kraft 0.x home that holds the old template configuration.