Kairokuplaybook

Orchestration

How agent runs are dispatched to their own machines.

Running one agent in your terminal needs no orchestration. Running several, concurrently, each under its own identity, on machines that are not your laptop, does — and the shape of that layer was decided deliberately rather than assembled.

You start work from the plan: Run this item on a plan item queues its run, and you watch it on the Floor. The machinery under that is one daemon per machine, dialling out.

Two layers, one of them programmatic

Paseo is a manual cockpit only. It is installed on the agent machines as a human-driven surface — watch a session, steer it by hand — and nothing more. It is Apache-2.0 licensed and is treated as a behavioural reference only: nothing from its codebase is copied, ported, or closely paraphrased. It is never pointed at by a dispatch path, and a machine without it passes every check. (Earlier versions of this page and of the spec said AGPL-3.0. That was wrong.)

The Kairoku daemon is the only programmatic dispatch path. A small daemon on each machine that claims a run from the app, launches one agent process per plan item in a fresh git worktree with an injected per-run credential, supervises each to a terminal state, and reports the facts back. Its API was designed fresh, which keeps it embeddable in the product.

Keeping those two apart is what makes the boundary legible: every automated run goes through the daemon, and anything a human does by hand in Paseo is visibly not that.

The daemon, in one paragraph

Bun and TypeScript, Bun.serve, exactly one runtime dependency — the Claude Agent SDK, imported by one module and nowhere else. State is a Map of live runs in memory plus one small JSON breadcrumb per run on disk — no database, by design. Giving the runner a database would give it state whose loss starts to matter, which is the opposite of the design: the ledger in the Kairoku app owns recovery. Each run also appends a JSONL event log and its captured stream to disk — a log, not state. Two providers drive the agents: the Claude Agent SDK, and codex exec --json.

It dials out; nothing dials in. The push API is retired. The daemon holds one credential — the app's — and reaches four paths on it: heartbeat, claim, update, and a drain acknowledgement. Its local listener is loopback-only, unauthenticated, and exists so kairoku doctor can ask the machine how it is doing.

It refuses; it never queues. The queue lives in the app, which is the thing with a database. A machine at capacity simply stops asking, and a dispatch it may not take — the wrong repository, a different machine requested — is never offered to it in the first place.

Two daemons

This page is about the runner: kairoku daemon, installed by the CLI on each agent machine. The desktop app carries a second, different daemon, kairokud, which holds the vault, Teammates, Slack intake, the integration catalog, automations and playbooks. The web app's Settings → Developers → Workflows and Automations pages do not reach it: they keep a separate catalog on your account, and nothing fires or runs from them in this release. See Desktop, Automations and Workflows.

The two do not talk to each other. An installed kairokud can, however, be linked to the app as a runner in its own right — kairoku setup --daemon --link enrols it after you approve a code in the app — and a linked kairokud heartbeats and claims Floor dispatches over the same routes. See kairokud as a runner. On a mac that is the only service form: kairoku daemon install refuses there, because the io.kairoku.daemon LaunchAgent is kairokud's. A recipe queued locally by an automation or a playbook on kairokud is not a Floor dispatch: the daemon's dispatch runner turns it into an ordinary agent chat session, and nothing appears on the Floor.

The v1 contract

The spec freezes the requirements below. RF-001, RF-002 and RF-004 were the push API and are retired; the ids are append-only, so they are struck rather than reused.

RequirementWhat it pins
RF-003events. Everything a provider emits is written to a per-run JSONL log on disk in full, and a curated, bounded, masked subset rides the next heartbeat to the app. Two logs, one truth.
RF-005GET /capacity → running count and configured maximum. Unauthenticated, loopback.
RF-006the bind is the boundary. The listener has no inbound credential, because with the push API gone there is nothing arriving from off the machine. It binds 127.0.0.1 and refuses a wildcard bind — an unauthenticated surface on a LAN address would be a different bargain.
RF-007 (amended)one Kairoku credential, and nothing else. The daemon talks to the Kairoku app and to no other service — no Jira, no forge, no ledger writes of its own. Every claim about the work arrives from the agents it launches, under their own per-run credentials. The runner reports on the run; the app decides about the work.
RF-008 (amended)credential distinctness. Each run carries its own token, minted by the app for that run and deleted when it ends, so two concurrent agents genuinely carry two identities into the ledger.
RF-009 (amended)roles are a fixed table in the daemon: implementer, reviewer, planner, researcher, driven by the plugin's own agent definitions. An undeclared role is refused. The allowlist is a property of the machine's configuration, not of the agent's choice.
RF-010teardown on every exit path. Normal exit, time limit, cancel, and daemon shutdown all run the same teardown: process-group kill so no child is orphaned, services down, worktree removal, a terminal status written and reported. The one opt-out is a post-mortem flag that keeps the worktree when a run failed.
RF-011the app link. appUrl in the config, one credential in token.env, proved with a heartbeat before the service is ever installed.
RF-012the loop. Two timers — heartbeat and claim — with backoff on failure, and a full stop on 401.
RF-013the restart rule. A run left non-terminal by a dead daemon is reported failed, never relaunched. A prompt is not something to re-run on a machine's behalf.
RF-014providers. One interface — launch(run) → { events, interrupt(), exit } — behind which Claude and Codex are interchangeable.
RF-015teams are deterministic code over run records, not a conversation. Testable against a fake provider.
RF-016the QA step has no model. It runs the repository's own check and test commands and parses the four counts off the summary.
RF-017the tool policy is data, and one function applies it: implementer read/edit/write/run inside its own worktree, reviewer read and run, planner and researcher read-only plus MCP. Every denial is an event.
RF-018the per-run wall clock, defaulting to an hour, overridable per dispatch.
RF-019a run gets its own environment. The manifest from the base branch, the merged values, its own ports and its own compose project, torn down on every exit path.
RF-020the active flush. A third timer, 2 s: while any run is active its curated events go out on update directly rather than waiting for the next heartbeat. A send that fails is held and leads the next tick.
RF-021a run is held to the repo's own rules — .kairoku/rules/*.yml from the base branch, never the worktree, blocked at the write and failed in QA, with .kairoku/patterns.md read by every role before its first write. A machine that cannot check them fails the run closed.
RF-022CodeGraph, on probation, opt-in per repository through kairoku.json's intelligence key. One index per worktree, an unknown value refused by path, and silent degradation where the binary is absent.
RF-023the run transcript. The curated events carry what each tool call did (result), phase changes and the CodeGraph index line as kinds of their own, so the Floor can draw a run as a thread of turns.

What is deliberately not here

Approvals answered in the app (a needs input run is shown, not answerable) · live terminal attach and steering · schedules (on this runner — the desktop daemon's automations are where cron lives) · custom teams and an agent-led lead role · per-item claims spread across several machines · forge webhooks for merge detection · end-to-end encryption of secrets.

Each of those is triggered, not scheduled: it has a recorded condition that opens it, and it becomes real work when that condition is met rather than sitting on a roadmap waiting for a slot.

One item on that list is not a backlog item at all. Auto-approval of an agent's permission requests has no trigger — it is a gate, not deferred work.

In this section

On this page