Skip to content

Worlds ​

A World is the backend a run is recorded in. It provides three things:

  • Storage for each run's append-only event log and the run, step, and hook views derived from it.
  • A queue of deliveries, each of which makes the runtime execute a flow or a step.
  • Streams of bytes that runs write and readers consume.

Your workflow code is the same on every World. Choose one per environment.

Choosing a World ​

WorldStorage and deliveryProcessesEntry pointsBest for
LocalJSON files, in-process deliveryone per directoryWorld.Preparedevelopment, single-process apps
VercelRedis, Vercel Queues, optional QStashany numbervercelworld.QueueVercel Functions
AWSDynamoDB, SQS, optional EventBridge Schedulerany numberawsworld.QueueAWS Lambda
PostgresPostgreSQL, LISTEN/NOTIFY workersany numberpostgresworld.Workerservers and containers
Testmemoryonetestworld.Newtests

You can also implement your own.

HTTP handlers ​

Every World package except the test World exports the same handler constructors:

FunctionServes
HTTP(world)the RPC endpoint and the webhook endpoint under /v1/workflow
HTTPPrefix(world, prefix)the same endpoints under prefix, such as /internal/workflow
RPC(world)only the RPC endpoint, to mount yourself
Webhook(world)only the webhook endpoint, to mount yourself

HTTP(world) serves:

RoutePurpose
POST /v1/workflow/rpcCBOR RPC used by workflow.NewHTTPClient and the web UI
/v1/workflow/webhooks/{token}webhook requests

The Vercel World also serves /v1/workflow/callbacks/qstash when QStash is configured. See HTTP Endpoints for details.

Base URL ​

Set Config.BaseURL to the absolute public URL of the handler's prefix, including /v1/workflow (or the prefix you passed to HTTPPrefix), for example https://workflows.example.com/v1/workflow. Webhook URLs are built from it. It is required except on the local World, which defaults to http://localhost:3000/v1/workflow, and the test World, which always uses http://localhost/v1/workflow.

workflow.NewHTTPClient takes the same kind of URL: the server's address followed by the prefix, such as http://workflow-worker:8081/v1/workflow.

Retention ​

Runs are kept forever by default. A World that supports retention deletes a run's data Config.Retention after the run completes, fails, or is cancelled, so storage stays bounded. Config.Retentions sets it per workflow, by workflow name, and zero keeps that workflow's runs forever:

go
config.Retention = 24 * time.Hour
config.Retentions = worlds.Retentions(
	worlds.Retain[workflows.Chat](10 * time.Minute),
	worlds.Retain[workflows.Invoice](0),
)

Every World that supports retention follows the same rules:

  • Runs that are still pending or running are never deleted, however old.
  • A hook whose token retention has not ended keeps its run until it ends.
  • Runs that finished before retention was set are kept.

Callers read a run's result and streams right after it finishes, so a few minutes is the shortest practical retention. Each World's page describes how and when it deletes.

Delivery guarantees ​

Every World delivers at least once. The runtime decides whether a delivery may run by appending events, and every World admits or rejects appends atomically, so duplicate and stale deliveries are harmless. A failed delivery is retried with backoff.

WorldNative max delayFailed delivery backoff
Localunlimited100 ms up to 30 s
Vercel6 days (longer with QStash)5 s up to 5 min
AWS15 min (longer with Scheduler)1 s up to 15 min
Postgresunlimited1 s up to 5 min

Security ​

The RPC and webhook handlers don't authenticate requests.

  • RPC can start runs and read every run's input, output, and events. Keep it on a private network, or protect it with worlds.WithRPCToken.
  • Webhooks are authorized only by the token in the URL. Treat webhook URLs as bearer capabilities and verify provider signatures in the workflow.
  • Queue consumers must only be reachable by the queue infrastructure.

Protecting the RPC path ​

Pass worlds.WithRPCToken to HTTP(world) to require a bearer token on the RPC path. Webhook requests still pass, since webhook callers can't send your credentials:

go
handler := postgresworld.HTTP(world, worlds.WithRPCToken(os.Getenv("WORKFLOW_HTTP_TOKEN")))
log.Fatal(http.ListenAndServe(":8081", handler))

An empty token refuses every RPC request, so an unset secret never leaves the endpoint open. For other checks, such as a session or an IP allowlist, worlds.WithRPCMiddleware(middleware) wraps only the RPC path in your own middleware.

The world-http adapter, and so the web UI, sends WORKFLOW_HTTP_TOKEN as the bearer token. Go clients send it through their *http.Client:

go
type bearer struct{ token string }

func (b bearer) RoundTrip(r *http.Request) (*http.Response, error) {
	r = r.Clone(r.Context())
	r.Header.Set("Authorization", "Bearer "+b.token)
	return http.DefaultTransport.RoundTrip(r)
}

client := workflow.NewHTTPClient(
	"http://workflow-worker:8081/v1/workflow",
	&http.Client{Transport: bearer{os.Getenv("WORKFLOW_HTTP_TOKEN")}},
)

Alternatively, serve RPC(world) and Webhook(world) on separate listeners or routes, and expose only the webhook route publicly.

Released under the MIT License.