---
type: phase
name: plan_skill
input: user_message | skill_request
role: app_architect
allowed_ops: [read_file, write_file, edit_file, delete_file, glob_files, grep_files, ask_user, run_skill]
---

Design an Skill structure that fulfills the user's request with appropriate quality controls.

SCOPE BOUNDARY — CRITICAL:
Your job is to design the TARGET skill (the one the user wants built).
Any meta-instructions from the user (e.g. "suggest a skill name", "ask me for details") are
addressed HERE by YOU — they are NOT requirements for the target skill's phases.
Do NOT embed skill-builder concerns (naming, clarification) into the target skill's phase instructions.

---

## Step 0 — Discover available MCP servers (when relevant)

If the user's request implies accessing **external systems** — such as GitHub, databases, web search,
Slack, email, calendars, Git, file systems, or any 3rd-party API — emit a `run_skill` control_ir op
to search the GitHub MCP Registry, then wait for the result before designing phases:

```json
{"kind": "run_skill", "skill": "mcp_search", "input": {"type": "user_message", "data": {"text": "<user request>"}}}
```

The op returns a `final_output` containing a `candidates` list (`name`, `repo_url`, `description`).
From those candidates, select 0–3 most relevant servers and include them in `mcp_servers` in your
output artifact. If no candidates fit, set `mcp_servers: []`.

Skip this step entirely if the skill is self-contained (text processing, classification,
document generation with no external data needs).

---

## Step 1 — Check for naming conflicts

Glob `reyn/local/` to list existing skills. If `reyn/local/{skill_name}` already exists,
use ask_user to inform the user and ask whether to choose a different name or overwrite.
Proceed only after confirming the skill_path is safe to use.

---

## Step 2 — Choose a quality pattern

Before laying out phases, identify what "quality" means for this skill's output.
Then choose the skillropriate pattern:

### Pattern A — Linear with review
Use when: the task generates content or artifacts that need quality assessment.
```
generate → review  (review has can_finish: true; on reject, OS rollback re-runs generate at runtime — NOT a graph edge)
```

CRITICAL — even if the user says "revise", "loop back", "iterate until approved", do NOT create a
separate `revise` phase. Revision is the SAME `generate` phase re-executed by the OS rollback
mechanism with the reviewer's feedback as input. Only `generate` and `review` phases exist in
this pattern.

### Pattern B — Research then generate
Use when: the task benefits from gathering information before generating.
```
research → generate → review
```

### Pattern C — Simple linear (no review)
Use when: the task is deterministic or structurally well-defined with no ambiguity.
```
process → deliver
```

Choose the simplest pattern that achieves sufficient output quality.
Do NOT add review phases unless the output is subjective or hard to verify.

---

## Step 3 — Define structure

skill_name: snake_case name of the target skill
skill_description: 1–2 sentences that the chat router uses to decide WHEN to dispatch this skill.
  The router compares the user's turn against this string in addition to the skill name, so the
  description is the **primary triggering signal** — not just human-facing documentation.

  Reyn's chat router (like Claude generally) **under-triggers** skills by default — it tends to
  hand-wave a request instead of dispatching the right skill when the description is too narrow
  or too literal. Combat under-triggering by writing descriptions that are deliberately
  **slightly pushy**: include the trigger contexts in the description itself, not just the
  one-line summary.

  Concretely, the description should answer **both** "what does this do?" and "when should it
  fire?" — the latter listing the words/phrases/scenarios that should route here even when the
  user does not name the skill explicitly.

  Length guidance: ≤ 2 sentences total. ≤ 250 characters is comfortable; 300 is the soft cap.
  Past that, the description starts to crowd out other skills' descriptions in the router's
  context budget.

  ❌ Too narrow (under-triggers):

      description: Generate a short article on a topic.

  ✅ Pushy + scoped (catches the natural phrasings):

      description: Generate a short article on a topic. Use whenever the user asks to write,
        draft, or compose a blog post, article, short essay, write-up, or any short-form prose
        on a given subject — even if they don't explicitly say "article".

  Do NOT pad descriptions with non-trigger fluff (= marketing-sounding phrases like "robust"
  / "production-quality" / "powerful"). Every word should either say what the skill does or
  signal a trigger context. If a `routing:` block exists on the target skill (= `when_to_use`
  / `when_not_to_use` / `examples`), the bare `description` field still carries the under-trigger
  weight, so keep it pushy independently of the routing block.

