Introduction

The pieces of a Duraton run - steps, events, and runners - whether it is an AI agent or an ordinary background job.

An agent is an ordinary function - in TypeScript, Python, or Go. So is a nightly job, and Duraton treats them the same way. Wrap each unit of work in a step, and Duraton records that step's result the moment it completes. So a crash, a restart, or a deploy resumes the run at the next step instead of starting over, and a step that waits on a human holds no worker while it waits.

import { defineWorkflow } from "@duraton/sdk";

const ticketCreated = defineWorkflow<{ ticketId: string; subject: string }>({
  name: "ticket.created",
  retry: { maxAttempts: 3 },
  handler: async (ctx) => {
    const { text } = await ctx.step.ai.generate("triage", {
      model: "claude-opus-4-8",
      prompt: `Summarise this refund request: ${ctx.event.data.subject}`,
    });

    const decision = await ctx.step.approval("refund-gate", {
      tool: "issue-refund",
      args: { ticketId: ctx.event.data.ticketId },
      risk: "high",
      summary: text,
    });

    if (decision.status === "denied") return { outcome: "denied" };

    return await ctx.step.run("refund", () => issueRefund(decision.args));
  },
});

Each step executes once; its result is memoized. triage never pays the model twice. The approval parks the run holding no worker, for as long as the decision takes. If refund fails, only refund retries - the model is not called again and nobody is asked to approve a second time.

A step id ("triage", "refund-gate", "refund") is how its saved result is found on the next pass. Keep ids stable and unique within a handler, or a replay will not match the work it already did.

Not building an agent? Drop the ai and approval steps and the rest is unchanged. A nightly job, a cron, a webhook handler, or a fan-out gets the same durability, the same per-step retries, and the same replay - see Running anything else durably.

The pieces

PieceWhat it isReference
WorkflowA named function, triggered by an event or a cron schedule.Workflows
StepOne durable unit inside a handler. Runs once, result recorded, retried on its own.Steps
RunOne execution of one workflow, with its own status, steps, logs, and output.Runs API
EventThe message that starts a run. Persisted, and can fan out to many workflows.Events
RunnerYour process, holding your workflow code. It dials out with connect() or is served over HTTP.Runners
AppThe name a runner registers under; workflows are addressed by name + app.Workflows
ProjectThe isolated slice - its own runs, events, keys, and runners.Workspaces

What you can build with

CapabilitySurfaceReference
Durable workstep.run, step.sleep, step.sleepUntil, step.waitForEvent, step.runWorkflow, step.emitSteps
Parallel workPromise.all over stepsSteps
Failure handlingPer-step retries, NonRetriableError, RetryAfterError, an onFailure handlerRetries
TriggersEvent triggers with CEL filters and wildcards, cron triggersTriggers
Flow controlconcurrency, throttle, rateLimit, debounce, batch, priority, singleton, idempotencyFlow control
Run controlCancel, pause, resume, replay, retry-from-stepControl API
Live outputctx.log, and a streaming run timelineLogging, Realtime
AIstep.ai.generate, step.ai.loop, streaming, approvals, spend caps, evalsAI

However the work arrives, however the result leaves

An agent is only useful if something real can start it and something real happens when it finishes. You do not have to change how your business emits work to get either.

Getting a run started. Pick whichever you already have - a run can be started by more than one:

The work arrives asHow it starts a runReference
An event you send yourselfPOST /events, or duraton.events.send() from any serviceEvents API
A POST from a third partyA webhook source verifies the signature, then turns it into an eventWebhooks
A record on your own Kafka topicsThe engine consumes the topic and maps records to eventsKafka
Nothing at all - it is just timeA cron trigger, with no event behind itTriggers
A person deciding to run itA manual trigger from the console or the APITriggers

Getting the result out. The same three shapes, in reverse:

You want toUseReference
Hand off to another workflowctx.step.emitSteps
Tell an outside systemctx.webhook.send - signed, retried, every attempt loggedWebhooks
Publish back to your own topicsProduce to Kafka from inside a stepPublishing back

Both directions keep a durable attempt log you can inspect, redeliver, or replay - so "did the partner ever get it?" is a question with an answer.

And an agent can drive all of it. Duraton ships an MCP server, so an AI agent is a first-class operator: it can list and control runs, send events, score runs, and run comparisons. An agent is not only the thing being run - it can be the thing doing the running.

Next steps

On this page