car-multi 0.6.0

Multi-agent coordination patterns for Common Agent Runtime
Documentation
//! Core types for multi-agent coordination.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

/// Blueprint for an agent in a multi-agent system.
///
/// The runtime doesn't own the model — `metadata` carries provider/model info
/// that the caller's `AgentRunner` implementation uses to select the right model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSpec {
    pub name: String,
    pub system_prompt: String,
    /// Tool names this agent is allowed to use.
    pub tools: Vec<String>,
    pub max_turns: u32,
    /// Opaque metadata (provider, model, temperature, etc.).
    pub metadata: HashMap<String, Value>,
    /// Enable prompt caching for this agent's API calls.
    /// When true, the system prompt is marked for Anthropic prompt cache reuse.
    #[serde(default)]
    pub cache_control: bool,
}

impl AgentSpec {
    pub fn new(name: &str, system_prompt: &str) -> Self {
        Self {
            name: name.to_string(),
            system_prompt: system_prompt.to_string(),
            tools: Vec::new(),
            max_turns: 10,
            metadata: HashMap::new(),
            cache_control: false,
        }
    }

    pub fn with_tools(mut self, tools: Vec<String>) -> Self {
        self.tools = tools;
        self
    }

    pub fn with_max_turns(mut self, max_turns: u32) -> Self {
        self.max_turns = max_turns;
        self
    }

    pub fn with_metadata(mut self, key: &str, value: Value) -> Self {
        self.metadata.insert(key.to_string(), value);
        self
    }

    pub fn with_cache_control(mut self) -> Self {
        self.cache_control = true;
        self
    }
}

/// Token and cost accounting contributed by an AgentRunner. Runners opt in by
/// setting `AgentOutput::tokens`; benchmarks that need economic metrics read it.
///
/// Self-reported by the runner; not verified by CAR. Do not use as a trust
/// boundary — for benchmarking and observability only.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenAccounting {
    #[serde(default)]
    pub input_tokens: u64,
    #[serde(default)]
    pub output_tokens: u64,
    #[serde(default)]
    pub cost_usd: f64,
}

impl TokenAccounting {
    pub fn new(input_tokens: u64, output_tokens: u64, cost_usd: f64) -> Self {
        debug_assert!(cost_usd.is_finite(), "cost_usd must be finite");
        debug_assert!(cost_usd >= 0.0, "cost_usd must be non-negative");
        Self { input_tokens, output_tokens, cost_usd }
    }
}

/// Output from one agent's execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentOutput {
    pub name: String,
    pub answer: String,
    pub turns: u32,
    pub tool_calls: u32,
    pub duration_ms: f64,
    pub error: Option<String>,
    /// Structured outcome (when available). Products can use this instead of
    /// checking answer/error fields for completion semantics.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub outcome: Option<car_ir::AgentOutcome>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tokens: Option<TokenAccounting>,
}

impl AgentOutput {
    pub fn succeeded(&self) -> bool {
        self.error.is_none() && !self.answer.is_empty()
    }
}

/// A message between agents in a multi-agent system.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    pub from: String,
    pub to: String,
    pub kind: MessageKind,
    pub payload: Value,
    pub timestamp: DateTime<Utc>,
}

impl Message {
    pub fn new(from: &str, to: &str, kind: MessageKind, payload: Value) -> Self {
        Self {
            from: from.to_string(),
            to: to.to_string(),
            kind,
            payload,
            timestamp: Utc::now(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MessageKind {
    /// Task assignment from orchestrator.
    TaskAssignment,
    /// Agent's completed result.
    Result,
    /// Feedback from supervisor.
    Feedback,
    /// Delegation request.
    DelegateRequest,
    /// Delegation response.
    DelegateResponse,
    /// Custom inter-agent message.
    Custom,
}