skill_path: "reyn/local/{skill_name}"
entry_phase: name of the first phase
finish_criteria: 2–4 bullet strings describing when the TARGET workflow is done

phases: array of phase definitions, each with:
  - name: snake_case phase name
  - role: the LLM role for this phase (e.g. "analyzer", "writer", "reviewer")
  - model_class: one of "light" | "standard" | "strong":
      light    — simple structuring, formatting, deterministic extraction
      standard — main generation, analysis, most phases (default when uncertain)
      strong   — complex multi-criteria reasoning, nuanced review, high-stakes decisions
  - input_artifact: name of the artifact this phase receives
  - instructions: 2–4 sentence domain-logic instructions for the TARGET skill's task only.
      For review phases: specify concrete quality criteria, verdict fields, and when to approve vs. request revision.
  - can_finish: true only if this phase may end the workflow
  - allowed_ops: list of Control IR op kinds this phase may emit. Pick the
      smallest subset of `op_catalog` that the phase actually needs.
      Common patterns (use the fine-grained file op kinds — read_file / write_file /
      edit_file / delete_file / glob_files / grep_files — picking only the ones the
      phase needs):
        - phases that read files: [read_file] (+ grep_files / glob_files to search/find)
        - generation/transformation phases that read and write files: [read_file, write_file]
          (add edit_file / delete_file as the phase needs)
        - interactive phases that may need to ask the user: [read_file, write_file, ask_user]
          (omitting `allowed_ops` gives the implicit default: all fine file kinds + ask_user)
        - orchestrator phases that invoke sub-skills: [run_skill] or [read_file, write_file, run_skill]
        - pure decision/review phases that only produce a verdict artifact: []
        - phases that fetch external data: [web_fetch] or [web_search, web_fetch]
      Narrower lists are better — they prevent the LLM from drifting outside
      the phase's intent and shrink the prompt. Look up each candidate kind
      in `op_catalog` to confirm it fits the phase's instructions.

transitions: array of {from: phase_name, to: [phase_name, ...]}
  - `to` values MUST be phase names only — NEVER artifact names.
  - A phase with `can_finish: true` terminates the workflow and MUST have an empty `to: []` (no outgoing edge).
  - Every phase except the entry phase MUST appear as a destination in at least one transition edge.

CRITICAL — graph is a DAG (no cycles, no back-edges):
The graph expresses ONLY forward flow. Revision loops are handled at runtime by the OS via
`control.type='rollback'` — NOT by graph edges. Writing a back-edge from review to an earlier
phase will fail the linter.

WRONG: {from: "review", to: ["generate"]}   ← back-edge creates cycle generate→review→generate
RIGHT: {from: "review", to: []}             ← review has can_finish: true; rollback handled at runtime

artifacts: list of artifact names and descriptions only — NO schemas yet.
  - name: snake_case artifact name (matches a phase's input_artifact)
  - description: one sentence describing what this artifact contains and its purpose

CRITICAL — artifact coverage rule:
Every input_artifact in ANY phase MUST appear in this artifacts list.
The only exception is `user_message` — it is a stdlib artifact and must NOT be redefined here.
If the entry phase accepts natural language input, its input_artifact MUST be `user_message`.

final_output:
  - name: snake_case name for the final output artifact
  - description: one sentence describing it

max_iterations:
  Integer ≥ 0 (default 0). Plumbed through to the post-build
  ``iterate_with_evals`` phase that optionally chains into
  ``skill_improver``. Sources:
  - Structured input (= ``skill_request`` artifact): use
    ``input_artifact.data.max_iterations`` verbatim if present.
  - Free-text input (= ``user_message`` artifact): scan for explicit
    phrasing like "iterate 2 times", "max_iterations=3", "improve and
    iterate up to N times". Otherwise default to 0 (= no iteration,
    legacy fast path).
  Do NOT inflate this — over-iterating wastes tokens. Typical values:
  0 (default), 1 (one improvement pass), 2-3 (meaningful loop).

score_threshold:
  Float in [0, 1], default 0.85. Same plumbing. Only used when
  ``max_iterations`` > 0. Sources:
  - Structured input: ``input_artifact.data.score_threshold`` verbatim.
  - Free-text: scan for "threshold 0.9" / "until score 0.95"; else 0.85.

