v2.0.0next
Security and project

Architecture

One FastAPI process, the executor inside it, the worker sessions it spawns, how a work item flows through them, and where each trust boundary is enforced.

Kraft is one Python process on your machine. It serves a React SPA and an HTTP API, walks each work item's chain, and spawns a coding agent or a command for each task.

This page is for contributors and for anyone who wants to know what runs where. It shows how the parts fit and where each one is enforced in the code. Everything Kraft knows is in one SQLite database, run/orchestrator.db, beside a search index it can rebuild.

For what a work item does, step by step and status by status, read How a work item runs first. For the words, see Vocabulary. For which module under src/kraft/ holds what, see the Source map.

The parts

  • One process. kraft (or kraft admin start) runs uvicorn with the FastAPI app from src/kraft/api/. The same process serves the built SPA, answers the API under /api, pushes events over a WebSocket, and runs every work item's walk as an asyncio task. There is no queue, broker or second server.
  • Clients. The SPA, the kraft CLI and the MCP server all call the same HTTP API. The MCP server is kraft admin mcp: a separate stdio process that your coding agent starts, with a tool for most item and view verbs and for repo connect (every tool but permission_request is also a CLI subcommand). Both go through src/kraft/client/, so a verb and its tool cannot drift apart.
  • The executor. src/kraft/executor/ walks a work item's frozen chain node by node. It runs builtin and forge tasks inside the process and launches agent and subprocess tasks as child processes. It is started by an API request (resume, retry, approve, reject, skip) or by a poller.
  • Worker sessions. Each agent task is a headless agent CLI (Claude Code, Codex, Gemini, Antigravity, Cursor, OpenCode or Amp) started in the item's git worktree, with an environment built from an allowlist. With a sandbox configured, it runs in a container (Docker or Podman) instead, and reaches the network only through Kraft's egress proxy. A worker talks back to Kraft for permission decisions, plan progress and review replies.
  • Pollers. Background tasks in the same process re-enter items that are waiting or rate_limited, stop parked items whose time cap ran out, fire schedules, pick up ready issues from beads when auto-intake is on, fire delayed auto-escalations, archive old ended items, and notice a merge request closed on the forge without merging.
  • Stores. run/orchestrator.db holds work items, sessions, gates, review threads and an append-only events table. All writes go through one writer queue (src/kraft/db.py). The WebSocket, notifications and the search index each follow the events tail. run/index.db is the full-text and vector search index over your repos' documents.
  • Outside tools. Kraft calls git for worktrees and rebases, gh or glab for merge requests and CI, and bd for beads, each as a subprocess with your own login.

From filing to merge

The table follows one work item. Its second column gives the numbered steps of the diagram above.

StepWhat happensDiagramWhere in src/kraft/
1. Filekraft item create posts the item. Intake resolves its chain, freezes the chain and the effective policy onto a new row, and copies attachments. The item lands paused.1 to 2api/routes/work_items.py, executor/entry.py, store/work_items.py; chains in config/chains/; copies in run/attachments/
2. Resumekraft item resume claims the item with a conditional update from paused to active, checks the slot limit in the same statement, and starts its walk as a task.3 to 5api/routes/lifecycle.py, executor.run
3. WalkThe walk loads the chain, cuts the worktree, and runs each node's steps. An agent task builds its command line from the harness profile, then launches it, watches its log, and reads its result file and usage.6 to 8executor/walk.py, builtins.py, executor/dispatch.py, adapters/agent.py, harness.py, harnesses/*.yaml, adapters/subprocess.py
4. GateAt a gate node the item becomes needs_human and Kraft writes gate_requested. The walk task ends. The event reaches the board over the WebSocket and, if you configured one, a notification.9 to 10executor/gates.py, notify.py
5. Approvekraft item approve checks for open must-fix threads, sets the item active again, and starts a new walk at the node after the gate.11 to 14api/routes/gates.py
6. Merge requestForge nodes open the draft merge request and read CI. A pending pipeline is an external wait: the item goes waiting, the walk task ends, and the wait scheduler re-enters the walk when the next check is due.15 to 16adapters/forge/, waits.py
7. FinishAfter final_review, forge nodes mark the merge request ready, wait for any required approval, merge, and watch the post-merge pipeline. The walk marks the item completed and closes its issues in beads.17 to 19adapters/forge/, executor/entry.py

What a restart does to running items

Pause an item before you run kraft admin stop or restart. A clean stop ends each running agent, and the item then stops and waits for you. See A restart ends running agents for the commands.

A restart does not lose finished work. On startup, worker/reattach.py finds the sessions that were running, adopts the ones still alive, and resumes each item's walk from the node and step it had reached. A session outlives a crash or a kill -9 of the server, because each agent runs in its own process group.

A clean stop is different: it cancels every item's task, and the task kills its agent's process group on the way out. After a kraft admin stop or restart there is nothing left to adopt.

Trust boundaries

Four lines matter. The Security page states what each one does and does not stop by default; this is only where each one lives in the code.

BoundaryEnforced by
Network clients to the APIapi/perimeter.py (the local-client check and auth middleware), auth.py (password login and sessions), the bearer token in run/
A worker to KraftThe permission gate (api/routes/sessions.py, permission_hooks.py, permission_rules.py, grants.py), and the self-action guard: client/context.py in the CLI and MCP server, and forbid_self_action in api/deps.py for a worker that identifies itself
A worker to your machineNothing, unless the repo sets sandbox:. Then worker/backends/docker.py, worker/sandbox.py, worker/refstore.py, assert_on_branch in adapters/forge/git.py before every host-side commit, rebase or push from its worktree, and for network: the egress proxy in worker/egress.py, worker/channel.py and worker/inject.py
Kraft to your forgeYour own gh or glab login, used as is by adapters/forge/

A sandbox covers a workspace's members as it covers the root. Where each hold lives in the code is under Workspace members on the Source map.

State on disk

State lives in $KRAFT_HOME (default ~/.kraft). run/ holds the databases, session logs and result files, worktrees, attachment copies, the process id file, and the sandbox's sockets, CA and ref stores. config/ holds the YAML the Templates and Settings screens edit. Kraft seeds config/ from the shipped defaults on first run and never overwrites it, so an upgrade cannot clobber an edited policy.

Data and privacy lists every file in run/ with its mode. Configuration lists what is in config/.

  • Source map: which module under src/kraft/ holds what, and where to change a given thing.
  • Security: what each trust boundary does and does not stop by default.
  • How a work item runs: the flow above in terms of statuses and gates.
Copyright © 2026