Skip to content

Orchestration

The Orchestrator<P> coordinates multiple specialized agents. It is itself an AgentRunner equipped with two delegation tools that dispatch work to sub-agents.

Heartbit enforces a flat agent hierarchy: the orchestrator spawns sub-agents, but sub-agents never spawn further agents. This keeps execution predictable and avoids unbounded recursion.

Orchestrator
/ | \
Agent A Agent B Agent C <-- sub-agents (leaf nodes)

Dispatches independent parallel subtasks to one or more sub-agents. Each sub-agent works on its own task with no shared state. Results are collected and returned to the orchestrator.

Use delegate_task when subtasks don’t need to coordinate with each other.

Creates a collaborative squad of sub-agents that share a Blackboard — a key-value store for inter-agent communication. Sub-agents can read and write to the blackboard during execution.

Use form_squad when subtasks need to build on each other’s work. After each sub-agent completes, its result is automatically written to the blackboard under "agent:{name}".

use std::sync::Arc;
use heartbit::Orchestrator;
let mut orchestrator = Orchestrator::builder(provider.clone())
.sub_agent("researcher", "Research specialist", "You research topics thoroughly.")
.sub_agent("writer", "Writing specialist", "You write clear, engaging content.")
.on_text(Arc::new(|text| print!("{text}")))
.build()?;
let output = orchestrator.run("Write an article about Rust").await?;

The orchestrator’s LLM decides which sub-agents to invoke, what tasks to give them, and whether to use delegate_task or form_squad.

ModeBehavior
AutoOrchestrator decides whether to delegate or handle directly
AlwaysOrchestrateAlways routes through sub-agents
SingleAgentBypasses orchestration, runs a single agent directly

When the config contains only one agent, Heartbit automatically uses the single-agent fast path — it creates an AgentRunner directly, skipping orchestrator overhead entirely.

Each sub-agent can use a different LLM model. This lets you assign expensive models to complex tasks and cheaper models to simpler ones:

[provider]
name = "anthropic"
model = "claude-sonnet-4-20250514"
[[agents]]
name = "researcher"
description = "Deep research"
system_prompt = "You research thoroughly."
[[agents]]
name = "summarizer"
description = "Quick summaries"
system_prompt = "You write concise summaries."
provider = { name = "anthropic", model = "claude-haiku-4-5-20251001" }

The Blackboard is a shared Key -> Value store available to squad agents. It provides three tools:

ToolDescription
blackboard_readRead a value by key
blackboard_writeWrite a value to a key
blackboard_listList all keys

The default implementation (InMemoryBlackboard) uses a tokio::sync::RwLock<HashMap> for concurrent access.

MethodDescription
.sub_agent(name, desc, prompt)Add a sub-agent with default provider
.sub_agent_full(config)Add a sub-agent with full configuration
.on_text(cb)Streaming text callback
.on_event(cb)Structured event callback
.on_approval(cb)Human-in-the-loop callback
.blackboard(bb)Provide a custom Blackboard implementation
.shared_memory(m)Enable shared memory for the orchestrator
.allow_shared_write(bool)Control institutional memory write access
[orchestrator]
max_turns = 50
routing = "auto" # auto | always_orchestrate | single_agent
[[agents]]
name = "researcher"
description = "Research specialist"
system_prompt = "You are a research specialist."
max_turns = 30
[[agents]]
name = "writer"
description = "Writing specialist"
system_prompt = "You are a writing specialist."
max_turns = 20