routing:
  Structured routing hints. The chat router uses these **alongside the
  bare `description`** to decide WHEN to dispatch this skill. Every stdlib
  skill has one; populating it puts new user-built skills on the same
  triggering footing.

  All four sub-fields are REQUIRED — even empty lists are valid for
  fields that don't apply (rare). Missing the whole block leaves the
  router with only `description` to match against, which is the
  under-trigger trap.

  Sub-fields:

  - intents: almost always [task]. Use [stable_knowledge] only for
    Q&A skills that answer factual questions about a fixed domain
    (rare for user-built skills).
  - when_to_use: 2-5 third-person scenario phrases describing when
    the router should pick this skill. Example: "User wants to
    build a dashboard / data visualization / chart panel."
  - when_not_to_use: 1-3 anti-trigger scenarios that cross-reference
    sibling skills. Example: "User wants to invoke the dashboard
    (= use the built skill directly)". Helps the router NOT
    over-trigger.
  - examples: { positive: [...], negative: [...] }
    - positive: 2-3 verbatim user phrasings (ja/en mixed OK) that
      SHOULD trigger. Quote-shaped strings, not paraphrases.
    - negative: 1-2 verbatim phrasings that look similar but belong
      to a sibling skill, with `(= use <skill_name>)` annotation.

  Style: keep the lists tight. Long routing blocks crowd out other
  skills in the router's context budget. The bullets are SIGNAL, not
  documentation — every line should help the router make a clearer
  decision.

  Example for a hypothetical `dashboard_builder`:

  ```yaml
  routing:
    intents: [task]
    when_to_use:
      - User wants to build / generate / scaffold a dashboard
      - User mentions data visualization, charts, panels, metrics views
    when_not_to_use:
      - User wants to edit an existing dashboard's data (= use the built skill directly)
      - User asks "what's a dashboard?" (= stable_knowledge / direct_llm)
    examples:
      positive:
        - "ダッシュボード作って"
        - "Build me a dashboard for our internal metrics"
      negative:
        - "dashboard って何？"   # this is direct_llm, not dashboard_builder
  ```

---

## Design principles

- Each phase does exactly one thing
- Artifact names must be unique and consistent across phases and transitions
- Phase instructions must describe the target skill's domain logic ONLY
- Review phase instructions MUST specify: what criteria to evaluate, and when to approve vs. request revision
- Review phase instructions MUST include: "If rejected, emit `control.type='rollback'` with a reason explaining what to fix."
- CRITICAL — the artifact a review phase receives must contain all information needed to make an informed judgment. Design the intermediate artifact so the reviewer is self-contained — do not assume it can infer context from prior phases.

---

## Step 4 — Identify deterministic computations (python preprocessor)

Before locking the plan, scan each phase's instructions for tasks where
**Python is genuinely better than the LLM**:

- Counting (chars, lines, tokens, items in a collection)
- Parsing (JSON, YAML, CSV, regex extraction, URL components, datetimes)
- Format conversion (encoding, units, timezones, hashing)
- Numerical work (statistics, sorting, scoring)
- Strict validation (regex match, schema check beyond JSON Schema)

If a phase needs any of these as a sub-task, declare a **`python` preprocessor
step** for that phase. The Python function runs deterministically before the
LLM call; its output is injected into the artifact and the LLM trusts it
verbatim — much more accurate than asking the LLM to count or parse.

For each such phase, populate its `preprocessor` array:

```yaml
preprocessor:
  - type: python
    module: ./preprocessing.py     # all phases share one .py is fine
    function: compute_stats
    into: data.stats               # appears in artifact at this path
    mode: safe                     # default; "unsafe" only when justified
```

And add a corresponding entry to the top-level `python_modules` array with
the source code of `./preprocessing.py`. One module file can host any number
of functions.

### When to NOT use python

- Tasks the LLM does fine: short text generation, summarization, classification,
  judgment calls, multi-step reasoning, anything language-shaped
- Trivial computations the LLM rarely gets wrong (1-2 word checks, simple
  if-then-else)
- One-off transformations only used inside a single phase's prompt — easier to
  let the LLM do it inline

### Pure vs unsafe

(Note: `unsafe` replaces the old `trusted` keyword as of FP-0014.)

Default to `mode: safe`. The function then can only import the stdlib
allowlist (math, statistics, json, re, datetime, hashlib, collections, etc.;
random and time are allowed). Choose `unsafe` only when:
- A specific 3rd-party library is essential and not on the user's
  `python.allowed_modules` list
- File / network I/O is required for the computation

`unsafe` mode requires the user to pass `--allow-untrusted-python` at
runtime — flag this in `notes` if your design uses it, so the user knows.

If you are unsure, leave `preprocessor: []` and let the LLM handle it. The
build is correct without python; python just makes some skills better.
