@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
Maintainers
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:1981Needs 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 runningEach 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:
- Let the daemon listen on your network:
kraftwerk daemon install --lan(or runkraftwerk daemon --lan). It prints the address, for examplehttp://192.168.1.20:1980. - Open that address on the phone. It shows a pairing form, nothing else.
- 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:
kraftwerk remote on. The daemon connects to the kraftwerk relay.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 --versionWithout installing, every command works prefixed with npx, at the cost of a
download per call:
npx @netnodeag/kraftwerk --versionUpgrading 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 notConsume
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 uiYour 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 containersNothing 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.jsonWorkflows 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 remoteshows 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 onThe 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 knowledgekraftwerk-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 attributedThe 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 slugsA 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-chThe 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 noneprojects/<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 shapeA 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 50Agents 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 themA 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 (),
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.mdand 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.jsonbesidedeck.md, pointing at the slide'sid:(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/besidedeck.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 sentpresentation:<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: falsein the headmatter) or for one slide (number: falsein 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 knowledgeOne 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 trashOver 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 projectAgents 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, [] = noneSessions 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 | noteThe 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 syncedThe 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 userAgent 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:
