* feat: LRU embedding cache for workspace search (#165) Add CachedEmbeddingProvider that wraps any EmbeddingProvider with an in-memory LRU cache keyed by SHA-256(model_name + text). This avoids redundant HTTP calls when the same text is embedded multiple times (common during reindexing and repeated searches). - Cache uses HashMap + last_accessed tracking with manual LRU eviction (same pattern as llm::response_cache::CachedProvider) - Lock is never held during HTTP calls to prevent blocking - embed_batch() partitions into hits/misses and only fetches misses - Default 10,000 entries (~58 MB for 1536-dim vectors) - Configurable via EMBEDDING_CACHE_SIZE env var - Workspace.with_embeddings() auto-wraps; with_embeddings_uncached() available for tests Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address review comments on embedding cache - Validate embed_batch return count matches expected miss count - Replace unwrap_or_default() with proper error propagation - Fix batch eviction: run final eviction pass after insert to enforce cap - Fix test: use different-length inputs to verify ordering correctness - Reject EMBEDDING_CACHE_SIZE=0 in config validation (minimum is 1) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: replace .expect() with proper error handling in embed_batch The all-cache-hits early-return path used .expect("all cache hits") which violates the project convention of no .unwrap()/.expect() in production code. Replaced with the same ok_or_else pattern used in the normal path. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: clarify memory sizing docs and use saturating_add for eviction - Update memory comments in embedding_cache.rs, config/embeddings.rs, and workspace/mod.rs to note the ~58 MB figure is payload-only (actual memory is higher due to HashMap/key/allocation overhead) - Use saturating_add(1) instead of + 1 for eviction threshold to prevent overflow if max_entries is usize::MAX Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address Copilot review on embedding cache - Avoid double-clone per miss in embed_batch: move embedding into results, clone only for the cache entry - Evict per-insert instead of after all inserts to keep peak memory bounded during large batches - Clamp max_entries to at least 1 in constructor to prevent unexpected eviction behavior when set to 0 via the public API Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: reduce embedding_cache module visibility to private Types are already re-exported via `pub use`, so the module itself doesn't need to be public. Reduces unnecessary API surface. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address serrrfirat review feedback on embedding cache - Add TODO comment for O(n) LRU eviction scalability - Add thundering herd note at lock release in embed() - Warn when cache max_entries exceeds 100k - Use with_embeddings_uncached() in integration test - Add tests: error_does_not_pollute_cache, embed_batch_empty_input - Update README with cache-aware with_embeddings() docs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: prevent u32 wrapping in FailThenSucceedMock failure counter fetch_sub(1) wraps to u32::MAX when called past zero, silently breaking the mock for 3+ calls. Switch to load-then-store to avoid the wrapping bug in both embed() and embed_batch(). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address Copilot and serrrfirat review findings on embedding cache - Switch tokio::sync::Mutex to std::sync::Mutex (lock never held across .await — cheaper synchronous lock) - Extract DEFAULT_EMBEDDING_CACHE_SIZE constant to avoid 10_000 duplication between EmbeddingCacheConfig and EmbeddingsConfig Co-Authored-By: Claude Opus 4.6 <[email protected]> * test: add all-misses batch test for embedding cache Adds embed_batch_all_misses test covering the case where every text in a batch is a cache miss — fulfilling the commitment from serrrfirat's review. Co-Authored-By: Claude Opus 4.6 <[email protected]> * chore: trigger CI re-check after rebase * fix: use raw [u8;32] cache keys and pre-allocate HashMap capacity Address Copilot review findings: - cache_key() now returns [u8; 32] instead of hex String, avoiding a 64-byte allocation per lookup - HashMap::with_capacity(max_entries) avoids incremental reallocation - Fix pre-existing staging compilation error in cli/routines.rs (missing max_tool_rounds/use_tools fields) [skip-regression-check] * fix: make cache accessors sync and update doc for [u8;32] keys Address Copilot review: - len(), is_empty(), clear() are now sync since they only take a std::sync::Mutex lock with no .await points - Update cache_size doc comment to reflect [u8;32] keys instead of String keys [skip-regression-check] * fix: remove clone_on_copy for [u8; 32] cache keys [skip-regression-check] * ci: add safety comments to test code for no-panics check The CI no-panics grep check cannot distinguish test code inside src/ files from production code. Add // safety: test annotations to .unwrap(), .expect(), and assert!() calls in #[cfg(test)] modules. * fix: correct cache doc and demote hit/miss logs to trace - Fix misleading "String keys" in memory comment (cache uses [u8; 32]) - Demote per-request hit/miss logs from debug to trace to reduce noise on hot paths (batch summary stays at trace too) * docs: add missing Arc import in workspace README example * perf: batch eviction in embed_batch to avoid O(n×m) cost Replace per-insert evict_lru call with a single evict_k_oldest pass that computes eviction count upfront and removes the k oldest entries in one O(n) scan. Avoids O(n×m) HashMap iterations while holding the mutex during batch inserts. * fix: cap batch cache inserts at max_entries and use O(n) selection - evict_k_oldest now uses select_nth_unstable_by_key for O(n) average partial selection instead of O(n log n) full sort - embed_batch caps cached entries at max_entries when misses exceed capacity, preventing the cache from growing unbounded - Added test: batch_exceeding_capacity_respects_max_entries * fix: flatten test assert for fmt compatibility Shorten assert message to fit single line so cargo fmt doesn't split the safety annotation onto a separate line. * fix: address review feedback and improve embedding cache (takeover #235) - Fix merge conflict: add missing allow_always field in PendingApproval - Thread EmbeddingCacheConfig through CLI memory commands so they respect EMBEDDING_CACHE_SIZE instead of silently using default (fixes #235 review) - Cap HashMap pre-allocation at min(max_entries, 1024) to avoid upfront memory waste at large cache sizes - Fix FailThenSucceedMock race: replace load+store with atomic fetch_update - Remove noisy '// safety: test' comments (40+ lines of diff noise) - Fix collapsed lines from comment removal - Simplify redundant Ok(...collect()?) to just collect() Co-Authored-By: ztsalexey <[email protected]> Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(embedding-cache): skip eviction on concurrent duplicate insert When the lock is released for the HTTP call, another caller may insert the same key. Re-check under lock and just update the existing entry without evicting, avoiding unnecessary cache churn under concurrency. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: ztsalexey <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: ztsalexey <[email protected]>
IronClaw
Your secure personal AI assistant, always on your side
English | 简体中文 | Русский | 日本語
Philosophy • Features • Installation • Configuration • Security • Architecture
Philosophy
IronClaw is built on a simple principle: your AI assistant should work for you, not against you.
In a world where AI systems are increasingly opaque about data handling and aligned with corporate interests, IronClaw takes a different approach:
- Your data stays yours - All information is stored locally, encrypted, and never leaves your control
- Transparency by design - Open source, auditable, no hidden telemetry or data harvesting
- Self-expanding capabilities - Build new tools on the fly without waiting for vendor updates
- Defense in depth - Multiple security layers protect against prompt injection and data exfiltration
IronClaw is the AI assistant you can actually trust with your personal and professional life.
Features
Security First
- WASM Sandbox - Untrusted tools run in isolated WebAssembly containers with capability-based permissions
- Credential Protection - Secrets are never exposed to tools; injected at the host boundary with leak detection
- Prompt Injection Defense - Pattern detection, content sanitization, and policy enforcement
- Endpoint Allowlisting - HTTP requests only to explicitly approved hosts and paths
Always Available
- Multi-channel - REPL, HTTP webhooks, WASM channels (Telegram, Slack), and web gateway
- Docker Sandbox - Isolated container execution with per-job tokens and orchestrator/worker pattern
- Web Gateway - Browser UI with real-time SSE/WebSocket streaming
- Routines - Cron schedules, event triggers, webhook handlers for background automation
- Heartbeat System - Proactive background execution for monitoring and maintenance tasks
- Parallel Jobs - Handle multiple requests concurrently with isolated contexts
- Self-repair - Automatic detection and recovery of stuck operations
Self-Expanding
- Dynamic Tool Building - Describe what you need, and IronClaw builds it as a WASM tool
- MCP Protocol - Connect to Model Context Protocol servers for additional capabilities
- Plugin Architecture - Drop in new WASM tools and channels without restarting
Persistent Memory
- Hybrid Search - Full-text + vector search using Reciprocal Rank Fusion
- Workspace Filesystem - Flexible path-based storage for notes, logs, and context
- Identity Files - Maintain consistent personality and preferences across sessions
Installation
Prerequisites
- Rust 1.85+
- PostgreSQL 15+ with pgvector extension
- NEAR AI account (authentication handled via setup wizard)
Download or Build
Visit Releases page to see the latest updates.
Install via Windows Installer (Windows)
Download the Windows Installer and run it.
Install via powershell script (Windows)
irm https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.ps1 | iex
Install via shell script (macOS, Linux, Windows/WSL)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh
Install via Homebrew (macOS/Linux)
brew install ironclaw
Compile the source code (Cargo on Windows, Linux, macOS)
Install it with cargo, just make sure you have Rust installed on your computer.
# Clone the repository
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
# Build
cargo build --release
# Run tests
cargo test
For full release (after modifying channel sources), run ./scripts/build-all.sh to rebuild channels first.
Database Setup
# Create database
createdb ironclaw
# Enable pgvector
psql ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
Configuration
Run the setup wizard to configure IronClaw:
ironclaw onboard
The wizard handles database connection, NEAR AI authentication (via browser OAuth),
and secrets encryption (using your system keychain). Settings are persisted in the
connected database; bootstrap variables (e.g. DATABASE_URL, LLM_BACKEND) are
written to ~/.ironclaw/.env so they are available before the database connects.
Alternative LLM Providers
IronClaw defaults to NEAR AI but supports many LLM providers out of the box. Built-in providers include Anthropic, OpenAI, Google Gemini, MiniMax, Mistral, and Ollama (local). OpenAI-compatible services like OpenRouter (300+ models), Together AI, Fireworks AI, and self-hosted servers (vLLM, LiteLLM) are also supported.
Select your provider in the wizard, or set environment variables directly:
# Example: MiniMax (built-in, 204K context)
LLM_BACKEND=minimax
MINIMAX_API_KEY=...
# Example: OpenAI-compatible endpoint
LLM_BACKEND=openai_compatible
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_API_KEY=sk-or-...
LLM_MODEL=anthropic/claude-sonnet-4
See docs/LLM_PROVIDERS.md for a full provider guide.
Security
IronClaw implements defense in depth to protect your data and prevent misuse.
WASM Sandbox
All untrusted tools run in isolated WebAssembly containers:
- Capability-based permissions - Explicit opt-in for HTTP, secrets, tool invocation
- Endpoint allowlisting - HTTP requests only to approved hosts/paths
- Credential injection - Secrets injected at host boundary, never exposed to WASM code
- Leak detection - Scans requests and responses for secret exfiltration attempts
- Rate limiting - Per-tool request limits to prevent abuse
- Resource limits - Memory, CPU, and execution time constraints
WASM ──► Allowlist ──► Leak Scan ──► Credential ──► Execute ──► Leak Scan ──► WASM
Validator (request) Injector Request (response)
Prompt Injection Defense
External content passes through multiple security layers:
- Pattern-based detection of injection attempts
- Content sanitization and escaping
- Policy rules with severity levels (Block/Warn/Review/Sanitize)
- Tool output wrapping for safe LLM context injection
Data Protection
- All data stored locally in your PostgreSQL database
- Secrets encrypted with AES-256-GCM
- No telemetry, analytics, or data sharing
- Full audit log of all tool executions
Architecture
┌────────────────────────────────────────────────────────────────┐
│ Channels │
│ ┌──────┐ ┌──────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REPL │ │ HTTP │ │WASM Channels│ │ Web Gateway │ │
│ └──┬───┘ └──┬───┘ └──────┬──────┘ │ (SSE + WS) │ │
│ │ │ │ └──────┬──────┘ │
│ └─────────┴──────────────┴────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Agent Loop │ Intent routing │
│ └────┬──────────┬───┘ │
│ │ │ │
│ ┌──────────▼────┐ ┌──▼───────────────┐ │
│ │ Scheduler │ │ Routines Engine │ │
│ │(parallel jobs)│ │(cron, event, wh) │ │
│ └──────┬────────┘ └────────┬─────────┘ │
│ │ │ │
│ ┌─────────────┼────────────────────┘ │
│ │ │ │
│ ┌───▼─────┐ ┌────▼────────────────┐ │
│ │ Local │ │ Orchestrator │ │
│ │Workers │ │ ┌───────────────┐ │ │
│ │(in-proc)│ │ │ Docker Sandbox│ │ │
│ └───┬─────┘ │ │ Containers │ │ │
│ │ │ │ ┌───────────┐ │ │ │
│ │ │ │ │Worker / CC│ │ │ │
│ │ │ │ └───────────┘ │ │ │
│ │ │ └───────────────┘ │ │
│ │ └─────────┬───────────┘ │
│ └──────────────────┤ │
│ │ │
│ ┌───────────▼──────────┐ │
│ │ Tool Registry │ │
│ │ Built-in, MCP, WASM │ │
│ └──────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
Core Components
| Component | Purpose |
|---|---|
| Agent Loop | Main message handling and job coordination |
| Router | Classifies user intent (command, query, task) |
| Scheduler | Manages parallel job execution with priorities |
| Worker | Executes jobs with LLM reasoning and tool calls |
| Orchestrator | Container lifecycle, LLM proxying, per-job auth |
| Web Gateway | Browser UI with chat, memory, jobs, logs, extensions, routines |
| Routines Engine | Scheduled (cron) and reactive (event, webhook) background tasks |
| Workspace | Persistent memory with hybrid search |
| Safety Layer | Prompt injection defense and content sanitization |
Usage
# First-time setup (configures database, auth, etc.)
ironclaw onboard
# Start interactive REPL
cargo run
# With debug logging
RUST_LOG=ironclaw=debug cargo run
Development
# Format code
cargo fmt
# Lint
cargo clippy --all --benches --tests --examples --all-features
# Run tests
createdb ironclaw_test
cargo test
# Run specific test
cargo test test_name
- Telegram channel: See docs/TELEGRAM_SETUP.md for setup and DM pairing.
- Changing channel sources: Run
./channels-src/telegram/build.shbeforecargo buildso the updated WASM is bundled.
OpenClaw Heritage
IronClaw is a Rust reimplementation inspired by OpenClaw. See FEATURE_PARITY.md for the complete tracking matrix.
Key differences:
- Rust vs TypeScript - Native performance, memory safety, single binary
- WASM sandbox vs Docker - Lightweight, capability-based security
- PostgreSQL vs SQLite - Production-ready persistence
- Security-first design - Multiple defense layers, credential protection
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
