Repos, chains, harnesses and the server
This page lists the board's routes for connecting, changing and disconnecting a repo, chains and the library, harnesses, changes saved but not applied, the update check and editors. Like the other board routes they can change in any release: see Filing, reading and changing work items for the rule, and the stable routes for the two that keep their shape. Prefer the kraft command with --json.
| Route | What it does |
|---|---|
GET /api/repos | Lists the connected repos and workspaces. |
POST /api/repos/probe | Reads a repository and proposes how to connect it. |
POST /api/repos | Connects a repository. |
PATCH /api/repos?path= | Changes a connected repo's entry in repos.yaml. |
DELETE /api/repos?path= | Disconnects a repo. |
GET /api/templates/chains | Lists every chain. |
GET /api/templates/chains/{id}/resolved | Answers one chain with its extends expanded. |
GET /api/templates/library | Answers library.yaml and every component in it. |
GET /api/templates/library/{ref} | Answers one library component. |
GET /api/templates/steering/preview | Shows what an agent task reads at launch. |
GET /api/harnesses/profiles | Answers the harness profiles and the agent profiles. |
GET /api/harnesses | Lists each provider and whether its CLI is installed. |
GET /api/harnesses/providers | Answers each provider's capabilities. |
GET /api/apply | Lists what is saved and not running yet. |
POST /api/apply/reload | Rereads the library, policy.yaml and intake.yaml. |
POST /api/templates/reload | Rereads the library, policy.yaml and intake.yaml, and answers what it refused. |
POST /api/apply/restart | Restarts a server that a service manager started. |
GET /api/update | Answers the cached update check. |
POST /api/update/check | Asks the release feed now. |
GET /api/editors | Lists the editors this machine can open. |
POST /api/documents/{id}/open | Opens a document in an editor. |
POST /api/work-items/{id}/artifact/open | Opens the pending gate's document. |
POST /api/work-items/{id}/artifacts/{kind}/open | Opens a task's document. |
POST /api/work-items/{id}/open-worktree | Opens the item's worktree. |
Connecting a repo
GET /api/repos
Answers the connected repos and workspaces, and the checkout the server was started in when it is not connected yet.
| Field | Type | Meaning |
|---|---|---|
repos | list | One entry per connected repo, as repos.yaml holds it, defaults included. |
workspaces | object | The workspaces: section, by workspace id. |
suggested | string or null | The git checkout the server was started in, when it is one and not connected yet, else null. The first-run board offers it as the path to connect. |
POST /api/repos/probe
Reads a repository and changes nothing. The proposal answer says what it returns.
| Field | Type | Default | Meaning |
|---|---|---|---|
path | string | required | The absolute path of the repository. |
detect | boolean | true | With false the route answers only what needs no detector table: the resolved path, name, branch, submodules, beads and forge. |
test_command | string | none | The root's test command, in place of the proposed one. "" declares that the repo has no tests. |
With test_command the answer is the proposal that connecting with that command would save. The probe runs in a process of its own with a two-minute limit, so a repository's files cannot stall the server. How connect proposes commands says how a proposal is made, what limits the probe, and what connect does when the probe fails. The 429 below is only on this page.
| Status | When |
|---|---|
400 | The path is not a directory or not a git repository, detectors.yaml does not load (the answer names it), or the probe ran past two minutes. |
422 | path is relative. |
429 | detect is true and two probes are already running. Try again shortly. A probe with detect: false is never queued. |
POST /api/repos
Connects a repository: it probes it, saves the entry to repos.yaml, and indexes the repo for search. It answers 201 with the saved entry and the proposal answer.
| Field | Type | Default | Meaning |
|---|---|---|---|
path | string | required | The absolute path of the repository. |
name | string | the directory name | The repo's name. |
default_chain | string | default | The chain its items run when none is named. |
test_command | string | the probe's proposal | The root's test command. "" declares a repo with no tests, which may be enabled. |
setup_command | string | the probe's proposal | The command that prepares a worktree. "" declares none. |
test_scopes | list | the probe's nested scopes | Test scopes by directory. An empty list is read as unset. |
enabled | boolean | true with a test command or test scopes, else false | Whether new items may target the repo. |
forge, project, models, deny_tools, steering | the probe's forge and project, else empty | The fields of the same names in repos.yaml. |
A test_command or setup_command in the body replaces the proposal. If the body gives both, the route connects a repo the probe fails on, with those commands, and says why in probe_failed.
| Status | When |
|---|---|
400 | The path is not a git repository. Or the probe fails (detectors.yaml does not load, or it ran past two minutes) and the body does not give both commands. |
409 | The repo is already connected. It is checked before anything else is read. |
422 | The path is relative, the repository has no commit yet, enabled: true has no test command or scopes, or repos.yaml would not load with the entry. |
PATCH /api/repos?path=
Changes fields of the connected repo at path, the absolute path of its entry in repos.yaml. The Settings screen calls it. A field the body leaves out keeps its saved value, and one sent as null is cleared. Any save sets managed: true on the entry.
| Field | Type | Meaning |
|---|---|---|
name, forge, project, default_chain | string | As in POST /api/repos. |
test_command, setup_command | string | The commands. "" declares none. |
test_scopes | list | Test scopes by directory. |
enabled | boolean | Whether new items may target the repo. |
models, deny_tools, steering | object, list, list | The fields of the same names in repos.yaml. |
local_files, intent_dir | list, string | The fields of the same names in repos.yaml. |
The answer is the whole saved entry, defaults included.
| Status | When |
|---|---|
404 | path is not a connected repo, or is relative. |
422 | repos.yaml would not load with the change, and nothing is written: an unknown steering name, a glob in local_files, an intent_dir that leaves the repo. The detail says which. |
422 | The change enables a repo that has no test command or test scopes, or clears the last test command of an enabled repo. Disable it, or set test scopes in the same change. |
DELETE /api/repos?path=
Disconnects the repo at path: it removes the entry from repos.yaml and from the search index. No file of the repository is touched and no work item is dropped. kraft repo disconnect calls it. It answers 204 with no body.
| Status | When |
|---|---|
404 | path is not a connected repo, or is relative. |
409 | The repo has open items, paused ones included. The answer gives the count: complete, cancel or abandon them first. |
422 | A workspace in repos.yaml still names the repo. |
The proposal answer
POST /api/repos/probe and POST /api/repos answer the proposal beside the repo's facts: test_command, test_scopes, setup_command, and the evidence below. None of the evidence is stored in repos.yaml.
| Field | Meaning |
|---|---|
candidates | Every command the evidence supports: {dir, role, command, tier, source, marker, detector, family, corroborated, chosen}. role is test or setup. tier is runner, ci, toolchain or devenv. dir is "" for the root. chosen marks the proposal. |
scopes | One {dir, test, setup} per directory the proposal covers, root first, with commands as they run from inside that directory. |
missing_setup | Directories with a test command and nothing to prepare them (. for the root), so setup_command is left undeclared. |
test_markers | The file each proposed test command came from. |
read_from | The commit read: refs/remotes/origin/HEAD's target, refs/remotes/origin/main or HEAD, or null for a repo with no commit (read from its working copy). |
missing_tools | Programs a proposed command runs that the server's PATH does not have, one {dir, tool} each. A work item fails on them until they are installed. |
stopped | Why no test command was proposed, one {dir, reason, detector} per directory that stopped it (. for the root): a pyproject.toml with no lockfile, or a root project with no test command found beside nested ones. Empty when nothing stopped. |
probe_failed | Only on POST /api/repos, when both commands were given and the probe failed: why. |
Chain templates
These routes read the chains the Templates editor shows. Each answers 503 when the library or a chain does not load, with template library invalid, refusing work: and the file that broke it.
GET /api/templates/chains
Answers every chain, by id.
| Field | Type | Meaning |
|---|---|---|
id | string | The chain id. |
description | string or null | As authored, even when the chain does not resolve. |
nodes | list | The resolved nodes, empty when the chain does not resolve. Each carries icon, the node's own icon name, or null when it sets none (always for a gate). |
gates | integer | How many of the nodes are gates. |
error | string or null | Why the chain does not resolve. |
uses | object | Per node id, the ids of the library components that node is built from. A node built from none is left out. |
Answers 503 as under Chain templates.
GET /api/templates/chains/{id}/resolved
Answers one chain with its extends expanded.
| Field | Type | Meaning |
|---|---|---|
id | string | The chain id. |
chain | object | The chain as written plus what it inherited, with no defaults filled in. |
task_paths | list of strings | The canonical path of every task. |
steering | object | The steering text the chain selects, by profile. |
nodes | list | The resolved nodes, as in GET /api/templates/chains. |
documents | object | Each gate id mapped to the list of document kinds the execution nodes before it produce, in chain order: what that gate's artifact can name. |
It answers 404 for an unknown chain and 422 for one that does not resolve. Also 503 as under Chain templates.
GET /api/templates/library
Answers {file, text, components}: the path of library.yaml, its text, and every component in it, section by section. See The library component. Also 503 as under Chain templates.
GET /api/templates/library/{ref}
Answers one component, shaped as in the library component, by its id or a bare name no other section shares. It answers 404 when nothing matches, and 409 naming each id when two sections share the bare name. Also 503 as under Chain templates.
GET /api/templates/steering/preview
Answers what the agent task at canonical path task of chain chain reads at launch in the repository whose name or path is repo, section by section in launch order. Nothing launches. The answer is {sections: [{kind, source, text}]}.
| Query | Meaning |
|---|---|
chain | The chain's id. Required. |
task | The task's canonical path. Required. |
repo | The repository's name or path. Required. |
A missing parameter answers 422.
kind is one of:
kind | source |
|---|---|
contract | kraft |
document | The kind it produces. |
review_package | kraft |
skill | The skill. |
intent | The repository's intent_dir. |
steering | repo:<profile> or task:<profile>. |
usage | kraft. Only when the task's harness reports usage as a result file. |
There is one steering section per steering profile: the repository's first, then the task's. Joined, the texts are the launch's context. Per-launch values are placeholders (<work item id>, <session id>, <title>, <task instruction>).
| Status | When |
|---|---|
404 | The chain, the task or the repository is unknown. |
422 | The chain does not resolve, the task is not an agent task, or its harness, skill or steering cannot resolve. |
503 | repos.yaml does not load, or the library or a chain does not, as under Chain templates. |
The library component
GET /api/templates/library and GET /api/templates/library/{ref} return components with these fields:
| Field | Meaning |
|---|---|
id | The section and the name, tasks.implementer. |
kind | Its section. |
name | Its name within the section. |
definition | The component as written. |
issues | The lint issues that name it. |
used_by | The ids of the chains that use it. |
used_by_paths | [{chain, path, overrides, via?}]: each component of a chain that extends or selects it. See below. |
In used_by_paths, path is that component's canonical path, overrides is whether it sets any key besides id, extends and kind, and via is the library node it reaches the chain through, when it sits in that node's own definition. For example, {chain: "default", path: "verification.review.code_review", overrides: false, via: "verification"}.
Harnesses
The web UI edits harnesses through a draft (/api/drafts/harnesses/...). These routes read the published state.
GET /api/harnesses/profiles
Answers {profiles, agent_profiles, file, error}.
fileis the path ofharnesses.yaml, anderroris why it does not load, elsenull.- Each entry of
profilesis a harness profile:{id, provider, enabled, executable, defaults, used_by, chains}. - Each entry of
agent_profileshasid,used_by,chains,effort,model(provider to model id),providers(provider to{model, effort}),fallback,problemsandproblem_details. effortis the one effort every provider shares, ornullwhen they differ: readprovidersthen.problemsis the entry's list of messages, andproblem_detailsthe same list as{chain, task, profile, provider, field, message}, wherefieldismodel,effort, ornullfor a profile that is not defined.- Each
fallbackentry has its ownproblemsandproblem_details.
GET /api/harnesses
Answers one entry per provider: {id, label, executable, executable_found, efforts, models, capabilities}. executable_found is whether the command is on PATH now. There is no version.
GET /api/harnesses/providers
Answers {valid, invalid}: each provider's capability surface by id, and the providers whose files did not load, with why. A provider also lists efforts and models: the plain-word values it limits that option to, [] when it takes any string or a pattern.
Saved but not applied
A restart or a reload applies what you saved. The apply_changed live message says when this list changes.
GET /api/apply
Answers what is saved and not running yet: {restart: [{id, file, text}], reload: [{id, file, text, problem?}], managed}. Both lists are empty when nothing is pending. managed is true when a service manager started this server, the only case POST /api/apply/restart can restart it.
- A
restartitem isaccess.bindoraccess.port:access.yamlnames an address the server is not bound to. It is left out whenKRAFT_HOSTorKRAFT_PORTis set, since the environment wins and a restart would change nothing. - A
reloaditem isdisk:<file>forlibrary.yaml,chains/*.yaml,policy.yamlorintake.yamlwhose bytes differ from the ones last loaded. A file rewritten with the same bytes is no change.problemis the first problem found in the file as it is on disk.
POST /api/apply/reload
Rereads the library, policy.yaml and intake.yaml (and replaces the intake poller), and answers the new GET /api/apply. A policy or an intake.yaml that does not load is refused and stays pending, with its problem, and the running one is kept.
POST /api/templates/reload
Does the same as POST /api/apply/reload, and answers {valid, invalid_templates, refused_policy, refused_intake}: the valid chain ids, the chains that do not load, and the last two each the refusal's reason or null. kraft admin reload prints them and exits 1 on either refusal.
POST /api/apply/restart
Restarts the server when a service manager started it (the launchd or systemd unit kraft admin install-service writes). It answers 202 with {"restarting": true} and runs kraft admin restart detached. Nothing restarts the server unless this is called.
The restart ends the agent of every active item, and each stops as failed until it is retried, as kraft admin restart warns: see A restart ends running agents. The web UI counts the active items from GET /api/work-items (the rows whose status is active) and shows the number in its confirmation.
| Status | When |
|---|---|
409 | The server was started from a terminal. The answer is {"detail": "started from a terminal: restart it there"}. |
500 | The restart could not start. |
Update check
GET /api/update
Answers {installed, latest, channel, python, behind, checked_at} from the daily cached check. python is the interpreter the server runs on, as 3.14. ?channel= is one of stable, rc, beta and alpha (400 otherwise) and defaults to the installed version's own. It is not stored.
When the feed cannot be reached, latest and behind are null: unknown, not "up to date". checked_at is the time of the last check that succeeded on that channel (null when there has been none), so it can be old while latest is null. Installing the update is kraft admin update; the server never does it.
POST /api/update/check
Skips the cache, asks the release feed now, and answers the same shape as GET /api/update. It takes the same ?channel=.
Editors
The routes that open a file in an editor start a process on the machine that hosts the server. They are loopback only: any other caller gets 403.
GET /api/editors
Answers {available, system, default}.
availablelists the editors (code,cursor,zed,obsidian) whose executable this server's machine has on itsPATH, or, on macOS, whose app is installed in/Applicationsor~/Applications(it is then opened withopen -a).systemsays whether the system opener (open,xdg-open) is there.defaultiseditorfromtheme.yaml, elseKRAFT_EDITOR, elsenull.
POST /api/documents/{id}/open
Opens an indexed document, by its absolute path, in an editor.
| Field | Type | Default | Meaning |
|---|---|---|---|
editor | string or null | null | A name from available, "system" for the system opener, or null for the default above, else the system opener. |
The answer is {document_id, path, editor}.
| Status | When |
|---|---|
400 | editor is not a known editor name, or the document's path leaves its repo. |
403 | The caller is not on the server's own machine. It is checked first. |
404 | There is no such document, or an attachment's file is not found. |
409 | The document exists only in Kraft's index and was never written to the repo, or it is an attachment of an item that has not started. |
501 | Nothing on the server can open a window, or the editor you named is not installed. The web UI then hands the path to your own machine. |
POST /api/work-items/{id}/artifact/open
Opens the pending gate's document in the item's worktree. GET /api/work-items/{id}/artifact carries its absolute_path. The answer is {work_item_id, path, editor}.
| Field | Type | Default | Meaning |
|---|---|---|---|
editor | string or null | null | As for POST /api/documents/{id}/open. |
| Status | When |
|---|---|
400 | editor is not a known editor name. |
403 | The caller is not on the server's own machine. It is checked first. |
404 | There is no such item, or the gate has no artifact. |
501 | Nothing on the server can open a window, or the editor you named is not installed. |
POST /api/work-items/{id}/artifacts/{kind}/open
Opens a task's document of that kind in the item's worktree. The answer is {work_item_id, path, editor}.
| Field | Type | Default | Meaning |
|---|---|---|---|
editor | string or null | null | As for POST /api/documents/{id}/open. |
| Status | When |
|---|---|
400 | editor is not a known editor name. |
403 | The caller is not on the server's own machine. It is checked first. |
404 | There is no such item, or it has no such document there. |
501 | Nothing on the server can open a window, or the editor you named is not installed. |
POST /api/work-items/{id}/open-worktree
Opens the item's worktree directory. The answer is {path, editor}.
| Field | Type | Default | Meaning |
|---|---|---|---|
editor | string or null | null | As for POST /api/documents/{id}/open. |
This route checks the item and its worktree before it checks the caller.
| Status | When |
|---|---|
400 | editor is not a known editor name. |
403 | The caller is not on the server's own machine. It is checked after the 404. |
404 | There is no such item, or it has no worktree on disk, even for a caller that is not local. |
501 | Nothing on the server can open a window, or the editor you named is not installed. |
Related
- Drafts: the routes the Templates and Settings editors use to change what the read routes here answer.
repos.yaml: the fields of the repo entry that connecting and changing a repo write.- Repo verbs: the
kraft repocommands that call the repo routes. - Filing, reading and changing work items and Gates, run control, ending and retry: the routes for a work item, whose editors routes are here.
Budget, events, diff and documents
The board's routes for the daily spend total, raising a budget, the dry run of a create, an item's events, its diff and review, and its documents.
Drafts
The routes the Templates and Settings editors use to keep, change, publish and discard drafts, the policy and intake-check routes beside them, and the `result` they answer.