af-agent-runtime 0.5.0

Recoverable Turn/Step loop, tool pipeline, retry and context compaction.
Documentation
use af_context::{InputId, InteractionId, RunId, SessionId};
use std::time::Duration;
use std::time::Instant;

use af_agent_session::{ContentBlock, Event, SessionEvent};
use af_llm::ChatMessage;
use async_trait::async_trait;

use crate::{CancellationToken, RuntimeError};

/// Hard limits for one Run.
#[derive(Debug, Clone)]
pub struct RuntimeLimits {
    /// Upper bound on model steps per Run.
    pub max_steps: u32,
    /// Maximum tool calls per Run.
    pub max_tool_calls: u32,
    /// Upper bound on prompt plus completion tokens.
    pub max_tokens: u64,
    /// Maximum tools executed concurrently within a step.
    pub max_parallel_tools: usize,
    /// Model attempts before a step fails.
    pub provider_attempts: u32,
    /// Deadline for one model attempt or extension call.
    pub provider_deadline: Duration,
}

impl Default for RuntimeLimits {
    fn default() -> Self {
        Self {
            max_steps: 8,
            max_tool_calls: 24,
            max_tokens: 64_000,
            max_parallel_tools: 4,
            provider_attempts: 3,
            provider_deadline: Duration::from_secs(90),
        }
    }
}

/// Everything the loop needs to run one Turn.
#[derive(Debug, Clone)]
pub struct TurnRequest {
    /// Caller identity and entitlements for this operation.
    pub context: af_context::RequestContext,
    /// Session this record belongs to.
    pub session_id: SessionId,
    /// Run this record belongs to.
    pub run_id: RunId,
    /// Queued input this record refers to.
    pub input_id: InputId,
    /// Content blocks carried by this record.
    pub content: Vec<ContentBlock>,
    /// Committed Session events the Turn resumes from.
    pub history: Vec<SessionEvent>,
}

/// Terminal result of a Turn.
#[derive(Debug, Clone, PartialEq)]
pub struct RuntimeOutcome {
    /// Current lifecycle status.
    pub status: String,
    /// Final assistant text, when the Run completed with prose.
    pub final_text: Option<String>,
    /// Prompt tokens consumed.
    pub prompt_tokens: u64,
    /// Completion tokens produced.
    pub completion_tokens: u64,
    /// Interaction the Run is parked on, when it stopped to wait for input.
    pub waiting_interaction_id: Option<InteractionId>,
}

/// Fenced append/read access to the Session log for one Run.
#[async_trait]
pub trait EventWriter: Send + Sync {
    /// Append events atomically and return them with assigned sequences.
    async fn append(&self, events: Vec<Event>) -> Result<Vec<SessionEvent>, RuntimeError>;
    /// Events committed after `seq`.
    async fn load_after(&self, seq: u64) -> Result<Vec<SessionEvent>, RuntimeError>;
}

/// Token estimator used for limits and compaction decisions.
pub trait TokenMeter: Send + Sync {
    /// Estimated prompt tokens for `messages` under `model`.
    fn count(&self, model: &str, messages: &[ChatMessage]) -> u64;
}

/// Character-based estimate (about four characters per token).
pub struct ApproximateTokenMeter;

impl TokenMeter for ApproximateTokenMeter {
    fn count(&self, _model: &str, messages: &[ChatMessage]) -> u64 {
        messages
            .iter()
            .map(|message| {
                message
                    .content
                    .as_deref()
                    .map_or(0, |content| content.chars().count().div_ceil(4) as u64)
            })
            .sum()
    }
}

/// Produces the summary that replaces old transcript when the context overflows.
#[async_trait]
pub trait Compactor: Send + Sync {
    /// Stable name recorded in compaction events.
    fn name(&self) -> &str {
        "compactor"
    }

    /// Summarize `transcript`, returning the summary and the usage it cost.
    async fn summarize(
        &self,
        model: &str,
        messages: &[ChatMessage],
        operation_id: &str,
        cancellation: CancellationToken,
        deadline: Instant,
    ) -> Result<CompactionResult, RuntimeError>;
}

/// Summary text and its token usage.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompactionResult {
    /// Short human-readable summary.
    pub summary: String,
    /// Prompt tokens consumed.
    pub prompt_tokens: u64,
    /// Completion tokens produced.
    pub completion_tokens: u64,
}