Documentation
Duraton is where your AI agents run: they wait for a human on the risky moves, pick up where they stopped after a crash, and keep a record of what every run cost.
Duraton is where your AI agents run. They wait for a human on the risky moves, pick up where they stopped after a crash, and keep a record of what every run cost.
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));
},
});from duraton import ApprovalRequest, GenerateOptions, RetryConfig, define_workflow
from duraton.context import StepContext
async def handle_ticket(ctx: StepContext) -> object:
triage = await ctx.step.ai.generate("triage", GenerateOptions(
model="claude-opus-4-8",
prompt=f"Summarise this refund request: {ctx.event.data['subject']}",
))
decision = await ctx.step.approval("refund-gate", ApprovalRequest(
tool="issue-refund",
args={"ticketId": ctx.event.data["ticketId"]},
risk="high",
summary=triage.text,
))
if decision.status == "denied":
return {"outcome": "denied"}
return await ctx.step.run("refund", lambda: issue_refund(decision.args))
ticket_created = define_workflow(
"ticket.created",
handle_ticket,
retry=RetryConfig(max_attempts=3),
)import "duraton.dev/sdk-go/duraton"
var ticketCreated = duraton.DefineWorkflow(duraton.WorkflowDefinition{
Name: "ticket.created",
Retry: &duraton.RetryConfig{MaxAttempts: 3},
Handler: func(c *duraton.Context) (any, error) {
ticket, err := duraton.EventData[Ticket](c)
if err != nil {
return nil, err
}
triage, err := duraton.Generate(c, "triage", duraton.GenerateOptions{
Model: "claude-opus-4-8",
Prompt: "Summarise this refund request: " + ticket.Subject,
})
if err != nil {
return nil, err
}
decision, err := duraton.Approval[RefundArgs](c, "refund-gate", duraton.ApprovalRequest{
Tool: "issue-refund",
Args: RefundArgs{TicketID: ticket.ID},
Risk: duraton.RiskHigh,
Summary: triage.Text,
})
if err != nil {
return nil, err
}
if decision.Status == duraton.Denied {
return map[string]any{"outcome": "denied"}, nil
}
return duraton.Run(c, "refund", func() (Refund, error) {
return issueRefund(decision.Args)
})
},
})triage runs once and its answer is saved - a replay never pays the model twice. The run then parks
on a human decision, holding no worker while it waits, for minutes or for days. When someone decides,
it resumes at exactly that point. If refund fails, only refund retries: the model is not called
again, and nobody is asked to approve a second time.
Run your first workflow, or start with a durable AI step.
The four tabs
AI
Model calls that run once, human approvals, spend caps, streaming, and evals.
Documentation
Quickstart, worked examples, and how a run survives a crash.
SDK
The Duraton SDKs for TypeScript, Python, and Go: defineWorkflow, steps, connect, createClient.
REST API
Start runs, read them, and decide the ones waiting on a human - plain JSON HTTP, no SDK required.
Trigger a workflow
An agent run starts from an event. Send one from another workflow with the SDK, or from anywhere over the REST API:
// inside a workflow handler
await ctx.step.emit("notify", {
name: "ticket.created",
app: "support-app",
data: { ticketId: "T-421", subject: "Charged twice" },
});# inside a workflow handler
await ctx.step.emit("notify", name="ticket.created", app="support-app", data={"ticketId": "T-421", "subject": "Charged twice"})// inside a workflow handler
err := duraton.Emit(c, "notify", duraton.EmitOptions{
Name: "ticket.created",
App: "support-app",
Data: json.RawMessage(`{"ticketId":"T-421","subject":"Charged twice"}`),
})curl -X POST "$DURATON_URL/events" \
-H "Authorization: Bearer $DURATON_API_KEY" \
-d '{"name":"ticket.created","app":"support-app","data":{"ticketId":"T-421","subject":"Charged twice"}}'Start with your goal
Find the sentence closest to your own problem.
Running AI agents
| What you want | Where to go |
|---|---|
| "My agent spends money and I can't sleep" | Cost controls - halt before the call that would overspend, or pause over a rolling window |
| "It must not act without a human on the risky stuff" | Approvals - park the run on a person's decision, holding no worker |
| "My long agent dies halfway and starts over" | Durable execution - why a run resumes instead of restarting |
| "I can't tell if the new prompt is better" | Evals - score a run, grade it after it finishes, or fork it with one change |
| "The model is down and my agent just fails" | Fallback chains - advance to the next model, and cache repeats at zero spend |
| "I want to see what it's doing right now" | Realtime - watch a run as it happens instead of polling |
| "I need to show someone what the agent did" | AI observability - token and cost rollups read from the durable journal |
Running anything else durably
Duraton is a durable-execution engine, and the model call is optional. If you came here with an ordinary backend job rather than an agent, start here.
| What you want | Where to go |
|---|---|
| "My background job dies halfway and starts over" | Durable execution - each step is saved when it completes, and the job resumes at the next one |
| "I need this to run on a schedule" | Cron triggers - a schedule with no event behind it at all |
| "A flaky API keeps killing the whole job" | Retries - only the failing step retries; the ones before it keep their results |
| "It has to wait hours or days for something" | Steps - the run suspends holding no worker, and survives a restart while it waits |
| "The same job must not run twice for one input" | Idempotency - collapse repeats into a single run |
| "Too many jobs at once, and I'm rate-limited" | Flow control - concurrency, throttle, rate limit, debounce, batch, priority, singleton |
| "A job failed and I need to re-run it" | Control API - replay a finished run, or retry from the exact step that failed |
Getting work in and out
| What you want | Where to go |
|---|---|
| "The work arrives from somewhere else" | Inbound sources - events, webhooks, your own Kafka topics, or a schedule |
| "I need to receive a webhook from a third party" | Webhooks - a signature-verified POST becomes an event that starts a run |
| "My events are already on Kafka" | Kafka - consume your topics, and publish back from inside a step |
| "Something has to happen when it finishes" | Webhooks - signed outbound delivery, retried, every attempt logged |
Or work straight through the runnable recipes - one complete, paste-and-run workflow per task.