Workflow Agents Examples
These examples demonstrate the advanced workflow agent patterns. For API details, see Advanced Orchestration. For the basic patterns (Sequential, Parallel, Loop), see Workflow Agents.
Debate: Code Review
Section titled “Debate: Code Review”Two reviewers debate code quality, then a lead synthesizes actionable feedback.
use heartbit::{AgentRunner, DebateAgent};
let security_reviewer = AgentRunner::builder(provider.clone()) .name("security_reviewer") .system_prompt( "You are a security-focused code reviewer. \ Identify vulnerabilities, injection risks, and auth issues. \ Reference specific lines. When you agree with all points, say CONSENSUS." ) .build()?;
let perf_reviewer = AgentRunner::builder(provider.clone()) .name("perf_reviewer") .system_prompt( "You are a performance-focused code reviewer. \ Identify N+1 queries, unnecessary allocations, and bottlenecks. \ Reference specific lines. When you agree with all points, say CONSENSUS." ) .build()?;
let lead = AgentRunner::builder(provider.clone()) .name("lead") .system_prompt( "You are the tech lead. Synthesize the reviewers' findings into \ a prioritized list of actionable items. Deduplicate overlapping concerns." ) .build()?;
let review = DebateAgent::builder() .debater(security_reviewer) .debater(perf_reviewer) .judge(lead) .max_rounds(2) .should_stop(|text| text.contains("CONSENSUS")) .build()?;
let output = review.execute(&code_to_review).await?;println!("Review:\n{}", output.result);Voting: Sentiment Classification
Section titled “Voting: Sentiment Classification”Three classifiers vote on sentiment. Majority wins.
use heartbit::{AgentRunner, VotingAgent};
let build_voter = |name: &str, style: &str| -> Result<AgentRunner<_>, _> { AgentRunner::builder(provider.clone()) .name(name) .system_prompt(format!( "Classify the sentiment as exactly one of: POSITIVE, NEGATIVE, NEUTRAL. \ Approach: {style}. Reply with only the label." )) .build()};
let voting = VotingAgent::builder() .voter(build_voter("intuitive", "Go with your gut feeling")?) .voter(build_voter("analytical", "Count positive vs negative words")?) .voter(build_voter("contextual", "Consider tone, sarcasm, and context")?) .vote_extractor(|output| { let text = output.result.trim().to_uppercase(); if text.contains("POSITIVE") { "POSITIVE".into() } else if text.contains("NEGATIVE") { "NEGATIVE".into() } else { "NEUTRAL".into() } }) .build()?;
let result = voting.execute("The update broke everything but support was helpful").await?;println!("Sentiment: {} (votes: {:?})", result.winner, result.tally);Mixture of Agents: Content Generation
Section titled “Mixture of Agents: Content Generation”Two proposers draft content from different angles, a synthesizer merges them.
use heartbit::{AgentRunner, MixtureOfAgentsAgent};
let technical = AgentRunner::builder(provider.clone()) .name("technical") .system_prompt( "Write a technically detailed blog section about the given topic. \ Include code snippets and architecture details." ) .build()?;
let storyteller = AgentRunner::builder(provider.clone()) .name("storyteller") .system_prompt( "Write an engaging narrative blog section about the given topic. \ Use analogies and real-world examples to make concepts accessible." ) .build()?;
let editor = AgentRunner::builder(provider.clone()) .name("editor") .system_prompt( "You receive multiple draft sections. Combine them into a single \ cohesive blog post that balances technical depth with readability. \ Preserve code snippets from the technical draft." ) .build()?;
let moa = MixtureOfAgentsAgent::builder() .proposer(technical) .proposer(storyteller) .synthesizer(editor) .layers(1) .build()?;
let output = moa.execute("Building multi-agent systems in Rust").await?;println!("{}", output.result);DAG: Data Processing Pipeline
Section titled “DAG: Data Processing Pipeline”A pipeline that fetches data, analyzes it, and conditionally branches to an alert path.
use heartbit::{AgentRunner, DagAgent};
let ingest = AgentRunner::builder(provider.clone()) .name("ingest") .system_prompt("Parse the raw input into structured JSON.") .build()?;
let validate = AgentRunner::builder(provider.clone()) .name("validate") .system_prompt( "Validate the data. If any field is missing or invalid, \ prefix your response with INVALID: and explain. \ Otherwise prefix with VALID: and return the clean data." ) .build()?;
let enrich = AgentRunner::builder(provider.clone()) .name("enrich") .system_prompt("Add derived fields and computed metrics to the data.") .build()?;
let report = AgentRunner::builder(provider.clone()) .name("report") .system_prompt("Format the enriched data as a markdown report.") .build()?;
let error_handler = AgentRunner::builder(provider.clone()) .name("error_handler") .system_prompt("Explain what went wrong with the data and suggest fixes.") .build()?;
let dag = DagAgent::builder() .node("ingest", ingest) .node("validate", validate) .node("enrich", enrich) .node("report", report) .node("error_handler", error_handler) .edge("ingest", "validate") .conditional_edge("validate", "enrich", |output| output.starts_with("VALID:")) .conditional_edge("validate", "error_handler", |output| output.starts_with("INVALID:")) .edge("enrich", "report") .build()?;
let output = dag.execute("name=Alice age=thirty email=alice@example.com").await?;println!("{}", output.result);Batch: Bulk Classification
Section titled “Batch: Bulk Classification”Process many items through the same agent with controlled concurrency.
use heartbit::{AgentRunner, BatchExecutor};
let classifier = AgentRunner::builder(provider.clone()) .name("classifier") .system_prompt( "Classify the support ticket into exactly one category: \ BUG, FEATURE_REQUEST, QUESTION, COMPLAINT. \ Reply with only the category." ) .build()?;
let batch = BatchExecutor::builder(classifier) .max_concurrency(4) .build()?;
let tickets = vec![ "My app crashes when I click save".into(), "Can you add dark mode?".into(), "How do I reset my password?".into(), "Your service has been down for 3 hours!".into(), "Login button does nothing on mobile".into(),];
let results = batch.execute(tickets).await;
for (i, result) in results.iter().enumerate() { match result { Ok(output) => println!("Ticket {}: {}", i, output.result.trim()), Err(e) => eprintln!("Ticket {} failed: {}", i, e), }}
let usage = batch.aggregate_usage(&results);println!( "Total tokens: {} input, {} output", usage.input_tokens, usage.output_tokens);