Cold open · Platformatic · Vancouver · October 2026

agents are workflows

Determinism, journals, and state without a landlord.

Luca Maraschi · CEO, Platformatic→ next · ← back · f fullscreen · p presenter

Cold open · Who is talking

Luca Maraschi, Platformatic

Me

Co-founder and CEO of Platformatic. Three companies founded and exited before this one.

Twenty years running engineering, the Canadian chapter at mobileLIVE and Telus Digital. Based in Vancouver.

I work on the runtime layer where durable execution, agents and self-hosted infrastructure meet.

Platformatic

Node.js infrastructure, co-founded with Matteo Collina.

Watt: an application server that runs Node.js services as worker threads with in-memory routing.

ICC: the control plane that runs them on Kubernetes: deployments, scaling, upgrade safety.

Core maintainers of Fastify, Undici and Pino, the libraries under both products.

Papers, with Matteo Collina

  • 2309.06238A risk index for breaking changes across a microservice graph, computed from OpenTelemetry traces (2023)
  • 2604.19705Predictive autoscaling for Node.js on Kubernetes
  • 2607.13617A telemetry-driven model for quantifying upgrade risk in durable workflow execution (WUR)
  • 2609.17645State Without a Landlord: peer-to-peer replication of durable workflow state. The last section of this talk.

Cold open · Minimum viable product

A production agent is a long-running program that calls a nondeterministic function in a loop

modelplan toolread repo modelpatch humanapprove? tooldeploy nondeterministic nondeterministic 3 days, compute held waits on a person process dies here in-memory turn state: gone That is a durable workflow. Most agent frameworks rediscover it the hard way.

Cold open · How it usually gets built

Ad hoc durability

  • CheckpointsSerialize "where we are" into a database at hand-picked points. Every point is a guess.
  • Retry wrappersDecorators around tool calls. Idempotency is the tool author's problem.
  • State in a tableA status column that the code and the table disagree about after a crash.
  • Resume logicA second code path that nobody tests, executed only on the worst day.

Each piece is reasonable. Together they have no invariant.

There is no statement of the form "after any crash, the system is in state S" that you can check. You cannot replay a checkpoint. You cannot prove a retry was safe. You cannot tell a truncated history from a complete one.

Season 01

S01 · E01

Middle-Out

One ordinary async function. A compiler splits it from the middle out into a deterministic orchestrator and a set of steps, and an append-only journal makes the whole thing recoverable.

S01 · Middle-Out · Enter Workflow SDK

Workflow SDK: durable execution as a library, not a platform

What it is

Vercel's open-source durable execution SDK for TypeScript. github.com/vercel/workflow

Two directives, "use workflow" and "use step", on ordinary async functions.

Next.js, Express, Fastify, Hono, Nitro, SvelteKit, Astro, Nest. A Python SDK in beta.

Where it is

GA in April 2026 with 200K+ weekly downloads and 75+ beta releases behind it.

v5 stable in October: workflow@5.2.3 on 20 Oct, up to 5x faster than v4 with no API change.

Same family as AI SDK and Chat SDK.

The World

The runtime talks to one interface for storage, queue and streams.

First-party: Vercel, Postgres, Local. Community: Rivet, browser, MongoDB, Redis, Turso, and ours.

Even Vercel's own backend is one World among several.

Where we stand

We do not own the SDK. We run it self-hosted: PlatformaticWorld on Watt and Kubernetes, ICC for deployment safety.

