Skip to content
Get Started

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.

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.

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=usability

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.

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.

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.

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.