Watching a run
Monitor a running workflow as a person through the Viewer, or as an agent through JSON.
Workflow authoring, execution, monitoring, and approval are available as commands with --json
forms, so the thing driving Kilin can equally be a person at a terminal or an outer agent runtime
supervising work it delegated. Non-interactive setup uses an explicit provider selection such as
kilin skills link --providers agents. The Viewer remains a human inspection surface, while an
outer agent can launch and supervise its attached CLI process through --json.
┌─────────────────────────────────────────────────────────────────┐
│ OUTER AGENT RUNTIME │
│ a person at a terminal · or Codex / Claude Code / a script │
└─────────────────────────────────────────────────────────────────┘
│ ▲
│ create kilin workflow init │ monitor runs list · runs show
│ update edit WORKFLOW.yaml │ track runs wait --json
│ start kilin run · kilin trigger │ inspect kilin ui
│ decide runs approve · runs reject │
▼ │
┌─────────────────────────────────────────────────────────────────┐
│ KILIN │
│ validate → compile → immutable revision → execute │
│ SQLite history · captured logs · JSONL event stream │
└─────────────────────────────────────────────────────────────────┘
│ ▲
│ spawns provider subprocesses │ results · logs · decisions
▼ │
┌─────────────────────────────────────────────────────────────────┐
│ INNER WORKFLOW — one run │
│ │
│ analyze ──▶ implement ──▶ [approval] ──▶ verify │
│ read_only workspace_write barrier read_only │
│ Claude Code Codex Codex │
└─────────────────────────────────────────────────────────────────┘The outer runtime stays outside the run. It cannot reach into a running node, and no edit to
WORKFLOW.yaml can change what this run executes, because Kilin records an immutable revision
before the first node starts.
Both views below read the same local SQLite history. Neither is a reduced version of the other.
As a person
Open the Viewer for the workflow you are running:
kilin ui change-review --cwd /absolute/path/to/projectThe Viewer binds numeric 127.0.0.1 on an operating-system-selected port and shows the compiled
graph, node states as they change, the newest 50 runs for that exact workflow identity and working
directory, lineage, approval metadata, failures, bounded captured output, and Decision Packets.
On load the Viewer opens the most relevant stored run — one waiting for approval first, then a running run, then the newest finished run — and selects the node that explains the run status. With no stored runs it keeps the current definition view. The selected run, node, stream, and rendered or raw view persist in the URL hash, so a reload restores your place.
The top bar carries a Refresh control that requests the current state at once and restarts the poll backoff. When a captured stream fails to load, the output panel offers a Retry control that requests that stream again, and Refresh requests it again too. When the graph is taller than the strip that holds it, an Expand control appears beside the workflow status and raises the height cap, so a workflow with many parallel branches needs less scrolling; the choice survives polling until you collapse it again.
Beside Refresh, a notification control appears where your browser supports notifications. Granting permission lets the Viewer tell you when a run starts waiting for approval while its tab is hidden — it raises a notification for that run, and clicking it brings you back there. A hidden tab keeps polling at a reduced fifteen-second cadence rather than stopping, so an approval that arrives while you are elsewhere is still picked up; switching back polls at once and resumes the normal cadence.
Selecting a node scrolls the graph to it, so a node below or beside the visible area of a large workflow comes into view without scrolling the strip yourself.
A loop node keeps the word loop on its card and adds its iteration progress, loop · 2/3, once
an iteration starts; until then it keeps its bound, loop · up to 3. Selecting it expands the card
into a container that draws the body pipeline at the
latest started iteration — the body nodes with their statuses, the revise feedback edge, and the
pass exit — and selecting a body node opens that execution's evidence. Selecting an execution from
an earlier iteration draws that iteration instead. A workflow whose only node is a loop opens
expanded. The Loop iterations panel lists every recorded iteration and names the body before the
first one runs.
When a run stops at an approval, the Viewer shows guarded Approve and Reject buttons alongside the
equivalent CLI commands. The optional note is a multi-line field, so Enter starts a new line and
only Approve or Reject submits. A running run also offers a Cancel run button beside the equivalent
kilin runs cancel command. Those two are its whole state-mutation surface, a closed set: it cannot
edit workflows, start provider runs, or schedule work. Pass --no-open to print the launch URL instead
of opening a browser, and treat that URL as a credential.
Without a browser, the same history is available as text:
kilin runs list
kilin runs show <run-id>As an agent
The bundled run skill validates the visible package, starts
kilin ui <id> --cwd <directory> --no-open --json as a managed attached process when the
requester's browser can reach the same loopback, and returns the viewer.started URL directly.
The Viewer remains alive after run success or failure until it is stopped or the outer-agent
session ends. If the agent runs on another machine or cannot retain the process, it returns the
manual command with the requester's local project path instead of claiming the remote
127.0.0.1 URL is accessible. That local Viewer sees the same history only when it can access the
same Kilin data.
kilin run --json streams one JSON object per line. Every event carries outputVersion: 1 and a type:
run.started · node.started · node.finished · approval.requested
approval.resolved · run.finished · errorRather than polling, block until the run next needs attention or reaches a terminal state:
kilin runs wait <run-id> --jsonA controller records its decision from a second local process while the run stays attached in the first, so nothing has to scrape a terminal:
kilin runs approve <run-id> <approval-node-id> --actor agent
kilin runs cancel <run-id>Stopping the attached kilin run process itself also stops the run. SIGINT, SIGTERM, and
SIGHUP all route through the same cancellation path, so a supervisor, container stop, CI
cancellation, or a closed terminal terminates the provider process tree instead of orphaning it,
and the command exits 130. If a run is killed outright and cannot clean up, the next
command to work in that directory — including another kilin run — ends the processes it left
behind before it starts.
Failures carry stable error codes — NODE_TIMEOUT, LOOP_LIMIT_REACHED, APPROVAL_REJECTED, and
the rest — so a supervising agent can branch on the code rather than parse prose. runs list,
runs show, and workflow validate also accept --json.
What monitoring never exposes
Declared run parameters are deliberately excluded from lifecycle events, runs list, runs show,
and the Viewer. Loop iterations are grouped without exposing parameter, feedback, decision-choice,
or result values.
Captured stdout, stderr, and results remain private local files under the data directory rather than event payloads. See trust boundaries for what that history can contain, and the command reference for the full flag surface.