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
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.
Observe
The observability surface is the heart of the control plane — watch everything rupu is doing, live, in the browser.
- Dashboard. An at-a-glance landing page with aggregated counts and charts, including multi-model token spend over time so you can see what each provider is costing you.
- Runs. A list of every run, and a live run view that paints the workflow as a left→right graph with each step's status updating in place, plus a streaming transcript of the focused step as it works.
- Live Events. A merged, real-time event timeline across everything on the host (streamed over server-sent events).
- Sessions. Every session, with its transcript streaming token-by-token.
- Coverage & Findings. The coverage status of each project and the findings filed against it, read from the coverage ledger.
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:
- Launch runs. Dispatch a workflow or an agent with a target and a permission mode, straight from the UI.
-
Approve & reject gated steps. When a run pauses at a
gate awaiting approval, approve or reject it in place. Approve marks the
run for resume; a background resume worker inside
rupu cp serveclaims the marked run and finishes the gate. - Drive sessions. Send a prompt to a live session and watch the turn stream back as a chat.
- Manage autoflows. See and manage your autoflows — the scheduled, trigger-driven workflows — from the same place you watch them run.
-
Archive, restore & delete. Tidy up the workspace by
archiving finished runs and sessions (moving them out of
the active lists without losing them), restoring them
when you need them back, or deleting them for good.
Archived items honour retention windows from config — the
[storage]section'sarchived_session_retentionandarchived_transcript_retention(e.g.30d) set how long archived artifacts are kept before they're pruned.
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.
[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.