Skip to main content
Serve mode starts a Hono-based HTTP server alongside a running workflow. Every route operates on the single active run — no workflow path or run ID needed in requests.

CLI

smithers up workflow.tsx --serve --port 3000 --host 0.0.0.0
FlagDefaultDescription
--servefalseEnable HTTP server mode
--port7331TCP port
--host127.0.0.1Bind address
--auth-tokenSMITHERS_API_KEY envBearer token for auth
--metricstrueExpose /metrics Prometheus endpoint
The process stays alive after the workflow completes so you can still query final state. Ctrl+C stops both the server and the workflow. Detached mode works too:
smithers up workflow.tsx --serve --port 8080 -d

Programmatic

import { createServeApp } from "smithers-orchestrator/serve";

const app = createServeApp({
  workflow,
  adapter,
  runId,
  abort: new AbortController(),
  authToken: "sk-secret",
});

Bun.serve({ port: 3000, fetch: app.fetch });
createServeApp returns a standard Hono app. Mount it with Bun.serve, pass it to another Hono app via app.route(), or use app.fetch directly in tests.

ServeOptions

type ServeOptions = {
  workflow: SmithersWorkflow<any>;
  adapter: SmithersDb;
  runId: string;
  abort: AbortController;
  authToken?: string;
  metrics?: boolean;
};
OptionTypeDescription
workflowSmithersWorkflowLoaded workflow instance
adapterSmithersDbDatabase adapter for the workflow
runIdstringActive run ID
abortAbortControllerShared abort controller for cancellation
authTokenstringBearer token. Falls back to SMITHERS_API_KEY. Disabled if unset.
metricsbooleanExpose /metrics endpoint. Default: true.

Authentication

When authToken is configured, every route except /health requires:
  • Authorization: Bearer <token>, or
  • x-smithers-key: <token>
Missing or invalid tokens receive 401.

Routes

GET /health

Always returns 200 regardless of auth.
{ "ok": true }

GET /

Run status and node summary.
{
  "runId": "run-1234",
  "workflowName": "bugfix",
  "status": "running",
  "startedAtMs": 1707500000000,
  "finishedAtMs": null,
  "summary": { "finished": 3, "in-progress": 1, "pending": 2 }
}

GET /events

SSE stream of lifecycle events. Same format as the multi-workflow server.
ParameterTypeDefaultDescription
afterSeqnumber-1Only events after this sequence
event: smithers
data: {"type":"NodeStarted","runId":"run-1234","nodeId":"analyze","iteration":0,"attempt":0}
id: 1

event: smithers
data: {"type":"NodeFinished","runId":"run-1234","nodeId":"analyze","iteration":0,"attempt":0}
id: 2
  • Polls every 500ms.
  • Auto-closes when the run reaches a terminal state.
  • Reconnect with ?afterSeq=N to resume from a known position.

GET /frames

Rendered workflow frames.
ParameterTypeDefaultDescription
limitnumber50Max frames
afterFrameNonumberFrames after this number

POST /approve/:nodeId

Approve a pending approval gate.
{
  "iteration": 0,
  "note": "Looks good",
  "decidedBy": "alice"
}
All fields optional. Returns { "runId": "run-1234" }.

POST /deny/:nodeId

Deny a pending approval gate. Same body as /approve/:nodeId.

POST /cancel

Cancel the running workflow.
StatusCodeCondition
200Cancelled successfully
409RUN_NOT_ACTIVERun already finished/failed/cancelled

GET /metrics

Prometheus text exposition. Same metrics as the multi-workflow server.

Error Format

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description"
  }
}
Unknown routes return 404 with code NOT_FOUND.

Serve Mode vs Multi-Workflow Server

Serve modeMulti-workflow server
ScopeSingle workflow, single runAny workflow, multiple concurrent runs
Startsmithers up --serve or createServeApp()startServer()
Routes/, /events, /approve/:nodeId, …/v1/runs, /v1/runs/:runId, …
FrameworkHonoNode.js http
Use caseDevelopment, single-purpose servicesProduction API gateway

Example

# Start a workflow with serve mode
smithers up workflow.tsx --serve --port 8080 --auth-token sk-secret

# Check status
curl http://localhost:8080/ -H "Authorization: Bearer sk-secret"

# Stream events
curl -N http://localhost:8080/events -H "Authorization: Bearer sk-secret"

# Approve a gate
curl -X POST http://localhost:8080/approve/deploy \
  -H "Authorization: Bearer sk-secret" \
  -H "Content-Type: application/json" \
  -d '{"note": "Ship it", "decidedBy": "alice"}'

# Health check (no auth needed)
curl http://localhost:8080/health