Write async Rust functions where every step is automatically persisted to a database. Process restarts, crashes, and timeouts become non-events — completed work is never repeated.
Give your AI agent durable memory across turns. Schedule long-running work, check status later, deliver approvals — all via CLI or HTTP. The workflow runs independently of the agent loop.
Define data pipelines where each transformation step persists its result. Recover from mid-run failures without reprocessing what already completed.
use zart_macros::{zart_durable, z_step, z_wait_event};
#[zart_durable("checkout", timeout = "10m")]
async fn checkout(order: Order) -> Result<Receipt> {
// Persisted — never charged twice on retry
let charge = z_step!("charge", || async {
stripe.charge(&order.card, order.total).await
}).await?;
// Persisted — never sent twice even if process dies
z_step!("receipt", || async {
mailer.send(&order.email, &charge).await
}).await?;
// Durable wait — survives restarts, times out after 24h
z_wait_event!("shipped", timeout = "24h").await?;
Ok(Receipt::from(charge))
} # Turn 1 — agent schedules a multi-step pipeline
$ zart schedule data-pipeline \
--data '{"dataset":"sales-q4-2025"}' \
--id pipeline-001
# scheduled | execution_id: pipeline-001
# Agent continues other work. Pipeline runs independently.
# Turn 2 — agent checks in later (different session)
$ zart status pipeline-001
# {"status":"waiting","step":"await-approval"}
# Turn 3 — human approves; agent delivers the event
$ zart event pipeline-001 approval \
--data '{"approved":true,"by":"alice"}'
# delivered
# Turn 4 — agent collects the result
$ zart wait pipeline-001
# {"status":"completed","rows_written":48291} Built for production Rust services. Every feature is designed to make distributed workflows reliable by default.
Each step's result is persisted before moving on. On restart, completed steps are replayed from storage — never re-executed.
Per-step retry policies with fixed, linear, or exponential backoff. Configure max attempts, delay, and jitter independently per step.
Multiple workers compete for tasks using SKIP LOCKED — zero contention, no coordination required, horizontal scaling out of the box.
PostgreSQL for production, SQLite for embedded or dev environments, MySQL for enterprise. Swap backends without changing workflow code.
Execution IDs prevent duplicate scheduling. A charge step that already succeeded will never fire again — even if the scheduler is called twice.
Workflows can durably wait for external signals — webhooks, human approvals, or other services — with configurable timeouts and automatic resumption.
Whether you prefer ergonomic macros, explicit trait implementations, CLI tooling, or REST — Zart has you covered.
use zart::{TaskHandler, TaskContext, TaskError, RetryConfig};
use async_trait::async_trait;
struct OnboardingTask;
#[async_trait]
impl TaskHandler for OnboardingTask {
type Data = OnboardingData;
type Output = ();
async fn run(
&self,
ctx: &mut TaskContext<impl Scheduler>,
data: OnboardingData,
) -> Result<(), TaskError> {
// Step 1 — send welcome email
ctx.step("send-email", || async {
send_welcome_email(&data.email).await
}).await?;
// Step 2 — set up billing (with 3 retries, exponential backoff)
ctx.step_with_retry(
"setup-billing",
RetryConfig::exponential(3, Duration::from_secs(2)),
|| async { setup_stripe_customer(&data.email).await },
).await?;
// Step 3 — wait for email verification (up to 48h)
let _: VerifyPayload = ctx.wait_for_event(
"email-verified",
Some(Duration::from_secs(172800)),
).await?;
Ok(())
}
fn max_retries(&self) -> usize { 3 }
fn timeout(&self) -> Option<Duration> {
Some(Duration::from_secs(600))
}
} use zart_macros::{zart_durable, z_step, z_step_with_retry, z_wait_event};
/// Proc-macro turns this async fn into a full durable TaskHandler
#[zart_durable("onboarding", timeout = "30m")]
async fn onboarding(data: OnboardingData) -> Result<()> {
// Each step auto-persists its result
z_step!("send-email", || async {
send_welcome_email(&data.email).await
}).await?;
// Step with retry: 3 attempts, exponential backoff starting at 2s
z_step_with_retry!(
"setup-billing",
retries = 3,
backoff = "exponential",
delay = "2s",
|| async { setup_stripe_customer(&data.email).await }
).await?;
// Durable wait — workflow suspends here, survives any restart
let event: VerifyPayload =
z_wait_event!("email-verified", timeout = "48h").await?;
Ok(())
} Manage executions, deliver events, run migrations, and inspect status — all from the terminal.
# Run database migrations
zart migrate
# Schedule a new execution
zart schedule onboarding --data '{"email":"user@example.com"}'
# Check execution status
zart status exec-abc-123
# Deliver an event to a waiting execution
zart event exec-abc-123 email-verified --data '{"token":"xyz"}'
# Wait for an execution to complete
zart wait exec-abc-123 --timeout 3600 # An AI agent orchestrates a multi-step data pipeline
# Step 1: Schedule the pipeline — get an execution ID back
$ zart schedule data-pipeline \
--data '{"dataset":"sales-q4","output":"s3://bucket/report"}' \
--id pipeline-001
# exec_id: pipeline-001 | status: running
# Agent continues with other tasks while pipeline runs...
# Step 2: Check status asynchronously
$ zart status pipeline-001
# {"status":"waiting","step":"await-approval","started_at":"..."}
# Step 3: Human reviews; agent delivers approval event
$ zart event pipeline-001 approved \
--data '{"reviewer":"agent-007","notes":"Looks good"}'
# Step 4: Wait for final completion with 1h timeout
$ zart wait pipeline-001 --timeout 3600
# {"status":"completed","output":{"rows":42891,"location":"..."}} HTTP endpoints for scheduling, status, event delivery, and execution management.
# Schedule an execution
POST /api/v1/executions
{
"task": "onboarding",
"data": { "email": "user@example.com" },
"idempotency_key": "signup-u123"
}
# Deliver an event
POST /api/v1/events/exec-abc-123/email-verified
{ "token": "xyz789" }
# Query status
GET /api/v1/executions/exec-abc-123