Rust SDK
vadylCreate the client
Configure the client once. Same object reaches every product surface — entities, workflows, agents, realtime.
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(())
}List, filter, paginate
Typed filter expressions, cursor pagination, eager relation loading.
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);Create, update, delete with idempotency
Every mutation supports idempotency keys. Retries are safe by default.
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?;Subscribe to entity changes
Field names only — never values. Re-read for values through CRUD with proper access enforcement.
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);
}Start and signal durable workflows
Long-running product behavior that survives process restart. Same client interface; canonical durability underneath.
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?;Run agents against the product model
Typed plan IR, capability-aware model routing, token accounting — all behind one method.
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?;Typed error contract
Stable error codes, stable reason codes, correlation IDs, and explainability links.
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()),
}Define handlers, workflows, agents
The same package you use as a client also powers your authored runtime — defineCoreHandler, defineWorkflow, defineAgent.
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.
Deep-dive documentation
Build with the Rust SDK in minutes.
Install the package and work with your product model through typed entities, workflows, agents, and events.