Tide

Workflow API

defineWorkflow, the workflow context, step, hitl, the VFS, and time.

A workflow is a TypeScript module that export defaults { schema, outputSchema?, settlement?, main }. Most of the API is global — no imports — while the typed authoring entry is importable from the virtual tide module. Type definitions live in runtime/src/tide.d.ts.

The workflow shape

const schema = S.object({ name: S.text() });

export default {
  schema,                                   // built with S — validates input
  outputSchema: S.object({ greeting: S.text() }),
  settlement: "promote_on_success",
  main: async (ctx: Infer<typeof schema>) => {
    return { greeting: `Hello ${ctx.name}` };
  },
};

defineWorkflow

defineWorkflow is typed sugar over the core export: it gives the body a discoverable core ctx. Plugin APIs are imported from generated modules. It returns the core export, so the module still export defaults its result.

import { defineWorkflow, S } from "tide";

export default defineWorkflow({
  input: S.object({ ... }),    // optional: defaults to S.object({})
  output: S.object({ ... }),   // validates and types the durable result
  settlement: "discard_on_success", // optional; default promotes
  async run(input, ctx) {      // input typed via Infer<typeof input>
    return { ... };
  },
});

When output/outputSchema is declared, the return value must be JSON and pass that schema. Tide canonicalizes it, refuses results above 1 MiB, and returns its sha256: digest. Returning a value without declaring an output schema is an error. Completed results are persisted by the runner and exposed by the control API; failed, running, and suspended invocations never expose a result.

settlement controls successful world-state writes:

ValueSuccessful run
promote_on_successPromote the run fork into main. This is the default.
discard_on_successDiscard the run fork but retain completed status and the durable result.

Failure always discards. Suspension retains the run fork for resume.

The workflow context (ctx)

FieldWhat it is
stepJournaled, replayable step runner.
hitlHuman-in-the-loop wait (hitl.wait(...)).
llmLarge-language-model calls (ai.llm).
aiThe full ai.* surface (llm, agent, tool, code).
log, sleep, readFile, writeFile, readFileBytes, writeFileBytes, removeFile, listFilesThe sandbox capabilities.

The same capabilities are available as globals in the core { schema, main } form.

step

step<T>(name: string, fn: () => Promise<T>, opts?: { retries?: number }): Promise<T>

Runs fn as an atomic, journaled transaction. On success its return value is recorded; on replay a completed step returns that value without re-running the body. On failure the step's buffered operations and VFS changes are rolled back. With { retries: n } the body is retried up to n times before the error propagates. Nested steps are not supported. See Steps.

const result = await step("charge", async () => {
  await writeFile("receipt.txt", "charged");
  return { ok: true };
}, { retries: 3 });

hitl

hitl.wait(request: HitlWaitRequest): Promise<HumanWaitOutcome>

Suspends the run for a human decision; resumes via tide resume. request requires a title; description, tags, priority, payload, classification, moduleName, and others are optional. The returned outcome carries action, payload, comment, resolvedBy, and resolvedAt. See Human-in-the-loop.

Virtual file system

All file operations run against an in-memory, sandboxed VFS — never the host filesystem. State is derived from journal replay.

FunctionDescription
readFile(path): Promise<string>Read a text file.
writeFile(path, contents): Promise<void>Write a text file.
readFileBytes(path): Promise<Uint8Array>Read a binary file.
writeFileBytes(path, data): Promise<void>Write a Uint8Array.
removeFile(path): Promise<void>Delete a file.
listFiles(path?): Promise<{ name, isFile }[]>List directory entries.

Console and logging

log(...) and console.log(...) write to stdout; console.error(...) to stderr. Output is journaled and replayed deterministically — live logging during replay is suppressed to avoid duplicates. Changing log messages does not break determinism.

Time and sleep

sleep(ms: number): Promise<void>

sleep pauses on first run and is skipped on replay. Date.now() and new Date() return a frozen timestamp captured on first run and replayed thereafter; performance.now() returns 0. See Deterministic time.

AI capabilities

ai.llm.generate(model, prompt, options?) returns text, or a typed object when an outputSchema (built with S) is supplied. ai.agent(...) runs a stateful ReAct tool loop, ai.tool(...) declares a tool, and ai.code.* drives cloud coding agents. These require the host to be configured with the relevant credentials; in the disabled default posture they refuse deterministically. Full signatures are in runtime/src/tide.d.ts.

The tide module exports

Most APIs are global; the importable surface is:

ImportDescription
import { defineWorkflow } from "tide"Typed authoring entry.
import { S, step, log, sleep, hitl, ai, readFile, … } from "tide"The globals, importable when you prefer explicit imports.
import { describe, test, it, assert, expect } from "tide:test"The test harness.
import { argon } from "tide:plugins/argon"A generated, installed plugin API; never a global.

On this page