Workflow App surfaces
All packaged surfaces accept the same workflowApp() and execute the same
workflow semantics. The adapters differ in interaction and projection, not in
orchestration.
Surface overview
| Surface | Entry point | Use |
|---|---|---|
| CLI | runWorkflowCli() | Compact execution for commands and scripts |
| Verbose CLI | runWorkflowCli() with --verbose | Full terminal event log |
| TUI | runWorkflowTui() | Interactive terminal application |
| GUI | runWorkflowGui() | Executable browser canvas and inspector |
| SVG | runWorkflowSvg() | Lighter executable graph surface |
| Server | runWorkflowServer() | HTTP control plane, persistence, and signal delivery |
| MCP | createWorkflowControlPlaneMcpProvider() | Harness discovery, triggering, inspection, and hook resume |
The TUI, GUI, SVG, and bundled server currently require Bun. Bind network
surfaces to loopback during development. runWorkflowServer() refuses a
non-loopback bind without an auth verifier.
CLI
await runWorkflowCli(reviewApp);
bun run cli.ts --list-workflows
bun run cli.ts "Review this response"
bun run cli.ts --workflow response-review "Review this response"
bun run cli.ts --interactive "Review and clarify this response"
bun run cli.ts --verbose "Review this response"
--interactive resolves human questions in the terminal. Without it, a run
that needs input cannot be driven to completion automatically. The result uses
the authored workflow's output keys. Positional arguments are joined into one
prompt; --workflow overrides the app's default workflow. A non-interactive
run that remains waiting returns exit code 1. Call
runWorkflowCli(app, {exit: false}) when the caller needs to inspect the
returned result instead of setting process.exitCode.
TUI
import { runWorkflowTui } from "@dromio/workflow";
await runWorkflowTui(reviewApp);
The TUI selects workflows, starts runs, renders nested graph state, shows
events and artifacts, and resolves pending input. If the app declares signals,
it starts a loopback signal server by default; pass
{signals: {listen: false}} to disable that ingress.
GUI and SVG
const gui = await runWorkflowGui(reviewApp, {
defaultInput: "Explain workflow traces.",
hostname: "127.0.0.1",
port: 3210,
signals: {storage: {kind: "sqlite", path: "./runs.sqlite"}},
});
console.log(gui.url);
const svg = runWorkflowSvg(reviewApp, {
hostname: "127.0.0.1",
port: 3211,
});
console.log(svg.url);
Both return a server with url, hostname, port, and stop(). The GUI adds
artifact inspection and signal integration; the SVG surface is a lighter graph
projection. Use an OS signal handler or application shutdown hook to call
stop(). runWorkflowSvg() returns synchronously after binding its Bun server;
runWorkflowGui() is async because it may create a durable signal host first.
GUI SQLite persistence is activated through signals.storage when the app
declares signals; without signals, the GUI uses its process-local runtime.
Durable server
const server = await runWorkflowServer(reviewApp, {
hostname: "127.0.0.1",
port: 4323,
storage: {kind: "sqlite", path: "./runs.sqlite"},
});
console.log(server.url);
The returned server also exposes controlPlane, runtime, runtimeStore,
fetch(), deliverSignals(), and stop().
HTTP route map
The server publishes the authoritative OpenAPI document at
GET /api/openapi.json. Core run routes are:
| Method and path | Purpose |
|---|---|
POST /api/runs | Start with {workflowId, input, runId?} |
GET /api/runs/{runId} | Read a run snapshot |
GET /api/runs/{runId}/events | Stream server-sent events |
POST /api/runs/{runId}/questions/{questionId}/answer | Submit {value} |
POST /api/runs/{runId}/resume | Resume after an accepted answer |
POST /api/hooks/{token}/resume | Deliver a hook {value} |
POST /api/signals/{signalId}/occurrences | Publish a correlated signal occurrence |
Send Authorization: Bearer <token> when auth is configured. Capabilities are
route-specific, such as runs.read, runs.write, and
signal.publish:<signalId>.
The server exposes the Workflow App host's Fetch adapter and runs signal
delivery passes. For a non-loopback host, supply auth implementing the
exported token-verifier contract and place normal transport security in front
of the process.
Choose a surface
Use direct workflow.start() for libraries and tests, CLI for the smallest
operator contract, TUI for terminal interaction, GUI for detailed inspection,
SVG for a lighter graph UI, and the host/server APIs for durable integration.
Do not put business decisions in a renderer or command parser.
GUI, TUI, CLI, and SVG are user-facing projections of a Workflow App. SDK, HTTP, and MCP are control and integration paths. They share app semantics, but they do not automatically share one live run unless they connect to the same durable host and run ID.