Skip to main content

car_multi/
types.rs

1//! Core types for multi-agent coordination.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8/// Blueprint for an agent in a multi-agent system.
9///
10/// The runtime doesn't own the model — `metadata` carries provider/model info
11/// that the caller's `AgentRunner` implementation uses to select the right model.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AgentSpec {
14    pub name: String,
15    pub system_prompt: String,
16    /// Tool names this agent is allowed to use.
17    pub tools: Vec<String>,
18    pub max_turns: u32,
19    /// Opaque metadata (provider, model, temperature, etc.).
20    pub metadata: HashMap<String, Value>,
21    /// Enable prompt caching for this agent's API calls.
22    /// When true, the system prompt is marked for Anthropic prompt cache reuse.
23    #[serde(default)]
24    pub cache_control: bool,
25}
26
27impl AgentSpec {
28    pub fn new(name: &str, system_prompt: &str) -> Self {
29        Self {
30            name: name.to_string(),
31            system_prompt: system_prompt.to_string(),
32            tools: Vec::new(),
33            max_turns: 10,
34            metadata: HashMap::new(),
35            cache_control: false,
36        }
37    }
38
39    pub fn with_tools(mut self, tools: Vec<String>) -> Self {
40        self.tools = tools;
41        self
42    }
43
44    pub fn with_max_turns(mut self, max_turns: u32) -> Self {
45        self.max_turns = max_turns;
46        self
47    }
48
49    pub fn with_metadata(mut self, key: &str, value: Value) -> Self {
50        self.metadata.insert(key.to_string(), value);
51        self
52    }
53
54    pub fn with_cache_control(mut self) -> Self {
55        self.cache_control = true;
56        self
57    }
58}
59
60/// Token and cost accounting contributed by an AgentRunner. Runners opt in by
61/// setting `AgentOutput::tokens`; benchmarks that need economic metrics read it.
62///
63/// Self-reported by the runner; not verified by CAR. Do not use as a trust
64/// boundary — for benchmarking and observability only.
65#[derive(Debug, Clone, Default, Serialize, Deserialize)]
66pub struct TokenAccounting {
67    #[serde(default)]
68    pub input_tokens: u64,
69    #[serde(default)]
70    pub output_tokens: u64,
71    #[serde(default)]
72    pub cost_usd: f64,
73}
74
75impl TokenAccounting {
76    pub fn new(input_tokens: u64, output_tokens: u64, cost_usd: f64) -> Self {
77        debug_assert!(cost_usd.is_finite(), "cost_usd must be finite");
78        debug_assert!(cost_usd >= 0.0, "cost_usd must be non-negative");
79        Self { input_tokens, output_tokens, cost_usd }
80    }
81}
82
83/// Output from one agent's execution.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AgentOutput {
86    pub name: String,
87    pub answer: String,
88    pub turns: u32,
89    pub tool_calls: u32,
90    pub duration_ms: f64,
91    pub error: Option<String>,
92    /// Structured outcome (when available). Products can use this instead of
93    /// checking answer/error fields for completion semantics.
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub outcome: Option<car_ir::AgentOutcome>,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub tokens: Option<TokenAccounting>,
98}
99
100impl AgentOutput {
101    pub fn succeeded(&self) -> bool {
102        self.error.is_none() && !self.answer.is_empty()
103    }
104}
105
106/// A message between agents in a multi-agent system.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Message {
109    pub from: String,
110    pub to: String,
111    pub kind: MessageKind,
112    pub payload: Value,
113    pub timestamp: DateTime<Utc>,
114}
115
116impl Message {
117    pub fn new(from: &str, to: &str, kind: MessageKind, payload: Value) -> Self {
118        Self {
119            from: from.to_string(),
120            to: to.to_string(),
121            kind,
122            payload,
123            timestamp: Utc::now(),
124        }
125    }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129#[serde(rename_all = "snake_case")]
130pub enum MessageKind {
131    /// Task assignment from orchestrator.
132    TaskAssignment,
133    /// Agent's completed result.
134    Result,
135    /// Feedback from supervisor.
136    Feedback,
137    /// Delegation request.
138    DelegateRequest,
139    /// Delegation response.
140    DelegateResponse,
141    /// Custom inter-agent message.
142    Custom,
143}