Skip to main content

oxios_kernel/capability/
template.rs

1//! Capability templates — preset CSpace configurations for common agent roles.
2//!
3//! Templates provide a declarative way to define an agent's initial
4//! capability set. They encode the "principle of least privilege" by
5//! starting from minimal access and layering on rights as the role
6//! demands.
7//!
8//! # Hierarchy
9//!
10//! ```text
11//! worker()     → Exec + Browser
12//!   standard() → worker + Memory(READ)
13//!   operator() → standard + Space + Agent + A2a + Program + MCP + Memory(WRITE)
14//!   supervisor() → operator + Security + Budget + Resource + Cron
15//! ```
16//!
17//! # Example
18//!
19//! ```
20//! use oxios_kernel::capability::template::CapabilityTemplate;
21//! use oxios_kernel::types::AgentId;
22//!
23//! let cspace = CapabilityTemplate::standard().build_for(AgentId::new_v4());
24//! assert!(cspace.len() > 0);
25//! ```
26
27use crate::space::SpaceId;
28use crate::types::AgentId;
29
30use super::types::{CSpace, Capability, CapabilityId, Issuer, ResourceRef, Rights};
31
32/// Builder for constructing preset capability spaces.
33///
34/// Use the associated constructors (`worker`, `standard`, `operator`,
35/// `supervisor`, `with_programs`) to start from a template, then call
36/// [`build`] or [`build_for`] to produce a [`CSpace`].
37#[derive(Debug, Clone)]
38pub struct CapabilityTemplate {
39    caps: Vec<(ResourceRef, Rights)>,
40}
41
42impl CapabilityTemplate {
43    // ── Preset constructors ─────────────────────────────────────────
44
45    /// **Worker** — minimal execution capability.
46    ///
47    /// Rights: shell exec + headless browser.
48    pub fn worker() -> Self {
49        let mut t = Self { caps: Vec::new() };
50        t.caps.push((
51            ResourceRef::Exec {
52                mode: "shell".into(),
53            },
54            Rights::EXECUTE | Rights::READ,
55        ));
56        t.caps
57            .push((ResourceRef::Browser, Rights::READ | Rights::EXECUTE));
58        t
59    }
60
61    /// **Standard** — worker + memory read access.
62    ///
63    /// Suitable for most agents that need to recall but not modify
64    /// persistent state.
65    pub fn standard() -> Self {
66        let mut t = Self::worker();
67        t.caps.push((
68            ResourceRef::KernelDomain {
69                domain: "memory".into(),
70            },
71            Rights::READ,
72        ));
73        t
74    }
75
76    /// **Operator** — standard + space, agent, A2A, persona, program,
77    /// MCP, and memory write.
78    ///
79    /// Intended for agents that coordinate work across multiple
80    /// subsystems (e.g., a project lead agent).
81    pub fn operator() -> Self {
82        let mut t = Self::standard();
83        let extra = vec![
84            (
85                ResourceRef::Space { id: SpaceId::nil() },
86                Rights::READ | Rights::WRITE,
87            ),
88            (
89                ResourceRef::Agent { id: AgentId::nil() },
90                Rights::READ | Rights::WRITE,
91            ),
92            (
93                ResourceRef::A2a,
94                Rights::READ | Rights::WRITE | Rights::EXECUTE,
95            ),
96            (
97                ResourceRef::KernelDomain {
98                    domain: "persona".into(),
99                },
100                Rights::READ | Rights::WRITE,
101            ),
102            (
103                ResourceRef::KernelDomain {
104                    domain: "program".into(),
105                },
106                Rights::READ | Rights::WRITE | Rights::EXECUTE,
107            ),
108            (
109                ResourceRef::Mcp { server: "*".into() },
110                Rights::READ | Rights::EXECUTE,
111            ),
112            // Upgrade memory to RW
113            (
114                ResourceRef::KernelDomain {
115                    domain: "memory".into(),
116                },
117                Rights::READ | Rights::WRITE,
118            ),
119        ];
120        t.caps.extend(extra);
121        t
122    }
123
124    /// **Supervisor** — operator + security, budget, resource, and cron
125    /// kernel domains.
126    ///
127    /// The most privileged built-in template. Use sparingly.
128    pub fn supervisor() -> Self {
129        let mut t = Self::operator();
130        let admin = vec![
131            (
132                ResourceRef::KernelDomain {
133                    domain: "security".into(),
134                },
135                Rights::ALL,
136            ),
137            (
138                ResourceRef::KernelDomain {
139                    domain: "budget".into(),
140                },
141                Rights::READ | Rights::WRITE,
142            ),
143            (
144                ResourceRef::KernelDomain {
145                    domain: "resource".into(),
146                },
147                Rights::READ | Rights::WRITE,
148            ),
149            (
150                ResourceRef::KernelDomain {
151                    domain: "cron".into(),
152                },
153                Rights::READ | Rights::WRITE | Rights::EXECUTE,
154            ),
155        ];
156        t.caps.extend(admin);
157        t
158    }
159
160    /// **With programs** — worker + specific named programs.
161    ///
162    /// Creates a worker-level agent with EXECUTE rights on the listed
163    /// programs only. This is the recommended template for agents that
164    /// should have access to a known set of tools.
165    pub fn with_programs(names: &[&str]) -> Self {
166        let mut t = Self::worker();
167        for name in names {
168            t.caps.push((
169                ResourceRef::Program {
170                    name: (*name).into(),
171                },
172                Rights::EXECUTE | Rights::READ,
173            ));
174        }
175        t
176    }
177
178    // ── Builder methods ─────────────────────────────────────────────
179
180    /// Add an additional capability to the template.
181    pub fn with(mut self, resource: ResourceRef, rights: Rights) -> Self {
182        self.caps.push((resource, rights));
183        self
184    }
185
186    /// Build a CSpace with kernel-issued capabilities for a fresh agent ID.
187    pub fn build(&self) -> CSpace {
188        self.build_for(AgentId::new_v4())
189    }
190
191    /// Build a CSpace with kernel-issued capabilities for a specific agent.
192    pub fn build_for(&self, agent_id: AgentId) -> CSpace {
193        let mut cspace = CSpace::new(agent_id);
194        for (resource, rights) in &self.caps {
195            let cap = Capability {
196                id: CapabilityId::new(),
197                resource: resource.clone(),
198                rights: *rights,
199                issuer: Issuer::Kernel,
200            };
201            cspace.insert(cap);
202        }
203        cspace
204    }
205
206    /// Returns the number of capabilities in this template.
207    pub fn len(&self) -> usize {
208        self.caps.len()
209    }
210
211    /// Returns true if the template has no capabilities.
212    pub fn is_empty(&self) -> bool {
213        self.caps.is_empty()
214    }
215}
216
217impl Default for CapabilityTemplate {
218    fn default() -> Self {
219        Self::worker()
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn worker_has_exec_and_browser() {
229        let cs = CapabilityTemplate::worker().build();
230        assert!(cs.can(
231            &ResourceRef::Exec {
232                mode: "shell".into()
233            },
234            Rights::EXECUTE
235        ));
236        assert!(cs.can(&ResourceRef::Browser, Rights::READ));
237        assert_eq!(cs.len(), 2);
238    }
239
240    #[test]
241    fn standard_adds_memory_read() {
242        let cs = CapabilityTemplate::standard().build();
243        assert!(cs.can(
244            &ResourceRef::KernelDomain {
245                domain: "memory".into()
246            },
247            Rights::READ
248        ));
249        assert!(!cs.can(
250            &ResourceRef::KernelDomain {
251                domain: "memory".into()
252            },
253            Rights::WRITE
254        ));
255    }
256
257    #[test]
258    fn operator_has_a2a_and_mcp() {
259        let cs = CapabilityTemplate::operator().build();
260        assert!(cs.can(&ResourceRef::A2a, Rights::EXECUTE));
261        assert!(cs.can(&ResourceRef::Mcp { server: "*".into() }, Rights::EXECUTE));
262    }
263
264    #[test]
265    fn supervisor_has_security_all() {
266        let cs = CapabilityTemplate::supervisor().build();
267        assert!(cs.can(
268            &ResourceRef::KernelDomain {
269                domain: "security".into()
270            },
271            Rights::ALL
272        ));
273    }
274
275    #[test]
276    fn with_programs_scoped() {
277        let cs = CapabilityTemplate::with_programs(&["git", "gh"]).build();
278        assert!(cs.can(
279            &ResourceRef::Program { name: "git".into() },
280            Rights::EXECUTE
281        ));
282        assert!(cs.can(&ResourceRef::Program { name: "gh".into() }, Rights::EXECUTE));
283        assert!(!cs.can(
284            &ResourceRef::Program {
285                name: "curl".into()
286            },
287            Rights::EXECUTE
288        ));
289    }
290
291    #[test]
292    fn builder_chaining() {
293        let cs = CapabilityTemplate::worker()
294            .with(
295                ResourceRef::KernelDomain {
296                    domain: "custom".into(),
297                },
298                Rights::READ,
299            )
300            .build();
301        assert!(cs.can(
302            &ResourceRef::KernelDomain {
303                domain: "custom".into()
304            },
305            Rights::READ
306        ));
307    }
308
309    #[test]
310    fn build_for_specific_agent() {
311        let id = AgentId::new_v4();
312        let cs = CapabilityTemplate::worker().build_for(id);
313        assert_eq!(cs.agent_id, id);
314    }
315}