All SDKs
Rs

Rust SDK

vadyl
Available
The Rust SDK is async-first (tokio), feature-flagged for transports, and ships with full type derivation via build.rs. Compatible with axum, actix, warp, leptos.

Install

cargo add
cargo add vadyl
Cargo.toml
vadyl = "1"
Read the documentation
1. Initialize

Create the client

Configure the client once. Same object reaches every product surface — entities, workflows, agents, realtime.

main.rsRust
use vadyl::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let vadyl = Client::builder()
        .api_url("https://api.vadyl.app/v1")
        .token(std::env::var("VADYL_TOKEN")?)
        .tenant("acme")
        .project("billing")
        // optional — pin to a feature branch
        .branch_opt(std::env::var("VADYL_BRANCH").ok())
        .build()?;

    let me = vadyl.identity().me().await?;
    println!("{:?}", me);
    Ok(())
}
2. Read

List, filter, paginate

Typed filter expressions, cursor pagination, eager relation loading.

list_orders.rsRust
use rust_decimal_macros::dec;
use vadyl::types::OrderStatus;

let recent = vadyl
    .orders()
    .list()
    .filter(|f| f
        .status_in(&[OrderStatus::Paid, OrderStatus::Fulfilled])
        .total_gt(dec!(100))
        .created_at_gte("now-30d"))
    .sort_desc("createdAt")
    .page_size(50)
    .include(&["customer", "items.product"])
    .send()
    .await?;

// recent.data: Vec<Order>
// recent.page: { number, size, total_count, next, prev }
println!("{} orders, next: {:?}", recent.data.len(), recent.page.next);
3. Write

Create, update, delete with idempotency

Every mutation supports idempotency keys. Retries are safe by default.

create_customer.rsRust
use vadyl::types::{CustomerCreateInput, CustomerUpdateInput};

// Idempotent create — safe to retry
let customer = vadyl
    .customers()
    .create(CustomerCreateInput {
        email: "ada@example.com".into(),
        name:  "Ada Lovelace".into(),
    })
    .idempotency_key(format!("signup:{}", session.id))
    .send()
    .await?;

// Update with optimistic concurrency
let updated = vadyl
    .customers()
    .update(&customer.id, CustomerUpdateInput {
        name: Some("Ada L.".into()),
        ..Default::default()
    })
    .if_match(&customer.concurrency_token)
    .send()
    .await?;

// Delete (soft-delete on entities with that lifecycle)
vadyl.customers().delete(&customer.id).send().await?;
4. Realtime

Subscribe to entity changes

Field names only — never values. Re-read for values through CRUD with proper access enforcement.

subscribe.rsRust
use futures::StreamExt;
use vadyl::types::OrderStatus;

let mut sub = vadyl
    .orders()
    .subscribe()
    .filter(|f| f.status_eq(OrderStatus::Paid))
    .kinds(&[ChangeKind::Created, ChangeKind::Updated])
    .send()
    .await?;

while let Some(evt) = sub.next().await {
    let evt = evt?;
    // evt.kind, evt.entity_id, evt.changed_fields, evt.occurred_at
    // Field NAMES only — re-read for values
    let order = vadyl.orders().read(&evt.entity_id).await?;
    println!("paid: {}", order.id);
}
5. Workflows

Start and signal durable workflows

Long-running product behavior that survives process restart. Same client interface; canonical durability underneath.

workflow_client.rsRust
use vadyl::types::FulfillOrderInput;

// Start a durable workflow
let run = vadyl
    .workflows()
    .fulfill_order()
    .start(FulfillOrderInput { order_id: "ord_abc".into() })
    .await?;

// Send a signal (e.g. from a webhook)
vadyl
    .workflows()
    .fulfill_order()
    .signal(&run.id, "shipped", json!({ "trackingNumber": "1Z..." }))
    .await?;

// Query run state
let state = vadyl.workflows().fulfill_order().query(&run.id).await?;
6. Agents

Run agents against the product model

Typed plan IR, capability-aware model routing, token accounting — all behind one method.

