npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@netnodeag/kraftwerk

v0.85.0

Published

Open-source agentic workspace for teams — where people and AI agents get work done together: agents (Claude Code, Codex, Pi), skills, shared knowledge, repeatable workflows, and deterministic verification

Readme

Kraftwerk

Agentic workspace. Kraftwerk is an open-source agentic workspace where humans and persistent AI coworkers work on real tasks together. Agents, knowledge, skills and workflows belong to the team, not to the person who happened to set them up, and every result is checked before anyone relies on it.

npm install -g @netnodeag/kraftwerk    # once per machine

cd your-project                        # any folder: a repo, a team folder, an empty one
kraftwerk init                         # make it a workspace: kraftwerk.yml + kraftwerk-data/
kraftwerk doctor                       # check that the agents (Claude Code, Codex, Pi) are there
kraftwerk ui                           # start it, then open http://localhost:1981

Needs Node 20+ and at least one agent harness (Claude Code, Codex, or Pi). Start and use kraftwerk explains the ways to run it (one workspace, several at once, from your phone); Install has the details.

Why teams need it. AI work today lives in individual silos. Personal prompts, Claude Code sessions, one-off scripts. What one person figures out stays with that person. Kraftwerk gives a team one place where that work becomes shared practice.

  • Shared Agentic Workspace. Prompts, sessions and scripts turn into workflows, skills and knowledge that everyone can see, use and improve.
  • Persistent AI Coworkers. Agents have a name, a role, a memory and standing orders. They keep the knowledge they work with current, and every conversation continues where the last one ended.
  • Combine agentic workflows with deterministic workflows. Every agent step has to pass a check that reads the actual files before the work moves on. Each run leaves a trace of what happened, how long it took and what it cost.
  • RSI (Recursive Self-Improvement). By default, all system components are built around self-improving.

What's in the workspace.

  • Agents. Persistent AI coworkers, each with its own identity, memory and standing orders, running on Claude Code, Codex, Pi, and others.
  • Knowledge. Shared organisational and project context, browsed like a wiki, edited with autosave, verified by humans, exportable as PDF.
  • Skills. Reusable capabilities and ways of working.
  • Workflows. Repeatable multi-step processes.
  • Verification. Gates that check the work against the files on disk.
  • Inspector. The web UI that brings it all together.

This README is the reference for the framework and CLI. For the overview, start at the project README.

Start and use kraftwerk

A workspace is a folder with a kraftwerk.yml: its agents, knowledge, workflows, projects and chats live in it (under kraftwerk-data/), and git can share it with your team. kraftwerk init makes any folder one. kraftwerk is then used in the browser, the inspector, which a kraftwerk process serves from your machine. There are three ways to run that process.

One workspace: kraftwerk ui. In the workspace folder, kraftwerk ui starts a server for it and prints the address, http://localhost:1981 (port: in kraftwerk.yml, or --port, sets another). It runs in the foreground; Ctrl-C stops it. That is all most people need.

Several workspaces: kraftwerk daemon. With more than one workspace, run one kraftwerk for all of them instead of a server and a port each. Start the daemon once and leave it running, or let your Mac start it at login:

kraftwerk daemon                       # the machine's kraftwerk, on http://localhost:1980
kraftwerk daemon start                 # or in the background (`kraftwerk daemon -d`)
kraftwerk daemon install               # or: start it at login from now on (macOS; `uninstall` undoes it)

kraftwerk daemon status                # does it run? pid, uptime, version, workspaces, remote access
kraftwerk daemon logs -f               # what it says (a daemon in the background writes ~/.kraftwerk/logs/daemon.log)
kraftwerk daemon restart               # relaunch it with the current install; `daemon stop` stops it

cd ~/work/team-blau && kraftwerk ui    # opens this workspace in the daemon:
                                       #   http://team-blau.localhost:1980
cd ~/work/relaunch && kraftwerk ui     #   http://relaunch.localhost:1980
kraftwerk workspaces                   # every workspace on this machine, running or not
kraftwerk workspaces stop team-blau    # close one; the daemon and the others keep running

Each workspace is addressed by its slug: the folder name, or slug: in kraftwerk.yml (the name: is only the title and can change freely). Browsers resolve <slug>.localhost to your machine by themselves, so there is nothing to set up, and each workspace keeps its own browser origin. Where a host name per workspace is not possible, http://localhost:1980/w/<slug>/ reaches the same workspace. http://localhost:1980 lists what is open. A workspace with a port: of its own is served there too, so old links keep working. The daemon remembers what was open and opens it again when it starts (after a reboot or an update). Each workspace still runs on its own inside it, with its own .env, routines, git sync and chats. The workspace menu in the header switches between them, and starts a stopped one right in the daemon. The Kraftwerk desktop app starts the daemon for you when it opens. kraftwerk ui hands the workspace to the daemon whenever one is running; kraftwerk ui --standalone starts a server of its own anyway.

From your phone or another computer on the same network:

  1. Let the daemon listen on your network: kraftwerk daemon install --lan (or run kraftwerk daemon --lan). It prints the address, for example http://192.168.1.20:1980.
  2. Open that address on the phone. It shows a pairing form, nothing else.
  3. On this computer, run kraftwerk devices pair (or settings → devices → pair a device) and type the code on the phone.

The phone now sees your workspaces, each at /w/<slug>/ on that address, and the workspace menu switches between them. One pairing covers all of them (see Inspector on your phone).

From anywhere (mobile data, another Wi-Fi), with no port opened:

  1. kraftwerk remote on. The daemon connects to the kraftwerk relay.
  2. kraftwerk remote pair, or settings → devices → pair a device, which shows a QR code. Open the link (scan the code) on the phone and tap Pair.

The phone runs the same web UI, end to end encrypted through the relay (see From anywhere: the relay).

What to do first in the inspector. Talk to Ralv, the workspace's general chat, in plain words: it knows what is in the workspace and helps set up the rest. From there:

  • Agents are persistent coworkers with a role, a memory and routines. Open one and chat with it; its sessions continue where they ended.
  • Projects gather a goal, a brief and links; every chat in a project starts with that context.
  • Contacts, places and events are the workspace's records of who, where and when — one Markdown file each, referenced anywhere as contact:<slug>, place:<slug>, event:<slug>.
  • Workflows are repeatable processes with checked results; run one from its page or with kraftwerk run <workflow> "<request>".
  • Knowledge is the shared, human-verified context agents read.

Everything you see in the inspector is files in the workspace folder, so the CLI (kraftwerk --help) and git work on the same things.

Under the hood

Deterministic workflow-as-code over headless agent harnesses, in the spirit of super-simple-software-factory. Code owns the control flow, agents work inside bounded phases. "Agent proposes, code disposes."

