Skip to main content

car_multi/
shared.rs

1//! Shared infrastructure — creates Runtimes that share state, log, and policies.
2
3use crate::budget::{BudgetError, BudgetLimits, CoordinationBudget};
4use crate::types::AgentOutput;
5use car_engine::Runtime;
6use car_eventlog::EventLog;
7use car_policy::PolicyEngine;
8use car_state::StateStore;
9use std::sync::Arc;
10use tokio::sync::{Mutex as TokioMutex, RwLock as TokioRwLock};
11
12/// Factory for creating Runtime instances with shared state, event log, and policies.
13///
14/// In a multi-agent system, all agents see the same state store and write to the
15/// same event log. Each agent gets its own tool set and executor.
16///
17/// A [`CoordinationBudget`] is always present (unbounded by default). Patterns
18/// gate each spawn through [`begin_agent`](Self::begin_agent) and report spend
19/// through [`record_output`](Self::record_output), so budget enforcement is a
20/// uniform, always-on code path that costs nothing when no limits are set.
21pub struct SharedInfra {
22    pub state: Arc<StateStore>,
23    pub log: Arc<TokioMutex<EventLog>>,
24    pub policies: Arc<TokioRwLock<PolicyEngine>>,
25    pub budget: Arc<CoordinationBudget>,
26}
27
28impl SharedInfra {
29    pub fn new() -> Self {
30        Self {
31            state: Arc::new(StateStore::new()),
32            log: Arc::new(TokioMutex::new(EventLog::new())),
33            policies: Arc::new(TokioRwLock::new(PolicyEngine::new())),
34            budget: Arc::new(CoordinationBudget::unbounded()),
35        }
36    }
37
38    /// Attach a coordination budget built from the given limits. Patterns run
39    /// against this infra will refuse to start agents once a limit is crossed.
40    pub fn with_budget(mut self, limits: BudgetLimits) -> Self {
41        self.budget = Arc::new(CoordinationBudget::new(limits));
42        self
43    }
44
45    /// Attach a pre-built (possibly shared) coordination budget.
46    pub fn with_shared_budget(mut self, budget: Arc<CoordinationBudget>) -> Self {
47        self.budget = budget;
48        self
49    }
50
51    /// Reserve a budget slot for one agent. `Ok(())` means the agent may run;
52    /// `Err` carries why it was denied. Patterns call this immediately before a
53    /// spawn and record a [`budget_skipped_output`](crate::budget::budget_skipped_output)
54    /// on denial.
55    pub fn begin_agent(&self) -> Result<(), BudgetError> {
56        self.budget.try_begin_agent()
57    }
58
59    /// Record an agent's reported token/cost spend against the budget.
60    pub fn record_output(&self, out: &AgentOutput) {
61        self.budget.record_output(out);
62    }
63
64    /// Create a Runtime that shares this infra's state, log, and policies.
65    ///
66    /// Each runtime gets its own tool set, executor, and idempotency cache.
67    pub fn make_runtime(&self) -> Runtime {
68        Runtime::with_shared(
69            Arc::clone(&self.state),
70            Arc::clone(&self.log),
71            Arc::clone(&self.policies),
72        )
73    }
74
75    /// Create a Runtime with per-agent isolated state overlay.
76    /// Writes go to a local StateStore; reads fall through to shared state.
77    /// Call `AgentContext::merge_to_parent()` after the agent completes.
78    pub fn make_isolated_runtime(
79        &self,
80        agent_name: &str,
81    ) -> (Runtime, crate::task_context::AgentContext) {
82        let ctx = crate::task_context::AgentContext::new(agent_name, Arc::clone(&self.state));
83        let rt = Runtime::with_shared(
84            Arc::clone(&ctx.local_state),
85            Arc::clone(&ctx.local_log),
86            Arc::clone(&self.policies),
87        );
88        (rt, ctx)
89    }
90}
91
92impl Default for SharedInfra {
93    fn default() -> Self {
94        Self::new()
95    }
96}