1use kernel::{
10 AxError, Completion, EventDraft, EventKind, Evidence, Ledger, Model, Payload, StopReason,
11};
12
13use crate::conversation::Conversation;
14use crate::handoff::Handoff;
15use crate::prefix::shape::PromptShape;
16use crate::reminder::ContextGauge;
17use crate::turn::{Generating, Interrupt, PhaseOutcome, RunPrompt, Turn, TurnReport};
18
19use super::checkpoint::{CheckpointPolicy, Wave, WaveCheckpoint};
20use super::{Active, Advance, Frozen, Run, RunHooks, RunPlan, SafePoint};
21
22fn fold_steer(conversation: &mut Conversation, interrupt: &Interrupt) {
31 if let Interrupt::Steer { source, text } = interrupt {
32 conversation.push_steer(source, text);
33 }
34}
35
36fn concluded(report: &TurnReport) -> Result<Completion, AxError> {
48 if report.assistant().is_empty() || report.stop() == Some(StopReason::MaxTokens) {
49 return Ok(Completion::Limit);
50 }
51 Ok(Completion::Done(Evidence::new(vec![
52 *report.model_returned(),
53 ])?))
54}
55
56impl Run<Active> {
57 pub fn dispatch(
66 plan: RunPlan,
67 ledger: &mut dyn Ledger,
68 hooks: &mut RunHooks<'_>,
69 ) -> Result<Run<Active>, AxError> {
70 plan.charter().open(ledger, &mut *hooks.now)?;
71
72 let mut conversation = Conversation::new();
73 conversation.push_inherited(&plan.inherited);
77 conversation.push_task_lines(&plan.task, &plan.goal, plan.opening);
78 let gauge = ContextGauge::new(
79 kernel::Tokens::new(plan.shape.context_tokens),
80 plan.second_threshold,
81 );
82 Ok(Run {
83 plan,
84 state: Active {
85 conversation,
86 turns: 0,
87 last_turn_t: None,
88 gauge,
89 prior_shape: None,
90 prompt: crate::turn::PromptRecord::default(),
91 checkpoint: CheckpointPolicy::opening(),
92 },
93 })
94 }
95
96 pub fn advance(
104 &mut self,
105 ledger: &mut dyn Ledger,
106 model: &mut dyn Model,
107 hooks: &mut RunHooks<'_>,
108 ) -> Result<Advance, AxError> {
109 let index = self.state.turns;
110 let t = (hooks.now)()?;
111 self.state.last_turn_t = Some(t);
112
113 let turn = Turn::begin(self.plan.run, self.plan.who.clone(), t, &mut *hooks.now);
114 let opening = (hooks.interrupt)(SafePoint::BeforeAssemble { turn: index });
115 fold_steer(&mut self.state.conversation, &opening);
116 let turn = match turn.assemble(
117 opening,
118 ledger,
119 RunPrompt::new(&self.plan.prefix, &mut self.state.prompt),
120 &self.state.conversation,
121 &self.plan.tools,
122 &self.plan.shape,
123 )? {
124 PhaseOutcome::Advanced(next) => next,
125 PhaseOutcome::Cancelled(_) => return Ok(Advance::Concluded(Completion::Cancelled)),
126 };
127 let shape = PromptShape::of(
133 &self.plan.prefix,
134 &self.plan.tools,
135 self.state.conversation.messages(),
136 )?;
137 let changed = shape.attribute(self.state.prior_shape.as_ref());
138 ledger.append(EventDraft {
139 run: self.plan.run,
140 t,
141 who: self.plan.who.clone(),
142 addr: Some(self.plan.addr.clone()),
143 kind: EventKind::PromptShapeCompared,
144 data: Payload::of(&shape.recorded(changed)?)?,
145 ig: false,
146 })?;
147 self.state.prior_shape = Some(shape);
148 let calling = (hooks.interrupt)(SafePoint::BeforeCall { turn: index });
149 let called = turn.call(
150 calling.clone(),
151 ledger,
152 model,
153 &self.plan.policy,
154 Generating::Speculating {
157 deltas: hooks.deltas.as_deref_mut(),
158 tools: &*hooks.invoke,
159 },
160 );
161 self.state.conversation.mark_sent();
166 fold_steer(&mut self.state.conversation, &calling);
167 let turn = match called? {
168 PhaseOutcome::Advanced(next) => next,
169 PhaseOutcome::Cancelled(_) => return Ok(Advance::Concluded(Completion::Cancelled)),
170 };
171
172 if let Some(usage) = turn.usage() {
175 self.plan.context.record(usage.input_tokens);
176 }
177 let touches = Wave::of(turn.calls(), hooks.writes);
180 let decided = self.state.checkpoint.for_wave(touches);
181 if let (WaveCheckpoint::Stage, Some(checkpoint)) = (decided, hooks.checkpoint.as_mut()) {
182 let committed = checkpoint(t)?;
183 ledger.append(EventDraft {
184 run: self.plan.run,
185 t,
186 who: self.plan.who.clone(),
187 addr: Some(self.plan.addr.clone()),
188 kind: EventKind::CheckpointCommitted,
189 data: committed,
190 ig: false,
191 })?;
192 }
193
194 self.state.checkpoint.record_wave(decided, touches);
195
196 let wave = (hooks.interrupt)(SafePoint::BeforeWave { turn: index });
197 fold_steer(&mut self.state.conversation, &wave);
198 let asking = &mut hooks.interrupt;
205 let conversation = &mut self.state.conversation;
206 let mut still_going = |call: u32| {
207 let arrived = asking(SafePoint::BeforeToolCall { turn: index, call });
208 fold_steer(conversation, &arrived);
209 arrived
210 };
211 let turn =
212 match turn.execute_concurrent(wave, ledger, &mut *hooks.invoke, &mut still_going)? {
213 PhaseOutcome::Advanced(next) => next,
214 PhaseOutcome::Cancelled(_) => return Ok(Advance::Concluded(Completion::Cancelled)),
215 };
216
217 let settling = (hooks.interrupt)(SafePoint::BeforeSpawn { turn: index });
218 fold_steer(&mut self.state.conversation, &settling);
219 let report = match turn.record(settling, ledger)? {
220 PhaseOutcome::Advanced(report) => report,
221 PhaseOutcome::Cancelled(_) => return Ok(Advance::Concluded(Completion::Cancelled)),
222 };
223 self.state.turns = self.state.turns.saturating_add(1);
224 self.state
225 .conversation
226 .push_assistant(report.assistant().to_vec());
227 self.state
228 .conversation
229 .push_tool_results(report.wave_results().to_vec());
230 if let Some(reminder) = report
234 .usage()
235 .and_then(|usage| self.state.gauge.observe(usage.input_tokens))
236 {
237 self.state.conversation.push_reminder(&reminder);
238 }
239 if report.calls_made() == 0 {
240 return Ok(Advance::Concluded(concluded(&report)?));
241 }
242 Ok(Advance::Turned)
243 }
244
245 pub fn freeze(
256 self,
257 ledger: &mut dyn Ledger,
258 handoff: &Handoff,
259 completion: Completion,
260 hooks: &mut RunHooks<'_>,
261 ) -> Result<Run<Frozen>, AxError> {
262 let t = match (&completion, self.state.last_turn_t) {
263 (Completion::Cancelled, Some(turn_t)) => turn_t,
264 _ => (hooks.now)()?,
265 };
266 self.plan.charter().close(ledger, handoff, &completion, t)?;
267 let turns = self.state.turns;
268 Ok(Run {
269 plan: self.plan,
270 state: Frozen {
271 completion,
272 turns,
273 conversation: self.state.conversation,
274 },
275 })
276 }
277
278 pub fn turns_taken(&self) -> u32 {
279 self.state.turns
280 }
281}
282
283#[cfg(test)]
284#[allow(
285 clippy::unwrap_used,
286 clippy::expect_used,
287 clippy::panic,
288 clippy::indexing_slicing,
289 reason = "test code"
290)]
291mod tests;