tide serve
Run the HTTP control surface over the workflow runner.
tide serve runs a long-lived runner behind a small HTTP /v1 control
surface. Instead of invoking tide run per workflow, you enqueue invocations
over HTTP, poll their status, and resolve human waits — and suspended
invocations survive a restart and resume when resolved.
Start the server
tide serve --workflows workflows --port 4100| Flag | Default | What it does |
|---|---|---|
--port <port> | 4100 | TCP port to bind on 127.0.0.1. |
--workflows <dir> | workflows | Workflow source root. A request for workflow <name> resolves <dir>/<name>/main.ts. |
--store <fs|sqlite|postgres> | fs | Durable store backend for journals. |
--workers <n> | 1 | Worker-pool size. >1 runs invocations in parallel (one V8 isolate per OS thread). |
--argon <url> | — | Serve state.* against a remote ox runtime serve /v1 endpoint instead of the embedded Argon backend. |
The shipped binary serves with the embedded Argon backend by default; --argon <url> switches to a remote runtime. The server runs until the process is
terminated.
Workers
--workers sizes the pool of OS threads, each owning one V8 isolate, all
draining the shared queue. --workers 1 (the default) runs one invocation's
event loop at a time; --workers 4 runs up to four invocations in parallel.
Every worker shares one backend handle, so they dispatch state.* against the
same durable world.
The /v1 surface
Three routes (plus GET /health). Errors use the envelope
{ error: { code, message }, requestId }, mirroring Argon's runtime serve.
Enqueue an invocation
POST /v1/workflows/{workflow}/invocationsBody (optional): { "input": <json> }. An unknown workflow name is a clean
404. Returns 202 Accepted:
{ "invocationId": "…", "status": "pending" }curl -s -X POST http://127.0.0.1:4100/v1/workflows/onboard/invocations \
-H 'content-type: application/json' \
-d '{"input":{"company":"Acme","name":"Ada","salary":120000}}'Check status
GET /v1/invocations/{id}{
"invocationId": "…",
"status": "pending | running | completed | failed | suspended",
"error": "…", // present on failed
"pending": { "waitOrdinal": 0, "taskId": "…" } // present on suspended
}A suspended invocation is parked on a hitl.wait; the pending object names
the wait to resolve.
Resolve a human wait
POST /v1/invocations/{id}/resolveBody: { "action": "approved", "payload": {…}, "comment": "…" } (action
required; payload and comment optional). This records the decision into the
journal and re-drives the invocation under the same id — the HTTP analogue of
tide resume.
curl -s -X POST http://127.0.0.1:4100/v1/invocations/$ID/resolve \
-H 'content-type: application/json' \
-d '{"action":"approved","comment":"verified"}'Durability across restarts
With a durable queue (the fs store's FsQueue), a suspended invocation
survives a tide serve restart: bring the server back up, POST …/resolve, and
it resumes from its journal. This is the same exactly-once, replay-deterministic
core that tide run uses — serve simply orchestrates many invocations over
it.
Related
- Human-in-the-loop — the
hitl.wait/ resume modelresolvedrives. - CLI reference —
serveflags. - Driving Argon — embedded vs remote
--argon.