af-agent-runtime 0.3.0

Recoverable Turn/Step loop, tool pipeline, retry and context compaction.
Documentation
//! Loop-level hook dispatch: pre-step admission, model request policy,
//! request-error recovery and turn-stop steering. Hooks run in mount order;
//! the first non-default decision wins so plugin ordering stays deterministic.

use af_agent::{
    ModelFailure, ModelRequestPolicy, PreStepDecision, RequestErrorAction, StepContext,
    TurnStopDecision,
};

use crate::{AgentRuntime, TurnRequest};

impl AgentRuntime {
    pub(super) fn step_context(&self, request: &TurnRequest, step: u32) -> StepContext {
        StepContext {
            request: request.context.clone(),
            session_id: request.session_id.clone(),
            run_id: request.run_id.clone(),
            turn: 1,
            step,
        }
    }

    pub(super) async fn pre_step(&self, context: &StepContext) -> PreStepDecision {
        for hook in &self.hooks {
            if let PreStepDecision::Reject { reason } = hook.pre_step(context).await {
                return PreStepDecision::Reject { reason };
            }
        }
        PreStepDecision::Enter
    }

    pub(super) async fn request_policy(&self, context: &StepContext) -> ModelRequestPolicy {
        let mut policy = ModelRequestPolicy {
            model: self.model_name.clone(),
            reasoning_effort: self.reasoning_effort,
            temperature: 0.3,
            max_tokens: None,
        };
        for hook in &self.hooks {
            hook.request(context, &mut policy).await;
        }
        policy
    }

    pub(super) async fn request_error(
        &self,
        context: &StepContext,
        failure: &ModelFailure,
    ) -> RequestErrorAction {
        for hook in &self.hooks {
            let action = hook.request_error(context, failure).await;
            if action != RequestErrorAction::Default {
                return action;
            }
        }
        RequestErrorAction::Default
    }

    pub(super) async fn turn_stopping(&self, context: &StepContext) -> TurnStopDecision {
        for hook in &self.hooks {
            if let TurnStopDecision::Steer { content } = hook.turn_stopping(context).await {
                return TurnStopDecision::Steer { content };
            }
        }
        TurnStopDecision::Stop
    }
}