Control plane

A local web app for rupu — launch runs, watch them stream live, author agents and workflows, and review findings and coverage, all in one browser tab.

A web command center

The control plane is rupu's window onto everything it's doing on this host. One command opens a browser UI where you can observe every run, session, and event as it happens; control what runs and approve the steps that ask for it; and author your agents and workflows — without leaving the page. It's a thin web adapter over the same on-disk stores the CLI uses (your runs, agents, workflows, sessions, and the coverage ledger under ~/.rupu/), so the browser and the terminal are always looking at the same reality.

Start it with one command:

# serve the control plane and open it in your browser
$ rupu cp serve

By default it binds to 127.0.0.1:7878 and opens the URL in your browser when you run it interactively. Useful flags: --bind <addr> to choose a different address or port, --token <token> to guard the API with a bearer token, and --no-open to print the URL without launching a browser.

# bind a custom port and require a bearer token on /api/*
$ rupu cp serve --bind 127.0.0.1:9000 --token "$RUPU_CP_TOKEN" --no-open
Single binary, no setup. The web UI is compiled into the rupu binary — there's nothing to install or build. rupu cp serve serves the embedded app and reads your existing file-based stores directly; there is no database.
control plane rupu cp serve runs live graph + log sessions chat turns authoring agents + workflows findings coverage ledger
One local web app, hub for everything rupu is doing on this host.

Observe

The observability surface is the heart of the control plane — watch everything rupu is doing, live, in the browser.

Live updates arrive over server-sent events: a global stream for the event timeline and per-run streams for each run view. Runs the control plane started stream straight from the executor; runs started elsewhere (the CLI, a cron trigger, an autoflow) are tailed from their on-disk event log — so the same live view works no matter what kicked the run off.

Control

Beyond watching, the control plane drives rupu from the browser:

Same engine, new surface. The control plane doesn't reimplement any logic — launching, approving, and sending all route through the very same code the CLI uses. The browser is just another way in.

Author

You can edit your agents (.md) and workflows (.yaml) right in the browser. Agents open in a code editor; workflows open in the visual workflow editor.

The workflow editor is a single screen: a drag-and-drop DAG on top and the live YAML below, kept in lock-step. Drag node cards from a palette onto the canvas, wire steps left→right exactly like the Runs graph, and watch the YAML rewrite itself underneath — or type in the YAML and watch the graph reconcile as you go. YAML stays the source of truth: the graph is a projection of it, and every save passes through the same validation the orchestrator already applies.

Every step kind has its own shape. The Flow Designer paints nodes as flowchart silhouettes rather than identical cards, so a workflow still reads when it's zoomed out far enough to see whole: a branch is a decision diamond, an action connector a parallelogram, an approval gate the trapezoid of a manual operation, a for_each a hexagon, and parallel / panel keep growing rectangular bodies for their sub-steps. Connection handles anchor to the silhouette itself, so nodes drag and wire by their real shape — and shape carries the kind independently of colour.

A Blocks palette with its own tab. The rail is one tab bar — Blocks · Step · Settings · Reference — so the palette and the editor no longer compete for height. Blocks sits on a dense chip grid with a filter across both the step kinds and the whole connector catalog, each chip previewing the silhouette it will place. Selecting a chip shows what it does, its required fields, and a short example before you commit it; drag-to-place or Add to canvas drops the node and jumps you straight to its form.

Per-kind authoring forms. The Step tab renders the form the selected node actually needs. Connector (action) steps get a form generated from the tool catalog's own JSON schema — required parameters marked, values that round-trip whether they're numbers, booleans, lists, or {{ … }} expressions — so new tools appear in the editor with no editor changes. Approval gates get editors for their notify hooks and their on-reject cleanup steps; panels and branches get their sub-schemas, with constrained choices (severity thresholds, minimums) instead of free text you can typo into a failed run.

One visual language, author and run. The live run graph now paints from the same kind palette as the editor, with connectors animating along the active path — kind colour carries identity while status stays the overlay, so a running step pulses, a gate awaiting you glows amber, and a failure still reads as a failure. Watching a run and editing it look like the same thing.

Opt in while it settles. The Flow Designer and the matching run-graph visuals ship behind one switch — [cp]'s workflow_editor_ui = "next" in your config (the default is "classic", today's editor).

Every expression field — a step's prompt, when, a branch's condition, or a panel's subject — gets syntax highlighting and context-aware autocomplete. Inside {{ … }} it suggests the right vocabulary for where you are: declared inputs, the outputs of steps that run before this one (never a forward reference), loop locals like item inside a for_each, panel findings, and the available filters. A searchable expression reference panel surfaces the whole vocabulary and inserts at the cursor. Validity is checked live — at the field, on the node, and in a header badge — so mistakes show up before you ever save.

Settings & project config

You can edit rupu's configuration from the browser. A global Settings page writes the global ~/.rupu/config.toml, and a Config tab on each Project detail view writes that project's .rupu/config.toml. Config is layered global → project → env, and the control plane shows the effective value for every key together with its provenance — which layer won.

Each surface is hybrid: a typed Form tab with per-field validation, plus a raw TOML tab with validate-on-save, both writing the same file. Alongside them sit a policy view (what's locked) and a runtime status view — the latter shows restart-required settings such as bind and token read-only (the token masked).

Policy enforcement. An admin can lock specific settings at the global level so projects — and the UI — can't override them. Locked keys are pinned to the global value wherever config is resolved (CLI, agent runs, cron, and the control plane alike), and appear read-only on the project Config tab. Locks are declared as dotted key paths in a [policy] section of the global config:

# ~/.rupu/config.toml — lock keys so projects can't override them
[policy]
lock = ["permission_mode", "autoflow.max_active", "cp.max_workspace_bytes"]

# control-plane runtime settings live in their own section
[cp]
max_workspace_bytes = 104857600

Writes go through validated config endpoints — GET the effective config with provenance, and PUT the global config, a project's config, or the policy lock list. Every write is validated, backed up, and written atomically; project-config paths are confined under the project's .rupu/ (no path traversal), and writes require the full rupu cp serve runtime.

Multi-host

One control plane can drive rupu running on other machines, not just the one it's on. Register a remote host (any machine running rupu cp serve) and the central CP federates to it over HTTP — launch runs, agents, and sessions on a chosen host, observe and control them, all from one browser, with a Fleet → Hosts surface and host filtering on run lists. See Multi-host for setup and the transport roadmap.