* feat(workspace): multi-scope workspace reads Adds the ability for a workspace to read from multiple user scopes while keeping writes isolated to the primary scope. Configuration via WORKSPACE_READ_SCOPES env var (comma-separated user IDs). Includes identity file isolation (read_primary), multi-scope search, list, and read operations, WorkspaceConfig refactor, and comprehensive integration tests. * fix: address review feedback for multi-scope workspace reads - fix(memory): deduplicate timezone parsing for daily_log target parse_timezone was called twice when target was "daily_log" without a layer — once in path resolution, again in the fallback. Now computed once and reused. - fix(config): add character validation for WORKSPACE_READ_SCOPES and layer scopes — both enforce [a-zA-Z0-9_-] to prevent path traversal or injection via scope strings used as user_id in SQL queries. - fix(config): use chars().take(32) instead of byte-index slicing for scope length error messages (UTF-8 safety). - fix(error): remove unused WorkspaceError::NotFound variant Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: downgrade search log to debug, add comments on list iteration - Downgrade hybrid_search_multi tracing::info! to debug! — fires on every multi-scope search with the default backend, too noisy for info - Add comments explaining why list/list_all iterate per-scope instead of using _multi trait methods (identity path filtering needs scope attribution that merged results lose) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: [email protected] <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Tool System
Adding a New Tool
Built-in Tools (Rust)
- Create
src/tools/builtin/my_tool.rs - Implement the
Tooltrait - Add
mod my_tool;andpub useinsrc/tools/builtin/mod.rs - Register in
ToolRegistry::register_builtin_tools()inregistry.rs - Add tests
WASM Tools (Recommended)
WASM tools are the preferred way to add new capabilities. They run in a sandboxed environment with explicit capabilities.
- Create a new crate in
tools-src/<name>/ - Implement the WIT interface (
wit/tool.wit) - Create
<name>.capabilities.jsondeclaring required permissions - Build with
cargo build --target wasm32-wasip2 --release - Install with
ironclaw tool install path/to/tool.wasm
See tools-src/ for examples.
Tool Architecture Principles
CRITICAL: Keep tool-specific logic out of the main agent codebase.
The main agent provides generic infrastructure; tools are self-contained units that declare their requirements through capabilities files.
What Goes in Tools (capabilities.json)
- API endpoints the tool needs (HTTP allowlist)
- Credentials required (secret names, injection locations)
- Rate limits and timeouts
- Auth setup instructions (see below)
- Workspace paths the tool can read
What Does NOT Go in Main Agent
- Service-specific auth flows (OAuth for Notion, Slack, etc.)
- Service-specific CLI commands (
auth notion,auth slack) - Service-specific configuration handling
- Hardcoded API URLs or token formats
Tool Authentication
Tools declare their auth requirements in <tool>.capabilities.json under the auth section. Two methods are supported:
OAuth (Browser-based login)
For services that support OAuth, users just click through browser login:
{
"auth": {
"secret_name": "notion_api_token",
"display_name": "Notion",
"oauth": {
"authorization_url": "https://api.notion.com/v1/oauth/authorize",
"token_url": "https://api.notion.com/v1/oauth/token",
"client_id_env": "NOTION_OAUTH_CLIENT_ID",
"client_secret_env": "NOTION_OAUTH_CLIENT_SECRET",
"scopes": [],
"use_pkce": false,
"extra_params": { "owner": "user" }
},
"env_var": "NOTION_TOKEN"
}
}
To enable OAuth for a tool:
- Register a public OAuth app with the service (e.g., notion.so/my-integrations)
- Configure redirect URIs:
http://localhost:9876/callbackthroughhttp://localhost:9886/callback - Set environment variables for client_id and client_secret
Manual Token Entry (Fallback)
For services without OAuth or when OAuth isn't configured:
{
"auth": {
"secret_name": "openai_api_key",
"display_name": "OpenAI",
"instructions": "Get your API key from platform.openai.com/api-keys",
"setup_url": "https://platform.openai.com/api-keys",
"token_hint": "Starts with 'sk-'",
"env_var": "OPENAI_API_KEY"
}
}
Auth Flow Priority
When running ironclaw tool auth <tool>:
- Check
env_var- if set in environment, use it directly - Check
oauth- if configured, open browser for OAuth flow - Fall back to
instructions+ manual token entry
The agent reads auth config from the tool's capabilities file and provides the appropriate flow. No service-specific code in the main agent.
WASM Tools vs MCP Servers: When to Use Which
Both are first-class in the extension system (ironclaw tool install handles both), but they have different strengths.
WASM Tools (IronClaw native)
- Sandboxed: fuel metering, memory limits, no access except what's allowlisted
- Credentials injected by host runtime, tool code never sees the actual token
- Output scanned for secret leakage before returning to the LLM
- Auth (OAuth/manual) declared in
capabilities.json, agent handles the flow - Single binary, no process management, works offline
- Cost: must build yourself in Rust, no ecosystem, synchronous only
MCP Servers (Model Context Protocol)
- Growing ecosystem of pre-built servers (GitHub, Notion, Postgres, etc.)
- Any language (TypeScript/Python most common)
- Can do websockets, streaming, background polling
- Cost: external process with full system access (no sandbox), manages own credentials, IronClaw can't prevent leaks
Decision guide:
| Scenario | Use |
|---|---|
| Good MCP server already exists | MCP |
| Handles sensitive credentials (email send, banking) | WASM |
| Quick prototype or one-off integration | MCP |
| Core capability you'll maintain long-term | WASM |
| Needs background connections (websockets, polling) | MCP |
| Multiple tools share one OAuth token (e.g., Google suite) | WASM |
The LLM-facing interface is identical for both (tool name, schema, execute), so swapping between them is transparent to the agent.