kolu
Docs

the workspace daemon & its CLI · alpha

Padi: the memory
under kolu.

padi — the per-host workspace daemon

padi is the per-host daemon that remembers the memory under kolu — what a Kolu workspace is: terminals, sessions, restore state, repo context, the code tree, git status, diffs, and agent awareness. It sits above kaval, which owns the live PTYs. The split is simple: kaval keeps shells alive; padi keeps the workspace legible. The whole layer stack is in Architecture; padi is a @kolu/surface daemon, with the drain-capable live recycle that keeps a client connected across an upgrade.

Where it sits

The browser talks to kolu-server, the server binds padi, and padi supervises its own kaval. Restart the web shell and padi still has the workspace. Restart padi and kaval still has the PTYs. The layers recover on different clocks because they own different state.

kolu-server binds padi, padi supervises kaval, and kaval owns PTYs. padi holds the workspace record while kaval holds the live shells.FACEWORKSPACEPTY OWNERkolubrowser + serverpadisession · registry · agentsthe workspace recordkavalPTYs + screenssurfaceunix socketstate-root on disk

Steady-state supervision

“padi supervises its own kaval” is continuous, not a boot-time step. Every ten seconds padi asks its kaval three read-only questions over the socket, and a kaval that cannot answer them in five seconds is not busy — it is stuck. Three such answers in a row (about half a minute) and padi runs, by itself, exactly what the Restart kaval button runs: save the session, stop the stuck daemon, start a fresh one, and offer your terminals back for restore. One slow moment changes nothing; only a real stretch of silence does.

If three restarts in a row do not produce a daemon that answers, padi stops restarting and leaves the card and the button — a hot restart loop is not a repair. The kaval-down card is for the genuinely unrepairable now, not for every case.

The line between padi and its kaval can drop while the daemon behind it is perfectly healthy — still running, still holding every terminal, still able to answer. From the outside that looks exactly like a stuck kaval, and it is the opposite: nothing is broken, and there is only a connection to re-make. padi re-makes it by itself, about a second after it drops, and keeps trying — backing off to once every half-minute — for as long as it takes. It only ever re-makes a line to a daemon it has just checked is still answering, so the ordinary outcome is that it takes that daemon back over: your terminals and the agents inside them keep running throughout, there is nothing to restore, and kolu says so once before the card clears itself.

Two narrower cases end somewhere else, and kolu tells you which one you got rather than showing you the same line for all three. If the daemon exits in the moment between that check and the re-connect, the re-connect starts a fresh one; and if a re-connected daemon turns out to hold terminals kolu cannot account for, kolu replaces it deliberately rather than leave terminals it cannot see. In both, the message is the restart one — “kaval was unresponsive — kolu restarted it; your session is ready to restore” — and your session comes back through the restore card.

A daemon that is genuinely gone is a different fact and gets the different answer. Re-making a line to a daemon that is not there would mean starting a new one, which is a restart — so padi leaves that to the ten-second check above, and to its three-strike limit, which is what keeps a kaval that dies as fast as it starts from being restarted forever.

kaval watches itself from the other side of the same wire: it asks its own address a trivial question on the same cadence, and three unanswered self-checks in a row make it exit non-zero rather than sit there accepting connections it cannot serve. That covers the case padi cannot — a kaval nobody is currently watching — and turns “stuck forever”, which nothing handles, into “stopped”, which the supervisor already handles.

While a host’s kaval is down, that host’s dot is amber, not green.

What padi knows

kaval only knows terminals. padi is where terminal observations become a workspace: repo-aware, agent-aware, resumable, and readable from more than one face.

terminal registry

which terminals exist, where they belong, and how to re-open them after the web layer moves.

workspace memory

the session record, activity feed, restore target, and durable state-root for one host.

repo context

repo, branch, pull request, checks, cwd, foreground command, file browsing, git status, and diffs.

agent state

whether a terminal’s agent is working, waiting, or asking for you, folded into one live surface.

The thin shell face — padi-tui

padi-tui reads the same workspace surface kolu uses. Where kaval-tui shows what’s running in each PTY, padi-tui shows what each terminal is in: repo, PR, checks, foreground command, live byte activity, and agent state. That is the state behind the multi-agent dock.

padi-tui status
padi-tui watch
padi-tui wait a3f10000 --until awaiting,waiting
padi-tui create --toplevel -- claude
ID        REPO·BRANCH         PR        AGENT             FOREGROUND        IDLE
a3f10000  kolu·feat/dial-ssh  #1412 ✓   claude · working  node              2s
b7c20000  kolu·master         —         codex · waiting   codex             1m
c9d40000  kolu·fix/fold       #1408 ✗   —                 nvim              5m

Usually no flag. padi keys its socket by a digest of its state-root, so inside a kolu terminal $PADI_SOCKET makes padi-tui flag-less — an agent driving its siblings never guesses a path. padi also puts the tools themselves on that terminal’s $PATHpadi-tui, kaval-tui, and the kolu whose mcp face agents connect to — taken from padi’s own build. On a remote host they arrive in the closure kolu provisions, so an agent working there has the whole loop available with nothing installed on the box, and can never end up holding a tool from a different build than the daemon it is talking to. Elsewhere it autodiscovers the running padi — and when several are running (your everyday padi plus a dev or test one), it picks the one keyed to the state root your environment names rather than asking you to choose; --socket <path> or --state-root <dir> point it at a different local daemon, and --host <ssh> reaches a padi on another machine — the twin of kaval-tui --host: it provisions the daemon with Nix, runs padi --stdio over ssh, and runs every verb unchanged against the remote (padi-tui status --host nix@prod). Read verbs are safe and create lands a real terminal on the host (which survives the link); a tui is a dial, so it never drains or converges the remote padi. --host is mutually exclusive with --socket / --state-root. Pointing kolu’s browser at a remote padi is a separate path that already ships — see Add another machine for adding a machine to the canvas.

wait <id> --until awaiting,waiting blocks until a terminal’s agent finishes its turn — a done-signal read from real padi state, not guessed from terminal silence. For a robust driver, wait in two phases: --until working for pickup, then --until awaiting,waiting for turn-end, so a stale previous-turn state cannot satisfy the wait early. --timeout <ms> fails loud (exit 2), a terminal that exits first exits 3, and --json prints { id, agent }. The deeper design notes live in the padi atlas note.

CommandWhat it does
padi-tui status · --jsonPrint a one-shot workspace snapshot: terminals, repos, agent state, foreground process, and recency.
padi-tui watch · --jsonFollow the workspace live, including state changes and activity indicators.
padi-tui wait <id> --until awaiting,waiting · --timeout <ms> · --jsonBlock until an agent turn ends using padi’s real agent-state fold, not a guess from terminal silence.
padi-tui create (--toplevel | --parent <id>) -- claude · --worktree <branch> · --repo <path> · --jsonCreate a kolu terminal from the shell, appearing live on the canvas. Placement is required and has no default: --toplevel for a tile of its own, or --parent <id> for a split under any existing terminal (including a split of a split; the canvas flattens descendants into the root tile’s tab strip, the Dock keeps the true tree). Add --worktree for a worktree’d agent.

Why it is not inside kolu-server

The web server is a face: HTTP, assets, websocket transport. The workspace is a daemon: durable folder, terminal registry, session restore, agent awareness. Keeping those apart means a deploy can replace the web shell without making the workspace disappear.