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    executor::AgentExecutionNode,
7    stream::{AgentStreamEvent, AgentStreamRecord},
8    AgentResult,
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 failed after preserving recoverable context state.
44    RunFailed,
45}
46
47/// One inspected runtime iteration step.
48#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
49pub struct AgentIterationStep {
50    /// Monotonic iteration index.
51    pub index: usize,
52    /// Source stream record sequence.
53    pub stream_sequence: usize,
54    /// Current model/tool loop step when available.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub run_step: Option<usize>,
57    /// Iteration kind.
58    pub kind: AgentIterationKind,
59    /// Execution node when the event is tied to a durable boundary.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub node: Option<AgentExecutionNode>,
62}
63
64impl AgentIterationStep {
65    const fn from_record(index: usize, record: &AgentStreamRecord) -> Self {
66        let (kind, run_step, node) = match &record.event {
67            AgentStreamEvent::RunStart { .. } => (AgentIterationKind::RunStart, None, None),
68            AgentStreamEvent::NodeStart { node, step, .. } => {
69                (AgentIterationKind::NodeStart, Some(*step), Some(*node))
70            }
71            AgentStreamEvent::NodeComplete { node, step, .. } => {
72                (AgentIterationKind::NodeComplete, Some(*step), Some(*node))
73            }
74            AgentStreamEvent::Custom { .. } => (AgentIterationKind::Custom, None, None),
75            AgentStreamEvent::ModelRequest { step } => {
76                (AgentIterationKind::ModelRequest, Some(*step), None)
77            }
78            AgentStreamEvent::ModelStream { step, .. } => {
79                (AgentIterationKind::ModelStream, Some(*step), None)
80            }
81            AgentStreamEvent::ModelResponse { step, .. } => {
82                (AgentIterationKind::ModelResponse, Some(*step), None)
83            }
84            AgentStreamEvent::Checkpoint { node, step } => {
85                (AgentIterationKind::Checkpoint, Some(*step), Some(*node))
86            }
87            AgentStreamEvent::Suspended { node, .. } => {
88                (AgentIterationKind::Suspended, None, Some(*node))
89            }
90            AgentStreamEvent::ToolCall { step, .. } => {
91                (AgentIterationKind::ToolCall, Some(*step), None)
92            }
93            AgentStreamEvent::ToolReturn { step, .. } => {
94                (AgentIterationKind::ToolReturn, Some(*step), None)
95            }
96            AgentStreamEvent::OutputRetry { .. } => (AgentIterationKind::OutputRetry, None, None),
97            AgentStreamEvent::SteeringGuard { step, .. } => {
98                (AgentIterationKind::SteeringGuard, Some(*step), None)
99            }
100            AgentStreamEvent::RunComplete { .. } => (AgentIterationKind::RunComplete, None, None),
101            AgentStreamEvent::RunFailed { .. } => (AgentIterationKind::RunFailed, None, None),
102        };
103        Self {
104            index,
105            stream_sequence: record.sequence,
106            run_step,
107            kind,
108            node,
109        }
110    }
111}
112
113/// Compact run iteration trace for debuggers, CLIs, and durable service UIs.
114#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
115pub struct AgentIterationTrace {
116    /// Iteration steps derived from stream records.
117    pub steps: Vec<AgentIterationStep>,
118}
119
120impl AgentIterationTrace {
121    /// Build an iteration trace from recorded stream events.
122    #[must_use]
123    pub fn from_stream_records(records: &[AgentStreamRecord]) -> Self {
124        Self {
125            steps: records
126                .iter()
127                .enumerate()
128                .map(|(index, record)| AgentIterationStep::from_record(index, record))
129                .collect(),
130        }
131    }
132
133    /// Return all iteration steps.
134    #[must_use]
135    pub fn steps(&self) -> &[AgentIterationStep] {
136        &self.steps
137    }
138
139    /// Return whether the trace includes a completed run.
140    #[must_use]
141    pub fn is_complete(&self) -> bool {
142        self.steps
143            .iter()
144            .any(|step| step.kind == AgentIterationKind::RunComplete)
145    }
146}
147
148/// Result returned by collection-based iteration inspection runs.
149#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
150pub struct AgentIterResult {
151    /// Final agent result.
152    pub result: AgentResult,
153    /// Compact iteration trace.
154    pub iterations: AgentIterationTrace,
155    /// Source stream records used to build the trace.
156    pub events: Vec<AgentStreamRecord>,
157}
158
159impl AgentIterResult {
160    /// Return the final agent result.
161    #[must_use]
162    pub const fn result(&self) -> &AgentResult {
163        &self.result
164    }
165
166    /// Return the iteration trace.
167    #[must_use]
168    pub const fn iterations(&self) -> &AgentIterationTrace {
169        &self.iterations
170    }
171
172    /// Return the source stream records.
173    #[must_use]
174    pub fn events(&self) -> &[AgentStreamRecord] {
175        &self.events
176    }
177}