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//! ```ignore
17//! use std::sync::Arc;
18//! use oxi_sdk::{ToolRegistry, SearchCache};
19//! use oxios_kernel::capability::template::CapabilityTemplate;
20//! use oxios_kernel::tools::registration::register_tools_from_cspace;
21//!
22//! let registry = ToolRegistry::new();
23//! let cspace = CapabilityTemplate::standard().build();
24//! let cache = Arc::new(SearchCache::new());
25//! register_tools_from_cspace(&registry, &kernel, &cspace, cache, agent_id);
26//! ```
27
28use std::sync::Arc;
29
30use oxi_sdk::{
31    EditTool, FindTool, GetSearchResultsTool, GrepTool, LsTool, ReadTool, SearchCache,
32    WebSearchTool, WriteTool, ToolRegistry,
33};
34
35use crate::capability::{CSpace, ResourceRef, Rights};
36use crate::tools::kernel::*;
37use crate::tools::{
38    A2aDelegateTool, A2aQueryTool, A2aSendTool, ExecTool, MemoryReadTool, MemorySearchTool,
39    MemoryWriteTool,
40};
41use crate::types::AgentId;
42use crate::KernelHandle;
43
44#[cfg(feature = "browser")]
45use crate::tools::BrowserTool;
46
47/// Register the always-on tool set into a [`ToolRegistry`].
48///
49/// Every agent receives these tools regardless of its capability space.
50/// This consists of file-system tools (read, write, edit, grep, find, ls)
51/// and web search tools.
52///
53/// This helper is also useful for unit tests that need a basic tool set
54/// without constructing a full CSpace.
55pub fn register_always_on(registry: &ToolRegistry, search_cache: Arc<SearchCache>) {
56    registry.register(ReadTool::new());
57    registry.register(WriteTool::new());
58    registry.register(EditTool::new());
59    registry.register(GrepTool::new());
60    registry.register(FindTool::new());
61    registry.register(LsTool::new());
62    registry.register(WebSearchTool::new(search_cache.clone()));
63    registry.register(GetSearchResultsTool::new(search_cache));
64}
65
66/// Register tools into `registry` based on the agent's [`CSpace`].
67///
68/// First registers the always-on tier (file ops + web search), then walks
69/// every capability in the CSpace and conditionally registers the
70/// corresponding kernel tools.
71///
72/// # Arguments
73///
74/// * `registry` — The agent's tool registry to populate.
75/// * `kernel` — Handle to the kernel for constructing tool instances.
76/// * `cspace` — The agent's capability space (determines which tools are available).
77/// * `search_cache` — Shared search cache for web search tools.
78/// * `agent_id` — The agent's ID (used by A2A tools for routing).
79///
80/// # CSpace → Tool mapping
81///
82/// | ResourceRef | Required rights | Registered tools |
83/// |-------------|----------------|-----------------|
84/// | `Exec { .. }` | `EXECUTE` | `ExecTool` |
85/// | `Browser` | `EXECUTE` | `BrowserTool` *(browser feature)* |
86/// | `KernelDomain { "memory" }` | `READ` | `MemoryReadTool`, `MemorySearchTool` |
87/// | `KernelDomain { "memory" }` | `WRITE` | `MemoryWriteTool` |
88/// | `KernelDomain { "space" }` | any | `SpaceTool` |
89/// | `KernelDomain { "agent" }` | any | `KernelAgentTool` |
90/// | `KernelDomain { "a2a" }` | any | `A2aDelegateTool`, `A2aSendTool`, `A2aQueryTool` |
91/// | `KernelDomain { "persona" }` | any | `PersonaTool` |
92/// | `KernelDomain { "program" }` | any | `ProgramTool` |
93/// | `KernelDomain { "cron" }` | any | `CronTool` |
94/// | `KernelDomain { "security" }` | any | `SecurityTool` |
95/// | `KernelDomain { "budget" }` | any | `BudgetTool` |
96/// | `KernelDomain { "resource" }` | any | `ResourceTool` |
97/// | `KernelDomain { "mcp" }` | any | `McpToolWrapper` |
98/// | `Program { .. }` | — | *(not registered; surfaced via ToolRetriever)* |
99pub fn register_tools_from_cspace(
100    registry: &ToolRegistry,
101    kernel: &KernelHandle,
102    cspace: &CSpace,
103    search_cache: Arc<SearchCache>,
104    agent_id: AgentId,
105) {
106    // ── Tier 1: Always-on tools ─────────────────────────────────────
107    register_always_on(registry, search_cache);
108
109    // ── Tier 2: CSpace-driven tools ─────────────────────────────────
110    for cap in cspace.iter() {
111        match &cap.resource {
112            // Command execution
113            ResourceRef::Exec { .. } if cap.rights.contains(Rights::EXECUTE) => {
114                registry.register(ExecTool::from_kernel(kernel));
115            }
116
117            // Headless browser
118            ResourceRef::Browser if cap.rights.contains(Rights::EXECUTE) => {
119                #[cfg(feature = "browser")]
120                {
121                    registry.register(BrowserTool::from_kernel(kernel));
122                }
123            }
124
125            // Kernel domain tools
126            ResourceRef::KernelDomain { domain } => match domain.as_str() {
127                "memory" => {
128                    if cap.rights.contains(Rights::READ) {
129                        registry.register(MemoryReadTool::from_kernel(kernel));
130                        registry.register(MemorySearchTool::from_kernel(kernel));
131                    }
132                    if cap.rights.contains(Rights::WRITE) {
133                        registry.register(MemoryWriteTool::from_kernel(kernel));
134                    }
135                }
136                "space" => registry.register(SpaceTool::from_kernel(kernel)),
137                "agent" => registry.register(KernelAgentTool::from_kernel(kernel)),
138                "a2a" => {
139                    registry.register(A2aDelegateTool::from_kernel(kernel, agent_id));
140                    registry.register(A2aSendTool::from_kernel(kernel, agent_id));
141                    registry.register(A2aQueryTool::from_kernel(kernel));
142                }
143                "persona" => registry.register(PersonaTool::from_kernel(kernel)),
144                "program" => { /* ProgramTools are registered individually by agent_runtime, not via CSpace */
145                }
146                "cron" => registry.register(CronTool::from_kernel(kernel)),
147                "security" => registry.register(SecurityTool::from_kernel(kernel)),
148                "budget" => registry.register(BudgetTool::from_kernel(kernel)),
149                "resource" => registry.register(ResourceTool::from_kernel(kernel)),
150                "mcp" => { /* MCP tools are enumerated dynamically per agent */ }
151                _ => {} // Unknown domain — silently skip
152            },
153
154            // Programs are not registered as separate tools.
155            // ToolRetriever shows them in the capability index;
156            // agents use exec to run program commands.
157            ResourceRef::Program { .. } => {}
158
159            // Space, Agent, Mcp resource refs are handled through
160            // their respective KernelDomain registrations above
161            // or through dedicated tool paths.
162            _ => {}
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn register_always_on_registers_eight_tools() {
173        let registry = ToolRegistry::new();
174        let cache = Arc::new(SearchCache::new());
175        register_always_on(&registry, cache);
176
177        // The always-on set is: read, write, edit, grep, find, ls, web_search, get_search_results
178        // ToolRegistry doesn't expose a count, but we can verify individual tool names.
179        let tool_names = registry.names();
180        assert!(
181            tool_names.contains(&"read".to_string()),
182            "read tool should be registered"
183        );
184        assert!(
185            tool_names.contains(&"write".to_string()),
186            "write tool should be registered"
187        );
188        assert!(
189            tool_names.contains(&"edit".to_string()),
190            "edit tool should be registered"
191        );
192        assert!(
193            tool_names.contains(&"grep".to_string()),
194            "grep tool should be registered"
195        );
196        assert!(
197            tool_names.contains(&"find".to_string()),
198            "find tool should be registered"
199        );
200        assert!(
201            tool_names.contains(&"ls".to_string()),
202            "ls tool should be registered"
203        );
204        assert!(
205            tool_names.contains(&"web_search".to_string()),
206            "web_search tool should be registered"
207        );
208        assert!(
209            tool_names.contains(&"get_search_results".to_string()),
210            "get_search_results tool should be registered"
211        );
212    }
213}