Nothing binds to an SDK. Every agent phase spawns one short-lived CLI process on the agent's harness, then judges the result afterwards against a typed JSON envelope and file gates. Failed checks are corrected in the same session, never by a cold restart. Every run leaves a trace.jsonl event log and ends with a time, token and cost summary table.

Install

You need Node 20 or newer and at least one agent harness on your PATH: Claude Code (claude), Codex (codex), or Pi (pi). See Harnesses for how to get them. kraftwerk doctor checks for all of it and names whatever is missing. It also lists what older versions left behind: a workspace still served by an older kraftwerk (relaunch it), several kraftwerk installs on your PATH at different versions, the old ~/.kraftwerk/projects registry, chats and runs in old formats. Those are notes, never failures, and doctor changes nothing.

Install it globally to get the kraftwerk command in every project:

npm install -g @netnodeag/kraftwerk
kraftwerk --version

Without installing, every command works prefixed with npx, at the cost of a download per call:

npx @netnodeag/kraftwerk --version

Upgrading is the same command with @latest, or "check for updates" in the inspector's info popover: a newer version gets an "update now" that runs the install through the server (POST /api/update, output in the popover, GET /api/update for its state) with the npm that belongs to the Node the inspector runs on. It is refused inside a container (rebuild the image) and when the global folder is not writable by the inspector's user (a system Node that needs sudo) — then the command is shown to run by hand. A running inspector serves the version it started with and offers a relaunch once a newer version is on disk, right after "update now" or after a manual install.

npm install -g @netnodeag/kraftwerk@latest
kraftwerk workspaces               # every workspace on this machine, running or not

Consume

Any repo with workflow folders under workflows/ (or src/workflows/) is already a complete consumer. No package.json, no local dependency, YAML workflows only:

cd your-project
kraftwerk init                     # scaffold kraftwerk.yml + kraftwerk-data/ (workflow, agent, knowledge)
kraftwerk doctor                   # preflight: harness CLIs, docker, workflows, declared env vars, legacy leftovers
kraftwerk run hello "Was ist kraftwerk?"
kraftwerk ui                       # inspector on http://localhost:1981 (see Start and use kraftwerk)

The same without a global install:

npx @netnodeag/kraftwerk init
npx @netnodeag/kraftwerk run hello "Was ist kraftwerk?"
npx @netnodeag/kraftwerk ui

Your own workflows are just more folders under workflows/, each a workflow.yml plus prompt files, discovered automatically (see YAML workflows). Or let a coding agent build one. npx @netnodeag/kraftwerk create "<what it should do>" prints a self-contained brief that Claude Code or Codex follows end to end.

For a local checkout or a programmatic consumer with TS workflows, custom gates and approval loops, add the dependency. The kraftwerk alias keeps imports short:

// package.json of your workflow project
"dependencies": { "kraftwerk": "file:../kraftwerk" }   // or: "npm:@netnodeag/kraftwerk"
import { defineAgent, Run, runCli, fileNonEmpty, envelopeContract } from "kraftwerk";

The kraftwerk CLI

The CLI ships with the package. Use kraftwerk … after a global install, or npx @netnodeag/kraftwerk … anywhere without one. npm link in a dev checkout also gives you the bare command. Workflows are auto-discovered under src/workflows/ (or workflows/), meaning every folder with a workflow.yml and every top-level .yml file. Every command works from any subdirectory, because the CLI walks up to the project root, marked by kraftwerk.yml, a workflows root, or .git.

kraftwerk init                          # make this repo a consumer: kraftwerk.yml, workflows/, example
kraftwerk list                          # table: workflows, steps, agents (with harness/model); --json
kraftwerk run tagline "https://..."     # run; --yes, --verbose
kraftwerk run                           # interactive: pick workflow, type the request
kraftwerk runs                          # past runs from output/*/trace.jsonl; runs show <id> for detail
kraftwerk knowledge                     # Knowledge: OKF bundles (list/get/put/verify/search/...)
kraftwerk ui                            # start this workspace: http://localhost:1981 (or open it in the daemon); --port, --lan, --standalone
kraftwerk daemon                        # one kraftwerk for every open workspace: http://<slug>.localhost:1980; --port, --lan
kraftwerk daemon install                # start the daemon at login (macOS LaunchAgent); --lan, --port; `daemon uninstall` removes it
kraftwerk daemon status                 # whether it runs (exit 3 when not; --json); daemon start|stop|restart; daemon logs [-n N] [-f]
kraftwerk workspaces                    # every workspace on this machine; workspaces start|stop|forget <ref>
kraftwerk devices                       # paired devices (a phone, another computer); devices pair|revoke
kraftwerk remote on|off|pair            # reach this machine from anywhere through the relay, end to end encrypted
kraftwerk projects                      # goal-scoped project folders; projects create|show|link|log|remove
kraftwerk boards|tasks                  # task boards per project or workspace; tasks create|show|move|assign|log|pick|remove
kraftwerk presentations                 # slide decks (HTML slides, themes, PPTX/PDF/HTML export) per project or workspace; show|create|check|render|remove|templates
kraftwerk contacts|places|events        # who, where and when, one Markdown file each; … show|create|set|remove, events --from/--to
kraftwerk ref <kind:slug>               # resolve a reference (contact:anna-meier, project:relaunch, …); --file <md> resolves every one in a file
kraftwerk trash                         # what was deleted; trash restore|purge <id>, trash empty
kraftwerk journal <agent> ["<entry>"]   # an agent's memory across sessions: print it, add a line (--kind)
kraftwerk doctor                        # preflight: harness CLIs, docker, workflows, declared env vars, legacy leftovers
kraftwerk validate                      # all discovered: schema + semantics + files, exit 1 on failure
kraftwerk validate src/workflows/pitch  # specific paths
kraftwerk create "was der Workflow tun soll"   # for LLM agents: prints a build brief
kraftwerk runner build                  # build the Docker sandbox image (once)
kraftwerk run --sandbox website-check "https://..."   # isolated container per run; --ssh forwards the agent
kraftwerk runner ps / stop <run-id>     # see / stop running sandbox containers

