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    ToolRegistry, WebSearchTool, WriteTool,
33};
34
35use crate::capability::{CSpace, ResourceRef, Rights};
36use crate::tools::kernel::*;
37use crate::tools::{
38    A2aDelegateTool, A2aQueryTool, A2aSendTool, ExecTool, KnowledgeTool, MemoryReadTool,
39    MemorySearchTool, 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                "knowledge" => registry.register(KnowledgeTool::from_kernel(kernel)),
151                "mcp" => { /* MCP tools are enumerated dynamically per agent */ }
152                _ => {} // Unknown domain — silently skip
153            },
154
155            // Programs are not registered as separate tools.
156            // ToolRetriever shows them in the capability index;
157            // agents use exec to run program commands.
158            ResourceRef::Program { .. } => {}
159
160            // Space, Agent, Mcp resource refs are handled through
161            // their respective KernelDomain registrations above
162            // or through dedicated tool paths.
163            _ => {}
164        }
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn register_always_on_registers_eight_tools() {
174        let registry = ToolRegistry::new();
175        let cache = Arc::new(SearchCache::new());
176        register_always_on(&registry, cache);
177
178        // The always-on set is: read, write, edit, grep, find, ls, web_search, get_search_results
179        // ToolRegistry doesn't expose a count, but we can verify individual tool names.
180        let tool_names = registry.names();
181        assert!(
182            tool_names.contains(&"read".to_string()),
183            "read tool should be registered"
184        );
185        assert!(
186            tool_names.contains(&"write".to_string()),
187            "write tool should be registered"
188        );
189        assert!(
190            tool_names.contains(&"edit".to_string()),
191            "edit tool should be registered"
192        );
193        assert!(
194            tool_names.contains(&"grep".to_string()),
195            "grep tool should be registered"
196        );
197        assert!(
198            tool_names.contains(&"find".to_string()),
199            "find tool should be registered"
200        );
201        assert!(
202            tool_names.contains(&"ls".to_string()),
203            "ls tool should be registered"
204        );
205        assert!(
206            tool_names.contains(&"web_search".to_string()),
207            "web_search tool should be registered"
208        );
209        assert!(
210            tool_names.contains(&"get_search_results".to_string()),
211            "get_search_results tool should be registered"
212        );
213    }
214}