mod executor;
mod routing;
mod state_api;
mod types;
pub use types::{CompiledGraph, GraphExecution, GraphInput, ResumeTarget, StateSnapshot};
pub(crate) use types::AsyncCheckpointWrites;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crate::graph::builder::{
BarrierRelief, Branch, BuilderNode, END, ForkId, NodeContext, NodeFuture, NodeHandler,
NodeMeta, START,
};
use crate::graph::checkpoint::{
BarrierArrivals, Checkpoint, CheckpointConfig, CheckpointTuple, Checkpointer, DurabilityMode,
PendingActivation,
};
use crate::graph::command::{Command, Interrupt, NodeResult, RouteTarget};
use crate::graph::recursion::{
ChildRun, ChildRunSink, RecursionFrame, RecursionPolicy, RecursionStack,
};
use crate::graph::reducer::StateReducer;
use crate::graph::status::GraphRunStatus;
use crate::graph::stream::{GraphEvent, GraphEventSink};
use crate::harness::ids::{
CheckpointId, ExecutionStatus, GraphId, InterruptId, NodeId, RunId, ThreadId,
};
use crate::harness::retry::is_retryable;
use crate::{Result, TinyAgentsError};
fn next_checkpoint_id() -> String {
crate::harness::ids::new_checkpoint_id()
.as_str()
.to_string()
}
fn snapshot_from_tuple<State>(tuple: CheckpointTuple<State>) -> StateSnapshot<State> {
let CheckpointTuple {
config,
checkpoint,
parent_config,
..
} = tuple;
let metadata = checkpoint.to_metadata();
let next_nodes = checkpoint.next_nodes.clone();
StateSnapshot {
values: checkpoint.state,
tasks: next_nodes.clone(),
next_nodes,
config,
metadata,
parent_config,
pending_interrupts: checkpoint.interrupts,
}
}
struct StepRun<Update> {
updates: Vec<Update>,
goto_map: HashMap<usize, Vec<RouteTarget>>,
interrupt: Option<(usize, Interrupt)>,
failure: Option<StepFailure>,
}
struct StepFailure {
failed_index: usize,
error: TinyAgentsError,
}
#[derive(Clone)]
struct Activation {
node: NodeId,
send_arg: Option<serde_json::Value>,
}
impl Activation {
fn node(node: NodeId) -> Self {
Self {
node,
send_arg: None,
}
}
}
impl From<&Activation> for PendingActivation {
fn from(a: &Activation) -> Self {
PendingActivation {
node: a.node.clone(),
send_arg: a.send_arg.clone(),
}
}
}
impl From<&PendingActivation> for Activation {
fn from(p: &PendingActivation) -> Self {
Activation {
node: p.node.clone(),
send_arg: p.send_arg.clone(),
}
}
}
fn barriers_to_persisted(map: &HashMap<NodeId, HashSet<NodeId>>) -> Vec<BarrierArrivals> {
map.iter()
.map(|(node, arrived)| BarrierArrivals {
node: node.clone(),
arrived: arrived.iter().cloned().collect(),
})
.collect()
}
fn barriers_from_persisted(persisted: &[BarrierArrivals]) -> HashMap<NodeId, HashSet<NodeId>> {
persisted
.iter()
.map(|b| (b.node.clone(), b.arrived.iter().cloned().collect()))
.collect()
}
fn activation_nodes(active: &[Activation]) -> Vec<NodeId> {
active.iter().map(|a| a.node.clone()).collect()
}
impl<State, Update> CompiledGraph<State, Update> {
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn from_parts(
graph_id: GraphId,
name: Option<String>,
nodes: HashMap<NodeId, BuilderNode<State, Update>>,
edges: HashMap<NodeId, NodeId>,
branches: HashMap<NodeId, Branch<State>>,
command_nodes: HashSet<NodeId>,
waiting: HashMap<NodeId, HashSet<NodeId>>,
entry: NodeId,
reducer: Arc<dyn StateReducer<State, Update>>,
recursion_limit: usize,
parallel: bool,
max_concurrency: Option<usize>,
node_timeout: Option<Duration>,
node_meta: HashMap<NodeId, NodeMeta>,
barrier_reliefs: Vec<BarrierRelief>,
) -> Self {
Self {
graph_id,
name,
nodes: Arc::new(nodes),
edges: Arc::new(edges),
branches: Arc::new(branches),
command_nodes: Arc::new(command_nodes),
waiting: Arc::new(waiting),
barrier_reliefs: Arc::new(barrier_reliefs),
node_meta: Arc::new(node_meta),
entry,
reducer,
recursion_limit,
recursion_policy: crate::graph::recursion::RecursionPolicy::default(),
recursion_frames: Vec::new(),
recursion_node: None,
checkpointer: None,
event_sink: None,
journal: None,
status_store: None,
namespace: Vec::new(),
parallel,
max_concurrency,
node_timeout,
run_deadline: None,
durability: crate::graph::checkpoint::DurabilityMode::default(),
node_retry: None,
}
}
pub fn graph_id(&self) -> &GraphId {
&self.graph_id
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn namespace(&self) -> &[String] {
&self.namespace
}
pub fn with_checkpointer(mut self, checkpointer: Arc<dyn Checkpointer<State>>) -> Self
where
State: Send + Sync + 'static,
{
self.checkpointer = Some(checkpointer);
self
}
pub fn with_event_sink(mut self, sink: Arc<dyn GraphEventSink>) -> Self {
self.event_sink = Some(sink);
self
}
pub fn with_durability(mut self, durability: DurabilityMode) -> Self {
self.durability = durability;
self
}
pub fn with_node_retry(mut self, policy: crate::harness::retry::RetryPolicy) -> Self {
self.node_retry = Some(policy);
self
}
pub fn with_run_deadline(mut self, deadline: std::time::Duration) -> Self {
self.run_deadline = Some(deadline);
self
}
pub fn with_namespace(mut self, namespace: Vec<String>) -> Self {
self.namespace = namespace;
self
}
pub fn with_recursion_policy(mut self, policy: RecursionPolicy) -> Self {
self.recursion_policy = policy;
self
}
pub fn with_recursion_frames(mut self, frames: Vec<RecursionFrame>) -> Self {
self.recursion_frames = frames;
self
}
pub fn with_recursion_node(mut self, node: NodeId) -> Self {
self.recursion_node = Some(node);
self
}
pub fn with_event_journal(
mut self,
journal: Arc<dyn crate::graph::observability::GraphEventJournal>,
) -> Self {
self.journal = Some(journal);
self
}
pub fn with_status_store(
mut self,
status_store: Arc<dyn crate::graph::observability::GraphStatusStore>,
) -> Self {
self.status_store = Some(status_store);
self
}
fn emit(&self, event: GraphEvent) {
if let Some(sink) = &self.event_sink {
let terminal = matches!(
event,
GraphEvent::RunCompleted { .. } | GraphEvent::RunFailed { .. }
);
sink.emit(event);
if terminal {
sink.flush();
}
}
}
}
#[cfg(test)]
mod test;