---
type: skill
name: skill_improver
description: "Iterate a skill to improve it: repeatedly run eval, plan DSL changes, apply them, and re-evaluate until a score threshold is met. Output: revised skill version. Does NOT just score — modifies the skill."
entry: prepare
final_output: improvement_result
final_output_description: |
  Outcome of the improver loop — score progression, files modified, and the
  reason the loop terminated (threshold met, max iterations, regression, etc.).
finish_criteria:
  - The improvement loop has run at least one iteration and terminated
  - improvement_result records the score history and a termination reason
  - If score_threshold_met, improved files have been copied back to the original skill directory
  - The user has a concrete next-step command to verify the result
graph:
  prepare: [collect_traces, copy_to_work]
  collect_traces: [copy_to_work]
  copy_to_work: [run_and_eval]
  run_and_eval: [plan_improvements]
  plan_improvements: [apply_improvements]
  apply_improvements: [finalize]
  finalize: []
routing:
  intents: [task]
  when_to_use:
    - User wants to improve / refine / iterate on an existing skill
    - User mentions raising eval score or fixing failing criteria
  when_not_to_use:
    - User wants to build a fresh skill (use skill_builder)
    - User wants only to *evaluate* without modifying (use eval)
    - User wants to generate eval criteria (use eval_builder)
  examples:
    positive:
      - "skill X を改善して"
      - "eval の点数を上げたい"
      - "失敗してる criteria を直して"
    negative:
      - "skill X を eval して"   # this is eval, not improver
      - "新しい skill を作って"   # this is skill_builder, not improver
permissions:
  # copy_to_work reads skill DSL files from the resolved skill directory.
  # Stdlib skills live under src/reyn/stdlib/skills/ which may be outside
  # the project root when running from a worktree (B8-NEW-1). Declare
  # recursive read access for all three skill search paths so startup_guard
  # prompts once and saves approval for the run.
  file.read:
    - path: src/reyn/stdlib/skills
      scope: recursive
    - path: reyn/local
      scope: recursive
    - path: reyn/project
      scope: recursive
    - path: .reyn/events
      scope: recursive
    # R-PURE-MODE Wave 3b: finalize preprocessor step D-1 reads reyn.yaml to
    # extract the self_improvement config (on_propose, max_versions) via the
    # OS file_read op. Replaces the former load_config() unsafe python call.
    - path: reyn.yaml
      scope: single
  recall: allow
  python:
    - module: ./copy_to_work.py
      function: extract_skill_name
      mode: safe
      timeout: 5
    # R-PURE-MODE Class D: pure resolver consuming skill_resolve op output (mode: safe).
    # Receives data._resolved (set by the skill_resolve run_op preprocessor step) and
    # produces the same path dict as the legacy resolve_paths, without any fs I/O.
    # FP-0042 Phase 2.7 (2026-05-23): the legacy unsafe copy_to_work_resolver.py
    # was deleted — its only direct caller was the test suite.
    - module: ./copy_to_work_resolver_pure.py
      function: resolve_paths_from_op
      mode: safe
      timeout: 5
    - module: ./copy_to_work.py
      function: build_copy_plan
      mode: safe
      timeout: 5
    - module: ./copy_to_work.py
      function: build_write_ops
      mode: safe
      timeout: 5
    - module: ./copy_to_work.py
      function: validate_copy
      mode: safe
      timeout: 5
    - module: ./copy_to_work.py
      function: inject_resolved_paths
      mode: safe
      timeout: 5
    # FP-0042 Phase 2.7 (2026-05-23): all skill_improver python steps now
    # run mode: safe. File I/O goes through reyn.safe.file; the
    # .reyn/events/ + .reyn/skill-versions/ writes stay in the default
    # write zone so no explicit file.write declaration is needed.
    - module: ./trace_collector_pure.py
      function: dispatch_traces
      mode: safe
      timeout: 10
    - module: ./trace_collector.py
      function: collect_traces_fallback
      mode: safe
      timeout: 30
    # FP-0006 B: version snapshot (finalize preprocessor). Reads original
    # skill.md, writes .reyn/skill-versions/<name>/v<N>.md, manages the
    # `current` pointer file — all via reyn.safe.file as of FP-0042
    # Phase 2.7. max_versions is read from data._on_propose_config which
    # the file_read + parse_on_propose_config_minimal chain populates.
    - module: ./version_snapshot.py
      function: save_snapshot
      mode: safe
      timeout: 10
    # R-PURE-MODE Wave 3b: pure regex parser for self_improvement subset
    # of reyn.yaml. The file_read step (D-1) passes raw YAML text via
    # data._reyn_yaml_text; this step extracts the two fields
    # (on_propose, max_versions) without any fs I/O.
    - module: ./version_snapshot_pure.py
      function: parse_on_propose_config_minimal
      mode: safe
      timeout: 5
# FP-0016 D: this skill needs no static secrets / OAuth tokens.
required_credentials: []
---

## Overview

