Tide

Core concepts

Journaled replay, exactly-once effects, and deterministic time — in plain terms.

Tide makes ordinary TypeScript durable: a workflow that crashes, restarts, or is redeployed resumes exactly where it stopped, without repeating completed work. Four ideas make that work.

The journal

As your workflow runs, Tide records every operation — file writes, sleeps, console output, steps, ontology mutations — as an entry in a journal, persisted to a storage backend. The journal is the single source of truth for what the workflow has already done.

Replay

Re-run a workflow with the same invocation id and Tide enters replay mode. Your code runs again from the top, but each operation returns its stored result from the journal instead of executing live. Once the journal is exhausted, execution switches to live mode and new operations are appended. A crash is just a replay that runs a little further than the last one.

This is why an invocation id matters: it names the journal. The same id replays; a new id starts fresh. (Omit -i and Tide generates a UUID — a one-shot run with no replay.)

Determinism

Replay is only safe if the code produces the same sequence of operations every time. Tide enforces this: if a replayed run asks for a different operation than the journal recorded at that position, it raises a determinism violation.

In practice this means: don't branch on Math.random() or wall-clock time, and don't reorder already-journaled operations. You can freely change log messages, add operations after the last journaled one, and change logic inside a step (a step is one atomic entry from the journal's point of view). See Durable execution for the full rules.

Exactly-once effects

Some operations reach beyond the workflow — a human-approval request, an LLM call, an ontology mutation. Tide reserves a deterministic effect intent before such a call runs, keyed by (invocation id, effect ordinal). The reservation is idempotent: a crash-and-replay re-reads the existing reservation instead of re-invoking the host. So these effects happen at most once, even across crashes — on every storage backend, not only in a database.

Deterministic time

On first run, Tide captures Date.now() and freezes it. Every Date.now() and new Date() for the rest of the invocation returns that same value, and the frozen timestamp is replayed on later runs. A workflow that ran at 2 PM and replays at 3 PM sees the same time — so time-dependent branching never breaks replay. (performance.now() always returns 0.)

Steps

A step wraps several operations into an atomic unit: they all commit together or roll back together. On replay a completed step returns its stored result without re-running its body. Steps are how you make multi-operation work reliable and how you isolate logic that might change across deploys. See Steps.

Putting it together

  1. Write { schema, main }. schema validates and persists the input.
  2. First run: validate → run main → journal every operation.
  3. Re-run with the same id: replay the journal, then go live.
  4. Steps commit atomically; external effects are reserved exactly-once; time is frozen.

Next

On this page