getWorld

Retrieves the World instance for direct access to workflow storage, queuing, and streaming backends. This async function returns a Promise<World> which provides low-level access to manage workflow runs, steps, events, and hooks.

Use this function when you need direct access to the underlying workflow infrastructure, such as listing all runs, querying events, or implementing custom workflow management logic.

import { getWorld } from "workflow/runtime";

const world = await getWorld();

API signature

Parameters

This function does not accept any parameters.

Returns

Returns a Promise<World> object:

NameTypeDescription
analyticsAnalyticsOptional analytics read namespace for observability surfaces. These APIs return metadata-only rows intended for UI/CLI listing and trace views. Payload-bearing fields remain on the canonical runtime storage APIs (runs, steps, events, hooks) and their RemoteRef resolution path.
specVersionnumberThe Workflow protocol spec version this World implements, and the version stamped on every run it creates. Declare SPEC_VERSION_CURRENT rather than a literal. The runtime checks this against [SPEC_VERSION_CURRENT, SPEC_VERSION_MAX_SUPPORTED] before it creates or replays anything, and refuses a World outside that range: below the floor the World allocates event ids the runtime cannot read positions out of (see the event log contract above), above the ceiling it speaks a spec this runtime has not learned.
capabilitiesWorldCapabilitiesFeature capabilities this World implementation supports. See WorldCapabilities. Absent (or absent members) means "unsupported": runtime optimizations gated on a capability fail closed.
validateRunExecutionContext(value: Record<string, unknown>) => voidValidates a dynamic run's complete execution context against World-specific limits. start() calls it only for dynamic starts, before any durable start side effect, so implementations throw to refuse the start.
getRuntimeDeadline() => Promise<Date | undefined>Absolute wall-clock time when the current function invocation will be terminated by the hosting platform, if known. Used to optimize runtime behavior.
start() => Promise<void>A function that will be called to start any background tasks needed by the World implementation. For example, in the case of a queue backed World, this would start the queue processing.
close() => Promise<void>Release any resources held by the World implementation (connection pools, listeners, etc.). After calling close(), the World instance should not be used again. This is important for CLI commands and short-lived processes that need to exit cleanly without relying on process.exit().
resolveLatestDeploymentId() => Promise<string>Resolve the most recent deployment ID for the current deployment's environment. Used when deploymentId: 'latest' is passed to start(). The implementation determines the latest deployment that shares the same environment (e.g., same "production" target or same git branch for "preview" deployments) as the current deployment. Not all World implementations support this: it is only implemented by world-vercel where deployment routing is meaningful.
getEncryptionKeyForRun{ (run: { runId: string; deploymentId: string; workflowName: string; attributes: Record<string, string>; createdAt: Date; updatedAt: Date; status: "pending" | "running"; ... 10 more ...; completedAt?: undefined; } | { ...; } | { ...; } | { ...; }): Promise<...>; (runId: string, context?: Record<...> | undefined): Pr...Retrieve the AES-256 encryption key for a specific workflow run. The returned key is a ready-to-use 32-byte AES-256 key. The World implementation handles all key retrieval and derivation internally (e.g., HKDF from a deployment key). The core encryption module uses this key directly for AES-GCM encrypt/decrypt operations. Two overloads: - getEncryptionKeyForRun(run): Preferred. Pass a WorkflowRun when the run entity already exists. The World reads any context it needs (e.g., deploymentId) directly from the run. - getEncryptionKeyForRun(runId, context?): Used when the run entity is not locally available, such as start() before run creation or a forwarded writable stream carrying its owning deployment context. The context parameter carries opaque world-specific data (e.g., { deploymentId } for world-vercel) needed to resolve the correct key. When context is omitted, the World assumes the current deployment. When not implemented, encryption is disabled: data is stored unencrypted.
createRunId(options?: Readonly<Record<string, unknown>> | undefined) => stringMint a new workflow run ID. Called by start() to generate the unique ID for a newly-created run. The returned value is the "bare" ID (without any wrun_ prefix); the core attaches the prefix. Implementations are free to embed world-specific metadata in the ID (e.g., a region identifier) as long as the returned string remains a valid ULID. When omitted, start() falls back to generating a standard monotonic ULID.
uploadDynamicWorkflowCode(runId: string, params: { workflowName: string; code: Uint8Array<ArrayBufferLike>; }) => Promise<string>Upload a dynamic run's serialized workflow VM code to the World's blob storage ahead of run_created, returning the ref key to attach to the run. The inline path — sending the bytes on run_created itself — is the common case and needs nothing from this method: a generated orchestration function is usually a couple of KB, and keeping it on the creating write costs no extra round-trip. This exists for the tail: a definition too large to ride the event wire, which has to be streamed separately and referenced. Worlds that store run records whole (local, Postgres) have no size pressure and leave this unset; start() then always sends inline, and a definition over its own source limit is rejected client-side rather than silently truncated. The upload necessarily precedes the run it belongs to, so implementations must accept a runId that does not exist yet, and must scope the stored object to the caller's tenant and that run so it is reclaimed with the run's other storage.
getEnvironment() => string | undefinedThe environment this World's writes are attributed to by the backend (@workflow/world-vercel: 'production' | 'preview' | 'development'). Synchronous and side-effect free: implementations derive this from configuration or environment variables they already hold, never from a network call. Return undefined when the environment can't be determined. The value MUST match the attribution the backend will actually apply to this client's writes: for world-vercel that means keeping it in lockstep with the x-vercel-environment header (proxy path) and the OIDC token's environment claim (in-deployment path). A value that merely looks plausible is worse than undefined, because callers use it to detect cross-tenant mismatches and a wrong answer manufactures a false one. start() stamps this into the queue message's runInput so the consuming deployment can tell that a message it was handed was created against a different environment than its own. Not all Worlds have an environment dimension: local dev and Postgres have exactly one tenant, so they omit this and the check is skipped.
describeRun(run: Readonly<Record<string, unknown>>) => Record<string, string | null> | Promise<Record<string, string | null> | null> | nullWorld-specific display fields for a run. Tooling (e.g. the workflow inspect CLI) calls this to enrich a run's listing row / detail output with fields only the world can derive: a region decoded from the run ID, placement read off the run's executionContext, a shard, a billing tier, etc. Consumers render each returned key as an additional column/property; when the hook is absent, no extra fields appear at all. The contract: - **Cheap and pure.** Called once per displayed run, so avoid I/O. Prefer deriving fields from the entity you are given. - **Read only what you recognize.** The argument is the run entity as the caller has it (a full storage run, or a leaner analytics row), typed loosely for the same reason as createRunId. Tolerate missing fields. - **Must not throw.** - A null field value means "applicable but undeterminable" and is preserved as null in structured output (vs. the hook being absent, where the key does not exist at all). Return null or an empty object to add nothing for a given run.
telemetryTelemetryOptional telemetry write namespace for non-critical observability signals.
getDeploymentId() => Promise<string>
invoke(runId: string, payload: unknown, options?: InvokeOptions | undefined) => Promise<unknown>Send an invocation to a run's executor. Must be enabled via capabilities.invoke. An invocation carries an out-of-band request that must be processed by the executor. The executor's response is propagated back and errors are rethrown by invoke(). A transport error is an unknown outcome: the request may have been successfully processed by the executor, for example, and the success result may be lost.
isDeploymentUnavailableError(error: unknown) => booleanReturns true only when a queue error definitively means the explicitly targeted deployment cannot receive the message. Unknown and transient errors must return false so the current delivery can be retried safely.
queue(queueName: string, message: { __healthCheck: true; correlationId: string; runId?: string | undefined; } | { runId: string; invoke?: true | undefined; requestId?: string | undefined; input?: unknown; ... 13 more ...; hookResumeTiming?: { ...; } | undefined; }, opts?: QueueOptions | undefined) => Promise<...>Enqueues a message to the specified queue.
queueBatch(queueName: string, messages: readonly { message: { __healthCheck: true; correlationId: string; runId?: string | undefined; } | { runId: string; invoke?: true | undefined; requestId?: string | undefined; ... 14 more ...; hookResumeTiming?: { ...; } | undefined; }; opts?: QueueOptions; }[]) => Promise<...>Enqueues several messages to the SAME logical queue in as few round trips as the backing transport allows. Optional: callers MUST fall back to per-message Queue.queue when a World does not implement it. Exists for wide fan-outs. Publishing an N-branch Promise.all one message at a time costs N round trips through a bounded connection pool, and that cost is paid before the fan-out's first step body runs, so it lands directly on time-to-first-step. Contract: - Results are returned in input order, one per input message. Returning a different number of results than there were messages is a contract violation the runtime rejects the whole batch on: an omitted result is indistinguishable from a message that was never published, and reading it as success would strand that step with no error anywhere. - Partial failure is normal. A rejected entry reports error; a retryable entry may succeed if the whole batch is published again. Implementations MUST NOT throw for a per-entry failure — reserve rejection for request-level failures where no entry outcome is known. - Callers are expected to pass opts.idempotencyKey per message, because the recovery for both a request-level failure and a retryable entry is to republish the batch: without keys that redelivers the entries that already succeeded. - Every message must target one logical queueName. Implementations are free to split the batch (by transport cap, or by any per-message routing dimension they derive from the payload, such as region or physical topic); the split must not be observable in the returned order.
createQueueHandler(queueNamePrefix: string, handler: (message: unknown, meta: { attempt: number; deliveryCount?: number; createdAt?: Date; queueName: string; messageId: string & $brand<"MessageId">; requestId?: string; }) => Promise<...>) => (req: Request) => Promise<...>Creates an HTTP queue handler for processing messages from a specific queue. A rejected handler must retry the same message with an incremented attempt. With invoke: true, the return value is response data delivered by World. Only ordinary wake results interpret { timeoutSeconds } as queue control. meta.messageId SHOULD be stable across redeliveries of the same message (one ID per enqueued message, reused on every delivery attempt). The runtime's inline step ownership uses it as a liveness lease: the lazy step_started records the handling invocation's messageId, and only a delivery of that same message may re-execute the step before the ownership lease expires (crash recovery via queue redelivery). A World whose queue mints a fresh ID per delivery degrades gracefully: owner redeliveries fall back to the delayed-backstop path instead of executing immediately, adding recovery latency but never wedging or duplicating. meta.deliveryCount, when the World reports it, is 1 on a message's first delivery and increments on every redelivery, whatever caused it (lease lapse, a rejected handler, a { timeoutSeconds } result). A World whose queue cannot say leaves it undefined. meta.createdAt, when known, is when the message was first enqueued, unchanged across its redeliveries.
registerHandler(prefix: string, handler: (req: Request) => Promise<Response>) => voidOptional. Delivers every message whose queue name starts with prefix to handler inside this process, without HTTP. For Worlds whose queue runs in the app's own process (world-local, world-postgres). handler takes the same request a route serving createQueueHandler(prefix, ...) would, so passing that route handler works. Takes precedence over Queue.registerRoute for the prefix; a later registration for the same prefix replaces an earlier one. Keyed by the exact prefix: registering __wkf_workflow_ also takes the Workflow SDK's own messages. A queue consumer other than the Workflow SDK uses a prefix of its own, __{namespace}_wkf_workflow_.
registerRoute(prefix: string, path: string) => voidOptional. The route (a path on the app's base URL, such as /api/agent/queue) that this World's HTTP delivery POSTs the messages of prefix to. The app serves it with createQueueHandler(prefix, ...). A prefix without a registered route goes to the Workflow SDK's flow route (WORKFLOW_FLOW_ROUTE), so only the Workflow SDK relies on that path, and a queue consumer of its own registers its route here, before the World's start() when it has one (start() re-enqueues active runs). An in-process handler for the prefix (Queue.registerHandler) takes precedence; a later registration for the same prefix replaces an earlier one. Worlds that deliver by subscription rather than by path (world-vercel) do not implement this: subscribe the route to the prefix's topics there.
streamFlushIntervalMsnumberNumber of milliseconds a stream waits for additional chunks to arrive before flushing to the underlying transport. Default 0: the first chunk dispatches immediately, and chunks arriving while a request is in flight coalesce into the next group. Setting this to > 0 trades first-chunk latency for fewer requests. The WORKFLOW_STREAM_FLUSH_INTERVAL_MS environment variable, when set, overrides this option. Not supported by all worlds.
streams{ createWriteSession?(runId: string, name: string, options: CreateStreamWriteSessionOptions): StreamWriteSession; ... 6 more ...; getInfo(runId: string, name: string): Promise<...>; }
runs{ get(id: string, params: GetWorkflowRunParams & { resolveData: "none"; }): Promise<WorkflowRunWithoutData>; get(id: string, params?: (GetWorkflowRunParams & { ...; }) | undefined): Promise<...>; get(id: string, params?: GetWorkflowRunParams | undefined): Promise<...>; ... 4 more ...; cancelMany?(request: { ...; }):...
steps{ get(runId: string, stepId: string, params: GetStepParams & { resolveData: "none"; }): Promise<StepWithoutData>; get(runId: string, stepId: string, params?: (GetStepParams & { ...; }) | undefined): Promise<...>; get(runId: string, stepId: string, params?: GetStepParams | undefined): Promise<...>; list(params: ListW...
events{ create<T extends RunCreatedEventRequest>(runId: string | null, data: T, params?: CreateEventParams | undefined): Promise<EventResult<T["eventType"]>>; create<T extends CreateEventRequest>(runId: string, data: T, params?: CreateEventParams | undefined): Promise<...>; createBatch?(runId: string, events: BatchEventRe...The event log, and the one part of this interface with a requirement the types cannot express: **the World allocates every event id, and every id is a slot**: evnt_ followed by the event's dense, 1-based position in its run's log, zero-padded to 26 characters. Use slotToEventId() to format one. Not a capability to opt into. The runtime reads a position out of every id it loads (requireEventSlot) and fails the run if it cannot, so a World whose ids are not positions cannot replay anything at all. Two properties are what the runtime actually relies on: - **Density.** A run's slots are contiguous from 1, so the number of events a reader holds *is* the position of the last one. That is what makes CreateEventParams.eventCount a complete statement of the writer's snapshot in a single integer, and what lets a reader tell a complete log from a truncated one by its length. - **Bump and report.** A create never fails because its requested slot is taken. The World advances to the next free slot, commits there, and returns the events occupying the slots it skipped over on the success response (see EventResult.events ). The writer learns its snapshot was stale without the write being rejected, which is why no World needs a precondition guard. Allocating at the commit is what makes a reader's log a *prefix* of the run's log rather than a prefix with a hole in it. A World that hands a position out earlier, and can therefore let an event land behind one a reader has already passed, breaks the property every replay depends on.
hooks{ get(hookId: string, params?: GetHookParams | undefined): Promise<{ runId: string; hookId: string; token: string; ownerId: string; projectId: string; environment: string; createdAt: Date; ... 7 more ...; claimedFrom?: { ...; } | undefined; }>; getByToken(token: string, params?: GetHookParams | undefined): Promise<....
experimental_snapshots{ save(runId: string, data: Uint8Array<ArrayBufferLike>, metadata: { eventsCursor: string | null; createdAt: Date; eventCount?: number | undefined; ... 5 more ...; formatVersion?: number | undefined; }): Promise<...>; load(runId: string): Promise<...>; delete(runId: string): Promise<...>; }VM snapshot storage for the QuickJS engine's VM-memory snapshotting.

World SDK

The World object provides access to several entity interfaces. See the World SDK reference for complete documentation:

Data hydration

Step and run data is serialized using the devalue format. Use workflow/observability to hydrate it for display:

import { hydrateResourceIO, observabilityRevivers } from "workflow/observability";

const step = await world.steps.get(runId, stepId);
const hydrated = hydrateResourceIO(step, observabilityRevivers);

See workflow/observability for the full hydration and parsing API.

List workflow runs (display names)

List workflow runs and derive human-readable names from the workflowName field:

import { getWorld } from "workflow/runtime";
import { parseWorkflowName } from "workflow/observability";

export async function GET(req: Request) {
  const url = new URL(req.url);
  const cursor = url.searchParams.get("cursor") ?? undefined;

  try {
    const world = await getWorld();
    const runs = await world.runs.list({
      pagination: { cursor },
      resolveData: "none",
    });

    return Response.json({
      data: runs.data.map((run) => {
        const parsed = parseWorkflowName(run.workflowName);

        return {
          runId: run.runId,
          // Use shortName for UI display (e.g., "processOrder")
          displayName: parsed?.shortName ?? run.workflowName,
          // Module info available for debugging
          module: parsed?.moduleSpecifier,
          status: run.status,
          startedAt: run.startedAt,
          completedAt: run.completedAt,
        };
      }),
      cursor: runs.cursor,
    });
  } catch (error) {
    return Response.json(
      { error: "Failed to list workflow runs" },
      { status: 500 }
    );
  }
}

The workflowName field contains a machine-readable identifier like workflow//./src/workflows/order//processOrder. Use parseWorkflowName() to extract the shortName (e.g., "processOrder") and moduleSpecifier for display in your UI.

  • getRun() - Higher-level API for working with individual runs by ID.
  • start() - Start a new workflow run.