run_agent.rsRust
use vadyl::types::{AgentRunInput, AgentBudget, RecallRequest};
use futures::StreamExt;

// Run an agent — typed plan IR, capability-aware model routing
let run = vadyl
    .agents()
    .support_agent()
    .run(AgentRunInput {
        prompt:  "Refund order #12345 due to defect".into(),
        user_id: session.user_id.clone(),
        budget:  AgentBudget { max_tokens: 50_000, max_tool_calls: 30 },
    })
    .await?;

// Stream the run
let mut stream = run.stream().await?;
while let Some(step) = stream.next().await {
    let step = step?;
    println!("{:?}: {}", step.kind, step.summary);
}

// Recall memory
let facts = vadyl
    .agents()
    .support_agent()
    .memory()
    .recall(RecallRequest {
        kind:    Some("preference".into()),
        subject: Some(session.user_id.clone()),
        ..Default::default()
    })
    .await?;
7. Errors

Typed error contract

Stable error codes, stable reason codes, correlation IDs, and explainability links.

errors.rsRust
use vadyl::error::VadylError;

match vadyl.orders().update(&id, OrderUpdate { status: Some("refunded".into()), ..Default::default() }).send().await {
    Ok(_) => {}
    Err(VadylError::AccessDenied { reason_code, correlation_id, .. }) => {
        eprintln!("denied: {reason_code} ({correlation_id})");
    }
    Err(VadylError::Conflict { .. }) => {
        // optimistic-concurrency mismatch — re-read and retry
    }
    Err(VadylError::Validation { field_errors, .. }) => {
        for fe in field_errors { eprintln!("{}: {}", fe.field, fe.reason_code); }
    }
    Err(VadylError::QuotaExceeded { kind, reset_at, .. }) => {
        eprintln!("quota {kind:?} exceeded; resets {reset_at}");
    }
    Err(err) => return Err(err.into()),
}
8. Authoring runtime

Define handlers, workflows, agents

The same package you use as a client also powers your authored runtime — defineCoreHandler, defineWorkflow, defineAgent.

src/handlers/orders/charge.rsRust
use vadyl::handler::{core_handler, Ctx};

#[derive(Deserialize)]
struct ChargeInput  { order_id: String }

#[derive(Serialize)]
struct ChargeResult { ok: bool, charge_id: String }

#[core_handler]
pub async fn charge(ctx: &Ctx, input: ChargeInput) -> Result<ChargeResult, vadyl::Error> {
    let order = ctx.entities().order().read(&input.order_id).await?;

    let charge = ctx.connections().stripe().create_charge(StripeChargeInput {
        amount:          order.total,
        currency:        order.currency.clone(),
        idempotency_key: format!("charge:{}", order.id),
    }).await?;

    ctx.entities().order().update(&order.id, OrderUpdate {
        status:    Some("paid".into()),
        charge_id: Some(charge.id.clone()),
        ..Default::default()
    }).await?;

    ctx.events().emit("order.paid", json!({ "orderId": &order.id })).await?;
    Ok(ChargeResult { ok: true, charge_id: charge.id })
}

What you get

Build-script typed

build.rs pulls your project's contract manifest and derives strongly-typed records, filters, and operations.

Manifest verification

The SDK refuses to talk to a server whose generated-format version it does not support. Fail closed.

Realtime built-in

WebSocket (tokio-tungstenite) and SSE (eventsource-stream). Streams reconnect with backoff.

Async-first

tokio runtime. Cancellation-safe futures. Optional sync wrapper via the blocking feature.

Feature-flagged

Default-on http, opt-in webrtc, blocking, native-tls / rustls, agents.

Branch-aware

Set Client::builder().branch() or VADYL_BRANCH env. Sandbox testing without leaving prod.

tracing integration

Automatic spans on every canonical operation via the tracing crate.

Agent primitives

client.agents() exposes the Agent plane: definitions, runs, memory, MCP exposure.

Connections

Typed governed-connection clients per registered governed connection.

Build with the Rust SDK in minutes.

Install the package and work with your product model through typed entities, workflows, agents, and events.