> ## Documentation Index
> Fetch the complete documentation index at: https://smithers.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Live UIs and the Gateway

> Watch and steer a running workflow through the Gateway control plane: the Monitor, workflow-owned custom UIs, the TUI, and why closing the tab never kills the run.

A background agent run that you cannot see is a run you cannot trust. The failure mode is concrete: a five-hour workflow stalls at hour two on an approval gate, and nobody notices until the morning. Or a lane goes sideways — the agent misread the spec — and the only options are "wait for it to finish wrong" or "kill everything and start over." Long-running agent work needs the same thing long-running infrastructure needed: a live control surface where a human can observe state, decide at gates, and correct mid-run without destroying the work already done.

There is a second, subtler requirement. In most agent tools the UI *is* the process: close the ChatGPT tab or kill the terminal and the work dies with it. That coupling is fine for a thirty-second chat. It is fatal for a run that spans a night. The UI has to be a **view over durable state** — something you can close, reopen from another machine, or hand to a teammate, while the run itself keeps executing against the database.

Smithers splits the two cleanly. Runs execute against durable storage (see [Durability](/capabilities/durability)); the **Gateway** is a workspace-level RPC/WebSocket control plane over that state; and every UI — the built-in Monitor, the full-screen TUI, or a custom per-workflow browser app — is a disposable client of the Gateway. Kill any of them and nothing happens to the run.

## How the industry solves it

Every mature orchestrator grew a live UI, and two adjacent traditions feed into agent steering: "UI from your code" frameworks and agent-UI protocols.

