use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::harness::ids::{GraphId, NodeId, RunId, TaskId};
use crate::{Result, TinyAgentsError};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecursionFrame {
pub graph_id: GraphId,
pub node_id: Option<NodeId>,
pub run_id: RunId,
pub task_id: Option<TaskId>,
pub namespace: Vec<String>,
pub depth: usize,
pub parent: Option<RunId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecursionPolicy {
pub max_depth: usize,
pub max_visits_per_node: Option<usize>,
pub max_total_steps: usize,
}
impl Default for RecursionPolicy {
fn default() -> Self {
Self {
max_depth: 25,
max_visits_per_node: None,
max_total_steps: 1000,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct RecursionStack {
frames: Vec<RecursionFrame>,
policy: RecursionPolicy,
}
impl RecursionStack {
pub fn new(policy: RecursionPolicy) -> Self {
Self {
frames: Vec::new(),
policy,
}
}
pub fn with_frames(frames: Vec<RecursionFrame>, policy: RecursionPolicy) -> Self {
Self { frames, policy }
}
pub fn policy(&self) -> &RecursionPolicy {
&self.policy
}
pub fn depth(&self) -> usize {
self.frames.len()
}
pub fn frames(&self) -> &[RecursionFrame] {
&self.frames
}
pub fn push(&mut self, frame: RecursionFrame) -> Result<()> {
if self.frames.len() + 1 > self.policy.max_depth {
return Err(TinyAgentsError::SubAgentDepth(self.policy.max_depth));
}
self.frames.push(frame);
Ok(())
}
pub fn pop(&mut self) -> Option<RecursionFrame> {
self.frames.pop()
}
pub fn check_total_steps(&self, steps: usize) -> Result<()> {
if steps >= self.policy.max_total_steps {
return Err(TinyAgentsError::RecursionLimit(self.policy.max_total_steps));
}
Ok(())
}
pub fn record_node_visit(
&self,
counts: &mut std::collections::HashMap<NodeId, usize>,
node: &NodeId,
) -> Result<()> {
let Some(max) = self.policy.max_visits_per_node else {
return Ok(());
};
let count = counts.entry(node.clone()).or_insert(0);
*count += 1;
if *count > max {
return Err(TinyAgentsError::NodeVisitLimit {
node: node.to_string(),
limit: max,
});
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildRun {
pub node: NodeId,
pub graph_id: GraphId,
pub run_id: RunId,
pub root_run_id: RunId,
#[serde(default)]
pub usage: crate::harness::usage::UsageTotals,
}
#[derive(Clone, Debug, Default)]
pub struct ChildRunSink {
inner: Arc<Mutex<Vec<ChildRun>>>,
}
impl ChildRunSink {
pub fn new() -> Self {
Self::default()
}
pub fn record(&self, child: ChildRun) {
if let Ok(mut guard) = self.inner.lock() {
guard.push(child);
}
}
pub fn drain(&self) -> Vec<ChildRun> {
match self.inner.lock() {
Ok(mut guard) => std::mem::take(&mut *guard),
Err(_) => Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunTree {
pub run_id: RunId,
pub root_run_id: RunId,
pub parent_run_id: Option<RunId>,
pub children: Vec<ChildRun>,
}
impl RunTree {
pub fn is_root(&self) -> bool {
self.parent_run_id.is_none()
}
}