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