Durable Execution for Rust

Workflows that survive failure

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.

checkout_flow.rs
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))
}
agent_session.sh
# 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}
Storage backends
PostgreSQL SQLite MySQL Custom

Everything durable execution needs

Built for production Rust services. Every feature is designed to make distributed workflows reliable by default.

State Recovery

Each step's result is persisted before moving on. On restart, completed steps are replayed from storage — never re-executed.

🔄
Smart Retries

Per-step retry policies with fixed, linear, or exponential backoff. Configure max attempts, delay, and jitter independently per step.

⚙️
Concurrent Workers

Multiple workers compete for tasks using SKIP LOCKED — zero contention, no coordination required, horizontal scaling out of the box.

🗄️
Pluggable Storage

PostgreSQL for production, SQLite for embedded or dev environments, MySQL for enterprise. Swap backends without changing workflow code.

🔒
Idempotency Built-in

Execution IDs prevent duplicate scheduling. A charge step that already succeeded will never fire again — even if the scheduler is called twice.

📡
Events & Async Waits

Workflows can durably wait for external signals — webhooks, human approvals, or other services — with configurable timeouts and automatic resumption.

Use Zart your way

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(())
}
Coming Soon

CLI tooling in development

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":"..."}}
Coming Soon

REST API in development

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