pub struct AgentLoop<M: Model> {
pub model: M,
pub tools: ToolRegistry,
pub guides: Vec<Arc<dyn Guide>>,
pub sensors: Vec<Arc<dyn Sensor>>,
pub hooks: HookBus,
pub compactor: Arc<dyn Compactor>,
pub response_format: ResponseFormat,
pub streaming: bool,
pub recall: Option<Arc<dyn RecallStore>>,
pub recall_auto_inject: bool,
pub learning: Option<LearningConfig>,
}Expand description
The agent loop.
Fields§
§model: M§tools: ToolRegistry§guides: Vec<Arc<dyn Guide>>§sensors: Vec<Arc<dyn Sensor>>§hooks: HookBus§compactor: Arc<dyn Compactor>§response_format: ResponseFormatDefault response format applied to every run unless overridden by
run_typed. See ResponseFormat.
streaming: boolWhen true, the loop drives each model turn via Model::stream()
instead of complete(), firing Event::ModelTokenDelta for each
text fragment. Tool-call deltas are still assembled inside the loop;
only the terminal ModelOutput shape is observable downstream.
recall: Option<Arc<dyn RecallStore>>Optional cross-session recall store. When set, the loop captures every
turn and the session_search tool is registered. See with_recall.
recall_auto_inject: boolWhen true (and recall is set), a RecallGuide auto-injects top-k
past context at session start.
learning: Option<LearningConfig>Implementations§
Source§impl<M: Model> AgentLoop<M>
impl<M: Model> AgentLoop<M>
pub fn new(model: M) -> Self
Sourcepub fn with_streaming(self, enable: bool) -> Self
pub fn with_streaming(self, enable: bool) -> Self
Opt in to streaming the model’s terminal turn token-by-token via
Model::stream(). Hooks subscribed to Event::ModelTokenDelta see
each fragment as it arrives; the rest of the loop is unchanged.
pub fn with_compactor(self, c: Arc<dyn Compactor>) -> Self
pub fn with_tool(self, t: Arc<dyn Tool>) -> Self
pub fn with_guide(self, g: Arc<dyn Guide>) -> Self
pub fn with_sensor(self, s: Arc<dyn Sensor>) -> Self
pub fn with_hook(self, h: Arc<dyn Hook>) -> Self
Sourcepub fn with_macro_hooks(self) -> Self
pub fn with_macro_hooks(self) -> Self
Pull in every #[hook]-registered hook.
Sourcepub fn with_recall(self, store: Arc<dyn RecallStore>) -> Self
pub fn with_recall(self, store: Arc<dyn RecallStore>) -> Self
Enable cross-session recall: capture every turn into store and
register the session_search tool. Owner + session id are read from
world.profile.extra["recall_owner"|"recall_session"] at run time.
Sourcepub fn auto_inject(self) -> Self
pub fn auto_inject(self) -> Self
After with_recall, also auto-inject top-k relevant past context at
session start (off by default — tool-only is prompt-cache friendly).
Sourcepub fn with_learning_loop(self, cfg: LearningConfig) -> Self
pub fn with_learning_loop(self, cfg: LearningConfig) -> Self
Enable the self-evolving learning loop: after a session that made
>= cfg.nudge_interval tool calls, fork a review subagent (white-listed to
cfg.tools) to update skills + memory from the transcript. Best-effort.
Sourcepub fn with_response_format(self, fmt: ResponseFormat) -> Self
pub fn with_response_format(self, fmt: ResponseFormat) -> Self
Set the default response format for all runs through this loop. See
ResponseFormat. For typed deserialisation, prefer run_typed::<T>().
Sourcepub fn with_response_schema(
self,
name: impl Into<String>,
schema: Value,
) -> Self
pub fn with_response_schema( self, name: impl Into<String>, schema: Value, ) -> Self
Shortcut for with_response_format(ResponseFormat::JsonSchema { name, schema }).
Accepts a raw serde_json::Value so callers can hand-roll the schema or
pull it from schemars::schema_for!(T).
pub async fn run( &self, task: Task, world: &mut World, ) -> Result<Outcome, HarnessError>
pub async fn run_with_max_iters( &self, task: Task, world: &mut World, max_iters: u32, ) -> Result<Outcome, HarnessError>
Sourcepub async fn run_typed<T>(
&self,
task: Task,
world: &mut World,
) -> Result<T, HarnessError>where
T: DeserializeOwned + JsonSchema + 'static,
pub async fn run_typed<T>(
&self,
task: Task,
world: &mut World,
) -> Result<T, HarnessError>where
T: DeserializeOwned + JsonSchema + 'static,
Run the agent and deserialise the terminal reply into T.
The schema for T is derived via schemars::schema_for!(T) and
installed as ResponseFormat::JsonSchema for this run only — any
pre-existing self.response_format is ignored. On success the
returned T is parsed from Outcome::Done.text (or, on budget
exhaustion, from Outcome::BudgetExhausted.last_text).
Errors:
HarnessError::Otherif the model returns no text at allHarnessError::Otherifserde_json::from_str::<T>(text)fails — the original text is included in the message for debugging.
Sourcepub async fn run_typed_with_max_iters<T>(
&self,
task: Task,
world: &mut World,
max_iters: u32,
) -> Result<T, HarnessError>where
T: DeserializeOwned + JsonSchema + 'static,
pub async fn run_typed_with_max_iters<T>(
&self,
task: Task,
world: &mut World,
max_iters: u32,
) -> Result<T, HarnessError>where
T: DeserializeOwned + JsonSchema + 'static,
Like run_typed but with explicit max_iters.
Sourcepub async fn run_with_response_format(
&self,
task: Task,
world: &mut World,
max_iters: u32,
fmt: ResponseFormat,
) -> Result<Outcome, HarnessError>
pub async fn run_with_response_format( &self, task: Task, world: &mut World, max_iters: u32, fmt: ResponseFormat, ) -> Result<Outcome, HarnessError>
Run with a one-off ResponseFormat override (doesn’t touch self).
Sourcepub async fn run_with_seed_history(
&self,
task: Task,
seed: Vec<Turn>,
world: &mut World,
max_iters: u32,
) -> Result<Outcome, HarnessError>
pub async fn run_with_seed_history( &self, task: Task, seed: Vec<Turn>, world: &mut World, max_iters: u32, ) -> Result<Outcome, HarnessError>
Like run_with_max_iters but seeds ctx.history with seed before
the current user task is appended. Use this for multi-turn REPLs so
prior conversation lives in ctx.history (where the Compactor can see
it) instead of being concatenated into task.description (where it
previously bypassed compaction entirely — see audit #2).