Nothing is deleted at once. Removing an agent, a project, a workflow, a knowledge bundle, a vibeable, a channel, a repository, a chat or a run, in the inspector or the CLI, moves its folder to kraftwerk-data/trash/. The trash mirrors the workspace (trash/agents/<slug>, trash/knowledge/<bundle>, trash/projects/<slug>, trash/chats/<id>, …) and ignores itself in git. The trash screen (#/trash) or kraftwerk trash restore <id> puts an entry back where it came from. Deleting it there, or kraftwerk trash empty, makes it final. A chat's agent transcripts are only removed then, so a restored chat can resume.

The inspector binds 127.0.0.1: its chat runs coding agents against the repo, so it stays off the network unless you ask (--lan), and there only paired devices get in. While bound to loopback it only answers requests whose Host is a loopback name or <slug>.localhost (so a page on another site cannot reach it by re-pointing its own DNS at 127.0.0.1). Inside a container it binds all interfaces instead, because the port mapping is the boundary there (deploy-starter/ publishes to localhost, and its traefik override adds basic-auth). KRAFTWERK_UI_HOST overrides the default either way. A reverse proxy in front of it must set X-Forwarded-Host to the host the browser addressed (Caddy and traefik do by default; nginx needs proxy_set_header X-Forwarded-Host $host;), and its visitors pair like any other device.

The kraftwerk.yml project config

Optional, at the project root, and also the root marker for the walk-up. All fields are optional. workflows: sets the workflows root, output: the run-artifact directory (default output/), knowledge: the OKF bundle root (default knowledge/), and agents: the agent-definition root (default agents/). repos: turns on the repositories folder, projects: the projects, and contacts:, places: and events: each one of the contacts, places and events. cloud: { enabled: false } leaves the workspace out of this machine's record in the kraftwerk cloud.

name: is the title shown in the UI and can change freely. slug: is the workspace's address on this machine. It defaults to the folder name (~/Sites/team-blau becomes team-blau). It must be lowercase letters, digits and dashes, and unique among the workspaces on the machine. kraftwerk ui and kraftwerk doctor warn when two workspaces share one.

A .env next to kraftwerk.yml (KEY=value lines, # comments, quotes) is loaded into every kraftwerk process at start: kraftwerk ui and the server it supervises, so a restart from the UI re-reads the file; the chat agents, routines and workflow runs the inspector spawns; requires: checks in run and doctor. A variable the shell already sets wins over the file. A project started from another workspace's inspector gets its own .env, not the other one's. The file is never synced (it is on the workspace git's deny list and in the .gitignore kraftwerk init writes); kraftwerk doctor lists the names it loaded.

The workspace name in the inspector header is the workspace menu. It lists every workspace this machine knows. Running ones open with a click, and a stopped one starts from its row (in the daemon, when one runs). Each link uses the address you opened this page with: <slug>.localhost on this machine, /w/<slug>/ from a paired device on the network, the path form when you are in it. switcher: adds workspaces kraftwerk cannot discover, such as one on another machine:

switcher:
  - name: other space
    url: https://localhost:1985
    icon: "🛰"   # optional

⌘K (Ctrl K) anywhere in the inspector opens the palette: type a few letters of an agent's, project's or channel's name, description, goal or workspace and hit enter to jump to it. It lists the active agents, the projects (all but archived ones) and the channels of every workspace on this machine, grouped by workspace and running or not (a stopped one is started on the way), read from the workspace registry under ~/.kraftwerk/workspaces, which every inspector keeps current with its roster, project and channel lists — and this workspace's contacts, places and events, by title, role, organisation, address or date.

Triggering from CI, cron, or webhooks

run --json is the machine mode. It runs non-interactively, prints one JSON result object on stdout (ok, runDir, per-phase stats, totals), and sends all narration to stderr. KRAFTWERK_YES=1 equals --yes, and --quiet silences narration. Exit codes: 0 ok, 2 usage or config error (unknown workflow, missing env), 3 run failed (gate, blocked, harness), 1 unexpected.

KRAFTWERK_YES=1 npx @netnodeag/kraftwerk run tagline "https://..." --json > result.json

Workflows declare the env vars they need via top-level requires: [MATOMO_TOKEN, ...]. The engine checks them before anything spawns, and both kraftwerk list and kraftwerk doctor list them.

Remote workflows with --from

list and run accept --from github:org/repo[@ref], or any git URL. The repo is shallow-cloned to ~/.cache/kraftwerk/remotes/, refreshed per call and cached for offline use, and its workflows run locally. Artifacts land in YOUR output/, never in the cache. That way one workflow library serves many projects without vendoring:

npx @netnodeag/kraftwerk run --from github:NETNODEAG/workflows tagline "https://..."

Sandbox mode (--sandbox) runs the workflow in a kraftwerk-runner container, built from runner/Dockerfile. The workflow folder mounts read-only. The run directory bind-mounts straight into the host output/, so trace and artifacts appear live with no copy-back. Env vars come from <project>/runner.env, plus ANTHROPIC_API_KEY and OPENAI_API_KEY pass-through. --run-id pins the run folder name for external triggers, which is what the inspector uses. runner.json in the run dir records container, exit code, and timing.

Runs in the inspector

The runs screen shows every folder under output/runs/, live ones with their phase timeline, finished ones opening on their artifacts. A run's status comes from trace.jsonl first: a run_summary settles it, a failed or blocked phase fails it, and a trace nobody has written to for fifteen minutes counts as aborted. Two markers settle it earlier: runner.json (sandbox container exit code) and trigger.json, which the inspector writes when a launcher it started exits. A folder that never got a trace at all — the launcher died first, typically over a missing env var, and only trigger.log tells why — ages by its newest file and shows as failed instead of running forever; its workflow name is read from the run id.

Every live run has a stop button (POST /api/runs/:id/stop): it ends the detached launcher process group for a local run this inspector started, or docker stops the kw-<run-id> container for a sandboxed one, and answers 404 when neither exists (a run started from the CLI in another terminal). Every finished run has a remove button (DELETE /api/runs/:id) that deletes the folder; a run that still looks live is refused with 409 — stop it first.

run prompts for whatever is missing, both the workflow picker and the request input. Invalid workflows show up red in list with their validation error instead of breaking the listing. create is meant to be run BY an LLM agent such as Claude Code or Codex. It prints a self-contained brief covering the schema example, gates, harness rules and verify ladder, which the agent follows to author the workflow folder and validate and smoke it with this CLI.

Inspector on your phone (paired devices)

kraftwerk ui --lan (or kraftwerk daemon --lan, kraftwerk daemon install --lan for the login item) also listens on your network and prints the addresses (for example http://192.168.1.20:1981). On the daemon, a device opens a workspace at /w/<slug>/ on that address (http://192.168.1.20:1980/w/team-blau/), because <slug>.localhost only works on this machine. The daemon's own address lists them once the device is paired, and pairs it when it is not, and the workspace menu links the others the same way. Anyone who opens one gets a pairing screen, nothing else. To let a device in, make a one-time code on the machine kraftwerk runs on, with kraftwerk devices pair or settings → devices → pair a device, and enter it on the device. The code works once, for 10 minutes. The device stays paired until you unpair it in the same place (kraftwerk devices revoke <id>), and its access ends at once.

One pairing covers every workspace on the machine. The tokens live in ~/.kraftwerk/devices.json as hashes only. A browser keeps its token as an HttpOnly cookie. An app sends it as Authorization: Bearer, or as ?token= on the socket. A paired device can use everything except managing devices, which stays on the machine itself. Requests from the machine itself need no pairing. Inside a container nothing changes: the port mapping is the boundary. KRAFTWERK_UI_TRUST_NETWORK=1 brings back the old behaviour of trusting every network caller, and kraftwerk doctor warns while it is set. Behind a reverse proxy on the same machine, the proxy must send X-Forwarded-For (or X-Forwarded-Host), so its visitors pair like any other device. If it cannot, set KRAFTWERK_UI_REQUIRE_PAIRING=1, which makes every request pair, this machine's own included.

From anywhere: the relay

kraftwerk remote on makes the daemon hold a connection to the kraftwerk relay (kraftwerk cloud; --url for another one). A paired device reaches the machine through it from any network: no port forwarding, no tunnel, and the machine needs no public address. kraftwerk remote pair (or settings → devices → pair a device, with a QR code) gives a link to the cloud's remote page. It carries this machine's key and a one-time code in its # part, which never reaches a server. The phone pairs, then runs the whole web UI through the relay. kraftwerk remote off disconnects, kraftwerk remote shows the state, and settings → devices has the same switch.

Everything between the phone and the machine is end to end encrypted:

  • X25519 key agreement on the machine's long-lived key plus a fresh key on each side per connection, then AES-256-GCM with counters as nonces.
  • The relay sees which machine, when and how much, never what. It cannot forge or replay a message.
  • The phone checks the machine's key (the fingerprint kraftwerk remote shows is on the pairing page), so nobody else can answer in its place.
  • Through the relay, a request counts as a network device's: it needs the device's pairing like on your Wi-Fi, and unpairing ends it at once.
  • One trust remains: the remote page's code comes from the cloud that serves it. A native app will remove that.

Push notifications. The kraftwerk iOS app gets a notification when something new needs you: an approval, a question from an agent, a failed run or routine. Each paired phone gives the machine a key of its own, and the machine seals every notification with it. The relay and Apple pass along only "Something needs you" and the badge count. The app opens the sealed text on the phone. Turning notifications off on the phone, or unpairing it, stops them.

The machine's key and its relay secret live in ~/.kraftwerk/relay.json (owner only). The daemon must run (kraftwerk daemon install), and the Mac must be awake.

For a client of your own, @netnodeag/kraftwerk/client takes the token: createClient({ baseUrl, token }).

Inspector on a server

The repo root ships deploy-starter/. Copy it into your consumer repo as deploy/ and it builds a small image, this package from npm plus the claude, codex and pi CLIs, that serves kraftwerk ui for that repo bind-mounted at /work. compose.yml is localhost-only, reachable over an SSH tunnel. compose.traefik.yml layers traefik routing and mandatory basic-auth on top. Treat that auth as load-bearing. In a container the UI trusts whoever reaches it (the port mapping is the boundary, so device pairing does not apply), and its chat runs coding agents against the mounted repo. Agent logins made inside the container persist in the agent-home volume. This is a different image from the kraftwerk-runner sandbox (runner/Dockerfile) used by run --sandbox.

This machine in the kraftwerk cloud

The kraftwerk cloud is the ~/.kraftwerk registry made reachable over the internet: the daemon (kraftwerk daemon) registers this machine once when it starts and sends a heartbeat while it runs; the workspaces it serves ride along as children of that record, so you can see from anywhere which of your workspaces are up, and where. This is on by default. A standalone kraftwerk ui never registers. Registration is best-effort: a cloud that is down never affects the UI.

The machine's id is its relay id (the key in ~/.kraftwerk/relay.json, created at the daemon's first start with the relay still off), proven with the relay secret. Until someone claims the machine it registers anonymously: workspace names, slugs and ids only, no paths. Claiming happens in the browser — no password passes through the CLI:

kraftwerk login --open    # prints (and opens) the claim link and code, waits for the claim
kraftwerk cloud           # registered? claimed? url, machine id, last heartbeat
kraftwerk logout          # release the machine; a fresh claim code follows
kraftwerk cloud off       # turn it off for this machine (the daemon says goodbye); `on [--url]` back on

The claim code is also shown under Settings → Cloud. The switch and the cloud's origin live in ~/.kraftwerk/cloud.json ({ "enabled": false, "url": "…" }); KRAFTWERK_CLOUD_URL in the environment wins (off turns the feature off — what the test suites set). In kraftwerk.yml, cloud: is a per-workspace veto only:

cloud:
  enabled: false   # leave this workspace out of the machine's record

(cloud.url there is deprecated — honored with a warning when the machine names no origin — and cloud.interval is ignored: the cloud sets the cadence.) /api/meta and /api/cloud report the machine's state and claim code; kraftwerk doctor prints what happens to the workspace.

Projects

Turn on projects: in kraftwerk.yml (or the checkbox in settings) and the workspace gets one folder per project — a goal with everything the agents need to reach it:

projects:
  root: kraftwerk-data/projects   # default; part of the workspace, synced like agents and knowledge
kraftwerk-data/projects/<slug>/
  project.yml   # title, status, goal, records, links
  brief.md      # the goal in full: what done looks like, constraints, stakeholders
  state.md      # current state, rewritten at the end of a session
  log.md        # append-only, newest first: decisions and milestones, dated and attributed

The sidebar lists the projects in the workspace's order, grouped into named sections: drag a project to another place or onto a section, drag a section by its heading, or press Alt+↑/↓ on a project. The order lives in kraftwerk-data/projects/order.yml and travels with the workspace; a new project joins the top, and removing a section moves its projects up.

project.yml holds the one-line goal, a status (active, paused, done, archived), the harness, model and effort its chats run on (the same three fields an agent has; the harness decides, not the caller), the systems of record and the links:

title: Relaunch netnode.ch
status: active
goal: Ship the new site on NodeHive by 2026-11-30
harness: claude                 # claude | codex | pi — every chat in the project runs on it
model: sonnet                   # optional, like an agent's
effort: high                    # optional: low | medium | high | xhigh | max
records:
  - kind: my-netnode            # my-netnode | google-drive | github | bitbucket | notion | slack | url | anything
    workspace: 22
    url: https://my.netnode.ch/workspace/22
    note: tickets, roadmap and meetings
  - kind: google-drive
    title: Contracts and briefs
    url: https://drive.google.com/drive/folders/…
knowledge: [netnode-helpdesk]          # OKF bundle names
vibeables: [launch-tracker]            # folders under the vibeables root
repos: [netnode-frontend]              # folders under the repos root
workflows: [website-check]             # workflow slugs
agents: [max]                          # agent slugs

A system of record says where the truth of the project is managed outside kraftwerk and what usually lives there. It is context, not a credential and not a grant: the agent reaches it through the tools its harness already has (a CLI, an MCP server, the browser), and the harness decides what it may call. Kraftwerk knows a few kinds only to label them and phrase the context better; any other kind passes through as a link with a note.

Links are one-directional lists of slugs. A target that does not exist is shown as "not found" on the project page and told to the agent, never an error — the same rule an agent's knowledge list follows.

Working in a project is chat. Opening a project shows its overview: a box to start a new session — with the project's assistant, or with any agent picked under the box — and the recent sessions, each with a menu to rename or delete it. An open session is named next to the project's title ("Project / Session"); the title leads back to the overview, and the project's context stays beside both. A session scoped to a project starts with the brief, the current state, the records, every link with how to reach it, and the rule for keeping the project current: state.md is rewritten at the end of a session that changed something, so the next session (by anyone) continues without the transcript; log.md is appended through kraftwerk projects log <slug> "<line>" --actor <who> so every line is dated and attributed; brief.md belongs to the user. The working directory stays the workspace root, so linked repositories and knowledge are reachable by path; opening a vibeable moves it into the app folder as in any chat.

Artifacts. The rail's third row, under Chats, lists everything made in the workspace and its projects — workflows, knowledge bundles, apps, presentations, boards, contacts, places and events — newest first, with the project each belongs to. A tile per enabled kind filters the list to that kind (click again for everything), and the + beside its label makes a new one; the search matches titles. Selecting a row shows the thing in the right column — a workflow's own view (run it from there), a knowledge bundle with its concepts, a deck's slides, the app running, a board's columns, a contact to edit — with "open" to go where it lives. Places show on an OpenStreetMap map (all of them; a selected place is zoomed to, with its details under the map, and a dot on the map selects its place), events on a month calendar (a selected event brings its month, with its details under it).

Chats (Ralv) works like a project's overview: the rail's "Chats" row shows a composer that starts a chat with Ralv (or, from the picker, with a specific agent, which opens in that agent's sessions) and the recent chats — Ralv's and every chat in a project, tagged with the project. A project's chat opens in the project, with its context beside it. An open chat is named next to the workspace's title; there are no tabs.

An agent works the same way: clicking it in the rail shows a composer that starts a session with that agent and its recent sessions, each with the project it is filed under. An open session is named next to the agent's name, which leads back; there are no tabs.

In the rail. Home, Chats, Artifacts, the projects and the agents — nothing else. Boards and presentations are on the Artifacts page (and in a project's context column); channels open from a project's recents or #/channels. A project is one row, with nothing under it. Its agents are in the overview's session picker, and every recent session names who it is with (the assistant, an agent, or coworkers). Its apps (the linked vibeables) are cards on the overview, between the composer and the recents: pin an app from the apps tab of the project's context column, or unpin it there; a card opens the project with the app at full size (#/projects/<slug>?app=<vibeable>). The project's boards and presentations are tabs of the context column — the boards tab shows each board as its columns side by side, the presentations tab lists the decks with a "new presentation".

Coworkers. "Add coworker" on a project chat works as on an agent session: the chat becomes a channel whose members are the agents you pick, with everything said so far and the project's brief, state, records and links as context for every member (project: in channel.yml). The channel lives on the channels screen and stays listed under the project. The first agent picked answers messages that mention nobody.

kraftwerk projects                                   # list: title, status, goal, links
kraftwerk projects create "Relaunch netnode.ch" --goal "Ship by November"
kraftwerk projects show relaunch-netnode-ch          # definition, records, link states, state, log
kraftwerk projects set relaunch-netnode-ch --harness codex --model gpt-5.6-sol --effort high
kraftwerk projects link relaunch-netnode-ch workflows website-check
kraftwerk projects log relaunch-netnode-ch "Decided on NodeHive." --actor human:lukas
kraftwerk projects remove relaunch-netnode-ch

The registry of workspaces on this machine, which answered to kraftwerk projects until 0.48, is kraftwerk workspaces now.

Boards and tasks

Turn on boards: in kraftwerk.yml (or the checkbox in settings) and every project can have task boards, and the workspace its own:

boards:
  root: kraftwerk-data/boards   # default; the workspace's boards (a project's live in projects/<slug>/boards/)
  columns: [draft, todo, progress, review, done]   # default for boards that name none
projects/<slug>/boards/<board>/board.md            # title, columns, default agent, stale_after + the board's rules
projects/<slug>/boards/<board>/tasks/<n>-<slug>/   # one task: task.md, log.md, its files
kraftwerk-data/boards/<board>/…                    # workspace boards, same shape

A column is a slug or {slug, label, limit, done, review}: review: true means tasks there wait for a human (they count as attention), done: true means finished. The column is a field of task.md, so a move is a one-line diff and the task's folder never moves. Task folders are numbered per board (50-write-a-press-release); the number names the task in the CLI, the folder name is its reference (task:50-write-a-press-release). The log is in the journal format: ## 2026-11-05 days, * 16:40 Moved Todo → In progress: starting (lisa/claude) lines, newest first; kraftwerk writes it on every create, move, assign, pick and file change, and agents and humans add lines.

kraftwerk boards [--project <p>] [--all]            # the boards; boards show|create|set|remove
kraftwerk tasks [--project <p>] [--board <b>]       # the tasks of a board (default: the project's `tasks` board)
kraftwerk tasks create "<title>" --status todo --assign lisa --file $KRAFTWERK_TEMP/task.md -m "…"
kraftwerk tasks show 50 | move 50 review -m "…" | assign 50 max -m "…" | assign 50 human | log 50 "…" | pick --as lisa | remove 50

Agents and the board. A project chat and every agent see the boards in their context: columns with counts, the open tasks, the rules written in board.md, and the commands. Inside a session the CLI knows who it is (KRAFTWERK_AGENT, KRAFTWERK_ACTOR), so tasks pick and tasks log need no name. Waking is event-based: whenever a task has an agent as assignee and sits in a work column — created there, moved there, assigned, handed over — that agent gets a session with the task as its scope (the task block: task.md, the files, the whole log, so a hand-over reads like a message from the previous agent), or the message is posted into its open session for that task. Draft, Review and Done never wake anyone; an agent acting on its own task does not wake itself; a board's agent: is the default assignee for tasks entering Todo unassigned. The events are lines in <output>/boards/events.jsonl, local to the machine, so a clone of the workspace never starts the same work twice. Only a human moves a task into a done column; tasks assign <n> human clears the assignee and puts the task into Review.

Presentations

Turn on presentations: in kraftwerk.yml (or the checkbox in settings) and every project can have slide decks, and the workspace its own:

presentations:
  root: kraftwerk-data/presentations   # default; the workspace's decks and templates (a project's decks live in projects/<slug>/presentations/)
projects/<slug>/presentations/<name>/deck.md     # the slides: markdown (and HTML) on a template's layouts
projects/<slug>/presentations/<name>/assets/     # the pictures the slides show
projects/<slug>/presentations/<name>/exports/    # what `render` wrote: .pptx, .pdf, .html, slides/NN.png
kraftwerk-data/presentations/<name>/…            # workspace decks, same shape
kraftwerk-data/presentations/templates/<slug>/   # your own themes: template.yml, theme.css, fonts and pictures beside them

A deck is one markdown file. The headmatter names the title, the template and, optionally, CSS of the deck's own; a --- line starts the next slide, and an optional YAML block right after it sets the slide's layout: (and background:, slots:). The content before the first ::slot:: marker is the slide's title (a leading heading) and body; ::left::, ::image::, ::chart::, … fill the layout's named slots, ::notes:: are the speaker notes. A slot holds markdown, raw HTML, a picture (![caption](assets/x.png)), a table, or a fenced chart block (YAML: kind column | bar | line | pie | doughnut | area, categories, series with name and values).

---
title: Why we move to NodeHive
template: netnode
layout: title
---
# Why we move to NodeHive
::subtitle::
Content pipeline review · November 2026
::notes::
Open with the migration timeline.

---
layout: content
slots:
  title: { size: 30 }
  figures: { kind: html, x: 0.44, y: 3.2, w: 9.12, h: 1.6 }
---
# One source, every channel
- Editors write **once**, in markdown
- Every channel reads the same API
::figures::
<div class="kpis"><div class="kpi"><strong>−60%</strong><span>time per text</span></div></div>

Slides are HTML. The editor, the thumbnails, present mode, the HTML file, the PDF and the PNGs all show the same live HTML in the template's theme; PDF and PNG are printed from it by a headless browser (Playwright's Chromium when installed, else the installed Chrome; without either the PowerPoint model's raster stands in, with a warning). The PowerPoint file is a native model of the same slots — text, lists, pictures, native charts and tables stay editable — its HTML pieces placed as pictures of themselves. A slide may move or restyle a layout's slot, or add boxes of its own, in its slots: (positions in inches on a 10 × 5.625 canvas); kinds are text, markdown, image, chart, table, html and embed (a URL, or a whole page with scripts, run sandboxed).

The design is the template: template.yml names the canvas, fonts, colors, layouts (slots placed in inches, decorations behind them) and snippets — the theme's ready-made pieces (cards, key figures, a flow, a timeline …) for the editor's insert menu — and theme.css styles the slides, scoped to them (:root means the slide; @font-face fonts sit beside the file). kraftwerk presentations templates init <slug> copies the built-in one to start from; the deck's own css: goes on top for one deck. What a slot cannot show (an empty required slot, an unknown layout, a missing picture, a broken chart block) is a warning with the slide and the slot, in the editor, in show and in check.

kraftwerk presentations [--project <p>] [--all]            # the decks; presentations show|create|check|render|remove
kraftwerk presentations create "<title>" --project relaunch [--template netnode] [--from deck.md]
kraftwerk presentations show nodehive-pitch --project relaunch     # slides, the template's layouts and slots, its pieces, the warnings
kraftwerk presentations check nodehive-pitch --project relaunch    # exit 1 on errors
kraftwerk presentations render nodehive-pitch --project relaunch --png --pdf --pptx --html [--out <dir>]
kraftwerk presentations templates [init <slug>]

The editor (#/projects/<slug>/presentations/<name>) is the slide itself, live, and works like ChatGPT's presentation editor:

  • Slides. A numbered strip (drag to reorder; right click to duplicate, hide, move or delete), a grid of all slides, a new slide from a picture of each layout, zoom from fit to 150 %. Hidden slides stay in the deck and out of present mode and the exports.
  • On the slide. Every slot is a box: click to select, ⇧-click or drag a frame for several, drag to move with guides to the slide's edges and middle and to the other boxes, resize from the corners (⇧ keeps the proportions), rotate from the handle below, nudge with the arrows, ⌘C/⌘V/⌘D, Delete, ⌘Z/⇧⌘Z. A double click types into a box where it stands in the slide's own fonts, or opens the source of HTML, charts and embeds beside the slide, which follows as you type.
  • Format. The bar over a box sets font, size, bold, italic, alignment and color; the Format panel adds position, rotation, alignment to the slide, underline and strikethrough, text and fill colors (the theme's or any), corners, border, shadow, opacity and how a picture fits; several boxes align to each other; right click orders the layers. With nothing selected it sets the slide's background, a color or a picture.
  • Insert. Text, a picture (uploaded into assets/, or dropped onto the slide), a chart, a table, shapes (rectangle, rounded, circle, line, arrow, star), HTML, an embed, and the theme's pieces.
  • Design. The design panel switches the theme with a preview of the slide in each and takes CSS for this deck; slides carry their number (data-n, data-nn, data-of) for the theme to show.
  • Agent. "Ask an agent" under the slide opens a chat docked beside the deck with the deck and the slide named; the agent edits deck.md and the slide changes in front of you (the server watches an open deck's folder, so an agent's save shows at once). "ask" on a box has one-click requests: shorten, rewrite clearer, bullet points, make it a chart, translate to English or German, speaker notes, or something else.
  • From a brief. A new deck can start from what it is about, typed or from notes files (md, txt, csv, json, html, yaml): the agent beside it answers with the outline, then writes the slides in the theme while you watch.
  • Review. Comments on a slide or on one box (right click → Comment…, or the comments button): a pin on the box, a count on the slide and in the bar, answers, resolve and reopen. They live in comments.json beside deck.md, pointing at the slide's id: (given when its first comment lands), so they follow the slide when it moves. Agents read and resolve them: kraftwerk presentations comments|comment|resolve.
  • Share. Copy a link to the editor or to present mode (for anyone who reaches this workspace: paired devices, members), or download the deck as one HTML file that opens anywhere.
  • Several editors. A person, an agent and another window can work on one deck: every save names the version it was made on, a save over a newer file is refused (409) and the editor merges slide by slide — both sides' changes stay; a slide changed on both sides keeps theirs and says so (⌘Z brings yours back).
  • Charts. A chart opens as a data grid beside the slide: kind, title, a row per category, a column per series (add, rename, remove); the source tab shows the same block as YAML.
  • Version history. Earlier versions of the deck are kept in .history/ beside deck.md: before saves (once a minute while you edit) and whenever an agent or a tool changes the file; the newest 50 stay. The history panel shows each version's slides and restores one as an ordinary edit (⌘Z takes it back).
  • Transitions. Each slide comes in with a fade (the default), a slide, a zoom or a cut (transition: in its head, or the Format panel with nothing selected); present mode and the HTML file play them, the PowerPoint file gets fade, push and zoom.
  • Linked from tasks and chats. A task's links (its refs:) take a deck from a list; the chip opens it, and the agent working the task gets the deck's folder with the task. In a chat, presentation: offers the decks and the sent presentation:<name> links to the deck. The deck's share menu copies that reference and lists the tasks that link it.
  • Slide numbers. On by default, drawn by the theme bottom right (not on title and section slides). The Format panel with nothing selected turns them off for the deck (numbers: false in the headmatter) or for one slide (number: false in its head). The PowerPoint file carries them as slide-number fields, so they keep counting when slides move there.
  • Who else is here. Everyone with the deck open (other people, other tabs) shows as an avatar in the bar and a dot on the slide they are on; the box they have selected is outlined in their color with their name. Click an avatar to go to their slide. It lives in memory only and follows the live connection: a closed tab is gone at once.
  • Present. …/present[/<n>] full screen (arrows, space, Escape, N for the notes, F for fullscreen, a clock); …/presenter[/<n>] is the presenter view with the current and the next slide, the notes and the clock. PowerPoint, PDF and HTML download from the bar.

Agents and the deck. A project chat and every agent see the project's decks in their context with the format, the theme's pieces and the commands; agents write deck.md directly, put pictures into assets/, and look at the slide images render --png writes before handing the deck over.

Contacts, places and events

Who, where and when, kept by the workspace itself so people and agents can point at them reliably. Three features with one shape — turn each on in kraftwerk.yml (or its checkbox in settings):

contacts:                         # people and organisations
places:                           # addresses and points of interest
events:                           # dated one-off entries
  root: kraftwerk-data/events     # default kraftwerk-data/<kind>; part of the workspace, synced like agents and knowledge

One Markdown file per entry: the typed fields in YAML frontmatter, free notes in the body. Readable and editable by hand, by agents and by the UI; versioned with the workspace, deleted into the trash.

kraftwerk-data/contacts/anna-meier.md          # title, role, org, email, phone, place, url, tags, records + notes
kraftwerk-data/places/netnode-office.md        # title, address, lat, lng, url, tags, records + notes
kraftwerk-data/events/2026-11-03-kickoff.md    # title, start, end, place, contacts, project, url, tags, records + notes
---
title: Kickoff
start: 2026-11-03T14:00           # ISO date or date-time; events are one-off (recurrence lives in the calendar entry)
end: 2026-11-03T16:00
place: netnode-office             # a place's slug
contacts: [anna-meier]            # contact slugs
project: relaunch                 # a project's slug
records:
  - kind: calendar
    url: https://calendar.google.com/event?eid=…
---
Agenda: scope, timeline, who does what.

References. Fields that point at other entries hold slugs, and anything in the workspace is referenced in text as kind:slug — contact:anna-meier, place:netnode-office, event:2026-11-03-kickoff, and likewise project:, agent:, workflow:, repo:, vibeable:, knowledge:. The same resolver serves a project's links, an event's place and contacts, a token in notes or in a chat reply, the ⌘K palette and the context an agent starts with: a target that does not exist is reported ("not found"), never an error, and only these tokens match, so https: never does. Projects link to contacts, places and events like to anything else (kraftwerk projects link <slug> contacts anna-meier), and a project chat starts with them as one-liners.

Systems of record. As on a project: where the truth lives outside kraftwerk — a contact's CRM record and newsletter subscription (crm, newsletter), an event's calendar entry (calendar), a place on Google Maps (google-maps). A place without a maps record gets a Google Maps link derived from its coordinates or its address.

Agents get a "Contacts, places and events" block in every chat: what exists, the next events, the token syntax and the CLI — they read and write the files through it, never by hand:

kraftwerk contacts                                   # list (--json); places, events the same
kraftwerk contacts create "Anna Meier" --org ACME --email [email protected] --place netnode-office
kraftwerk contacts show anna-meier                   # fields, records, references with their state, notes
kraftwerk contacts set anna-meier --role CTO --email ""   # "" clears a field; --notes-file replaces the notes
kraftwerk places create "NETNODE Office" --address "Zentralstrasse 1, 6003 Luzern" --lat 47.05 --lng 8.3
kraftwerk places show netnode-office                 # prints the Google Maps link
kraftwerk events create "Kickoff" --start 2026-11-03T14:00 --place netnode-office --contacts anna-meier --project relaunch
kraftwerk events --from 2026-11-01 --to 2026-11-30   # the month's events
kraftwerk ref contact:anna-meier                     # ✔ with its label, or ? not found (exit 1); --file notes.md checks a whole file
kraftwerk contacts remove anna-meier                 # to the trash

Over the API each kind is its own route family — contacts.list|get|create|save|delete, places.*, events.* (GET /api/events?from=&to=) — and refs.resolve resolves tokens or every reference found in a text. In the inspector each kind is a page of the workspace (expert mode) with the list beside the open entry; a project's context column shows its contacts, places and events.

Files

Files are the material of the work, in any format: briefings, contracts, designs, spreadsheets, deliverables. They are plain folders with two kinds of owner, chosen by how long a file lives:

kraftwerk-data/files/                    # workspace files: shared, outlive any project
kraftwerk-data/projects/<slug>/files/    # a project's files: archived and trashed with the project

Agents own no files. They work in the project's files when they work on a project, and may link workspace folders in agent.yml (files: [contracts]) the way they link knowledge. Every session's context names the folders, the file count and the latest changes, not every file, and tells the agent to put what it produces in files/deliverables/ (or files/inbox/ without a project). Because they are plain folders, agents read and write them like any directory, and you can drop files in with Finder.

The files page (#/files) has the workspace and every project on the left, the folder in the middle, and a preview on the right for images, PDFs, video, audio, markdown, CSV and text. Drop files anywhere on the list to upload. A project's chat has the same browser in its context column. Files are versioned in the workspace git; a file over 25 MB stays local, listed in the files root's own .gitignore and marked "local only". Deleting goes to the trash (trash/files/…, mirroring the folders).

Files are not knowledge. A knowledge bundle holds curated, verified concepts, and a file becomes part of one as evidence, in the bundle's references/ folder, which concepts cite in sources (OKF §5.1, §6.3).

Persistent agents

The inspector's "agents" screen turns chat agents into persistent teammates. An agent is one folder under the project's agents/ root:

agents/max/
  agent.yml     # name, emoji, description, harness, model, effort, workflows, knowledge, vibeables
  system.md     # the agent's system prompt (its role)
  journal.md    # the agent's own memory across sessions (written through `kraftwerk journal`)
# agents/max/agent.yml
name: Max
emoji: 🛠️
description: Runs and explains this project's workflows
harness: claude        # claude | codex | pi (which chat backend runs it)
model: sonnet          # optional; harness default when omitted
effort: medium         # optional: low | medium | high | xhigh | max
workflows: [tagline, website-check]
knowledge: [customer-support]   # OKF bundles the agent consults & maintains
vibeables: [support-dashboard]  # optional: apps under the vibeables root the agent builds & maintains
skills: [report-html]           # optional allowlist; omit = all skills, [] = none

Sessions with an agent are ordinary chats scoped { kind: "agent", slug }, listed per agent in a second sidebar. On the first message the agent gets its role plus its connected workflows, knowledge bundles and vibeables injected as context. That context includes how to run workflows (KRAFTWERK_YES=1 npx kraftwerk run <workflow> "<request>") and how to read and write knowledge through kraftwerk knowledge, with writes stamped with the agent's own actor, <slug>/<harness>. So the agent triggers its own workflows when a request matches, and keeps its bundles current.

Every agent keeps a journal, agents/<slug>/journal.md: what it learned, decided, promised and finished, one line per entry under a dated heading, newest first. The recent days (about 4,000 characters) are part of every new session's context, together with how to add to it:

kraftwerk journal max                                          # print it
kraftwerk journal max "send the summary on Friday" --kind promised   # learned | decided | promised | done | note

The agent's own lines are not signed; a line from anyone else is stamped with its actor. In the inspector, the agent's context column has a journal tab, where you can also add a note for the agent to remember (GET/POST /api/agents/:slug/journal). That is what makes an agent the same colleague next week, whichever chat you open.

What needs you. Everything that blocks an agent, an approval or a question it asked, plus failures nobody has opened yet, is one list (GET /api/attention), oldest first, each item with its owner: the agent, the project or the channel. The rail puts a red count on every row that leads there: the agent, the project (its own chats and its agents'), Ralv, and the projects header while folded. The bell lists these items under "needs you", grouped by owner, with finished runs and routines below as "updates". next in the top bar (⌥N) opens the next waiting item where it lives: the chat, scrolled to the card, which lights up, with the project's or agent's context beside it. Answer, press next again. Notification titles name the owner first ("🦊 Lisa needs approval").

Under each agent's name the rail shows what it is doing now: waiting for you (an approval or a question, with a red light), working (in which chat, for how long, with a pulsing light), the next routine due within a day and a half, or when it was last active (GET /api/agent-status).

A chat keeps the agent's own session. The ACP session id is stored with the chat (sessions in meta.json), and the next process — after an inspector restart, or once the idle reaper released the agent — resumes it over session/resume, so the agent continues with its memory rather than with the transcript as a summary. A session that cannot be resumed (its transcript is gone) falls back to a fresh one and says so in the thread. "Fork" branches a chat: a new chat with the same transcript whose agent continues from a copy of the session (session/fork, claude), leaving the original as it is. Chats over ACP negotiate claude's native subagent and async task streams: a delegated subagent shows as its own card with its stream folded underneath, background work (backgrounded shells, monitors) as task cards that outlive the tool call, and a context compaction as a "compact" card with the token counts. Session failures the harness reports (a rate limit, an expired login, a provider outage) arrive structured, not as prose: a card names the category and the action the harness recommends, retry, sign in again, or start a fresh session (POST /api/chats/:id/reset-session forgets the stored session id).

The rest of what the adapters announce is in the chat too. The agent's plan is a checklist card that updates in place. Its questions (Claude's AskUserQuestion, MCP elicitations) are form cards answered in the thread — unattended sessions skip an unanswered question after the same deadline as permissions. A message sent while the agent works is steered into the running turn instead of waiting (POST /api/chats/:id/steer). The header shows who the agent is signed in as, and in expert mode its context use and cost plus the model and thinking settings it lets you change live (POST /api/chats/:id/config). The agent's slash commands join the skills in the / menu. Each turn ends with the files the agent says it changed. Background tasks that can be stopped have a stop button (POST /api/chats/:id/task-stop). Deleting a chat moves it to the trash; the agent's own sessions go when it is deleted from there, and "continue a session" on the new-chat screen lists the agent's sessions in the project (GET /api/agent-sessions) to pick one up as a chat.

Files dropped, pasted or attached in the composer go with the message (POST /api/chats/:id/attachments, then attachments on the message): they are stored under the chat's folder, images reach the agent as image blocks (a screenshot is seen, not described), text files as embedded resources, everything else by path.

Model and effort ride on backend-specific channels. The claude adapter takes the model via ACP session options and the thinking budget via MAX_THINKING_TOKENS. Codex gets a CODEX_CONFIG env override (model, model_reasoning_effort). Pi gets --model and --thinking flags. Agents are created and edited in the UI, or by editing the files, since the definition is read fresh for each new session.

Repositories

Turn on repos: in kraftwerk.yml (or the checkbox in settings) and the workspace gets one folder for the git repositories its agents work on:

repos:
  root: kraftwerk-data/repos   # default: repos/ — git-ignored, never synced

The folder is the registry. Whatever has a .git directly under the root is a repository, whether the "repositories" screen cloned it, kraftwerk repos add <url> did, or an agent ran git clone there. Every entry is read live from git: origin, branch, head, uncommitted changes, ahead/behind. Each clone has a page (#/repos/<name>, the "changes" button) that shows what is happening inside: the changed files with their diff against HEAD, the line counts, and the recent commits with the unpushed ones marked — click a commit for its patch. It polls, so an agent working in the clone is watched live. Secrets (.env, keys) are never shown, as on the git screen. The list clones by url, fetches and fast-forwards clean clones, and removes them (refusing while they hold unpushed or uncommitted work). Cloning uses your own git credentials and never prompts, so a private remote has to work from a terminal first.

Every agent, and the inspector assistant, gets the list as context: where the root is, what is cloned, and how to add more with the CLI. So "look at the widgets repo" works as soon as it is cloned, and "clone github:org/widgets and look at it" works before.

kraftwerk repos                                  # name, branch, head, state
kraftwerk repos add github:org/widgets           # or any https / ssh url
kraftwerk repos add <url> --name tools --branch dev
kraftwerk repos add <url> --depth 1              # shallow clone of a large repository
kraftwerk repos update widgets                   # fetch, fast-forward when clean
kraftwerk repos remove widgets [--force]

GET /api/repos returns the same list for automation. The root must be a folder inside the project (never the project itself): it is excluded from the workspace git sync, the first clone adds it to .gitignore, and kraftwerk doctor warns when git does not ignore it.

Skills in chat

Chats, both general ones and agent sessions, can use skills. These are Claude-style instruction packages, one folder per skill with a SKILL.md holding YAML frontmatter (name, description) and then the instructions. Four roots are discovered, each shadowing same-named skills in the roots below it:

<project>/agents/<slug>/skills/<name>/SKILL.md   # private to that one agent
<project>/<skills root>/<name>/SKILL.md          # workspace, git-tracked (kraftwerk.yml `skills`, default skills/)
<project>/.claude/skills/<name>/SKILL.md         # git-tracked, per project
~/.claude/skills/<name>/SKILL.md                 # personal, per user

Agent skills are visible only to that agent's sessions and always apply. A agent's skills: allowlist narrows the shared roots only. Manage them in the agent profile under "own skills", or by editing the files.

Every chat lists its visible skills as context under "## Your skills", so the agent reaches for one when the request matches. Typing / in the composer opens an autocomplete over them. Sending /<name> <args> expands the skill's SKILL.md into the prompt, which is what makes this work identically on claude, codex, and pi. On top of that, claude discovers .claude/skills natively (a agent's skills: allowlist narrows that via ACP session options) and pi loads each visible skill folder via --skill. Agents take an optional skills: