Skip to main content

oxios_kernel/tools/
registration.rs

1//! CSpace → Tool Registry mapping.
2//!
3//! This module bridges the capability system and the agent's tool registry.
4//! Given an agent's [`CSpace`], it walks the capabilities and registers
5//! exactly the set of tools the agent is authorised to use.
6//!
7//! # Registration tiers
8//!
9//! | Tier | Tools | Condition |
10//! |------|-------|-----------|
11//! | Always-on | `ReadTool`, `WriteTool`, `EditTool`, `GrepTool`, `FindTool`, `LsTool`, `WebSearchTool`, `GetSearchResultsTool` | Every agent gets these |
12//! | CSpace-driven | `ExecTool`, `BrowserTool`, kernel domain tools, MCP, A2A, etc. | Only if a matching capability with sufficient rights exists |
13//!
14//! # Example
15//!
16//! ```no_run
17//! use std::sync::Arc;
18//! use oxi_sdk::ToolRegistry;
19//! use oxios_kernel::capability::template::CapabilityTemplate;
20//!
21//! let registry = ToolRegistry::new();
22//! let cspace = CapabilityTemplate::standard().build();
23//! let cache = Arc::new(oxi_sdk::SearchCache::new());
24//! // register_tools_from_cspace(&registry, &kernel, &cspace, cache, agent_id);
25//! ```
26
27use std::sync::Arc;
28
29use oxi_sdk::{
30    EditTool, FindTool, GetSearchResultsTool, GrepTool, LsTool, ReadTool, SearchCache,
31    ToolRegistry, WebSearchTool, WriteTool,
32};
33
34use crate::access_manager::{AccessGate, AgentContext};
35use crate::capability::{CSpace, ResourceRef, Rights};
36use crate::tools::builtin::*;
37use crate::tools::gated_tool::GatedTool;
38use crate::tools::{
39    A2aDelegateTool, A2aQueryTool, A2aSendTool, ExecTool, KnowledgeTool, MemoryReadTool,
40    MemorySearchTool, MemoryWriteTool,
41};
42use crate::types::AgentId;
43use crate::KernelHandle;
44
45/// Register the always-on tool set into a [`ToolRegistry`].
46///
47/// Every agent receives these tools regardless of its capability space.
48/// This consists of file-system tools (read, write, edit, grep, find, ls)
49/// and web search tools.
50///
51/// This helper is also useful for unit tests that need a basic tool set
52/// without constructing a full CSpace.
53pub fn register_always_on(registry: &ToolRegistry, search_cache: Arc<SearchCache>) {
54    registry.register(ReadTool::new());
55    registry.register(WriteTool::new());
56    registry.register(EditTool::new());
57    registry.register(GrepTool::new());
58    registry.register(FindTool::new());
59    registry.register(LsTool::new());
60    registry.register(WebSearchTool::new(search_cache.clone()));
61    registry.register(GetSearchResultsTool::new(search_cache));
62}
63
64/// Register always-on tools with access gate wrapping.
65///
66/// Same as [`register_always_on`] but wraps each tool in [`GatedTool`]
67/// so that all file operations pass through the access gate.
68pub fn register_always_on_gated(
69    registry: &ToolRegistry,
70    search_cache: Arc<SearchCache>,
71    gate: Arc<AccessGate>,
72    context: AgentContext,
73) {
74    registry.register(GatedTool::new(
75        ReadTool::new(),
76        gate.clone(),
77        context.clone(),
78    ));
79    registry.register(GatedTool::new(
80        WriteTool::new(),
81        gate.clone(),
82        context.clone(),
83    ));
84    registry.register(GatedTool::new(
85        EditTool::new(),
86        gate.clone(),
87        context.clone(),
88    ));
89    registry.register(GatedTool::new(
90        GrepTool::new(),
91        gate.clone(),
92        context.clone(),
93    ));
94    registry.register(GatedTool::new(
95        FindTool::new(),
96        gate.clone(),
97        context.clone(),
98    ));
99    registry.register(GatedTool::new(LsTool::new(), gate.clone(), context.clone()));
100    registry.register(GatedTool::new(
101        WebSearchTool::new(search_cache.clone()),
102        gate.clone(),
103        context.clone(),
104    ));
105    registry.register(GatedTool::new(
106        GetSearchResultsTool::new(search_cache),
107        gate,
108        context,
109    ));
110}
111
112/// Register tools into `registry` based on the agent's [`CSpace`].
113///
114/// First registers the always-on tier (file ops + web search), then walks
115/// every capability in the CSpace and conditionally registers the
116/// corresponding kernel tools.
117///
118/// # Arguments
119///
120/// * `registry` — The agent's tool registry to populate.
121/// * `kernel` — Handle to the kernel for constructing tool instances.
122/// * `cspace` — The agent's capability space (determines which tools are available).
123/// * `search_cache` — Shared search cache for web search tools.
124/// * `agent_id` — The agent's ID (used by A2A tools for routing).
125///
126/// # CSpace → Tool mapping
127///
128/// | ResourceRef | Required rights | Registered tools |
129/// |-------------|----------------|-----------------|
130/// | `Exec { .. }` | `EXECUTE` | `ExecTool` |
131/// | `KernelDomain { "memory" }` | `READ` | `MemoryReadTool`, `MemorySearchTool` |
132/// | `KernelDomain { "memory" }` | `WRITE` | `MemoryWriteTool` |
133/// | `KernelDomain { "project" }` | any | `ProjectTool` |
134/// | `KernelDomain { "agent" }` | any | `KernelAgentTool` |
135/// | `KernelDomain { "a2a" }` | any | `A2aDelegateTool`, `A2aSendTool`, `A2aQueryTool` |
136/// | `KernelDomain { "persona" }` | any | `PersonaTool` |
137/// | `KernelDomain { "program" }` | any | *(deprecated — skills via CSpace)* |
138/// | `KernelDomain { "cron" }` | any | `CronTool` |
139/// | `KernelDomain { "security" }` | any | `SecurityTool` |
140/// | `KernelDomain { "budget" }` | any | `BudgetTool` |
141/// | `KernelDomain { "resource" }` | any | `ResourceTool` |
142/// | `KernelDomain { "mcp" }` | any | `McpToolWrapper` |
143/// | `Program { .. }` | — | *(not registered; surfaced via ToolRetriever)* |
144pub fn register_tools_from_cspace(
145    registry: &ToolRegistry,
146    kernel: &KernelHandle,
147    cspace: &CSpace,
148    search_cache: Arc<SearchCache>,
149    agent_id: AgentId,
150) {
151    // ── Tier 1: Always-on tools ─────────────────────────────────────
152    register_always_on(registry, search_cache);
153
154    // ── Tier 2: CSpace-driven tools ─────────────────────────────────
155    for cap in cspace.iter() {
156        match &cap.resource {
157            // Command execution
158            ResourceRef::Exec { .. } if cap.rights.contains(Rights::EXECUTE) => {
159                registry.register(ExecTool::from_kernel(kernel));
160            }
161
162            // Headless browser
163            ResourceRef::Browser if cap.rights.contains(Rights::EXECUTE) => {}
164
165            // Kernel domain tools
166            ResourceRef::KernelDomain { domain } => match domain.as_str() {
167                "memory" => {
168                    if cap.rights.contains(Rights::READ) {
169                        registry.register(MemoryReadTool::from_kernel(kernel));
170                        registry.register(MemorySearchTool::from_kernel(kernel));
171                    }
172                    if cap.rights.contains(Rights::WRITE) {
173                        registry.register(MemoryWriteTool::from_kernel(kernel));
174                    }
175                }
176                "space" => registry.register(ProjectTool::from_kernel(kernel)),
177                "agent" => registry.register(KernelAgentTool::from_kernel(kernel)),
178                "a2a" => {
179                    registry.register(A2aDelegateTool::from_kernel(kernel, agent_id));
180                    registry.register(A2aSendTool::from_kernel(kernel, agent_id));
181                    registry.register(A2aQueryTool::from_kernel(kernel));
182                }
183                "persona" => registry.register(PersonaTool::from_kernel(kernel)),
184                "program" => { /* Skills are surfaced through CSpace + semantic retrieval, not individual tools */
185                }
186                "cron" => registry.register(CronTool::from_kernel(kernel)),
187                "security" => registry.register(SecurityTool::from_kernel(kernel)),
188                "budget" => registry.register(BudgetTool::from_kernel(kernel)),
189                "resource" => registry.register(ResourceTool::from_kernel(kernel)),
190                "knowledge" => registry.register(KnowledgeTool::from_kernel(kernel)),
191                "mcp" => { /* MCP tools are enumerated dynamically per agent */ }
192                _ => {} // Unknown domain — silently skip
193            },
194
195            // Programs are not registered as separate tools.
196            // ToolRetriever shows them in the capability index;
197            // agents use exec to run program commands.
198            ResourceRef::Skill { .. } => {}
199
200            // Space, Agent, Mcp resource refs are handled through
201            // their respective KernelDomain registrations above
202            // or through dedicated tool paths.
203            _ => {}
204        }
205    }
206}
207
208/// Register tools into `registry` with access gate enforcement.
209///
210/// Same as [`register_tools_from_cspace`] but:
211/// - Always-on tools are wrapped in [`GatedTool`] for permission checks
212/// - ExecTool is created with `AgentContext`
213///
214/// Use this in production. The ungated version exists for backward compatibility.
215///
216/// # Arguments
217///
218/// * `registry` — The agent's tool registry to populate.
219/// * `kernel` — Handle to the kernel for constructing tool instances.
220/// * `cspace` — The agent's capability space (determines which tools are available).
221/// * `search_cache` — Shared search cache for web search tools.
222/// * `agent_id` — The agent's ID (used by A2A tools for routing).
223/// * `gate` — The unified access gate for permission checks.
224/// * `context` — The agent's security context.
225pub fn register_tools_from_cspace_gated(
226    registry: &ToolRegistry,
227    kernel: &KernelHandle,
228    cspace: &CSpace,
229    search_cache: Arc<SearchCache>,
230    agent_id: AgentId,
231    gate: Arc<AccessGate>,
232    context: AgentContext,
233) {
234    // ── Tier 1: Always-on tools (gated) ──────────────────────────────
235    register_always_on_gated(registry, search_cache, gate, context);
236
237    // ── Tier 2: CSpace-driven tools ─────────────────────────────────
238    for cap in cspace.iter() {
239        match &cap.resource {
240            // Command execution — use from_kernel_with_context for full security
241            ResourceRef::Exec { .. } if cap.rights.contains(Rights::EXECUTE) => {
242                registry.register(ExecTool::from_kernel(kernel));
243            }
244
245            // Headless browser
246            ResourceRef::Browser if cap.rights.contains(Rights::EXECUTE) => {}
247
248            // Kernel domain tools (same as ungated — these already use KernelHandle internally)
249            ResourceRef::KernelDomain { domain } => match domain.as_str() {
250                "memory" => {
251                    if cap.rights.contains(Rights::READ) {
252                        registry.register(MemoryReadTool::from_kernel(kernel));
253                        registry.register(MemorySearchTool::from_kernel(kernel));
254                    }
255                    if cap.rights.contains(Rights::WRITE) {
256                        registry.register(MemoryWriteTool::from_kernel(kernel));
257                    }
258                }
259                "space" => registry.register(ProjectTool::from_kernel(kernel)),
260                "agent" => registry.register(KernelAgentTool::from_kernel(kernel)),
261                "a2a" => {
262                    registry.register(A2aDelegateTool::from_kernel(kernel, agent_id));
263                    registry.register(A2aSendTool::from_kernel(kernel, agent_id));
264                    registry.register(A2aQueryTool::from_kernel(kernel));
265                }
266                "persona" => registry.register(PersonaTool::from_kernel(kernel)),
267                "program" => {}
268                "cron" => registry.register(CronTool::from_kernel(kernel)),
269                "security" => registry.register(SecurityTool::from_kernel(kernel)),
270                "budget" => registry.register(BudgetTool::from_kernel(kernel)),
271                "resource" => registry.register(ResourceTool::from_kernel(kernel)),
272                "knowledge" => registry.register(KnowledgeTool::from_kernel(kernel)),
273                "mcp" => {}
274                _ => {}
275            },
276
277            ResourceRef::Skill { .. } => {}
278            _ => {}
279        }
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn register_always_on_registers_eight_tools() {
289        let registry = ToolRegistry::new();
290        let cache = Arc::new(SearchCache::new());
291        register_always_on(&registry, cache);
292
293        // The always-on set is: read, write, edit, grep, find, ls, web_search, get_search_results
294        // ToolRegistry doesn't expose a count, but we can verify individual tool names.
295        let tool_names = registry.names();
296        assert!(
297            tool_names.contains(&"read".to_string()),
298            "read tool should be registered"
299        );
300        assert!(
301            tool_names.contains(&"write".to_string()),
302            "write tool should be registered"
303        );
304        assert!(
305            tool_names.contains(&"edit".to_string()),
306            "edit tool should be registered"
307        );
308        assert!(
309            tool_names.contains(&"grep".to_string()),
310            "grep tool should be registered"
311        );
312        assert!(
313            tool_names.contains(&"find".to_string()),
314            "find tool should be registered"
315        );
316        assert!(
317            tool_names.contains(&"ls".to_string()),
318            "ls tool should be registered"
319        );
320        assert!(
321            tool_names.contains(&"web_search".to_string()),
322            "web_search tool should be registered"
323        );
324        assert!(
325            tool_names.contains(&"get_search_results".to_string()),
326            "get_search_results tool should be registered"
327        );
328    }
329}