Tide

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
FlagDefaultWhat it does
--port <port>4100TCP port to bind on 127.0.0.1.
--workflows <dir>workflowsWorkflow source root. A request for workflow <name> resolves <dir>/<name>/main.ts.
--store <fs|sqlite|postgres>fsDurable store backend for journals.
--workers <n>1Worker-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}/invocations

Body (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}/resolve

Body: { "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.

On this page