Your first workflow
Write, run, and replay a durable workflow.
This walkthrough takes you from a freshly installed tide to a running,
crash-proof workflow — including a second example that drives ontology state.
If you have not installed Tide yet, start with
Installation.
1. Write a workflow
Every Tide workflow export defaults an object with two fields:
schema— the shape of the input, built with the globalSbuilder. It validates the input before your code runs.main— an async function that receives the validated input and contains your logic.
The file APIs (writeFile, readFile, …), step, log, and S/Infer are
all global — no imports needed.
Save this as greet.ts:
const schema = S.object({
name: S.text(),
greeting: S.default(S.text(), "Hello"),
});
export default {
schema,
main: async (ctx: Infer<typeof schema>) => {
log(`${ctx.greeting}, ${ctx.name}!`);
// Group related operations into an atomic, replayable step.
const receipt = await step("write-greeting", async () => {
await writeFile("greeting.txt", `${ctx.greeting}, ${ctx.name}!`);
return { wrote: "greeting.txt" };
});
log("step result:", JSON.stringify(receipt));
},
};Infer<typeof schema> gives ctx a precise type: name: string (required) and
greeting: string (defaulted, so always present in main). A wrong field is a
type error in your editor.
2. Run it
tide run greet.ts --input '{"name":"Ada"}'Tide prints the auto-generated invocation id to stderr, validates the input
against the schema, runs main, and journals every operation:
invocation: 7b3f…
Hello, Ada!
step result: {"wrote":"greeting.txt"}greeting was omitted, so its default ("Hello") was filled in before main
ran. To inspect the input shape a workflow accepts, ask for its schema as JSON:
tide schema greet.ts3. Replay it
Durable execution shows up when you re-run the same invocation id. Give the run an explicit id, then run it again:
tide run greet.ts -i greet-001 --input '{"name":"Ada"}' # first run — executes live
tide run greet.ts -i greet-001 # replay — input is rememberedOn the second run Tide loads the journal and replays it: the step returns its
stored result instantly without re-executing the file write, and the stored
input is reused (you do not pass --input again). If the first run had crashed
partway through, the replay would pick up exactly where it stopped — no work is
repeated, no side effect is doubled. That is the whole point of Tide.
4. Drive ontology state (optional)
The shipped binary embeds Argon, so a workflow can read and write a typed
ontology world with ctx.argon. Run this from an Argon project (a directory
with an ox.toml); tide run builds the ontology and generates a typed client
into .tide/, both in-process.
import { defineWorkflow, S } from "tide";
import { Company, Person, sdk } from "../.tide/argon-client.ts";
export default defineWorkflow({
argon: sdk,
input: S.object({
company: S.text(),
name: S.text(),
salary: S.integer(),
}),
async run({ company, name, salary }, { argon, log }) {
// World-state writes run at the top level (NOT inside step()): the runtime
// journals each write and replays it verbatim on resume — exactly-once.
const receipt = await argon.mutations.hire.receipt({
employer: Company.ref("#i1"),
employee: Person.ref("#i2"),
employer_name: company,
employee_name: name,
salary,
});
log("hired", name, "— events:", receipt.eventsEmitted.length);
// Read-your-writes within the run.
const employees = await argon.queries.all_employees.run({});
return { hired: name, headcount: employees.length };
},
});tide run workflows/onboard.ts --input '{"company":"Acme","name":"Ada","salary":120000}'The Argon client is fully typed, journaled, and fork-isolated per run. The full
story — defineWorkflow, the generated client, determinism rules, durable
worlds, and testing — is in Driving Argon.
Next
- Core concepts — the model behind replay.
- Steps — atomic, retryable transactions.
- Human-in-the-loop — suspend for a decision,
then
tide resume. - CLI reference — every command and flag.