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 {
80            input_tokens,
81            output_tokens,
82            cost_usd,
83        }
84    }
85}
86
87/// Output from one agent's execution.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct AgentOutput {
90    pub name: String,
91    pub answer: String,
92    pub turns: u32,
93    pub tool_calls: u32,
94    pub duration_ms: f64,
95    pub error: Option<String>,
96    /// Structured outcome (when available). Products can use this instead of
97    /// checking answer/error fields for completion semantics.
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub outcome: Option<car_ir::AgentOutcome>,
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub tokens: Option<TokenAccounting>,
102}
103
104impl AgentOutput {
105    pub fn succeeded(&self) -> bool {
106        self.error.is_none() && !self.answer.is_empty()
107    }
108}
109
110/// A message between agents in a multi-agent system.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct Message {
113    pub from: String,
114    pub to: String,
115    pub kind: MessageKind,
116    pub payload: Value,
117    pub timestamp: DateTime<Utc>,
118}
119
120impl Message {
121    pub fn new(from: &str, to: &str, kind: MessageKind, payload: Value) -> Self {
122        Self {
123            from: from.to_string(),
124            to: to.to_string(),
125            kind,
126            payload,
127            timestamp: Utc::now(),
128        }
129    }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
133#[serde(rename_all = "snake_case")]
134pub enum MessageKind {
135    /// Task assignment from orchestrator.
136    TaskAssignment,
137    /// Agent's completed result.
138    Result,
139    /// Feedback from supervisor.
140    Feedback,
141    /// Delegation request.
142    DelegateRequest,
143    /// Delegation response.
144    DelegateResponse,
145    /// Custom inter-agent message.
146    Custom,
147}