Pravah
Pravah (प्रवाह, pruh-VAH) — Sanskrit/Hindi for "flow" or "current".
A Rust library for building typed, stepwise agentic information flows.
Pravah is not a general workflow engine. A flow is a single-threaded graph that
moves information from one typed node to the next. Each call to next() does
one bounded unit of work: one LLM turn, one tool batch, one deterministic
transform, one branch, one fork, or one join. The caller decides when to
persist state and how to resume it.
Installation
[]
= "0.1"
Features
| Feature | Default | Description |
|---|---|---|
provider-openai |
✓ | OpenAI-compatible API client |
provider-anthropic |
✓ | Anthropic Claude API client |
provider-gemini |
✓ | Google Gemini API client |
provider-ollama |
✓ | Ollama local model client |
provider-genai |
— | Extra providers via the genai crate |
diagram-text |
— | Render flow graphs as Unicode box-drawing text in the terminal (adds mermaid-text) |
To use only specific providers, disable defaults:
= { = "0.1", = false, = ["provider-openai"] }
Getting Started
This minimal example defines a two-node flow: an agent that produces bullet points from a request, and a work node that assembles them into a final report.
use ;
use ;
use ToolBox;
use JsonSchema;
use ;
// ── Types ──────────────────────────────────────────────────────────────────
// ── Agent ──────────────────────────────────────────────────────────────────
// ── Work node ─────────────────────────────────────────────────────────────
async
// ── Flow ───────────────────────────────────────────────────────────────────
// ── Run ────────────────────────────────────────────────────────────────────
async
See the examples/ directory for runnable examples covering
linear flows, fork/join, nested flows, and snapshot-based resumption.
The Core Idea
Within a single flow graph, each input type identifies exactly one node.
PlanInput can be the input for one agent node, one work node, one branch
node, one fork node, or one join participant — never more than one. The builder
rejects duplicates. When a value of type PlanInput is present in flow state,
there is exactly one node that can consume it. This keeps routing unambiguous
and state checkpointable between steps.
Node Types
| Builder method | What it does |
|---|---|
agent::<A>() |
LLM-backed node; structured output or tool loop |
work::<From, Out>() |
Deterministic async transform |
either::<From, A, B>() |
Routes to one of two typed branches |
fork::<From, A, B>() |
Splits one value into two active branches |
join::<A, B, Out>() |
Combines two branches once both are ready |
Fork and join model information shape, not parallelism. A single-threaded runner represents non-linear graphs where information splits and recombines.
Why Types Matter
Pravah uses Rust types as the contract at every boundary: agent input structs define the first user message shape, output types define the result schema, tool structs define LLM-callable arguments, and all handlers receive typed values. State is stored as JSON internally so it can be serialized, but user code stays typed.
Agents
use JsonSchema;
use ;
use Agent;
use ToolBox;
With no tools, Pravah uses structured-output mode. With tools, it injects a typed exit sentinel so the model can submit the final value.
Tools
use JsonSchema;
use ;
use Context;
use ;
Context carries the working directory, command allowlist, dependency
container, and shared HTTP client.
Building A Flow
use ;
async
The builder validates: duplicate node identities, entry not in graph, unreachable nodes, no path to a terminal value, invalid fork/join definitions, and both branches of an either routing to the same type.
Running A Flow
use ;
use ;
let ctx = new;
let mut runtime = new?;
loop
Suspend And Resume
A tool returns ToolError::suspend(value) to pause the flow. The runtime
surfaces a suspension payload and a tool id. The caller can persist state,
show the request to a user, wait for a webhook — then call resume() with the
matching tool id and a JSON response. Useful for approval gates, missing
credentials, payments, or any action needing external confirmation.
Persistence
Call runtime.snapshot() to capture an opaque FlowSnapshot
(serializable, no closures). Restore it with FlowRuntime::from_snapshot(snap).
Conversation history is managed separately — re-attach it with
runtime.with_history(history) after restoring. Pravah defines the
serializable state; it does not prescribe where snapshots live.
Nested Flows
A flow has the same shape as a node: typed input, stepwise execution, typed output. Use nested flows to keep large agent systems modular — a planning flow can contain a research sub-flow, a coding flow can contain a review-and-fix sub-flow. The same node-identity rule applies at each graph boundary.
Example: Article Production Pipeline
Combines every node type — fork, join, work, either, agent, and two nested flows:
ArticleRequest
├─fork─┬─ ResearchTask ──agent──► ResearchNotes ─┐
└─ AudienceTask ──agent──► AudienceProfile ─┤join
▼
ContentBrief
│work
OutlineRequest
(nested OutlineFlow) │work
▼
Outline
┌──either──┐
QuickDraft LongDraft
(agent) (nested ReviewFlow)
│ │work
│ ReviewedDraft
│ (agent)
└──────────────┘
▼
FinalArticle ◉
ASCII art rendered from FlowGraphDiagram::for_flow::<ArticleRequest>()?.render_ascii():
+----+ +------------*------------+ fork+------------------------+ agent+-----------------------------+ join+-----------------------+ work+-------------------------+ work+----------*---------+ +--------------------+ work +-------------------------+ agent+---------------------+
|( )|----->| "ArticleRequest (fork)" |--fork| "AudienceTask (agent)" |------>|( "AudienceProfile (join)" )|----->| "ContentBrief (work)" |----->| "OutlineRequest (work)" |----->| "Outline (either)" |---either "LongDraft (work)" |------->| "ReviewedDraft (agent)" |-----+>|( "FinalArticle ◉" )|
+----+ +------------*------------+-----++------------------------+ +-----------------------------+ >+-----------------------+ +-------------------------+ +----------*---------+-------++--------------------+ +-------------------------+ +>+---------------------+
| | either --------------------+
| | | | agent
|+------------------------+ agent+---------------------------+ join| |+----------------------+ |
>| "ResearchTask (agent)" |------>|( "ResearchNotes (join)" )|-------+ >| "QuickDraft (agent)" |-------------------+
+------------------------+ +---------------------------+ +----------------------+
Clients
Provider-agnostic by default. Model URLs select the backend:
gemini://gemini-2.5-flash-liteopenai://gpt-4oanthropic://claude-sonnet-4-5ollama://localhost:11434/qwen3:8b
Default features include OpenAI, Anthropic, Gemini, and Ollama. An optional
provider-genai feature adds an experimental adapter for extra providers.
Inject a custom ClientFactory for testing, recording/replay, or hosted
gateways.
When To Use Pravah
Use it when you want agentic flows that are type-directed, inspectable, resumable, testable with fake clients, and explicit about information movement.
Don't use it as a distributed workflow engine, parallel job scheduler, queue processor, or durable storage system. Pravah can sit inside those systems but does not try to replace them.