Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
7460c3b
refactor(agent): extract run_one_tool + spawn_delta_forwarder into en…
senamakel May 30, 2026
0f29bab
refactor(agent): introduce TurnEngine; run_tool_call_loop becomes a shim
senamakel May 30, 2026
fa768bf
refactor(agent): generalize TurnEngine with progress/observer/checkpo…
senamakel May 30, 2026
20b9855
Merge remote-tracking branch 'upstream/main' into unify-agent-turn-en…
senamakel May 30, 2026
c53a6f7
refactor(agent): route the subagent loop through the unified TurnEngine
senamakel May 30, 2026
4d62c52
refactor(agent): add ResponseParser seam to the engine (Phase 4 groun…
senamakel May 30, 2026
6901e61
refactor(agent): make Agent.tool_dispatcher an Arc (Phase 4 groundwork)
senamakel May 30, 2026
bd1c2b6
refactor(agent): extract Agent per-call executor + enrich engine obse…
senamakel May 30, 2026
e57f9f9
refactor(agent): route Agent::turn (web chat) through the unified Tur…
senamakel May 30, 2026
bb13df4
Merge remote-tracking branch 'upstream/main' into unify-agent-turn-en…
senamakel May 30, 2026
40367ee
refactor(agent): Phase 5 cleanup — delete legacy executor, tidy impor…
senamakel May 30, 2026
96d0a41
Merge remote-tracking branch 'upstream/main' into unify-agent-turn-en…
senamakel May 30, 2026
021fbba
Merge remote-tracking branch 'upstream/main' into refactor/unify-agen…
senamakel May 30, 2026
8fdbc90
chore: apply pre-push fmt/lint auto-fixes
senamakel May 30, 2026
aac72b6
fix(agent): address CodeRabbit review on the turn engine
senamakel May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gitbooks/developing/architecture/agent-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ loop {

Every iteration emits a real-time `AgentProgress` event so the UI can render token-by-token streaming, "calling tool X" status, and per-iteration cost updates.

**One engine, three entry points.** This loop lives in one place — `engine::run_turn_engine` (`harness/engine/`) — and every caller drives it: `Agent::turn` (web/desktop chat), `run_tool_call_loop` (the `agent.run_turn` bus handler for other channels + triage), and `run_subagent` (spawned sub-agents). What varies per caller is supplied through small seams the engine calls into: a `ToolSource` (which tools are advertised + how a call executes), a `ProgressReporter` (top-level `Turn*` events with streaming vs. nested `Subagent*` events), a `TurnObserver` (context management, transcript persistence, history shape), a `CheckpointStrategy` (error vs. summarize when the iteration cap is hit), and a `ResponseParser` (the `ToolDispatcher` dialect). The per-call executor (`run_one_tool`), the repeated-failure circuit breaker, and the `ProviderDelta → AgentProgress` stream forwarder are shared across all three, so they can't drift.

### Tool dispatch and tool-call dialects

Different LLMs speak different tool-calling dialects. The harness abstracts that with a `ToolDispatcher` trait, which has three concrete implementations:
Expand Down
51 changes: 51 additions & 0 deletions src/openhuman/agent/harness/engine/checkpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Max-iteration checkpoint seam.
//!
//! When a turn exhausts its iteration budget the three callers diverge:
//!
//! * the channel/CLI loop returns the typed `AgentError::MaxIterationsExceeded`
//! so `Agent::run_single` can downcast and suppress Sentry noise
//! ([`ErrorCheckpoint`]);
//! * the subagent and `Agent::turn` instead summarize the run-so-far into a
//! resumable checkpoint string and return it as the turn's result (the
//! `SummarizeCheckpoint`, landed with the subagent/Agent migrations).
//!
//! [`CheckpointStrategy::on_max_iter`] receives the accumulated tool digest so a
//! summarizing strategy can produce a root-cause-aware checkpoint.

use anyhow::Result;
use async_trait::async_trait;

use crate::openhuman::inference::provider::UsageInfo;

/// A checkpoint result. `usage`, when present, is the provider usage from a
/// summarization call the strategy made — the engine folds it into the turn's
/// cost and reports it to the observer so token accounting stays complete.
pub(crate) struct CheckpointOutcome {
pub text: String,
pub usage: Option<UsageInfo>,
}

#[async_trait]
pub(crate) trait CheckpointStrategy: Send + Sync {
/// Produce the turn's result after the iteration cap is hit, or return an
/// error to surface the cap to the caller. `digest` is the accumulated
/// `tool → outcome` summary of the run so far.
async fn on_max_iter(&self, digest: &str, max_iterations: usize) -> Result<CheckpointOutcome>;
}

/// Surface the cap as the typed [`AgentError::MaxIterationsExceeded`], boxed
/// through `anyhow::Error`, so downstream wrappers — notably
/// `Agent::run_single` — can downcast and suppress Sentry emission for this
/// deterministic agent-state outcome (OPENHUMAN-TAURI-99 / -98).
pub(crate) struct ErrorCheckpoint;

#[async_trait]
impl CheckpointStrategy for ErrorCheckpoint {
async fn on_max_iter(&self, _digest: &str, max_iterations: usize) -> Result<CheckpointOutcome> {
Err(anyhow::Error::new(
crate::openhuman::agent::error::AgentError::MaxIterationsExceeded {
max: max_iterations,
},
))
}
}
Loading
Loading