Skip to main content

starweaver_runtime/
iteration.rs

1//! Run iteration inspection records derived from typed stream events.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    AgentResult,
7    executor::AgentExecutionNode,
8    stream::{AgentStreamEvent, AgentStreamRecord},
9};
10
11/// Coarse iteration event kind for run inspection.
12#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum AgentIterationKind {
15    /// A run started.
16    RunStart,
17    /// Runtime execution entered a durable node boundary.
18    NodeStart,
19    /// Runtime execution completed a durable node boundary.
20    NodeComplete,
21    /// A context sideband event was published.
22    Custom,
23    /// A model request was prepared.
24    ModelRequest,
25    /// A provider stream delta or part event was observed.
26    ModelStream,
27    /// A final model response was applied.
28    ModelResponse,
29    /// A durable checkpoint was observed.
30    Checkpoint,
31    /// Execution suspended at a checkpoint.
32    Suspended,
33    /// A tool call was observed.
34    ToolCall,
35    /// A tool return was observed.
36    ToolReturn,
37    /// Output validation requested another model turn.
38    OutputRetry,
39    /// Pending steering requested another model turn.
40    SteeringGuard,
41    /// The run completed.
42    RunComplete,
43    /// The run was cooperatively cancelled.
44    RunCancelled,
45    /// The run failed after preserving recoverable context state.
46    RunFailed,
47}
48
49/// One inspected runtime iteration step.
50#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
51pub struct AgentIterationStep {
52    /// Monotonic iteration index.
53    pub index: usize,
54    /// Source stream record sequence.
55    pub stream_sequence: usize,
56    /// Current model/tool loop step when available.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub run_step: Option<usize>,
59    /// Iteration kind.
60    pub kind: AgentIterationKind,
61    /// Execution node when the event is tied to a durable boundary.
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub node: Option<AgentExecutionNode>,
64}
65
66impl AgentIterationStep {
67    const fn from_record(index: usize, record: &AgentStreamRecord) -> Self {
68        let (kind, run_step, node) = match &record.event {
69            AgentStreamEvent::RunStart { .. } => (AgentIterationKind::RunStart, None, None),
70            AgentStreamEvent::NodeStart { node, step, .. } => {
71                (AgentIterationKind::NodeStart, Some(*step), Some(*node))
72            }
73            AgentStreamEvent::NodeComplete { node, step, .. } => {
74                (AgentIterationKind::NodeComplete, Some(*step), Some(*node))
75            }
76            AgentStreamEvent::Custom { .. } => (AgentIterationKind::Custom, None, None),
77            AgentStreamEvent::ModelRequest { step } => {
78                (AgentIterationKind::ModelRequest, Some(*step), None)
79            }
80            AgentStreamEvent::ModelStream { step, .. } => {
81                (AgentIterationKind::ModelStream, Some(*step), None)
82            }
83            AgentStreamEvent::ModelResponse { step, .. } => {
84                (AgentIterationKind::ModelResponse, Some(*step), None)
85            }
86            AgentStreamEvent::Checkpoint { node, step } => {
87                (AgentIterationKind::Checkpoint, Some(*step), Some(*node))
88            }
89            AgentStreamEvent::Suspended { node, .. } => {
90                (AgentIterationKind::Suspended, None, Some(*node))
91            }
92            AgentStreamEvent::ToolCall { step, .. } => {
93                (AgentIterationKind::ToolCall, Some(*step), None)
94            }
95            AgentStreamEvent::ToolReturn { step, .. } => {
96                (AgentIterationKind::ToolReturn, Some(*step), None)
97            }
98            AgentStreamEvent::OutputRetry { .. } => (AgentIterationKind::OutputRetry, None, None),
99            AgentStreamEvent::SteeringGuard { step, .. } => {
100                (AgentIterationKind::SteeringGuard, Some(*step), None)
101            }
102            AgentStreamEvent::RunComplete { .. } => (AgentIterationKind::RunComplete, None, None),
103            AgentStreamEvent::RunCancelled { .. } => (AgentIterationKind::RunCancelled, None, None),
104            AgentStreamEvent::RunFailed { .. } => (AgentIterationKind::RunFailed, None, None),
105        };
106        Self {
107            index,
108            stream_sequence: record.sequence,
109            run_step,
110            kind,
111            node,
112        }
113    }
114}
115
116/// Compact run iteration trace for debuggers, CLIs, and durable service UIs.
117#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
118pub struct AgentIterationTrace {
119    /// Iteration steps derived from stream records.
120    pub steps: Vec<AgentIterationStep>,
121}
122
123impl AgentIterationTrace {
124    /// Build an iteration trace from recorded stream events.
125    #[must_use]
126    pub fn from_stream_records(records: &[AgentStreamRecord]) -> Self {
127        Self {
128            steps: records
129                .iter()
130                .enumerate()
131                .map(|(index, record)| AgentIterationStep::from_record(index, record))
132                .collect(),
133        }
134    }
135
136    /// Return all iteration steps.
137    #[must_use]
138    pub fn steps(&self) -> &[AgentIterationStep] {
139        &self.steps
140    }
141
142    /// Return whether the trace includes a completed run.
143    #[must_use]
144    pub fn is_complete(&self) -> bool {
145        self.steps
146            .iter()
147            .any(|step| step.kind == AgentIterationKind::RunComplete)
148    }
149}
150
151/// Result returned by collection-based iteration inspection runs.
152#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
153pub struct AgentIterResult {
154    /// Final agent result.
155    pub result: AgentResult,
156    /// Compact iteration trace.
157    pub iterations: AgentIterationTrace,
158    /// Source stream records used to build the trace.
159    pub events: Vec<AgentStreamRecord>,
160}
161
162impl AgentIterResult {
163    /// Return the final agent result.
164    #[must_use]
165    pub const fn result(&self) -> &AgentResult {
166        &self.result
167    }
168
169    /// Return the iteration trace.
170    #[must_use]
171    pub const fn iterations(&self) -> &AgentIterationTrace {
172        &self.iterations
173    }
174
175    /// Return the source stream records.
176    #[must_use]
177    pub fn events(&self) -> &[AgentStreamRecord] {
178        &self.events
179    }
180}