Learn Runifold in 45 minutes
Build the right mental model, complete a real run, add a typed contract, bound execution, and choose what to learn next.
Five checkpoints. One working mental model.
- Explain the execution pathProvider → Runtime → Agent → Run → Outcome5 min
- Complete the first runInstall Runifold and receive one model response10 min
- Create a typed contractTurn model output into a Rust value10 min
- Bound the executionAdd budgets, a deadline, and explicit authority10 min
- Choose the next architectureOpen only the guides your product needs10 min
This course uses one Agent as a compact vertical slice through the kernel and model layers. Runifold can also be used directly as a model protocol, durable Workflow runtime, MCP edge, evaluation system, or observability layer. See the complete platform map for those entry points.
Build the mental model
Runifold is easiest to learn as one execution path:
- Provider translates one model service's wire protocol.
- Runtime adds routing and safe transport behavior around a model identity.
- Agent combines instructions, callable tools, and turn policy.
- Run carries identity, lifetime, budget, authority, and journal events.
- Outcome preserves visible text together with usage and execution facts.
The one sentence worth remembering is: the model proposes work; the Run defines what may happen and how it is accounted for.
Checkpoint: without looking above, explain where authentication, tools, budgets, and final text belong. If the four answers are Provider, Agent, Run, and Outcome, continue.
Complete the first run
Create a project, add the stable runtime, and select the provider adapter you use:
cargo new hello-runifold
cd hello-runifold
cargo add runifold@0.9.0
cargo add runifold-providers@0.9.0 --features openai
cargo add tokio --features macros,rt-multi-thread
export OPENAI_API_KEY="your-api-key"Replace src/main.rs with this complete program:
use runifold::ProviderModelExt;
use runifold_providers::openai::OpenAiClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OpenAiClient::from_api_key(
std::env::var("OPENAI_API_KEY")?
)?
.runtime("gpt-5")?;
let answer = runtime
.agent("assistant")
.system("Answer precisely and expose uncertainty.")
.prompt_text("Explain one benefit of durable execution.")
.await?;
println!("{answer}");
Ok(())
}Run cargo run. Success means the process exits normally and prints one
answer. The exact wording is not a test because model output is nondeterministic.
Read the code from the outside in: the client authenticates, runtime
chooses the model edge, agent defines behavior, and prompt_text creates a
convenient root Run.
Create a typed contract
Text is useful at a user interface. Application logic should normally receive a Rust value. Add:
cargo add serde --features derive
cargo add schemarsDefine the contract, then build a structured Agent:
use runifold::JsonSchema;
use serde::Deserialize;
#[derive(Debug, Deserialize, JsonSchema)]
struct Triage {
category: String,
urgent: bool,
explanation: String,
}
let agent = runtime
.agent("triage")
.system("Classify the request. Keep the explanation short.")
.build_structured::<Triage>("triage_result")?;
let run = agent.agent().default_run_context();
let result = agent.run("Payment failed twice", &run).await?;
println!("{:?}", result.output);The schema constrains the model-facing format; deserialization establishes the Rust boundary. Domain validation still belongs in ordinary Rust after decoding. Never silently fall back to unvalidated text when the contract fails.
Checkpoint: change urgent to a string temporarily. Your surrounding
application code should expose the contract mismatch instead of guessing.
Bound the execution
The default context is ideal for exploration. At an HTTP request, background
job, or workflow boundary, replace it with an explicit RunContext:
use std::time::{Duration, Instant};
use runifold::{Budget, BudgetTracker, CapabilitySet, RunContext};
let run = RunContext::root(
BudgetTracker::new(Budget {
tokens: Some(20_000),
turns: Some(8),
tool_calls: Some(4),
..Budget::default()
}),
CapabilitySet::new(),
)
.with_deadline(Instant::now() + Duration::from_secs(20));This creates one shared envelope for descendants. Child Agents and tools spend from the same accounting tree, observe cancellation, and receive only explicitly granted capabilities.
Three distinctions prevent most design mistakes:
| Do not confuse | Correct boundary |
|---|---|
| chat history with execution state | transcripts are model context; RunContext is control state |
| registered tools with permission | registration says what exists; capabilities say what this Run may use |
| network timeout with deadline | timeout bounds one operation; deadline bounds the useful lifetime of the run tree |
Pass the ship checkpoint
You now know the stable core. Choose only the branch your product needs:
| Product requirement | Read next | You can postpone |
|---|---|---|
| user-facing chat | Conversations and memory | workflows |
| calls application functions | Typed tools, then effects for writes | delegation |
| coordinates specialists | Delegation | durable workflows |
| survives restarts or long waits | Workflows, then durable workflows | streaming |
| must meet a quality bar | Testing, then evaluation | additional providers |
| needs production evidence | Observability, reliability | edge and WASM |
Before shipping a feature, answer these six questions:
- What is the root Run boundary?
- Which model and provider behavior has been verified?
- Which capabilities can this Run use?
- What bounds tokens, turns, tool calls, and wall time?
- Which external writes are idempotent or recoverable?
- How will a failed Run be explained and reproduced?
If any answer is “the prompt handles it,” move that responsibility into typed Rust policy before production.