From 108e0cd6320dfece9e3fe07cceae6ae8d42f56d4 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 30 Mar 2026 17:49:04 -0400 Subject: [PATCH] Split agent processing into thinking and executing --- docs/reference/sdk.mdx | 2 +- lib/crates/fabro-agent/src/session.rs | 27 +++++++++++++------ lib/crates/fabro-agent/src/types.rs | 4 +-- lib/crates/fabro-cli/src/commands/run/logs.rs | 8 ++++-- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/docs/reference/sdk.mdx b/docs/reference/sdk.mdx index 50771c9ef..76d8bc302 100644 --- a/docs/reference/sdk.mdx +++ b/docs/reference/sdk.mdx @@ -86,7 +86,7 @@ pub fn new( | Method | Description | |---|---| -| `state()` | Returns `SessionState`: `Idle`, `Processing`, `AwaitingInput`, or `Closed`. | +| `state()` | Returns `SessionState`: `Idle`, `Thinking`, `Executing`, or `Closed`. | | `history()` | Returns the conversation as `&History` (a sequence of `Turn` values). | | `subscribe()` | Returns a broadcast receiver for `SessionEvent` values. | diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index 29b64280f..d01979e56 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -481,9 +481,12 @@ impl Session { /// cleanup as appropriate for each transition. /// /// Valid transitions (matches the Attractor spec): - /// - Idle → Processing - /// - Processing → Idle (emits ProcessingEnd) - /// - Processing → Closed (emits SessionEnded) + /// - Idle → Thinking + /// - Thinking → Executing + /// - Thinking → Idle (emits ProcessingEnd) + /// - Executing → Thinking + /// - Thinking → Closed (emits SessionEnded) + /// - Executing → Closed (emits SessionEnded) /// - Idle → Closed (emits SessionEnded) /// - any → Closed (abort/error — emits SessionEnded) fn transition(&mut self, to: SessionState) { @@ -495,9 +498,13 @@ impl Session { debug_assert!( matches!( (from, to), - (SessionState::Idle, SessionState::Processing) - | (SessionState::Processing, SessionState::Idle) - | (_, SessionState::Closed) + ( + SessionState::Idle | SessionState::Executing, + SessionState::Thinking + ) | ( + SessionState::Thinking, + SessionState::Executing | SessionState::Idle + ) | (_, SessionState::Closed) ), "Invalid session state transition: {from:?} -> {to:?}" ); @@ -513,7 +520,9 @@ impl Session { .emit(self.id.clone(), AgentEvent::SessionEnded); } - if from == SessionState::Processing && to == SessionState::Idle { + if matches!(from, SessionState::Thinking | SessionState::Executing) + && to == SessionState::Idle + { self.event_emitter .emit(self.id.clone(), AgentEvent::ProcessingEnd); } @@ -608,7 +617,7 @@ impl Session { return Err(AgentError::SessionClosed); } - self.transition(SessionState::Processing); + self.transition(SessionState::Thinking); // Expand skill references in input let expanded = if self.skills.is_empty() { @@ -845,6 +854,7 @@ impl Session { round_count += 1; // Execute tool calls (parallel or sequential based on provider) + self.transition(SessionState::Executing); let results = execute_tool_calls( &tool_calls, true, @@ -881,6 +891,7 @@ impl Session { // Drain steering after tool execution self.drain_steering(); + self.transition(SessionState::Thinking); // Loop detection if self.config.enable_loop_detection diff --git a/lib/crates/fabro-agent/src/types.rs b/lib/crates/fabro-agent/src/types.rs index d9bdc4ed4..d6fe2fb6a 100644 --- a/lib/crates/fabro-agent/src/types.rs +++ b/lib/crates/fabro-agent/src/types.rs @@ -84,8 +84,8 @@ impl Turn { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SessionState { Idle, - Processing, - AwaitingInput, + Thinking, + Executing, Closed, } diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index 3c2a8c129..d8920655a 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -1,3 +1,4 @@ +use std::fmt::Write as _; use std::io::{self, BufRead, IsTerminal, Write}; use std::path::Path; use std::time::Duration; @@ -496,8 +497,11 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option duration, ); if token_total > 0 { - line.push_str(" "); - line.push_str(&styles.dim.apply_to(format_tokens(token_total)).to_string()); + let _ = write!( + line, + " {}", + styles.dim.apply_to(format_tokens(token_total)) + ); } Some(line) }