Skip to content

Autonomous Skill Drafting

Wayland Core observes every completed turn and, when the same task shape succeeds three times in a row, drafts a reusable skill automatically. The resulting draft is written to disk, registered in the running session, and persisted to the PromptStore so the next session’s skill router starts with prior knowledge of it.

This is the in-session self-improvement path, separate from the offline GEPA evolutionary optimizer described in Self-Evolution (GEPA). The two complement each other: drafting crystallizes a new skill from repeated work; GEPA mutates and scores existing skills to find better phrasings.

The U6 loop is implemented in crates/wcore-agent/src/auto_skill/ across three files: recorder.rs, bucketer.rs, and drafter.rs. The engine calls into this loop at the end of every AgentEngine::run() call (bootstrap.rs:1642).

At the end of each turn, the engine builds a TurnTrajectory value (recorder.rs):

FieldWhat it holds
user_inputThe raw text of the user’s request for this turn
picked_skillWhich skill the SkillRouter chose, if any
outcomeSuccess (natural EndTurn or ToolUse) or Failure (MaxTurns, error, abort)
summaryShort description such as “3 turns”, used later in the draft body
timestampUTC timestamp at turn end

The struct is cheap to construct: no I/O, no locks. The engine hands it straight to the bucketer.

The Bucketer (bucketer.rs) groups trajectories by a normalized signature derived from the user input. The signature algorithm:

  1. Lowercase the input.
  2. Tokenize on whitespace and ASCII punctuation.
  3. Drop tokens shorter than 2 characters and tokens in a fixed English stopword list.
  4. Deduplicate, preserving first-seen order.
  5. Take the top 3 tokens by length (ties broken by first-seen order).
  6. Sort alphabetically and join with -.

For example, "refactor the code", "code refactor please", and "please refactor the code" all normalize to the same signature because they share the same top-3 content words. This means the streak accumulates across phrasings of the same underlying task.

An empty signature (all stopwords, very short input) is ignored silently.

A Failure outcome drops the bucket for that signature entirely, resetting the streak to zero.

When a bucket accumulates 3 consecutive successes on the same signature, the bucketer emits a DraftTrigger containing the signature string and the three trajectories. The bucket is then cleared so the next success starts a fresh streak rather than immediately re-triggering.

The threshold is N=3, set at the call site in bootstrap.rs.

SkillDrafter::draft() (drafter.rs) takes the DraftTrigger and writes to two locations:

Primary (loader-visible):

<config_dir>/wayland-core/skills/auto-<signature>/SKILL.md
<config_dir>/wayland-core/skills/auto-<signature>/manifest.json

This path matches user_skills_dir() in wcore-skills::paths, so the skill loader picks it up on next boot without any additional configuration.

Secondary (legacy fallback, best-effort):

$WAYLAND_HOME/skills/auto/<signature>/SKILL.md

A failure on the secondary path is logged at WARN but does not abort the draft. A failure on the primary path does bubble up.

The manifest.json written alongside the skill contains:

{
"name": "auto-<signature>",
"auto_drafted": true,
"drafted_at": "<RFC3339 timestamp>",
"signature": "<signature>",
"evidence_count": 3,
"needs_review": true,
"score": 0.7,
"scorer": "auto_drafter"
}

The SKILL.md body includes a notice about the draft status, the signature, the evidence count, and up to three example inputs from the triggering trajectories.

Immediately after writing to disk, SkillDrafter calls register_bundled_skill (wcore_skills::bundled) to add the draft to the current session’s skill catalog. The strings are Box::leak-ed intentionally: plugin lifetime equals process lifetime, and the leak is bounded to one per drafted skill. The result is that the current session can invoke the skill by name without waiting for a restart.

If the engine has a database connection, the drafter inserts an EvolvedPrompt row into the evolved_prompts SQLite table with scorer: "auto_drafter" and score: 0.7. A PromptStore failure is logged at WARN but does not prevent the on-disk draft from landing.

The score of 0.7 translates to 4 simulated successes via the seed_pairs_for formula (score × 5, rounded). This places the draft ahead of cold-start skill arms without overriding proven GEPA winners, which are seeded first at bootstrap.

At bootstrap, the SkillRouter is seeded in layered order (bootstrap.rs:1253–1277):

  1. GEPA bench winners (scorer = "bench", limit 1 per skill): highest priority.
  2. Auto-drafted skills (scorer = "auto_drafter", limit 1 per skill): seeded next, so real GEPA wins take precedence when both exist for the same skill name.
  3. Prioritizer head-start: fills arms that neither GEPA nor auto-draft touched.

SkillDrafter is only installed when a real database handle is present (bootstrap.rs:1663). Without a database, drafts still land on disk but are not seeded into the router on the next boot.

Drafted skills live under <config_dir>/wayland-core/skills/. You can inspect them with:

Terminal window
wayland-core --skills-audit

Promote a draft to active or archive one you do not want:

Terminal window
wayland-core --skills-promote <PROCEDURE_ID>
wayland-core --skills-archive <PROCEDURE_ID>

See Skills Lifecycle for the full audit, promote, and archive workflow.

To edit a draft directly, open the SKILL.md at the path shown in the draft trigger trace and revise the body. Remove the notice block at the top when you are satisfied with the content.

Drafted skills are named auto-<signature>. For example, three successful turns on variations of “refactor this code” might produce a skill named auto-code-refactor-review (the exact name depends on which three content words score longest after stopword stripping).

Because the name is derived from the signature rather than from a UUID, two runs that share the same top-3 content words produce the same skill name. A second draft on the same signature overwrites the prior one on disk but inserts a new row in the PromptStore (each row uses a fresh UUID v4 as its primary key).