Skip to main content

pe_core/
agent.rs

1//! Agent — bounded execution context.
2//!
3//! Agent = Identity + Boundaries. As thin as an ID + prompt (minimum viable)
4//! or as rich as a fully governed execution context (production grade).
5//! Based on Decision 19 (REVISED) and Groups 30-31 of the pre-plan.
6
7use crate::boundaries::*;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Unique agent identifier
12pub type AgentId = String;
13
14/// The canonical agent type — bounded execution context.
15///
16/// Identity is the floor, not the ceiling. All boundary fields have sensible
17/// defaults (fully open). An agent with only id + system_prompt works like
18/// a thin identity agent. Boundaries are opt-in, not opt-out.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Agent {
21    // === Identity (the floor) ===
22    pub id: AgentId,
23    pub system_prompt: SystemPrompt,
24    pub model_preference: ModelPreference,
25    pub declared_tools: Vec<String>,
26    pub routing_examples: Vec<String>,
27    pub capabilities: AgentCapabilities,
28    #[serde(default)]
29    pub tool_access: AgentToolAccess,
30    #[serde(default)]
31    pub tool_class_policy: AgentToolClassPolicy,
32
33    // === Boundaries (what makes it production-grade) ===
34    pub permissions: PermissionSet,
35    pub guardrails: Vec<Guardrail>,
36    pub context_budget: ContextBudget,
37    pub tool_policy: ToolPolicy,
38    pub communication_rules: CommunicationRules,
39    pub write_governance: WriteGovernance,
40
41    // === Cognitive Architecture (optional inner subgraph) ===
42    /// When present, the agent's reasoning is decomposed into parallel
43    /// cognitive streams (emotional, logical, antithink, etc.) that run
44    /// as an inner subgraph before the main LLM call.
45    /// When `None`, normal single-call execution. Zero overhead.
46    #[serde(default)]
47    pub cognitive_architecture: Option<crate::cognitive::CognitiveArchitecture>,
48
49    // === Extension ===
50    #[serde(default)]
51    pub metadata: HashMap<String, serde_json::Value>,
52}
53
54// ── Identity types ────────────────────────────────────────────────────
55
56/// System prompt — either a static string or a template.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(untagged)]
59pub enum SystemPrompt {
60    Static(String),
61    Template {
62        template: String,
63        variables: HashMap<String, String>,
64    },
65}
66
67/// Model preference with fallback chain.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ModelPreference {
70    pub primary: String,
71    #[serde(default)]
72    pub fallbacks: Vec<String>,
73    #[serde(default)]
74    pub params: HashMap<String, serde_json::Value>,
75}
76
77/// What this agent declares it can do.
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79pub struct AgentCapabilities {
80    #[serde(default)]
81    pub can_read_files: bool,
82    #[serde(default)]
83    pub can_write_files: bool,
84    #[serde(default)]
85    pub can_execute_code: bool,
86    #[serde(default)]
87    pub can_search_web: bool,
88    #[serde(default)]
89    pub custom: HashMap<String, bool>,
90}
91
92/// First-class tool classification owned by the agent boundary surface.
93///
94/// These classes describe how tools should be reasoned about and governed,
95/// not whether runtime policy will ultimately allow them. Resolution may make
96/// them available to the runtime, but review-safe, approval, budgets, write
97/// governance, and communication rules still apply afterwards.
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct AgentToolAccess {
100    #[serde(default)]
101    pub collective_context_tools: Vec<String>,
102    #[serde(default)]
103    pub scoped_context_tools: Vec<String>,
104    #[serde(default)]
105    pub maintenance_tools: Vec<String>,
106    #[serde(default)]
107    pub scoped_memory_write_tools: Vec<String>,
108    #[serde(default)]
109    pub collective_memory_write_tools: Vec<String>,
110    #[serde(default)]
111    pub action_tools: Vec<String>,
112    #[serde(default)]
113    pub coordination_query_tools: Vec<String>,
114    #[serde(default)]
115    pub coordination_transfer_tools: Vec<String>,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
119pub enum AgentToolClass {
120    CollectiveContext,
121    ScopedContext,
122    Maintenance,
123    ScopedMemoryWrite,
124    CollectiveMemoryWrite,
125    Action,
126    CoordinationQuery,
127    CoordinationTransfer,
128}
129
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131pub struct AgentToolClassPolicy {
132    pub collective_context: ToolClassConstraint,
133    pub scoped_context: ToolClassConstraint,
134    pub maintenance: ToolClassConstraint,
135    pub scoped_memory_write: ToolClassConstraint,
136    pub collective_memory_write: ToolClassConstraint,
137    pub action: ToolClassConstraint,
138    pub coordination_query: ToolClassConstraint,
139    pub coordination_transfer: ToolClassConstraint,
140}
141
142#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143pub enum ToolClassConstraint {
144    #[default]
145    Inherit,
146    Allowed,
147    RequiresApproval,
148    Denied,
149}
150
151// ── Agent builder ─────────────────────────────────────────────────────
152
153impl Agent {
154    /// Create a minimal agent — just identity, all boundaries open.
155    pub fn new(id: impl Into<String>, system_prompt: impl Into<String>) -> Self {
156        Self {
157            id: id.into(),
158            system_prompt: SystemPrompt::Static(system_prompt.into()),
159            model_preference: ModelPreference {
160                primary: "default".into(),
161                fallbacks: vec![],
162                params: HashMap::new(),
163            },
164            declared_tools: vec![],
165            routing_examples: vec![],
166            capabilities: AgentCapabilities::default(),
167            tool_access: AgentToolAccess::default(),
168            tool_class_policy: AgentToolClassPolicy::default(),
169            permissions: PermissionSet::default(),
170            guardrails: vec![],
171            context_budget: ContextBudget::default(),
172            tool_policy: ToolPolicy::default(),
173            communication_rules: CommunicationRules::default(),
174            write_governance: WriteGovernance::default(),
175            cognitive_architecture: None,
176            metadata: HashMap::new(),
177        }
178    }
179
180    /// Enable cognitive architecture on this agent.
181    ///
182    /// When set, the agent's inner world runs before each LLM call,
183    /// enriching context through parallel lobe processing and synthesis.
184    /// When `None` (default), zero overhead — standard agent behavior.
185    #[must_use]
186    pub fn with_cognitive_architecture(
187        mut self,
188        arch: crate::cognitive::CognitiveArchitecture,
189    ) -> Self {
190        self.cognitive_architecture = Some(arch);
191        self
192    }
193
194    #[must_use]
195    pub fn with_tool_access(mut self, tool_access: AgentToolAccess) -> Self {
196        self.tool_access = tool_access;
197        self
198    }
199
200    #[must_use]
201    pub fn with_tool_class_policy(mut self, tool_class_policy: AgentToolClassPolicy) -> Self {
202        self.tool_class_policy = tool_class_policy;
203        self
204    }
205}
206
207impl AgentToolAccess {
208    #[must_use]
209    pub fn tool_names(&self) -> Vec<String> {
210        let mut names = std::collections::BTreeSet::new();
211        names.extend(self.collective_context_tools.iter().cloned());
212        names.extend(self.scoped_context_tools.iter().cloned());
213        names.extend(self.maintenance_tools.iter().cloned());
214        names.extend(self.scoped_memory_write_tools.iter().cloned());
215        names.extend(self.collective_memory_write_tools.iter().cloned());
216        names.extend(self.action_tools.iter().cloned());
217        names.extend(self.coordination_query_tools.iter().cloned());
218        names.extend(self.coordination_transfer_tools.iter().cloned());
219        names.into_iter().collect()
220    }
221
222    #[must_use]
223    pub fn with_collective_context_tools(mut self, tools: Vec<String>) -> Self {
224        self.collective_context_tools = tools;
225        self
226    }
227
228    #[must_use]
229    pub fn with_scoped_context_tools(mut self, tools: Vec<String>) -> Self {
230        self.scoped_context_tools = tools;
231        self
232    }
233
234    #[must_use]
235    pub fn with_maintenance_tools(mut self, tools: Vec<String>) -> Self {
236        self.maintenance_tools = tools;
237        self
238    }
239
240    #[must_use]
241    pub fn with_scoped_memory_write_tools(mut self, tools: Vec<String>) -> Self {
242        self.scoped_memory_write_tools = tools;
243        self
244    }
245
246    #[must_use]
247    pub fn with_collective_memory_write_tools(mut self, tools: Vec<String>) -> Self {
248        self.collective_memory_write_tools = tools;
249        self
250    }
251
252    #[must_use]
253    pub fn with_action_tools(mut self, tools: Vec<String>) -> Self {
254        self.action_tools = tools;
255        self
256    }
257
258    #[must_use]
259    pub fn with_coordination_query_tools(mut self, tools: Vec<String>) -> Self {
260        self.coordination_query_tools = tools;
261        self
262    }
263
264    #[must_use]
265    pub fn with_coordination_transfer_tools(mut self, tools: Vec<String>) -> Self {
266        self.coordination_transfer_tools = tools;
267        self
268    }
269
270    #[must_use]
271    pub fn classify_tool(&self, tool_name: &str) -> Option<AgentToolClass> {
272        if self
273            .collective_context_tools
274            .iter()
275            .any(|name| name == tool_name)
276        {
277            Some(AgentToolClass::CollectiveContext)
278        } else if self
279            .scoped_context_tools
280            .iter()
281            .any(|name| name == tool_name)
282        {
283            Some(AgentToolClass::ScopedContext)
284        } else if self.maintenance_tools.iter().any(|name| name == tool_name) {
285            Some(AgentToolClass::Maintenance)
286        } else if self
287            .scoped_memory_write_tools
288            .iter()
289            .any(|name| name == tool_name)
290        {
291            Some(AgentToolClass::ScopedMemoryWrite)
292        } else if self
293            .collective_memory_write_tools
294            .iter()
295            .any(|name| name == tool_name)
296        {
297            Some(AgentToolClass::CollectiveMemoryWrite)
298        } else if self.action_tools.iter().any(|name| name == tool_name) {
299            Some(AgentToolClass::Action)
300        } else if self
301            .coordination_query_tools
302            .iter()
303            .any(|name| name == tool_name)
304        {
305            Some(AgentToolClass::CoordinationQuery)
306        } else if self
307            .coordination_transfer_tools
308            .iter()
309            .any(|name| name == tool_name)
310        {
311            Some(AgentToolClass::CoordinationTransfer)
312        } else {
313            None
314        }
315    }
316}
317
318impl AgentToolClassPolicy {
319    #[must_use]
320    pub fn constraint_for(&self, class: AgentToolClass) -> &ToolClassConstraint {
321        match class {
322            AgentToolClass::CollectiveContext => &self.collective_context,
323            AgentToolClass::ScopedContext => &self.scoped_context,
324            AgentToolClass::Maintenance => &self.maintenance,
325            AgentToolClass::ScopedMemoryWrite => &self.scoped_memory_write,
326            AgentToolClass::CollectiveMemoryWrite => &self.collective_memory_write,
327            AgentToolClass::Action => &self.action,
328            AgentToolClass::CoordinationQuery => &self.coordination_query,
329            AgentToolClass::CoordinationTransfer => &self.coordination_transfer,
330        }
331    }
332
333    #[must_use]
334    pub fn with_collective_context(mut self, constraint: ToolClassConstraint) -> Self {
335        self.collective_context = constraint;
336        self
337    }
338
339    #[must_use]
340    pub fn with_scoped_context(mut self, constraint: ToolClassConstraint) -> Self {
341        self.scoped_context = constraint;
342        self
343    }
344
345    #[must_use]
346    pub fn with_maintenance(mut self, constraint: ToolClassConstraint) -> Self {
347        self.maintenance = constraint;
348        self
349    }
350
351    #[must_use]
352    pub fn with_scoped_memory_write(mut self, constraint: ToolClassConstraint) -> Self {
353        self.scoped_memory_write = constraint;
354        self
355    }
356
357    #[must_use]
358    pub fn with_collective_memory_write(mut self, constraint: ToolClassConstraint) -> Self {
359        self.collective_memory_write = constraint;
360        self
361    }
362
363    #[must_use]
364    pub fn with_action(mut self, constraint: ToolClassConstraint) -> Self {
365        self.action = constraint;
366        self
367    }
368
369    #[must_use]
370    pub fn with_coordination_query(mut self, constraint: ToolClassConstraint) -> Self {
371        self.coordination_query = constraint;
372        self
373    }
374
375    #[must_use]
376    pub fn with_coordination_transfer(mut self, constraint: ToolClassConstraint) -> Self {
377        self.coordination_transfer = constraint;
378        self
379    }
380}
381
382impl AgentToolClass {
383    #[must_use]
384    pub fn stable_name(self) -> &'static str {
385        match self {
386            AgentToolClass::CollectiveContext => "collective_context",
387            AgentToolClass::ScopedContext => "scoped_context",
388            AgentToolClass::Maintenance => "maintenance",
389            AgentToolClass::ScopedMemoryWrite => "scoped_memory_write",
390            AgentToolClass::CollectiveMemoryWrite => "collective_memory_write",
391            AgentToolClass::Action => "action",
392            AgentToolClass::CoordinationQuery => "coordination_query",
393            AgentToolClass::CoordinationTransfer => "coordination_transfer",
394        }
395    }
396}