krishna-505 a0bd021e31 feat(reborn): skill extraction & self-evolution with activation controls (#5061)
* 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>
2026-06-23 11:18:50 +03: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 | 简体中文 | Русский | 日本語 | 한국어

Reborn Quick StartPhilosophyFeaturesInstallationConfigurationSecurityArchitecture


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)
  • libclang and 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 standalone ironclaw-silk-decoder helper to be transcribable. It's excluded from the default workspace build because silk-codec pulls in bindgen/libclang. Build it separately with ./crates/ironclaw_silk_decoder/build.sh (needs libclang + a C toolchain) and put the resulting binary on $PATH, beside the ironclaw binary, or pointed at by IRONCLAW_SILK_DECODER. Without it, voice messages are still delivered — just as raw audio/silk blobs.

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.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%