| System                                                                                                                                           | Mechanism                                                                                                                          | Tradeoff                                                                                                                           |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Temporal Web UI](https://docs.temporal.io/web-ui)                                                                                               | Event-history viewer per workflow execution; query, signal, terminate, reset to an earlier event                                   | Generic history view; per-domain UIs are yours to build separately                                                                 |
| [Airflow UI](https://airflow.apache.org/docs/apache-airflow/stable/ui.html)                                                                      | Grid/graph views over DAG runs, task logs, clear-and-rerun                                                                         | Batch-oriented; no live token/agent streaming, steering is rerun-shaped                                                            |
| [Argo Workflows UI](https://argo-workflows.readthedocs.io/en/latest/argo-server/)                                                                | Argo Server UI over CRD state in etcd; retry, resubmit, suspend/resume                                                             | Kubernetes-native only; domain context lives outside the UI                                                                        |
| [Prefect UI](https://docs.prefect.io/v3/how-to-guides/self-hosted/server-cli)                                                                    | Dashboard over flow-run state in the Prefect server/Cloud API; pause, cancel, late-run detection                                   | Observation-first; custom operator surfaces are out of scope                                                                       |
| [Streamlit](https://docs.streamlit.io/get-started/fundamentals/main-concepts) / [Gradio](https://www.gradio.app/guides/quickstart)               | "UI from your code": a Python script *is* the app, rerun top-to-bottom on interaction                                              | Proved that app authors will write UIs when it costs a page of code — but the process serving the UI is the process doing the work |
| [AG-UI](https://docs.ag-ui.com/introduction) / [CopilotKit](https://docs.copilotkit.ai/)                                                         | Open event protocol (\~16 typed event kinds: lifecycle, text deltas, tool calls, state patches) between agent backend and frontend | Standardizes the wire, not the durability underneath; state lives in the agent session                                             |
| [Vercel AI SDK UI](https://ai-sdk.dev/docs/ai-sdk-ui/overview) + [RSC `streamUI`](https://ai-sdk.dev/docs/ai-sdk-rsc/streaming-react-components) | Generative UI: the model streams React components, not just text                                                                   | Bound to a live HTTP response; a dropped connection is a dropped generation                                                        |
| [LangGraph Studio](https://docs.langchain.com/langgraph-platform/langgraph-studio)                                                               | Graph visualization + thread state editing; interrupts and time travel over checkpoints                                            | Development tool over the LangGraph server; not a shipping operator surface for your own domain                                    |

Two lessons recur. First, the steerable systems (Temporal, LangGraph, Argo) all put a **durable state layer under the UI** — the UI reads and writes checkpoints, it doesn't host the computation. Second, the systems people actually enjoy building UIs *for* (Streamlit, Gradio, AI SDK generative UI) made the UI **belong to the code**, declared next to the logic instead of built as a separate frontend project. Smithers takes both: durable state under every surface, and a `<UI>` element declared inside the workflow itself.

## How Smithers does it

### The Gateway is the control plane

`smithers gateway` serves one process per workspace: a domain REST API, an RPC surface, and WebSocket event streams over every run in the workspace database. Clients authenticate with bearer tokens carrying [typed scopes](/integrations/gateway) (`Authorization: Bearer <token>` or `x-smithers-key`; JWT and trusted-proxy modes exist for shared deployments). On connect, the WebSocket handshake returns a `hello` payload with protocol, features, and the session's role/scopes, then pushes live event frames filtered by `runId` subscription. Runs launched through the Gateway expose `ctx.auth = { triggeredBy, role, scopes, createdAt }`, and `<Approval>` can restrict who may decide with `allowedScopes` / `allowedUsers` — enforced server-side before `submitApproval` is accepted.

Because all UI surfaces are Gateway clients over that durable state, the run/UI decoupling is structural, not a feature flag. Close the Monitor tab; the run continues. Reboot your laptop mid-run; `smithers ps` still lists it, and reopening the UI resumes the same view from the database.

### Three surfaces, one state

| Surface                    | Command                     | What it is                                                                                                                                                                                                                                                                                                                            |
| -------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Monitor](/guides/monitor) | `smithers monitor`          | Zero-setup web UI over **every run** in the workspace: a "Needs you" triage band (pending approvals, human-parked runs, 24h failures), live run rows, execution tree with per-node durations, a node inspector (prompt, tool calls, transcript, output, diff), frame scrubber, and an embedded PTY to hijack supported agent sessions |
| Custom workflow UI         | `smithers ui RUN_ID`        | The workflow's **own** browser app at `/workflows/<key>?runId=<id>`, built from `.smithers/ui/<key>.tsx`                                                                                                                                                                                                                              |
| TUI                        | `smithers up` (interactive) | Full-screen terminal monitoring; workflows can ship their own via [`<TUI>`](/components/tui)                                                                                                                                                                                                                                          |

`smithers gui` opens a directory's workspace at the most recent run's workflow UI; `smithers monitor RUN_ID` deep-links one run. All three autostart a Gateway if none is running (`--no-autostart` to opt out, `--gateway <url>` to point at a remote one).

### Workflow-owned UIs

A custom UI is declared *in the workflow* with [`<UI>`](/components/ui) — it renders nothing in the graph; the Gateway discovers it at `gateway.register()`, bundles the entry with `Bun.build` into a single browser script, and serves it with a boot config (`__SMITHERS_GATEWAY_UI__`) injected before your code runs:

```tsx .smithers/workflows/triage.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createSmithers, UI } from "smithers-orchestrator";

const { Workflow, Task, smithers } = createSmithers({ /* schemas */ });

export default smithers(() => (
  <Workflow name="triage">
    <UI entry="../ui/triage.tsx" title="Triage" />
    <Task id="scan" agent="claude" prompt="..." />
  </Workflow>
));
```

The browser side composes verified, shipped pieces — `smithers-orchestrator/gateway-react` hooks (`useGatewayRun`, `useGatewayRuns`, `useGatewayRunEvents`, `useGatewayNodeOutput`, `useGatewayNodeEvents`, `useGatewayApprovals`, `useGatewayActions`, `useGatewayRunDiff`, `useGatewayScores`, `useGatewayRunTokenUsage`, and more, over TanStack DB collections), `smithers-orchestrator/gateway-ui` run-aware widgets (`RunList`, `RunTree`, `RunEventLog`, `ApprovalPanel`, `NodeChatStream`, `NodeOutputCard`, `WorkflowGraph`, `LaunchButton`, `StatusPill`, `SimpleWorkflowDashboard`), and the `smithers-orchestrator/ui` shadcn-anatomy base library, all themed by the [workflow UI design tokens](/guides/workflow-ui-design) so light/dark is automatic:

```tsx .smithers/ui/triage.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
/** @jsxImportSource react */
import { createGatewayReactRoot, useGatewayApprovals, useGatewayRun } from "smithers-orchestrator/gateway-react";
import { ApprovalPanel, RunEventLog, RunTree } from "smithers-orchestrator/gateway-ui";
import { SmithersUiStyles } from "smithers-orchestrator/ui";

function App({ runId }: { runId?: string }) {
  const run = useGatewayRun(runId);
  const approvals = useGatewayApprovals({ runId });
  return (
    <>
      <SmithersUiStyles />
      <ApprovalPanel approvals={approvals.data ?? []} />
      <RunTree runId={runId} />
      <RunEventLog runId={runId} />
    </>
  );
}

createGatewayReactRoot(<App />);
```

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
bunx smithers-orchestrator ui RUN_ID     # open the workflow's own UI for a run
bunx smithers-orchestrator monitor       # or the all-runs Monitor, zero code
```

Steering is first-class, not read-only: approvals decide in place, `useGatewayActions()` launches runs and submits decisions, `smithers signal` delivers durable signals to `<Signal>`/`<WaitForEvent>` nodes, and pause/resume/cancel/retry-task are one-shot RPCs. Mid-run correction — the property [interruptible-agent arguments](https://arxiv.org/abs/1611.08219) center on — comes from the durable substrate: rewind, fork-from-checkpoint, and revert-attempt are all exposed through the same Gateway (see [Time Travel](/guides/time-travel-quickstart)).

### Hot reload

Workflow code hot-reloads under `RunOptions.hot` (schema-shape changes are blocked; in-flight tasks finish on the code they launched with — see [Recipes](/recipes#hot-reload-while-authoring)). For UI iteration, set `SMITHERS_GATEWAY_UI_NO_CACHE=1` on the Gateway to rebuild the bundle on every request, so edits to the entry or any import show up on a plain page reload.

## Guarantees and limits

**Guaranteed:** every surface is a stateless client over durable state — closing it never affects the run; reconnecting WebSocket streams resume via `afterSeq`; approvals and signals are durable RPCs that survive the deciding tab. Scoped tokens gate every method.

**Honest limits, verified in source and the tracker:**

* **Pack-extension UIs have no domain-data path.** The client side of the Gateway extension surface ships (`SmithersGatewayClient.extensionRpc`, `useGatewayExtensionResource` / `useGatewayExtensionAction` / `useGatewayExtensionStream`), but `smithers gateway` cannot load pack-declared extension *servers*, so a custom UI cannot yet read pack-specific domain data through the Gateway ([smithers#1438](https://github.com/smithersai/smithers/issues/1438), open). Workaround: ship the data as node outputs and read them with `useGatewayNodeOutput`.
* **UI bundle caching is build-once by default.** Without `SMITHERS_GATEWAY_UI_NO_CACHE=1` a long-lived Gateway serves the cached bundle, so UI edits do not appear on refresh ([smithers#1440](https://github.com/smithersai/smithers/issues/1440), open).
* **`--mint-token` gateways can't serve browser UIs yet.** The CLI reads the minted bearer from the runtime state file, but plain browser navigation cannot send it; run the local Gateway without `--mint-token` when you need browser-served workflow UIs.
* The Monitor is workspace-local by design; multi-workspace fleet views are not a shipped surface.

## Operating it

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
smithers gateway              # start the workspace control plane (or: gateway status | stop)
smithers monitor [RUN_ID]     # all-runs web UI, autostarts the gateway
smithers ui RUN_ID            # the run's workflow-owned custom UI
smithers gui [--workflow id]  # open a directory's workspace UI
smithers token issue --scopes run:read --ttl 15m   # scoped short-lived bearer
smithers approve | deny | signal | pause | resume  # steer from the terminal
```

Agents steer through the same state via the MCP tools: `watch_run`, `get_run_events`, `list_pending_approvals`, `resolve_approval`, `ask_human`.

## See also

* [Custom Workflow UIs](/guides/custom-workflow-ui) — the full build guide (boot config, stale-data-free model, testing, proxying)
* [The Smithers Monitor](/guides/monitor) · [`<UI>`](/components/ui) · [`<TUI>`](/components/tui)
* [Gateway](/integrations/gateway) — RPC/WS protocol, auth modes, scopes
* [Workflow UI Design Principles](/guides/workflow-ui-design) · [Sync](/guides/sync)
