Illia Polosukhin cad5e50f10 feat(llm): hot-reload provider chain from settings (supersedes #2059) (#2673)
* feat(llm): hot-reload provider chain from settings (#1350)

Adds SwappableLlmProvider and LlmReloadHandle so changes to the active
LLM backend/model via the settings API take effect without restarting
the daemon. The settings handlers trigger a chain rebuild from the
latest Config::from_db_with_toml whenever an LLM-relevant key is
written, and atomically swap the inner provider under the running
wrappers.

Addresses review feedback on the original PR #2059 (superseded):
- single RwLock<ProviderSnapshot> for atomic metadata updates (no
  torn reads across model_name / cost / cache multipliers)
- interned &'static str for model_name() to cap Box::leak at the set
  of distinct names a process ever sees, not one leak per swap
- single critical section around swap+snapshot refresh to kill the
  race between concurrent reloads
- tokio::sync::Mutex on LlmReloadHandle to serialize reloads and
  avoid overlapping OAuth refreshes / HTTP probes
- warn!, not silent Ok, when reload wiring is missing from the
  gateway state
- integration coverage per .claude/rules/testing.md: a test that
  drives settings_set_handler end-to-end and asserts the same
  Arc<dyn LlmProvider> reports the new active_model_name after swap

Co-authored-by: Nigel Coleman <coleman.nige@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(llm): gate hot-reload on scope + admin; re-hydrate secrets

Addresses review findings on #2673:

- Scope gate: reload only fires when the written scope actually feeds
  the global provider chain (admin scope or gateway owner scope). A
  member writing their own `selected_model` lands in their user row
  but no longer triggers a chain rebuild that would read back from a
  different scope — fixing both the "write ignored by reload" bug and
  the DoS vector where any authed user could force expensive rebuilds.

- Admin-only provider selection: `llm_backend` and `bedrock_{region,
  cross_region, profile}` join the existing admin-only LLM key list,
  matching the product directive "admins choose the provider, members
  pick the model within it". `selected_model` stays non-admin so every
  user can change their own model.

- Secret re-hydration on reload: `reload_llm_after_settings_change`
  now calls `re_resolve_llm_with_secrets` after the bare `from_db_with_toml`
  read, so a new OPENAI_API_KEY / NEARAI_SESSION_TOKEN added alongside
  a backend switch is visible to the rebuilt chain.

- Style cleanup: drop dead `llm_model` allowlist entry; drop unused
  `Clone` on `ProviderSnapshot`; document `reload_lock`'s purpose;
  explicit comment that `active_config.enabled_channels` is not
  refreshed (channel enablement is orthogonal to LLM config).

New regression tests (5149 → 5154 passing):

- `llm_reload_handle_preserves_old_chain_on_build_failure` — a failed
  reload leaves the primary wrapper pointing at the old chain.
- `settings_set_handler_rejects_member_writing_llm_backend` — member
  writing `llm_backend` gets 403 (admin-only).
- `settings_set_handler_member_selected_model_skips_reload` — member
  can set their own model, and it does NOT trigger a global reload.
- `settings_set_handler_owner_scope_triggers_reload` — owner writing
  their own scope (no `scope=admin`) still reloads the chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(llm): decouple reload from HTTP status; atomic set_model; cap interner

Addresses PR #2673 review comments from Copilot and gemini-code-assist:

- **Reload failure no longer 500s the setting write** (Copilot):
  `reload_llm_after_settings_change` is now infallible — it logs at
  `error!` when the chain rebuild fails but the handler still returns
  204. Returning 500 after a successful `set_setting` misrepresented
  the outcome (DB committed, chain stale) and drove client retries
  that re-ran the same failing reload.

- **set_model race with swap closed** (gemini): the write lock is now
  held across the inner `set_model` call and the snapshot refresh, so a
  concurrent `swap()` can't clobber the just-updated inner with a
  snapshot of the older one.

- **Interner leak capped** (gemini): `intern_model_name` now refuses
  names longer than 256 bytes and caps distinct entries at 1024, past
  either limit returning a static `<model-name-overflow>` sentinel and
  logging at `warn!`. Protects against adversarial `set_model` input.

New regression tests (5154 passing):

- `settings_set_handler_returns_success_when_reload_fails` — admin
  switches backend to a value with no credentials; handler returns
  204, DB has the new value, old chain still serving.
- `set_model_and_swap_are_mutually_atomic` — concurrent set_model +
  swap stress; final wrapper is readable and consistent.
- `intern_into_rejects_oversized_input` — oversized name never leaks,
  returns sentinel without touching the map.
- `intern_into_caps_distinct_entries` — past the cap, sentinel;
  already-interned names still resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(llm): load reload config from owner scope; rollback on build failure

Addresses PR #2673 review comments from @serrrfirat and Copilot.

- **Reload scope fix** (serrrfirat, Copilot): `reload_llm_after_settings_change`
  now reads with `state.owner_id` instead of the just-written `effective_user_id`.
  `Config::from_db_with_toml` skips the admin-merge step when `user_id == __admin__`,
  so reloading at admin scope was dropping owner-scope overlays that startup
  normally applies. Using `state.owner_id` matches `AppBuilder::init_config`
  and keeps the layering consistent.

- **Rollback on reload failure** (serrrfirat): handlers now snapshot the
  affected keys before the DB write and restore them if the chain rebuild
  returns `ConfigLoadFailed` or `BuildFailed`. The handler then returns
  422 with the rolled-back state. This closes the split-brain window where
  a bad `llm_backend=openai` write could leave the DB saying "openai"
  while the runtime kept serving "nearai". `set_setting`, `delete_setting`,
  and `set_all_settings` all participate.

- **`ReloadOutcome` enum** replaces the previous infallible return, so
  callers can distinguish transient "nothing wired" (skip) from actual
  "chain rebuild failed" (roll back) outcomes.

Regression tests (5184 passing):

- `reload_rebuilds_from_owner_scope_not_effective_scope` — pre-seeds an
  owner-scope `selected_model` overlay and has admin write a benign
  key under `scope=admin`. Assertion: after reload, the wrapper reports
  the owner's overlay, not admin's default. This fails pre-fix because
  reading at `__admin__` scope silently skipped the admin merge.
- `settings_set_handler_rolls_back_on_reload_failure` — pokes a poisoned
  `bedrock_cross_region` sibling into admin scope, triggers a handler
  write, asserts 422 and that the DB is back to its pre-request state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(llm): fail-loud on snapshot read errors; surface 422 reason in body

Addresses Copilot review comments on PR #2673.

- **Snapshot read errors** (c5, c6): `settings_{set,delete,import}_handler`
  used to `.unwrap_or(None)` when reading the previous value for rollback,
  which would silently treat a DB read failure as "no prior value" and
  turn a later rollback into `delete_setting` on a key whose prior value
  we couldn't actually read — a silent data-loss path. The handlers now
  map snapshot read errors to 500 and abort before persisting. The import
  handler does the same inside its per-key snapshot loop.

- **422 body carries the reason** (c7): the `ReloadOutcome::BuildFailed`
  and `ConfigLoadFailed` reason strings are now included in the 422
  response body. Handler error type changed from `Result<StatusCode,
  StatusCode>` to `Result<StatusCode, (StatusCode, String)>` (axum's
  `IntoResponse` for tuples). The web UI's `apiFetch` can surface the
  reason to the operator instead of a bare "Unprocessable Entity".
  Auth/validation paths keep empty-body semantics via the `no_body`
  helper.

Test updates:
- Existing handler tests: `.0` on the error tuple where they previously
  compared bare `StatusCode`.
- Extended `settings_set_handler_rolls_back_on_reload_failure` to assert
  the 422 body includes the failure reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nigel Coleman <coleman.nige@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:18:38 +09:00
2026-02-04 22:09:52 -08:00
2026-02-22 19:08:43 +00:00
2026-02-22 19:08:43 +00:00
2026-02-21 15:14:57 -07:00
2026-04-09 14:18:30 +02:00
2026-04-09 14:18:30 +02:00
2026-04-09 14:18:30 +02:00
2026-04-09 14:18:30 +02:00

IronClaw

IronClaw

Your secure personal AI assistant, always on your side

License: MIT OR Apache-2.0 Telegram: @ironclawAI Reddit: r/ironclawAI gitcgr

English | 简体中文 | Русский | 日本語 | 한국어

PhilosophyFeaturesInstallationConfigurationSecurityArchitecture


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, GitHub Copilot, 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/capabilities/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
  • Channels: See docs/channels/overview.mdx for setup of Telegram, Discord, and other channels.
  • Changing channel sources: Run ./channels-src/telegram/build.sh before cargo build so 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:

at your option.

Description
IronClaw is OpenClaw inspired implementation in Rust focused on privacy and security IronClaw 基于一个简单的原则:你的 AI 助手应该为你服务,而不是与你为敌。
Readme 1.8 GiB
Languages
Rust 86%
Python 7.5%
JavaScript 3.4%
Shell 1.9%
CSS 0.9%
Other 0.2%