Expand description
§funera-orchestrate
Easy-to-use orchestration layer for [funera-core].
This crate provides a high-level agent API (Agent) and a runtime container
(AgentRuntime) that together let you integrate funera’s LLM agent runtime
into your own projects with minimal boilerplate.
§Features
| Feature | Default | Description |
|---|---|---|
deepseek | ✅ | DeepSeek provider |
openai | ❌ | OpenAI provider |
tool | ✅ | Tool system (trait, registry, executor) |
funera-builtin-tools | ❌ | Built-in tools (Read, Write, Edit, Shell) |
security | ❌ | Tool policy enforcement |
middleware | ❌ | Event interception pipeline (Inspector + Mutator) |
skill | ❌ | Skill loading and prompt injection |
sandbox | ❌ | Kernel-level subprocess isolation |
§Quick Start
use funera_orchestrate::{Agent, AgentRuntime, DeepSeekProvider};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-v4-flash")
.build()?;
let agent = Agent::builder()
.system_prompt("You are a helpful assistant.")
.build();
let resp = agent.fire("Hello!", &runtime).await?;
println!("{}", resp.content);
Ok(())
}§Core Concepts
| Concept | Type | Description |
|---|---|---|
| Runtime | AgentRuntime | Shared infrastructure + conversation session |
| Agent | Agent | Behavioural config (system prompt, callbacks) |
| One-shot | Agent::fire | Temporary session, discarded after call |
| Multi-turn | Agent::send | Persistent session across calls |
| Streaming | fire_stream / send_stream | Token-by-token streaming |
| Approval | [ApprovalHandle] | Lightweight cloneable handle for tool-call approval |
§Examples
§One-shot query with stream
let runtime = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-v4-flash")
.build()?;
let agent = Agent::builder()
.on_token(|t| print!("{t}"))
.build();
let mut rx = agent.fire_stream("Explain Rust's ownership model", &runtime).await?;
while let Some(event) = rx.recv().await {
if let AgentEvent::Text(t) = event {
print!("{t}");
}
}§Multi-turn conversation with callbacks
let runtime = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-v4-flash")
.build()?;
let agent = Agent::builder()
.system_prompt("You are helpful.")
.on_tool_call(|name, _| eprintln!("[tool] {name}"))
.on_turn_start(|| eprintln!("--- turn ---"))
.build();
let handle = agent.send("Hi, I'm Alice.", runtime).await?;
let (runtime, _resp) = handle.await?;
let handle = agent.send("What's my name?", runtime).await?;
let (_runtime, _resp) = handle.await?;§Switching models on the same provider
let fast = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-v4-flash")
.build()?;
let powerful = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-r1")
.build()?;
let agent = Agent::builder().build();
let (fast, _) = agent.send("Hello", fast).await?.await?; // fast model
agent.fire("What is Rust?", &powerful).await?; // powerful model (temp)
let (_fast, _) = agent.send("Tell me more", fast).await?.await?; // back to fast§Security with tool-call approval
Requires the security feature (and optionally funera-builtin-tools, sandbox).
Use [ApprovalHandle] to approve or reject tool calls from a spawned task
while the agent is running — works with fire(), send(), and send_stream().
let (approval_tx, mut approval_rx) = tokio::sync::mpsc::unbounded_channel();
let runtime = AgentRuntime::<DeepSeekProvider>::builder()
.api_key(std::env::var("DEEPSEEK_API_KEY")?)
.model("deepseek-v4-flash")
.on_approval_required(move |call_id, tool, reason| {
eprintln!("[{tool}] needs approval: {reason}");
let _ = approval_tx.send(call_id.to_string());
})
.with_approval_timeout(Duration::from_secs(30))
.build()?;
// Clone the ApprovalHandle *before* send() consumes the runtime.
let approver: ApprovalHandle = runtime.approval_handle();
tokio::spawn(async move {
while let Some(call_id) = approval_rx.recv().await {
approver.approve_tool_call(&call_id, true).await.ok();
}
});
let agent = Agent::builder().build();
let (_runtime, resp) = agent.send("do something", runtime).await?.await?;
println!("{}", resp.content);§Module Structure
runtime—AgentRuntimeBuilderandAgentRuntimeagent—AgentBuilderandAgentsend_handle—SendHandle,SendStreamHandle,FireStreamHandle, [ApprovalHandle]dispatcher— Event bus subscription and callback dispatchevent—AgentEventenumresponse—ChatResponseandToolCallInfoerror—OrchestrateError
Re-exports§
pub use agent::Agent;pub use agent::AgentBuilder;pub use dispatcher::CallbackRegistry;pub use error::OrchestrateError;pub use event::AgentEvent;pub use event::RawAgentEvent;pub use response::ChatResponse;pub use response::ToolCallInfo;pub use runtime::Acquired;pub use runtime::AgentRuntime;pub use runtime::AgentRuntimeBuilder;pub use runtime::Idle;pub use send_handle::FireStreamHandle;pub use send_handle::SendHandle;pub use send_handle::SendStreamHandle;