* feat(reborn): skill-learning turn-end seam + extraction prompt Add the post-completion seam for learning reusable skills from successful runs, composed additively alongside trace capture (no behavior change to existing paths): - SkillLearningTurnEventSink: on a successful turn completion, reads the run transcript (load_context_window, preserving tool calls) and gates substantive runs (>=3 tool actions, >=5 messages) as skill-extraction candidates. Modeled on trace_capture.rs; detached, debug!-only. - CompositeTurnEventSink: fans the single turn_event_sink slot out to both trace capture and skill learning. - assets/prompts/skill_extraction.md: one-shot transcript -> SKILL.md prompt for the next increment (the distillation LLM call). - docs/plans: design + implementation log. Distillation, staging-for-approval, and the scoped skill write land in follow-up increments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): distillation logic crate (transcript -> SKILL.md) New leaf crate ironclaw_skill_learning owns the pure skill-learning logic, kept out of the composition root (per architecture guardrails) and reusable by both the autonomous sink and a future explicit CLI command: - distill_skill(transcript, &dyn SkillInferencePort) -> DistillOutcome: runs the extraction prompt through an abstracted inference port, then validates the output with ironclaw_skills::parse_skill_md (the SAME parser the install path uses) so a distilled skill is guaranteed installable. - parse_distillation: tolerates SKIP declines and accidental code-fence wraps; rejects chatty/invalid output. Inference is abstracted behind SkillInferencePort so the crate has no LLM/runtime/filesystem dependency. - Moves the extraction prompt here (co-located with the parser contract it must satisfy). 6 unit tests, clippy clean. Composition wiring (inference adapter over the runtime's non-run inference port + scoped write) lands next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): wire distillation into the turn-end sink On a successful, substantive run, the skill-learning sink now actually distills a SKILL.md (instead of just logging a candidate): - SkillLearningInferenceAdapter bridges a strong-model LlmProvider to the logic crate's SkillInferencePort, passing the learning model as a per-request override (NEAR AI honours it) — so distillation runs against a STRONGER model than the run's, without touching the run's model gateway. - build_skill_learning_provider builds that provider from the run's resolved NEAR config with only the model overridden (IRONCLAW_SKILL_LEARNING_MODEL), reusing existing credentials. No churn to build_llm_gateway / the gateway return tuple. - The sink formats the run transcript (tool names included) and calls distill_skill, logging the distilled skill / skip / error. The scoped write + stage-for-approval land in the next increment. - Skill learning is gated on root-llm-provider (it needs an LLM) and is active only when the learning model is configured; otherwise only trace capture runs. CompositeTurnEventSink fans the single turn_event_sink slot to both. Verified: check (default + root-llm-provider) 0 warnings; test + clippy (root-llm-provider,test-support,libsql) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): install distilled skills (scoped write + safety scan) The skill-learning sink now persists the distilled skill so it appears in Settings->Skills and loads into the next run (per the user's "scan + visible" choice; the pre-approval gate is the next increment): - SkillWriter seam (composition trait): the sink depends on a small write abstraction; PortSkillWriter implements it over the runtime's existing RebornLocalSkillManagementPort (install_for_scope, falling back to update_for_scope on re-learn). Tests use a stub writer (no filesystem). - Scope is derived from the EVENT: ResourceScope::local_default(owner, ...) with tenant_id overridden to the run's tenant, so the write lands where the WebUI lists it and the next run reads it (NOT the `default` tenant). - Distilled content is injection-scanned (ironclaw_safety:: validate_trusted_trigger_prompt with a Sanitizer, mirroring the WebUI facade) before install — it becomes trusted prompt text in the next run. - Sink wiring now also requires local_runtime (the skill port lives there); reuses local_runtime.skill_management rather than building a new port. Verified: check (default + root-llm-provider) 0 warnings; test + clippy (root-llm-provider,test-support,libsql) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): live "learned a skill" bubble on WebChat v2 When a skill is distilled + installed, the sink now emits a live notification to the run's thread stream, rendered by the EXISTING WebChat v2 chat bubble (reuses the SkillActivation projection — zero new wire variants): - LiveProjectionPublisher::publish_skill_learned: publishes a SkillActivation live item from raw pieces (owner, turn scope, run_id, name, feedback), the post-run analogue of the in-run SkillActivationObserver (which only fires at prompt-build for skill selection). Gated on root-llm-provider. - SkillLearnedNotifier seam (same testable pattern as SkillWriter): LiveSkillLearnedNotifier wraps the publisher; the sink emits the bubble after a successful install. Tests use a stub notifier. - runtime wiring clones the live projection publisher before the milestone-sink builder consumes it, and passes a notifier into the sink. The learned skill already appears in the existing Settings->Skills page (installed live in the prior increment); this adds the in-chat moment. Pre-approval gate (decision #3) is the next increment. Verified: check (default + root-llm-provider) 0 warnings; test + clippy (root-llm-provider,test-support,libsql) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(skill-learning): refresh implementation log; rename refinement (drop GEPA) - Phase 2 renamed to "Skill Refinement (eval-driven reflective improvement)"; removed the "GEPA-lite" name (DSPy/Hermes term) per review. - Implementation log updated to reflect increments 2 (logic crate), 2b (sink wiring + the SystemInferencePort rejection), 3 (scoped install + scan), and 4 (live learned-skill bubble), plus the per-increment verification gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(skill-learning): e2e fixes from a live ironclaw-reborn run Validated the whole loop end-to-end against a running `ironclaw-reborn serve` with a NEAR AI `openai/gpt-5.5` learning model: a completed multi-tool run was distilled into a real SKILL.md (with pitfalls captured from the transcript), injection-scanned, installed under the correct (tenant=reborn-cli, user) scope, and shown in Settings->Skills. Three real bugs the run surfaced: - Drop the temperature override: reasoning models (gpt-5.x) reject any non-default temperature with HTTP 400 ("temperature does not support 0.2"). - Bump the distillation output ceiling to 16384: a reasoning learning model spends tokens on reasoning before emitting the SKILL.md, so a 4096 cap would truncate it. - Lower the eligibility gate to >=2 tool actions / >=3 messages: an efficient agent can complete a skill-worthy multi-step task in two tool calls (e.g. `shell` mkdir + batch write). The gate is only a cheap pre-filter; the learning model's own SKIP judgement is the real quality gate. - Loosen the extraction prompt: distill any multi-step tool procedure (capture the general repeatable procedure); only skip purely conversational runs. Also removes the temporary info-level diagnostics added while debugging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): auto-activate learned skills on criteria match Local-dev composition hard-coded the skill selection mode to `ExplicitOnly`, so a learned skill only activated when the user typed `$name`/`/name`. That left the learn loop half-open: skills were distilled and installed but never reused unless named explicitly. Switch local-dev to `ExplicitAndCriteria` (the upstream default) so a learned skill auto-activates when a later request matches its keywords/patterns, closing the learn→reuse loop. Explicit mentions still force-activate; criteria selection is additive and bounded by `max_active_skills` / `max_context_tokens`. The selector-config unit test now locks `ExplicitAndCriteria` so a revert to explicit-only trips a clear failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): durable learned-skill feedback + dedup consolidation Two gaps in the learn loop, both surfaced while dogfooding against a live ironclaw-reborn run: 1. No visible "learned a skill" feedback. The post-run sink published a live `SkillActivation` projection bubble, but that is ephemeral — only delivered to a stream connected at publish time, ~seconds after the run when distillation finishes. Add a DURABLE path: after install, append a finalized assistant note to the run's thread, so the feedback renders from `get_timeline` and survives a reload even when no live stream was open. The spawned extraction body is lifted into `ExtractionJob::run` so the durable announce is testable end-to-end through its caller (the spawn is otherwise fire-and-forget). Two regression tests also lock that the live `SkillActivation` bubble drains to the WebUI projection stream (fresh and resume-from-advanced-cursor paths). 2. Near-duplicate skills accreted. The distiller names the same kind of task slightly differently each run, so the user's skill list filled with siblings (file-create-read-count-summary, file-character-count-roundtrip, create-read-count-file-characters …) that never get reused together. Before installing, `PortSkillWriter` now lists existing learned skills and, when one covers the same ground (Jaccard over the combined name/keyword/tag token sets ≥ 0.45), refines it in place under its existing name instead of installing a second one. Only `User`-source skills are merge targets; system/registry skills are never touched. `update_skill` requires the document name to match the target, so the merged content is retargeted first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skill-learning): self-evolving skill refinement on recurring tasks Builds on near-duplicate consolidation: when a learned task recurs and the freshly distilled candidate matches an existing learned skill, the existing skill is now *refined* in place rather than overwritten — the self-evolution step. The learning model folds the candidate's new evidence into the existing SKILL.md (converged steps, the UNION of real gotchas, a bumped version), so a skill gets strictly better each time its task comes around. - `ironclaw_skill_learning::refine_skill` + `parse_refinement` + `RefineOutcome`, driven by `prompts/skill_refinement.md`. Pure domain logic, validated by the install-path parser; tolerates a `KEEP` decline (existing already subsumes the candidate) and a code-fence wrap, same as distillation. - Composition `SkillRefiner`/`LlmSkillRefiner` seam: maps the model outcome to a `MergeAction` — `Replace` (refined, retargeted to the existing name, and injection-scanned), `KeepExisting` (leave the existing skill untouched), or `Overwrite` (fall back to plain consolidation when refinement is unavailable or the model output is unusable). The refined document is retargeted defensively (never trust the model to preserve the name) and re-scanned before install. - `PortSkillWriter` reads the existing skill and consults the refiner on the merge path; wired in `runtime.rs` from the same learning inference adapter. Unit-tested end to end through the refiner (replace+bump, model-rename retarget, keep, unparseable→overwrite) and in the logic crate (parse/keep/reject). The prompt's merge quality is verified live against the NEAR AI learning model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(skill-evolution): log increments 5-8 (durable feedback, auto-consume, dedup, refinement) Records tonight's work and the one known gap: the live SkillActivation bubble is published but not delivered in the running server (empirically confirmed), its mechanism passes deterministic tests, and it could not be instrumented live without the NEAR AI key — so a durable timeline note is the reliable fix shipped instead. Carries forward the remaining work: pinning the live-SSE gap, an eval-driven refinement loop, the pre-approval gate, CLI commands, and a one-off consolidation of the siblings already on disk. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(skill-learning): live-validation fixes for durable feedback + refinement Found by re-running the loop end to end against a live ironclaw-reborn with the NEAR AI learning model (the in-memory fakes missed both): 1. Durable note never persisted. The durable store dedups assistant drafts by `turn_run_id` and returns the existing one, so `announce_learned_skill` reusing the run's id handed back the run's already-finalized reply and the finalize failed `MessageNotDraft` ("message … is not an assistant draft"). Use a distinct `skill-learned:{run_id}` id so the note is its own message. The regression test now seeds the run's finalized reply first (reproducing the collision the fresh-thread test missed) and asserts the note is a separate, finalized message. 2. Re-learning the SAME skill name overwrote the refined version instead of refining it. The distiller derives the name from the task, so it often repeats; the old path skipped the similarity check for the same name and fell to a plain install→update-on-conflict, resetting an evolved v2 back to a fresh v1. `find_merge_target` now routes BOTH an exact-name re-learn and a renamed sibling through refinement, so the version climbs consistently (verified live: create-read-count-file-characters v1 -> v2, "refined existing learned skill", skill count held at 3, durable note rendered, zero finalize errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(skill-evolution): record live end-to-end validation + the two fixes it found Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skills): per-skill auto-activation flag honored by the selector Foundation for user-facing skill activation control. Adds a manifest `auto_activate` flag (frontmatter, defaults true so existing skills are unaffected) and has the activation selector honor it: a skill with `auto_activate: false` is excluded from criteria (keyword/regex) selection but stays available for an explicit `$name` / `/name` mention. State lives in the skill's own SKILL.md — no new storage layer. - `SkillManifest.auto_activate` (`#[serde(default = "default_auto_activate")]`). - `set_skill_auto_activate(content, enabled)`: line-edits the frontmatter flag, preserving the rest of the document byte-for-byte so a toggle does not reformat the skill (re-parses cleanly with the same name). Unit-tested (default-true, insert-then-replace). - `select_skill_activations` builds a criteria-candidate set filtered by `auto_activate`; explicit mentions still resolve against the full set. Existing skill construction sites updated for the new field; full workspace + reborn binary build green (266 crate tests pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skills): API + DTO to toggle a skill's auto-activation Wires the per-skill auto-activation flag end to end on the backend so the WebChat v2 UI can flip it: - `POST /api/webchat/v2/skills/{name}/auto-activate` ({ enabled }) — reads the skill, line-edits the frontmatter flag via `set_skill_auto_activate`, re-scans it for injection (parity with install/update), and persists. Added as a default method on `SkillsProductFacade` / `RebornServicesApi` (fail-closed unavailable) with the real implementation in the composition facade, plus the handler, descriptor, route, and exports. Descriptor contract test updated. - `SkillSummary.auto_activate` + `RebornSkillInfo.auto_activate` so the skills list reports each skill's current state for the UI toggle (defaults true). Full backend chain builds (reborn binary green); ironclaw_skills, extension ports, webui_v2, and product_workflow test suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(webui): per-skill auto-activation toggle in Settings → Skills Adds an "Auto-activate: On/Off" switch to each manageable skill card. Off makes the skill explicit-only (`/name`); on restores keyword/criteria auto-activation. Wires `setSkillAutoActivate` through settings-api → useSkills mutation (invalidates the skills query) → SkillsTab handler → SkillGroup → SkillCard, reading the `auto_activate` field the v2 skills DTO now reports. Mirrors the existing skill install/update/remove mutation pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(skills): global "auto-activate learned skills" master switch (live) Add a global toggle that disables default auto-activation while keeping explicit /name invocation. ON (default) selects ExplicitAndCriteria; OFF selects ExplicitOnly. It takes effect on the next turn with no restart, via one process-global Arc<AtomicBool> shared by reference between the activation selector (reads it every turn in select_skill_activations) and the WebUI skills facade (writes it). Not persisted by design — resets to ON on restart. Vertical: - factory.rs: RebornLocalRuntimeServices.skill_auto_activate_learned (default true), one instance shared with the selector and the facade. - activation.rs/skills.rs: thread the flag into SelectableSkillContextSource; gate the criteria branch on it. Explicit mentions always activate. - webui.rs: LocalSkillsProductFacade holds Option<Arc<AtomicBool>>; set_auto_activate_learned stores into it, list_skills surfaces it. When no flag-reading selector is wired (production assembly) the facade gets None and the toggle fails closed (503) instead of writing to an orphan flag — fixes a review finding where the production toggle silently no-oped and read back true. - reborn_services.rs/types.rs: facade + API trait method, delegation, and RebornSkillListResponse.auto_activate_learned DTO field (serde default true). - webui_v2: POST /api/webchat/v2/skills/auto-activate-learned route, handler, descriptor + contract row. - frontend: setAutoActivateLearned API, useSkills mutation, Settings → Skills LearnedAutoActivateCard master switch. Tests (regression): - global_auto_activate_flag_gates_criteria_and_honors_live_toggle: drives the real selector with a live flag flip (off → empty, flip on → activates). - set_auto_activate_learned_flips_shared_flag_and_surfaces_in_list. - set_auto_activate_learned_fails_closed_when_no_selector_is_wired. - set_auto_activate_learned_forwards_enabled_flag_to_facade (through the caller). - descriptor contract row. Live-validated against ironclaw-reborn serve: GET skills auto_activate_learned True → toggle OFF → False → toggle ON → True. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(skill-evolution): record increment 9 — global auto-activate master switch Document the live global toggle (shared Arc<AtomicBool>, ExplicitAndCriteria ⇄ ExplicitOnly, not persisted), the production orphan-flag review finding and its fail-closed fix, the regression tests, and the live end-to-end validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(skill-learning): scope extraction eligibility to the completed run The post-turn ExtractionJob loads the recent THREAD window (no run filter) and the eligibility gate counted tool-result messages across that whole window. A trivial follow-up turn after a tool-heavy task could re-pass the gate on the previous run's stale tool results and re-distill it — wasted inference plus a stale-transcript refine that can regress an evolved skill. Count tool actions only for the completed run, read from the history projection (which keeps message kind + turn_run_id and only nulls the tool metadata the transcript needs). The full window is still used as the multi-turn distillation context, which is intentional. The producer writes turn_run_id = run_id.to_string(), matching self.run_id. Localized to skill_learning.rs — no change to the shared ContextMessage / agent-loop model-context path. Regression test: eligibility_counts_tool_actions_for_the_completed_run_only (trivial follow-up under a fresh run id over stale prior-run tool results does not distill; a run with its own tool actions reaches distillation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(webui): make the skill auto-activation switch read as the global control it is The master switch gates the entire criteria-selection pass, so it affects every skill (learned, user-authored, and bundled), not only learned ones — but the Settings card said "Auto-activate learned skills". Rename the user-facing card to "Default skill auto-activation" with global wording (frontend strings only; behavior and the wire field are unchanged). Also give the card a light-red background and a black status line when disabled, as a persistent "default is off" cue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(skill-evolution): increment 10 — review-driven hardening Record the three review findings and their disposition: run-scoped extraction eligibility (fixed), the global master-switch relabel (fixed), and the deferred learned-skill prompt-injection approval gate with its residual risk and the reason the obvious low-risk mitigations don't apply (auto_activate=false is filtered out of criteria selection; trust attenuation needs a dedicated learned-skill source/dir first). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix skill manifest test fixture * Avoid bundled skill collision in runtime test * Avoid review keyword in filesystem skill test * Allow auto-activated skills in runtime asset test * fix(skill-learning): guard the two data-loss paths in learned-skill writes merge() now keeps the existing accumulated skill (KeepExisting) on a refiner error, an unparseable response, or a rejected injection scan of the merged doc, instead of overwriting it with the raw single-run candidate — a transient model hiccup must not discard a skill that accreted gotchas over many runs. Overwrite is reserved for the genuine no-existing-content case. install_or_update now matches SkillManagementErrorKind::Conflict specifically and fails loud on any other install error (filesystem/validation/resource), instead of treating every install failure as a name conflict and overwriting a live skill. Addresses review #1/#2 (data-loss/overwrite paths). Self-learning stays off by default (sink wired only with IRONCLAW_SKILL_LEARNING_MODEL + nearai), so these paths are unreachable in a default deployment; the broader hold-for-review / approval hardening lands in the stacked follow-up PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review feedback on skill activation --------- Co-authored-by: krishna <krishna@krishnadeMacBook-Air.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Robert Yan <mstr.raphael@gmail.com>
IronClaw
Your secure personal AI assistant, always on your side
English | 简体中文 | Русский | 日本語 | 한국어
Reborn Quick Start • Philosophy • Features • Installation • Configuration • Security • Architecture
IronClaw Reborn Quick Start
IronClaw Reborn is the standalone runtime on the reborn-integration branch.
It uses the separate ironclaw-reborn binary from the
ironclaw_reborn_cli package and a separate Reborn state root. It does not use
the legacy ironclaw state directory as its config root.
For the older ironclaw binary, see Installation and
Legacy IronClaw Usage.
Build or run the binary
From the repo root:
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- --help
Or build it first:
cargo build -p ironclaw_reborn_cli --bin ironclaw-reborn
./target/debug/ironclaw-reborn --help
The default Reborn home is $HOME/.ironclaw/reborn. Override it with an
absolute path when you want isolated state:
export IRONCLAW_REBORN_HOME="$PWD/.reborn-home"
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- config path
config path and doctor are safe diagnostics; they report the resolved home,
profile, config.toml, providers.json, and v1_state: not-used.
They do not create Reborn state or seed config files.
Configure the model route
The CLI-native way to configure Reborn's default model route is:
export IRONCLAW_REBORN_HOME="$PWD/.reborn-home"
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- models set-provider openai --model gpt-5-mini
That writes $IRONCLAW_REBORN_HOME/config.toml with [llm.default] and the
provider's credential env-var name. Check it with:
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- models status
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- models list openai
For OpenAI, set the secret value in the environment before starting:
export OPENAI_API_KEY="sk-..."
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- run --message "hello"
Omit --message or use repl for an interactive stdin session:
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- repl
config.toml shape
config init creates editable starter files:
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- config init
It writes:
$IRONCLAW_REBORN_HOME/config.toml$IRONCLAW_REBORN_HOME/providers.json
A minimal configured model route looks like:
[llm.default]
provider_id = "openai"
model = "gpt-5-mini"
api_key_env = "OPENAI_API_KEY"
config.toml may also include optional sections such as [boot],
[identity], [runner], and [skills]; config init writes commented
guidance for the supported fields.
If config.toml is missing, the first stateful runtime start through run,
repl, or serve seeds a sparse file with api_version and the safe
local-dev boot profile. Read-only commands and run --dry-run stay
side-effect-free. One-off environment selections such as
IRONCLAW_REBORN_PROFILE=local-dev-yolo are not persisted into the seeded
file.
Important: api_key_env is the name of an environment variable, not the secret
itself. Reborn rejects inline secret-shaped values in config.toml and
providers.json.
Production storage uses the same env-only pattern. A production Reborn config may name the PostgreSQL URL variable, but must not contain the raw URL:
[storage]
backend = "postgres"
url_env = "IRONCLAW_REBORN_POSTGRES_URL"
secret_master_key_env = "IRONCLAW_REBORN_SECRET_MASTER_KEY"
# Optional; defaults to 2. Keep below the PostgreSQL server or managed
# session-pool cap after reserving capacity for restarts and operator sessions.
pool_max_size = 2
[policy]
deployment_mode = "hosted_multi_tenant"
default_profile = "secure_default"
Set IRONCLAW_REBORN_POSTGRES_URL in the process environment, and set
IRONCLAW_REBORN_SECRET_MASTER_KEY to independent cryptographic key material.
Managed remote PostgreSQL providers must use TLS, for example by appending
sslmode=require.
Production run also requires an explicit [policy] section. The first
production launch slice supports runtime policies that do not require a
tenant-sandbox process binding.
Once [llm.default] exists, that config selects the provider. LLM_BACKEND is
only an env fallback when no default LLM slot is configured. To switch providers
after writing config, use models set-provider <provider> or edit
[llm.default].provider_id.
Env-only model selection
If $IRONCLAW_REBORN_HOME/config.toml is absent or has no [llm.default],
Reborn can resolve the LLM from environment variables. A sparse first-run
seeded config does not include [llm.default], so env-only model selection
continues to work:
export IRONCLAW_REBORN_HOME="$PWD/.reborn-env-only"
export LLM_BACKEND=openai
export OPENAI_API_KEY="sk-..."
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- run --message "hello"
Common provider env vars:
| Provider | Selector | Required env |
|---|---|---|
| OpenAI | LLM_BACKEND=openai |
OPENAI_API_KEY; optional OPENAI_MODEL, OPENAI_BASE_URL |
| Anthropic | LLM_BACKEND=anthropic |
ANTHROPIC_API_KEY; optional ANTHROPIC_MODEL, ANTHROPIC_BASE_URL |
| OpenAI-compatible | LLM_BACKEND=openai_compatible |
LLM_BASE_URL; optional LLM_API_KEY, LLM_MODEL |
| OpenRouter | LLM_BACKEND=openrouter |
OPENROUTER_API_KEY; optional OPENROUTER_MODEL |
| Ollama | LLM_BACKEND=ollama |
no key; optional OLLAMA_BASE_URL, OLLAMA_MODEL |
| Codex auth | LLM_BACKEND=openai_codex |
LLM_USE_CODEX_AUTH=true or CODEX_AUTH_PATH; optional OPENAI_CODEX_MODEL |
Use models list <provider> to see the exact provider metadata compiled into
the current branch.
Startup variables
| Variable | Purpose |
|---|---|
IRONCLAW_REBORN_HOME |
Absolute Reborn state root. Defaults to $HOME/.ironclaw/reborn. The resolver rejects unsafe paths and v1 state-root aliases such as $HOME/.ironclaw. |
IRONCLAW_REBORN_PROFILE |
Boot profile selector. Supported values: local-dev, local-dev-yolo, production, migration-dry-run. |
IRONCLAW_REBORN_POSTGRES_URL |
Production PostgreSQL storage URL when [storage].backend = "postgres" and [storage].url_env names this variable. Keep it out of config.toml; remote providers must use TLS. |
IRONCLAW_REBORN_POSTGRES_POOL_MAX_SIZE |
Optional override for the Reborn PostgreSQL client pool size. Use this when a managed provider enforces a small session-pool cap. |
IRONCLAW_FILESYSTEM_POSTGRES_MIGRATION_CONNECT_MAX_WAIT_SECS |
Optional startup wait window for Postgres filesystem migration connection retries. Defaults to 300 seconds. |
IRONCLAW_REBORN_SECRET_MASTER_KEY |
Production Reborn secret master key when [storage].secret_master_key_env names this variable. Keep it independent from the database URL and out of config.toml. |
IRONCLAW_REBORN_LOG |
Tracing filter for the Reborn binary, for example debug,ironclaw_reborn=trace. |
run and repl currently support local-dev and local-dev-yolo runtime
composition. local-dev-yolo grants trusted-laptop host access and must be
confirmed explicitly:
export IRONCLAW_REBORN_PROFILE=local-dev-yolo
cargo run -q -p ironclaw_reborn_cli --bin ironclaw-reborn -- repl --confirm-host-access
WebUI service
The Reborn WebUI is compiled behind the webui-v2-beta Cargo feature. Build or
run the binary with that feature to enable the serve command:
cargo run -q -p ironclaw_reborn_cli --features webui-v2-beta --bin ironclaw-reborn -- serve --help
cargo build -p ironclaw_reborn_cli --features webui-v2-beta --bin ironclaw-reborn
The WebUI listener defaults to 127.0.0.1:3000. The service requires an
env-bearer token and a user id at startup. It also needs the model route from
the earlier section, including that provider's credential env var:
export IRONCLAW_REBORN_HOME="$PWD/.reborn-home"
export OPENAI_API_KEY="sk-..." # or the required env var for your configured provider
export IRONCLAW_REBORN_WEBUI_TOKEN="$(openssl rand -hex 32)"
export IRONCLAW_REBORN_WEBUI_USER_ID="reborn-cli"
cargo run -q -p ironclaw_reborn_cli --features webui-v2-beta --bin ironclaw-reborn -- serve
Equivalent config.toml listener configuration:
[webui]
listen_host = "127.0.0.1"
listen_port = 3000
env_token_var = "IRONCLAW_REBORN_WEBUI_TOKEN"
env_user_id_var = "IRONCLAW_REBORN_WEBUI_USER_ID"
allowed_origins = ["http://127.0.0.1:3000", "http://localhost:3000"]
canonical_host = "127.0.0.1:3000"
env_token_var and env_user_id_var are env-var names. Keep the actual token
and user id in the environment.
Required WebUI env vars:
| Variable | Purpose |
|---|---|
IRONCLAW_REBORN_WEBUI_TOKEN |
Bearer token for WebUI requests. If SSO is enabled, this also signs sessions and must be at least 32 bytes. |
IRONCLAW_REBORN_WEBUI_USER_ID |
Reborn owner/user id for env-bearer requests. If [identity].default_owner is configured, it must match this value. |
Optional WebUI OAuth env vars:
| Variable | Purpose |
|---|---|
IRONCLAW_REBORN_WEBUI_BASE_URL |
Public base URL used for WebUI login and product-auth OAuth callbacks. Non-loopback deployments must use https://. |
IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_ID |
Enables Google SSO when set. |
IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_SECRET |
Required when Google SSO is enabled. |
IRONCLAW_REBORN_WEBUI_GOOGLE_ALLOWED_HD |
Optional Google hosted-domain restriction. |
IRONCLAW_REBORN_WEBUI_GITHUB_CLIENT_ID |
Enables GitHub SSO when set. |
IRONCLAW_REBORN_WEBUI_GITHUB_CLIENT_SECRET |
Required when GitHub SSO is enabled. |
IRONCLAW_REBORN_WEBUI_ALLOWED_EMAIL_DOMAINS |
Required when any SSO provider is enabled. Comma-separated verified email domains. |
IRONCLAW_REBORN_WEBUI_OAUTH_HTTP_TIMEOUT_SECS |
Optional OAuth HTTP timeout override. |
For Google SSO, create a Google OAuth web client and register the Reborn WebUI redirect URI as:
{IRONCLAW_REBORN_WEBUI_BASE_URL}/auth/callback/google
For example, with IRONCLAW_REBORN_WEBUI_BASE_URL=https://ironclaw.example.com,
the authorized redirect URI in Google Cloud is:
https://ironclaw.example.com/auth/callback/google
Notion MCP and other product-auth OAuth setup flows use the same public WebUI
base URL when registering provider callback URLs. Do not include a trailing
slash in IRONCLAW_REBORN_WEBUI_BASE_URL; Reborn trims it before building
callback URLs. If the base URL is omitted, Reborn uses the actual listener
address, such as http://127.0.0.1:3000, which is suitable only for
loopback/local OAuth testing. Public or non-loopback OAuth deployments must set
an https:// base URL.
Complete Google SSO startup env:
export IRONCLAW_REBORN_HOME="/var/lib/ironclaw-reborn"
export IRONCLAW_REBORN_PROFILE=local-dev
export OPENAI_API_KEY="sk-..." # or the required env var for your configured provider
export IRONCLAW_REBORN_WEBUI_TOKEN="$(openssl rand -hex 32)"
export IRONCLAW_REBORN_WEBUI_USER_ID="reborn-cli"
export IRONCLAW_REBORN_WEBUI_BASE_URL="https://ironclaw.example.com"
export IRONCLAW_REBORN_WEBUI_ALLOWED_EMAIL_DOMAINS="example.com,team.example.com"
export IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_ID="..."
export IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_SECRET="..."
cargo run -q -p ironclaw_reborn_cli --features webui-v2-beta --bin ironclaw-reborn -- serve --host 0.0.0.0 --port 3000
IRONCLAW_REBORN_WEBUI_ALLOWED_EMAIL_DOMAINS is the actual admission
allowlist. Google hd is only an optional provider-side hosted-domain hint; do
not rely on it instead of the Reborn allowed-domain list. IRONCLAW_REBORN_HOME
selects the state/config root for this service. IRONCLAW_REBORN_PROFILE
defaults to local-dev; local-dev-yolo grants trusted-laptop host access and
cannot be served on a non-loopback host.
Use serve --host <ip> --port <port> to override the listener from the CLI.
Binding to a non-loopback host is production-sensitive. local-dev-yolo serve
mode also requires --confirm-host-access and refuses non-loopback hosts.
Slack service
Slack support is compiled behind the slack-v2-host-beta Cargo feature. That
feature includes webui-v2-beta, so Slack runs on the same serve command:
export IRONCLAW_REBORN_HOME="$PWD/.reborn-home"
export OPENAI_API_KEY="sk-..." # or the required env var for your configured provider
export IRONCLAW_REBORN_WEBUI_TOKEN="$(openssl rand -hex 32)"
export IRONCLAW_REBORN_WEBUI_USER_ID="reborn-cli"
export IRONCLAW_REBORN_SLACK_SIGNING_SECRET="..."
export IRONCLAW_REBORN_SLACK_BOT_TOKEN="xoxb-..."
cargo run -q -p ironclaw_reborn_cli --features slack-v2-host-beta --bin ironclaw-reborn -- serve
Slack env vars alone do not enable Slack. Add a [slack] section to
config.toml:
[slack]
enabled = true
installation_id = "install-alpha"
team_id = "T123"
api_app_id = "A123"
# slack_user_id = "U123" # optional legacy static user mapping
# user_id = "reborn-cli" # defaults to the WebUI authenticated user
signing_secret_env = "IRONCLAW_REBORN_SLACK_SIGNING_SECRET"
bot_token_env = "IRONCLAW_REBORN_SLACK_BOT_TOKEN"
Required Slack settings and env vars:
| Name | Purpose |
|---|---|
[slack].enabled = true |
Mounts the Slack route during serve. |
[slack].installation_id |
Stable local installation id. |
[slack].team_id |
Slack workspace/team id. |
[slack].api_app_id |
Slack app id. |
IRONCLAW_REBORN_SLACK_SIGNING_SECRET |
Slack request signing secret, or the env var named by [slack].signing_secret_env. |
IRONCLAW_REBORN_SLACK_BOT_TOKEN |
Slack bot token, or the env var named by [slack].bot_token_env. |
More detailed command notes live in docs/reborn-binary.md.
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.92+
- PostgreSQL 15+ with pgvector extension
- NEAR AI account (authentication handled via setup wizard)
libclangand a working C toolchain if you build the WeChat voice/SILK path from source
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.
Optional: WeChat voice notes (
audio/silk) require the standaloneironclaw-silk-decoderhelper to be transcribable. It's excluded from the default workspace build becausesilk-codecpulls inbindgen/libclang. Build it separately with./crates/ironclaw_silk_decoder/build.sh(needs libclang + a C toolchain) and put the resulting binary on$PATH, beside theironclawbinary, or pointed at byIRONCLAW_SILK_DECODER. Without it, voice messages are still delivered — just as rawaudio/silkblobs.
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 |
Legacy IronClaw Usage
Engine v2 is opt-in right now. If you want to run the new engine instead of the legacy agent loop, start IronClaw with ENGINE_V2=true. See Engine v2 architecture for more details.
# First-time setup (configures database, auth, etc.)
ironclaw onboard
# Start interactive REPL
cargo run
# Start interactive REPL with engine v2
ENGINE_V2=true cargo run
# Engine v2 with debug logging
ENGINE_V2=true 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.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.
