Workflows
A workflow is more than one model call wired together: draft-then-edit, classify-then-route,
retrieve-then-answer, or fan several agents out and combine their results. In Rig you build these
with plain Rust — every agent call is an async function that returns a value, so you compose
them with let bindings, match, and futures::join!. There’s no DSL to learn.
Agent or workflow?
Section titled “Agent or workflow?”The central design decision in an LLM application is who controls the flow:
- An agent with tools lets the model decide — which tool to call, in what order, when to stop. Its path through a task is dynamic and different on every run. Use it for open-ended problems where you can’t enumerate the steps in advance.
- A workflow puts your code in charge: the steps, their order, and the routing between them are written down as ordinary Rust. Each step may call a model, but no model decides what happens next. Use it when the process is known — it’s cheaper (no turns spent deliberating), more predictable, and testable step by step.
Prefer the least agentic design that solves your problem. If a step doesn’t need a model at all, write a function; if the steps are known, write a workflow of model calls; reserve the agent loop for the parts that genuinely need the model’s judgment about what to do next. The two compose freely — a workflow step can itself be a tool-using agent.
Sequential: chain one step into the next
Section titled “Sequential: chain one step into the next”The output of one agent becomes the input to the next. Here a “writer” drafts a tagline and an “editor” tightens it:
let draft = writer.prompt("A tool that compile-checks docs code samples").await?;let tagline = editor.prompt(draft.trim()).await?;println!("{tagline}");Docs that always compile.Parallel: run independent steps concurrently
Section titled “Parallel: run independent steps concurrently”When steps don’t depend on each other, run them at the same time. futures::join! awaits them all
and hands back each Result (unlike try_join!, one failure doesn’t discard the others):
let review = "The new update is blazing fast, but the settings menu is confusing.";
// join! awaits both concurrently and keeps each Result.let (sentiment, topic) = futures::join!( async { sentiment_agent.prompt(review).await }, async { topic_agent.prompt(review).await },);
println!("sentiment={}, topic={}", sentiment?, topic?);sentiment=NEGATIVE, topic=usabilityConditional: route to different steps
Section titled “Conditional: route to different steps”Use a cheap agent to classify, then match to dispatch to a specialist. This is the core of
model routing:
let query = "How do I borrow a value mutably twice?";
let category = router.prompt(query).await?;let answer = match category.trim().to_lowercase().as_str() { "code" => coder.prompt(query).await?, "math" => mathematician.prompt(query).await?, _ => generalist.prompt(query).await?,};println!("{answer}");The router example compares raw strings, which is brittle — a model that answers "Code" or
"it's a code question" falls through to the generalist. For production routing, classify with a
typed extractor and match on an enum instead; the
Model Routing guide shows the pattern.
Iterate: evaluator-optimizer
Section titled “Iterate: evaluator-optimizer”When output quality matters more than latency, pair a generator with a critic and loop until the critic approves (or a retry budget runs out — always bound the loop):
let mut draft = writer.prompt("A tool that compile-checks docs code samples").await?;
for _ in 0..3 { let review = critic.prompt(draft.as_str()).await?; if review.trim().starts_with("APPROVED") { break; } let revision = format!("Revise this text:\n{draft}\n\nAddress this feedback:\n{review}"); draft = writer.prompt(revision.as_str()).await?;}println!("{draft}");Ship docs whose every code sample compiles — checked on each commit, before your readers ever see it.This is the workflow-shaped cousin of the agent loop: the structure of the iteration is fixed in your code, and only the content of each step comes from a model.
Handling failures mid-workflow
Section titled “Handling failures mid-workflow”Every step returns a Result, so workflow error handling is ordinary Rust: ? to abort the chain,
match to substitute a fallback step, or retry a single step without redoing the ones before it.
Error Handling covers distinguishing transient provider errors
(worth retrying) from fatal ones — apply it per step, not around the whole workflow, so a retry
never re-runs steps that already succeeded and had side effects.
Retrieve, then answer
Section titled “Retrieve, then answer”A RAG workflow is just “look up context, fold it into the prompt, then prompt the agent.” Because a vector search is fast, a plain sequential lookup is clearer than any parallel construct — see Vector Stores & RAG for the full pattern.
