Capability Grants (Developer Guide)
From an agent's perspective, capability grants are temporary extensions to the agent's capability set granted by a parent agent or operator.
When You Need a Grant
If invoke_tool returns a ToolFailed error containing "access denied" or "lacks capability", the agent needs either:
- A capability added to its manifest (permanent, requiring redeployment)
- A runtime capability grant (temporary, requiring no restart)
Requesting a Grant
Currently, grant requests are issued by operators via ash or by parent agents via IPC. Agent-initiated grant requests are planned.
The typical flow from the agent's side:
#![allow(unused)] fn main() { match agent.invoke_tool("web.search", json!({"query": "..."})).await { Err(AgentError::ToolFailed(ref msg)) if msg.contains("access denied") => { // Observe the denial agent.observe( "capability_denied", msg, vec!["error".to_string(), "capability".to_string()], ).await?; // Check for an escalation response (parent may grant) tokio::time::sleep(std::time::Duration::from_secs(5)).await; let escalations = agent.pending_escalations().await?; // ... process escalations ... } Ok(result) => { /* proceed */ } Err(e) => return Err(e.into()), } }
Handling Escalations from Children
If you are writing a parent agent that manages child agents:
#![allow(unused)] fn main() { let escalations = agent.pending_escalations().await?; for esc in &escalations { // Inspect the escalation type if esc["type"] == "capability_request" { let requested_cap = esc["capability"].as_str().unwrap_or(""); let child_id = esc["agent_id"].as_str().unwrap_or(""); // Decide: approve or deny // (parent agents issue grants via IPC - direct API planned) tracing::info!("Child {} requests capability: {}", child_id, requested_cap); } } }
Grant Expiry
Grants are temporary. Design agents to handle grant expiry gracefully:
#![allow(unused)] fn main() { loop { match agent.invoke_tool("web.search", input.clone()).await { Ok(result) => { /* success */ break; } Err(AgentError::ToolFailed(ref msg)) if msg.contains("access denied") => { // Grant may have expired - wait or escalate tracing::warn!("Access denied - grant may have expired"); break; } Err(e) => return Err(e.into()), } } }
Best Practice: Manifest Over Grants
Prefer declaring capabilities in the manifest over relying on runtime grants. Grants are intended for:
- One-off operations the agent didn't anticipate needing
- Temporary escalation during an unusual workflow
- Operator-approved access to sensitive resources
Regular, predictable tool usage should be in the manifest.