Copies the target skill to a temp work directory (`.reyn/skill_improver_work/<name>/`), then iteratively improves it: runs eval, plans DSL changes, applies them, and re-evaluates. On success (`score_threshold_met`), copies the improved files back to the original location. On any other stop condition (regression, stagnation, cap reached), the original skill is left untouched.

`skill_improver` invokes the `eval` and `eval_builder` skills via the `run_skill` Control IR op (no `--allow-shell` needed).

## Phase flow

```
prepare  →  [collect_traces]  →  copy_to_work  →  run_and_eval  →  plan_improvements  →  apply_improvements  →  finalize
                                                          ↑___________________________________|
                                                                 (rollback for next iteration)
```

`collect_traces` is conditional: only inserted when `improvement_source ∈ {traces, both}`.
Default `improvement_source: tests` keeps the existing `prepare → copy_to_work` path.

| Phase | Role | Responsibility |
|-------|------|----------------|
| `prepare` | coordinator | Parses the request, ensures an `eval.md` exists (auto-generates via `eval_builder` if not), picks a test case, initializes session state |
| `collect_traces` | trace_collector | (conditional) Queries the P6 events log via `recall(sources=["events"])` or raw `.reyn/events/*.jsonl` walk; writes a `traces_summary.md` artifact for `plan_improvements` to use alongside test scores |
| `copy_to_work` | workspace_initializer | Globs the target skill's DSL files, copies them to `.reyn/skill_improver_work/<name>/`, and updates `target_skill_root` in the session to the temp path |
| `run_and_eval` | evaluator | Invokes the `eval` stdlib skill via `run_skill`; records the score in `iteration_state` |
| `plan_improvements` | architect | Reads the target's DSL files from the work dir, diagnoses the weakest phase, and proposes minimal DSL changes targeting failing criteria. Adapts strategy from iteration history (regression / stagnation detection) |
| `apply_improvements` | implementer | Writes the proposed changes to the work dir, persists iteration state to `.reyn/improver_state.json`, then either **transitions to finalize** or **rolls back** to `run_and_eval` for iteration N+1 |
| `finalize` | finalizer | On `score_threshold_met`: copies improved files from work dir back to the original skill directory. On any other stop: leaves the original untouched and reports the work dir location for inspection |

The loop is realized via OS rollback: when `apply_improvements` rolls back, the next visit to `run_and_eval` reads the just-modified DSL from the work dir and starts a fresh iteration.

## Loop termination (defense in depth)

`apply_improvements` hands off to `finalize` when **any** of these conditions holds (first match wins):

1. `score >= score_threshold` (default 0.85) — the target is good enough → `finalize` copies files back.
2. `iteration >= max_iterations` (default 3) — hard cap reached → `finalize` skips copy-back.
3. `changes` array is empty — `plan_improvements` signaled "no more useful changes" → `finalize` skips copy-back.
4. **Regression**: latest score < previous iteration's score → `finalize` skips copy-back; original skill is unmodified.
5. **Stagnation**: |latest − previous| < 0.02 (and iteration > 1) → `finalize` skips copy-back.

The OS-level `max_phase_visits` cap (default 25) is the final safety net for any chain that gets stuck.

## Input

Either natural language (auto-wrapped to `user_message`) or a structured `improvement_session` JSON.

```
reyn run skill_improver "Improve reyn/local/my_skill, using its eval.md, max 3 iterations, threshold 0.9"
```

```
reyn run skill_improver '{
  "type": "improvement_session",
  "data": {
    "target_skill_path": "reyn/local/my_skill/skill.md",
    "max_iterations": 3,
    "score_threshold": 0.85,
    "improvement_focus": "review phase rejection logic"
  }
}'
```

Optional fields (FP-0006 Component C — trace-driven mode):

- `improvement_source`: `tests` (default) | `traces` | `both` — source of improvement signal.
  `tests` keeps the existing eval-loop behavior. `traces` or `both` inserts a `collect_traces`
  phase before `copy_to_work` that pulls historical run data from the P6 events log.
- `trace_lookback_runs`: integer (default `20`) — how many recent runs to inspect. Only used
  when `improvement_source` is `traces` or `both`.

If no `eval.md` exists at `<target_skill_root>/eval.md`, `prepare` invokes `eval_builder` to generate one before the loop starts. If `target_skill_path` is missing, `prepare` asks for it via `ask_user`.

## Output

`improvement_result` summarizes the score progression, the union of files modified across iterations, and the termination reason. `termination_reason` is one of:

| Value | Meaning |
|-------|---------|
| `score_threshold_met` | Reached the configured threshold |
| `max_iterations_reached` | Hit the iteration cap |
| `regression_detected` | Score went down — change reverted, loop stops |
| `stagnation_detected` | Two consecutive iterations with no score change |
| `no_more_changes_planned` | `plan_improvements` could not find a productive next change |

Changes are applied to a **temp work directory** (`.reyn/skill_improver_work/<name>/`) and only written back to the original skill when `score_threshold_met`. Inspect `.reyn/improver_state.json` for full iteration history and `.reyn/skill_improver_work/<name>/` for the improved DSL files. Re-run `reyn eval <eval_spec_path>` to independently confirm the improvement.