And we contribute upstream: build hooks (#3163), diagnostics_channel tracing (#3463).

Every slide from here is about this runtime.

S01 · Middle-Out · The claim

A workflow is an ordinary async function. The compiler does the rest.

export async function triageAgent(issue) {
  "use workflow";                       // orchestrator
  let plan = await callModel({ issue });
  while (!plan.done) {
    const out = await runTool(plan.tool);
    plan = await callModel({ issue, out });
  }
  const hook = createHook({ token: issue.id });
  await notifyReviewer(hook.token);    // suspends
  for await (const d of hook) {
    return d.approved ? await apply(plan) : plan;
  }
}

async function callModel(input) {
  "use step";                           // side effects
  return llm.generate(input);
}
  • "use workflow"The orchestrator. Plain control flow: loops, try/catch, Promise.all. No I/O.
  • "use step"The only place a side effect may happen. LLM calls, tools, writes.
  • hooks, sleepSuspension points. The run becomes a journal plus a queue message.
  • No framework objectNo ctx.step(...), no DSL. The language is the workflow engine.

S01 · Middle-Out · Mechanism 1 of 3

The compiler splits one function into an orchestrator and addressable steps

triageAgent.ts "use workflow" await callModel() while (…) { await runTool() await callModel() } await notifyReviewer() for await (hook) … SWC pass at build time orchestrator bundle runs inside a sandboxed VM, no I/O replayed from the journal on every invocation control flow only: loops, branches, Promise.all callModelstep id: stable runToolstep id: stable notifyReviewerstep id: stable step bundle: ordinary Node code, invoked by the queue, results written to the journal

S01 · Middle-Out · Mechanism 2 of 3

The journal is the run. Recovery is replay, not reconstruction.

append-only log, one run evnt_…01 run_created evnt_…02 step_created callModel evnt_…03 step_started callModel evnt_…04 step_completed callModel → plan evnt_…05 step_created runTool evnt_…06 step_started runTool evnt_…07 step_completed runTool → out evnt_…08 step_created callModel invocation 1 loads log · replays orchestrator · reaches the first unrecorded await writes step_created, dispatches the step through the queue, exits step runTool, attempt 1 ordinary Node process, runs the body, writes the result queue redelivers; step_started is refused only on a terminal run step runTool, attempt 2 same step id, same input, result lands at the next free position invocation 2 = replay reads events 1..7, every await resolves from the log, code continues past runTool

S01 · Middle-Out · What the one choice buys

Five properties agents need, for free

  • Suspension is freeA run waiting three days is a journal row plus a delayed queue message. No process, no connection, no spinner.
  • Recovery is replayAny invocation can be killed at any instant. The next one recomputes from the log prefix. There is no resume code path.
  • Retry at the step boundaryIdempotency is a property of the step id and input, not of the tool author's discipline.
  • Version skew is explicitA run is pinned to the deployment that created it. Deliveries that reach the wrong deployment are re-routed, then fail with DEPLOYMENT_MISMATCH.
  • The journal is the traceSpans are projected from events, deterministically, instead of sampled from a process that may not survive.

S01 · Middle-Out · Suspension, measured in compute

Waiting three days costs zero compute

compute queue store replay + step wait_created msg, delaySeconds no process, no memory, no socket for 3 days delivered replay, continue A hook works the same way: hook_received is written out of band, then the queue wakes the run.Resume writes the event and dispatches the wake concurrently, deduplicated on (runId, resumeId).

S01 · Middle-Out · Mechanism 3 of 3

Determinism is enforced, not requested

Not available inside the orchestrator

  • Time, randomnessDate.now, Math.random resolve from the journal. Atomics.waitAsync is not exposed: wall-clock timing cannot replay.
  • GC observationWeakRef, FinalizationRegistry and async WebAssembly compilation are removed from the sandbox.
  • Cryptocrypto.subtle.digest computes synchronously via node:crypto: identical bytes, deterministic timing under replay.

How

  • node:vm sandboxHost intrinsics captured at boot; serializing a workflow value never executes workflow code.
  • QuickJS in WASMOpt-in engine (WORKFLOW_VM=quickjs): same full replay, runs where node:vm does not, and enables VM memory snapshots so a 25K-event run stops replaying from zero.
  • Bounded replayServer-supplied per-run event limit, default 25K. Snapshotted QuickJS runs are exempt.

S01 · Middle-Out · Runtime-to-infrastructure separation

The runtime talks to exactly one interface: the World

@workflow/core replay engine, compiler output, flow handler interface World Storageruns, steps, hooks, the event log Queueat-least-once delivery, delaySeconds, batches Streamernamed per-run streams, chunks, sessions specVersion · capabilitiesevery capability fails closed:unadvertised means unsupported @workflow/world-vercel @workflow/world-postgres @workflow/world-local PlatformaticWorld @rivet-dev/workflow-world workflow-world-browser world-sim, Eveland, … Even the Vercel backend is one implementation of the spec. Nothing in the application code knows which.

S01 · Middle-Out · Self-hosted

The same runtime on Watt and Kubernetes, with ICC as the control plane

kubernetes namespace Watt pod worker: app worker: flow handler worker: steps in-memory routing between workers Watt pod worker: app worker: flow handler worker: steps Watt pod worker: app worker: flow handler worker: steps PlatformaticWorld Storage + Queue + Streamer journal in Postgres, queue in the cluster mintedSpecVersion(), inBandFence passes @workflow/world-testing ICC deployment identity per run pins a run to the build that created it gates promotion while runs are in flight no DEPLOYMENT_MISMATCH by construction Vercel ships skew protection as a platform property.Self-hosted it is a control plane property, so the control plane must know every run.

S01 · Middle-Out · Cross-deployment steps

A step can execute as an isolated workflow in another deployment and still belong to one run

deployment A · private evnt_…11 step_created settle remote('payments.settle', …) ICC rendezvous soft state only who handles which key, which transport, which version deployment B · payments run: settle (isolated) pull-claim: private side initiates no new ingress on either cluster evnt_…12 step_completed settle timeout: Promise.race(result hook, sleep(budget)) typed call sites in v1, string keys for discovery public handlers: push-dispatch, outbound result polling Temporal Nexus parity, built on hooks and sleep, without a second control plane.

S01 · Middle-Out · Observability

The journal is the trace

journal …01 run_started …03 step_started callModel …04 step_completed callModel …06 step_started runTool …07 step_completed runTool …09 run_completed OTLP projection run trace_id = f(runId) step callModel span_id = f(evnt 03) step runTool span_id = f(evnt 06) Same log, same spans, any machine, any time. Emit once, backfill allowed. WDK's live OTel spans (runId, stepId, attempt) nest under them. A dc tracingChannel sits beneath OTel: 21 ns per step unsubscribed.

Season 02

S02 · E01

Tabs versus Spaces

Four systems from three teams landed on the same invariant in 2026: commit the intent before the effect, recover from what was committed. Same invariant, four contracts. Then, on 1 October, one of them changed the equation for the other two.

Workflow SDK · Cloudflare Workflows · Cloudflare fibers · Pi Durable

S02 · Tabs versus Spaces · Cloudflare Agents SDK, Durable Objects

Fibers: a durable function invocation registered before it runs

Durable Object · one agent single-threaded, co-located SQLite, hibernates sqlite fiber "turn-42" status: running runFiber("turn-42", async ctx => { // row written to SQLite before this body starts snapshot: { step1 } const step1 = await model(); ctx.stash({ step1 }); const step2 = await tool(step1); evicted mid-turn onFiberRecovered(ctx) { ctx.snapshot → { step1 } // harness decides Project Think preview 15 Apr 2026 · primitives opened to any harness 17 Jun 2026 (Flue on Pi) · Code Mode isolates start under 10 ms

S02 · Tabs versus Spaces · Cloudflare Workflows v2, 15 Apr 2026

Cloudflare Workflows: memoized steps, one Engine per instance

export class Triage extends WorkflowEntrypoint {
  async run(event, step) {
    const plan = await step.do("plan", () => model(event));
    const out = await step.do("tool", () => tool(plan));
    await step.waitForEvent("approval", { timeout: "7d" });
    return step.do("deploy", () => deploy(out));
  }
}
Engine Durable Object 1 per instance, SQLite-backed, hibernates "plan" → cached return value "tool" → cached return value step name is the cache key
function dies during waitForEvent run() re-executes from the top. step.do("plan") and step.do("tool") return the cached values without running; code outside a step "may be repeated more than once". Same replay model as Workflow SDK. Enforcement is a rules page, not a sandbox. The log is one Durable Object per instance, 1 MiB per step return, not a swappable World. v2, 15 Apr 2026: 50,000 concurrent instances (from 4,500) · 300 creations/s per account (from 100) · Engine DO created 1:1 with the instance

S02 · Tabs versus Spaces · Earendil Pi Durable · Pi 1.0, 1 Oct 2026

Every tool call is a task whose intent is committed before it executes

storage backend memory · SQLite · JSONL, one process owns it entry user: "fix the flaky login test" task model-request checkpoint task tool bash intent committed A conversation is a transcript. Everything the harness runs is a task. Each task phase stores a checkpoint before it moves on. process dies harness.resume() opens the same storage, finds unfinished tasks, continues each from its last checkpoint replay: "safe" → tool reruns (search_issues) no replay flag → model is told the call was interrupted (deploy) memos: first write wins, so a hook that asked a human once never asks twice · requestId makes a submission exactly-once · about 15,000 lines total

S02 · Tabs versus Spaces · Three weeks in October

1 October changed the equation: the harness became a third party

1 Oct Pi Durable ships With Pi 1.0. A storage interface small enough to run inside a Durable Object. 2 Oct Cloudflare: PiHarness Pi's own Harness inside a Durable Object. "Built with Earendil", a first step to third-party harnesses. 12 Oct Vercel: World PR #4835 Route-agnostic queue delivery, so "a durable agent harness" can keep its own runs in a World. 19 Oct Vercel: durable-harness Pi Durable on a World as the recommended path. replay safe/never for Claude Code, Codex, OpenCode. 22 Oct Cloudflare: Pi 1.4 Agents SDK tracks the Pi release train.

Before October: each platform owned its harness (Project Think, eve, fx) and the durability model under it.
After October: the harness is a third party. The platform competes on the storage, queue and compute contract underneath it.

Durability stopped being a feature of the harness and became the product of the platform.

S02 · Tabs versus Spaces · Side by side

Same invariant, four different contracts

Workflow SDKCloudflare WorkflowsCloudflare fibersPi Durable
unit of durabilityevent in an append-only, slot-numbered logstep.do return, keyed by step name, in the Engine DOfiber row + stash snapshot in DO SQLitetask checkpoint + committed intent
recoverydeterministic replay of the log prefixrun() re-executes; done steps return cached valuessnapshot to onFiberRecovered; harness decidesresume each task from its last checkpoint
effect boundary"use step", compiler-enforcedstep.do; code outside may run twicewherever you call stash()tool, with a replay flag
idempotencystep id + input; dedup on (runId, resumeId)step name as cache key; steps "should ideally be idempotent"yours, between stashesreplay:"safe", memos, requestId
determinismenforced by the sandboxrequired by the rules pagenot requirednot required; model calls re-sent
where it runsany World, any hostCloudflareCloudflareany JS runtime; one process owns storage
who owns the journalthe Worldthe Engine Durable Objectthe Durable Objectthe storage backend

Season 03

S03 · E01

The New Internet

The World became the spec. In 4.x it was an adapter the SDK happened to use. In 5, the SDK is one client of a published protocol, and it checks the version of the thing it is talking to.

workflow@5.2.3 · @workflow/world@5.0.5 · world-vercel@5.2.3 · world-postgres@5.0.5 · world-local@5.1.2 · released 20 Oct 2026

S03 · The New Internet · What changed in the contract

Spec versions, slot-numbered events, a runtime that checks

4.x event id: any ULID runtime demands exact version equality spec 6 slot identity evnt_ + 1-based position, 26 chars, dense, allocated at commit spec 7 sealed log pre-assigned positions allowed, holes sealed with noop events spec 8 what mintedSpecVersion() stamps hook token takeover semantics, in-band writer fence capability workflow@5.2.x accepts a World declaring 6 through 8, refuses anything else at startup a run keeps the version it was created under for its whole life Density is the property: a reader can tell a truncated log from a complete one by its length alone.

S03 · The New Internet · The inversion

Workflow SDK is now a client of the World

Workflow SDK 4.x: the World is its storage adapter world adapter World (spec 8) Storage · Queue · Streamer · capabilities @workflow/core queue prefix __wkf_workflow_ flow route /.well-known/workflow/v1/flow vercel-workflow (Python) same .workflow-data JSON, same queue since 0.9.0, 7 Aug 2026 a durable agent harness its own runs, its own prefix: __agent_wkf_workflow_session routed by registerRoute / registerHandler getWorld() · invoke() dashboards and admin tools read the World directly invoke: request/response into a live run, one runner per runId world-vercel world-postgres · world-local PlatformaticWorld @rivet-dev/workflow-world workflow-world-browser world-testing: conformance suite The World is a protocol with a version, a conformance suite, and more than one client. The SDK is the first of them.

S03 · The New Internet · The uncomfortable question

If the journal is the agent, who owns the journal?

Today: the World owns it. Durability, timeouts and continuation after a deploy are the landlord's word. party A party B party C State Without a Landlord · arXiv 2609.17645 epoch 1 · writer A epoch 1 · replica epoch 1 · replica single-writer epoch core closure cert · B,C sign epoch 2 · writer B succession only on a quorum-signed closure durable + timeout certs asserted by certificates anyone can verify payloads on a content-addressed plane A signature proves who wrote what, in what order. It proves nothing about whether the effect happened. that gap is the whole threat model for agents that negotiate across companies, outlive their vendor, or cannot be trusted

Outro · Take with you

Build the agent as a workflow. Treat the World as the contract.

  • The invariantSide effects only in steps, results only in an append-only log, recovery as a pure function of that log.
  • The convergenceCloudflare Workflows, fibers and Pi Durable commit intent before effect too. The difference is whether recovery replays or decides, and whether determinism is enforced or requested.
  • The inversionThree weeks after Pi Durable, both platforms host it. The World is a versioned protocol with a conformance suite; the SDK is one client. Build against the spec.
  • The open problemA run with no host. Signatures prove order, not effects. Come break it.
useworkflow.dev · workflow-sdk.dev/worlds · platformatic.dev arXiv 2609.17645 (State Without a Landlord) · arXiv 2607.13617 (upgrade risk in durable execution) Luca Maraschi · @lucamaraschi · luca@platformatic.dev

End credits · Platformatic · Vancouver · October 2026

thank you.

Making the world a better place through append-only, slot-numbered event logs.

Luca Maraschi · @lucamaraschi · luca@platformatic.dev platformatic.dev · useworkflow.dev · arXiv 2609.17645
1 / 28
→ / space next · ← back · f fullscreen · n notes · p presenter view · home / end