Tide

Examples

Walkthrough workflows for common patterns.

These examples live under examples/ in the repository and run directly with tide run. Each one is small and self-contained.

Hello world

examples/hello-world/main.ts — write a file, read it back, clean up, sleep.

const schema = S.object({});

export default {
  schema,
  main: async (_ctx: Infer<typeof schema>) => {
    log("Hello from Tide!");

    await writeFile("test_output.txt", "Tide runtime wrote this file.");
    const contents = await readFile("test_output.txt");
    log("Read back:", contents);

    await removeFile("test_output.txt");
    log("Cleaned up.");

    await sleep(100);
    log("Sleep completed!");
  },
};

Demonstrates the empty schema (S.object({}) needs no input), the VFS (writeFile / readFile / removeFile), journaled log, and sleep (which is skipped on replay).

tide run examples/hello-world/main.ts -i hello-001   # first run
tide run examples/hello-world/main.ts -i hello-001   # replay — sleep is skipped

Typed input

examples/input-schema/main.ts — required, optional, defaulted, and nested fields, plus a step.

const schema = S.object({
  name: S.text(),
  count: S.integer(),
  priority: S.default(S.enum(["low", "medium", "high"]), "medium"),
  tags: S.optional(S.list(S.text())),
  config: S.object({
    retries: S.default(S.integer(), 3),
    verbose: S.optional(S.boolean()),
  }),
});

export default {
  schema,
  main: async (ctx: Infer<typeof schema>) => {
    log("Processing:", ctx.name, "priority:", ctx.priority);
    if (ctx.tags) log("Tags:", ctx.tags.join(", "));
    log("retries:", ctx.config.retries);

    await writeFile("result.txt", `Processed ${ctx.count} items for ${ctx.name}`);

    const result = await step("process", async () => {
      return { processed: ctx.count, name: ctx.name };
    });
    log("Step result:", JSON.stringify(result));
  },
};

priority and config.retries are defaulted, so callers may omit them and main still sees a value.

tide run examples/input-schema/main.ts \
  --input '{"name":"widgets","count":5}'

Validation reports every error at once with a JSON-path:

tide run examples/input-schema/main.ts --input '{"name":123}'
# Input validation failed:
# $.name: expected Text string, got number 123
# $.count: required but missing
# $.config: required but missing

See Schema (S) reference for the full builder.

Steps

examples/step-test/main.ts — atomic file writes, chained steps, sleep inside a step.

const result = await step("write-files", async () => {
  await writeFile("hello.txt", "Hello from step!");
  await writeFile("world.txt", "World from step!");
  return { hello: await readFile("hello.txt"), world: await readFile("world.txt") };
});

// Files written inside a committed step persist outside it.
const hello = await readFile("hello.txt");

const count = await step("count-files", async () => {
  return { fileCount: 2 };
});

await step("timed-step", async () => {
  await sleep(500);
  return "done";
});

Multiple writes inside a step commit atomically; a later step reads what an earlier one wrote. See Steps.

tide run examples/step-test/main.ts -i step-001
tide run examples/step-test/main.ts -i step-001   # replay — steps return stored results

Human-in-the-loop

examples/wait-for-human/main.ts — suspend for a human decision.

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

export default {
  schema,
  main: async (ctx: Infer<typeof schema>) => {
    const decision = await hitl.wait({
      title: `Review extracted evidence for ${ctx.documentId}`,
      priority: "high",
      moduleName: "evidence_review",
      payload: { summary: "", review_notes: "" },
    });
    log("Human action:", decision.action);
    await writeFile("human-decision.json", JSON.stringify(decision, null, 2));
  },
};
tide run examples/wait-for-human/main.ts -i review-1 --input '{"documentId":"doc-42"}'
# suspends; then:
tide resume review-1 examples/wait-for-human/main.ts --action approved

See Human-in-the-loop.

LLM generate

examples/ai-generate/main.ts — text and structured output from ai.llm.

const verdictSchema = S.object({
  decision: S.enum(["approved", "denied", "needs_review"]),
  riskScore: S.nat(),
  reason: S.optional(S.text()),
});

// Structured object out, typed via Infer<typeof verdictSchema>.
const verdict = await ai.llm.generate("gpt-5.4", [
  { role: "system", content: "You are a strict approver." },
  { role: "user", content: `Should we pay this invoice? ${ctx.invoiceText}` },
], { outputSchema: verdictSchema });

log("decision:", verdict.decision);   // "approved" | "denied" | "needs_review"

ai.* requires the host to be configured with provider credentials; the same S builder describes both input and structured output.

Driving an ontology

examples/employment-onboarding/ — a full Argon project: tide run builds the ontology and drives it through the typed ctx.argon client. See Driving Argon for the walkthrough.

On this page