Drop DocType::Playbook variant and all references — playbook extraction mission was already renamed to skill extraction in the previous session. Updates CLAUDE.md, architecture docs, context builder, retrieval weights, mission comments, and store adapter path mapping. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
9.0 KiB
Self-Improving Engine
This document describes how IronClaw improves itself at runtime — fixing bugs, evolving prompts, and patching its own execution loop without a Rust rebuild.
The Problem
During development, 5 consecutive debugging sessions revealed the same pattern:
- A thread runs and hits a bug (wrong tool name, bad output format, UTF-8 crash)
- The LLM tries to work around it but can't fix the Rust code
- A human reads the trace, identifies the root cause, edits Rust, rebuilds
- The fix takes effect on the next run
Every step of this loop is something the engine can do. The key insight: if the orchestration layer were Python (not Rust), the engine could fix its own bugs at runtime.
Architecture
Three Self-Improvement Levels
| Level | What Changes | Risk | Who Approves | Mechanism |
|---|---|---|---|---|
| 1: Prompt | System prompt rules | Low | Auto | MemoryDoc overlay appended to compiled preamble |
| 1.5: Orchestrator | Python execution loop | Medium | Auto (3-failure rollback) | Versioned MemoryDoc, loaded at thread start |
| 2: Config | Engine defaults, constants | Medium | Auto if tests pass | Git branch + cargo test |
| 3: Code | Rust source in engine/bridge | High | Human via PR | Proposed, not applied |
The Self-Improvement Mission
A built-in Mission with OnSystemEvent cadence fires when threads complete with issues:
Thread completes → Trace analysis (8 issue categories)
→ Reflection (produces Lesson/Spec/Issue docs)
→ Emit "thread_completed_with_issues" event
↓
MissionManager event listener
↓
Self-improvement Mission fires
↓
Mission thread (CodeAct, all tools)
├── Reads trigger payload (trace issues + error messages)
├── Checks fix pattern database for known solutions
├── Diagnoses root cause (PROMPT / ORCHESTRATOR / CONFIG / CODE)
└── Applies fix at appropriate level
Trigger Payload
The event listener builds a JSON payload containing:
{
"source_thread_id": "uuid",
"goal": "what the thread was trying to do",
"issues": [
{"severity": "Error", "category": "tool_error", "description": "Tool web_search not found", "step": 1}
],
"error_messages": ["web_search: no lease for action 'web_search'"],
"reflection": {"docs_produced": 3, "doc_types": ["Summary", "Lesson", "Spec"]}
}
The self-improvement thread receives this as state["trigger_payload"] and uses it to diagnose and fix the issue.
Level 1: Prompt Evolution
The system prompt is built from:
- Compiled preamble (
include_str!fromprompts/codeact_preamble.md) — never modified at runtime - Runtime overlay (MemoryDoc with tag
prompt_overlay) — appended after the preamble - Tool documentation — dynamically generated from available actions
- Compiled postamble — strategy section
The self-improvement thread can append rules to the overlay:
# In the self-improvement thread:
memory_write(
title="prompt:codeact_preamble",
content="9. Never call web_fetch — use http() instead.\n10. Always access state dict for prior results.",
tags=["prompt_overlay"]
)
The overlay is capped at 4000 characters. Next thread picks up the updated prompt.
Level 1.5: Orchestrator Patches
The execution loop itself is Python code stored as a versioned MemoryDoc:
v0 (compiled-in default.py)
→ v1 (self-improvement fix: better output formatting)
→ v2 (self-improvement fix: tool name alias)
→ v3 (bad fix, causes crashes)
↑ auto-rollback after 3 failures → back to v2
Versioning
Each orchestrator version is a MemoryDoc:
- Title:
orchestrator:main - Tag:
orchestrator_code - Metadata:
{"version": N, "parent_version": N-1}
Loading priority: highest version number wins. If the latest version has 3+ consecutive failures (tracked via orchestrator:failures doc), it's skipped and the previous version is loaded.
Auto-Rollback
Thread starts → load_orchestrator() checks failure tracker
├── Latest version has < 3 failures → use it
├── Latest version has >= 3 failures → skip, try previous
└── All versions failed → use compiled-in v0
Thread succeeds → reset failure counter
Thread fails → increment failure counter for current version
What the Orchestrator Controls
The Python orchestrator handles all the "glue" between the LLM and tools:
- Tool dispatch: How function calls are resolved and executed
- Output formatting: How tool results are presented to the LLM
- State management: How variables persist across code steps
- Truncation: How large outputs are compacted
- FINAL() extraction: How termination signals are parsed
- Nudge detection: When to prompt the LLM to write code instead of describing
These are exactly the functions that had bugs during development (wrong tool names, JSON double-serialization, UTF-8 panics, missing state). Now they can be fixed at runtime.
Level 2: Configuration Tuning
The self-improvement thread can create git branches and modify engine defaults:
# In the self-improvement thread:
shell("git checkout -b self-improve/increase-truncation")
read_file("crates/ironclaw_engine/src/executor/scripting.rs")
apply_patch(...)
result = shell("cargo test -p ironclaw_engine")
if "test result: ok" in result:
shell("git commit -am 'Increase output truncation to 12000 chars'")
else:
shell("git checkout main")
Level 3: Code Patches
For Rust bugs in the engine or bridge, the self-improvement thread describes the fix but does not apply it directly. The recommendation appears in the thread's FINAL() response and in the mission's approach_history.
Fix Pattern Database
A Note MemoryDoc maps known trace symptoms to fix strategies:
| Trace Pattern | Fix Strategy | Location |
|---|---|---|
| Tool X not found | Add name alias or prompt hint | prompt overlay or effect_adapter |
| TypeError: str indices must be integers | Parse JSON before wrapping | output conversion |
| NameError: name 'X' not defined | Add prompt hint about state dict | prompt overlay |
| byte index N is not a char boundary | Replace byte slicing with chars() | string truncation |
| Model calls nonexistent tool | Add prompt rule with correct name | prompt overlay |
| Model ignores tool results | Improve output metadata format | orchestrator |
| Excessive steps (>5) for simple task | Add prompt rule or fix tool schema | prompt overlay |
| Code error in REPL output | Add prompt hint about correct API | prompt overlay |
The database grows over time — after successfully fixing an issue, the self-improvement thread adds a new pattern entry.
Safety Boundaries
Hard boundaries (never auto-modify):
- Security-sensitive code (safety layer, policy engine, leak detection)
- Database schemas / migrations
- Test files (never weaken tests to make a fix pass)
- Files outside
crates/ironclaw_engine/andsrc/bridge/without human approval
Orchestrator safety:
- Auto-rollback after 3 consecutive failures
- Compiled-in v0 always available as last resort
- Each version tracked with parent_version for audit trail
- Resource limits (5min timeout, 128MB memory) on orchestrator VM
Creating the Self-Improvement Mission
On engine init (src/bridge/router.rs), ensure_self_improvement_mission() is called. It:
- Checks if a self-improvement mission already exists for the project
- If not, creates one with
OnSystemEvent { source: "engine", event_type: "thread_completed_with_issues" } - Seeds the fix pattern database with known patterns
- Starts the event listener (
start_event_listener())
The mission is capped at 5 threads per day (max_threads_per_day: 5).
Key Files
| File | Purpose |
|---|---|
crates/ironclaw_engine/orchestrator/default.py |
The v0 orchestrator (self-modifiable) |
crates/ironclaw_engine/src/executor/orchestrator.rs |
Loading, versioning, rollback, host functions |
crates/ironclaw_engine/src/executor/prompt.rs |
Prompt overlay loading |
crates/ironclaw_engine/src/runtime/mission.rs |
Self-improvement mission, OnSystemEvent wiring, fix patterns |
docs/plans/2026-03-23-self-improving-engine.md |
Original design doc |
docs/plans/2026-03-25-python-orchestrator.md |
Python orchestrator design doc |
Debugging Self-Improvement
Enable trace logging to see the self-improvement loop in action:
ENGINE_V2=true ENGINE_V2_TRACE=1 RUST_LOG=ironclaw_engine=debug cargo run
Look for:
"loaded runtime orchestrator"— which version was loaded"orchestrator version has too many failures, skipping"— rollback in action"self-improvement: updated prompt overlay"— Level 1 fix applied"event listener: failed to fire self-improvement"— event listener errorsSelfImprovementStarted/SelfImprovementCompleteevents in traces