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(®istry, &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(®istry, 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}