FORM NOT VOID, MIND NO CORE

Chapter 7: Procedural Knowledge: The Skill System and Draft Promotion

2026.08.10

An agent needs to know "how to do things" -- but "how to do things" and "what things are" are two different types of knowledge that require different carriers.


Pattern Layer

7.1 Procedural Knowledge vs. Declarative Knowledge

Within an agent's knowledge system, there are two qualitatively different types of knowledge:

Declarative knowledge describes "what something is." Code style conventions, API call signatures, database table structures, project constraints. Declarative knowledge is characterized by the need for precise reference, not "execution."

LLMs themselves are adept at storing declarative knowledge. Model training data contains vast amounts of facts -- language rules, framework documentation, best practices. For project-specific declarative knowledge (e.g., "this project uses nightly Rust"), it can be persisted through system prompt injection or memory tools.

Procedural knowledge describes "how to do something." The steps of a code review, the sequence of a debugging workflow, the checklist to confirm before writing API documentation, the workflow from issue to PR. Procedural knowledge is characterized by being an ordered set of operational steps that requires the LLM to "follow this sequence" in its decision-making.

Procedural knowledge is ill-suited for storage in LLM weights -- because steps may evolve as the project changes, and different projects require different steps. It is also ill-suited for full injection into the system prompt -- not every turn requires the use of all steps.

Skills are the carrier for procedural knowledge. Each Skill file is a complete operational workflow, stored in Markdown format in the skills/ directory. The agent reads it when needed.

Carrier selection principles:

Knowledge typeCarrierRationale
General facts (language rules, documentation)LLM training dataNo extra operation needed
Project-specific factsSystem prompt / memoryPersists across sessions, low cost for full injection
Procedural knowledge (steps, methods)Skill filesIndependently modifiable, loadable on demand, version-trackable

7.2 Three Injection Timings

Skills have three loading timings, corresponding to different usage frequencies and costs:

Full injection (resident registry).

All files under the skills/ directory are scanned by the Registry at startup, and their contents are woven into the system prompt. This means the agent can read all Skill content at the beginning of every turn.

The cost is token budget. Each additional Skill adds several hundred tokens to the system prompt prefix. At the scale of 6 Skills (the size when CodeCoder was being written), this cost is negligible. But if Skills grow to 50 or more, full injection will significantly compress the token budget available for conversation and tool calls.

Mitigation strategy: Skill content can be summarized to 2-3 lines of description before injection, with the full text loaded only when needed. The current CodeCoder approach is full-text injection -- because at the scale of 6 Skills, the complexity of summarization is not worth introducing.

On-demand activation (use_skill).

The agent actively requests loading a Skill via the use_skill tool. On invocation, the System injects the full Skill file text into the current context.

The resolution priority of use_skill is: first check skills/, then check prompts/. If neither has it, return the error "no such skill."

On-demand activation is suitable for Skills used at moderate frequency (a few times a week to several times a day). It avoids the token cost of full injection but adds an explicit step the agent must perform -- the agent needs to remember to call use_skill at the right moment.

Fallback draft (prompts/).

When use_skill cannot find a Skill in skills/, it falls back to the prompts/ directory. prompts/ holds Skills in draft state -- knowledge that has not yet been formalized but is worth trying.

Drafts are not automatically injected into the system prompt at startup. The content loaded via use_skill from prompts/ is behaviorally identical to content loaded from skills/ -- the only difference is loading priority.

7.3 Draft Promotion Design

Promotion from draft to formal Skill is not automatic.

When the agent writes a draft to prompts/ using generate_prompt, it does not automatically enter skills/. Promotion occurs when the agent explicitly calls promote_prompt:

promote_prompt("my-new-skill")
-> Check if prompts/my-new-skill.md exists
-> Check if a file with the same name already exists in skills/ (name collision error)
-> Move file from prompts/ to skills/
-> Registry updates the resident registry table
-> Starting from the next turn, this Skill is automatically injected into the system prompt

After promotion, the file in the drafts directory is deleted -- preventing the inconsistency of the same Skill existing in both directories.

The promotion threshold is not a code quality check -- CodeCoder does not validate the content quality of Skill files. The threshold is that "the agent, after trial use, considers this Skill worth formalizing." The trial phase (where the draft in prompts/ is called via use_skill) aims to gather usage feedback: Are the steps reasonable? Do they cover the right scenarios? Are any edge cases missed?

If trial use reveals that a Skill needs substantial revision, the agent can delete the draft and regenerate it, or edit the draft file directly. Editing a draft triggers no special operation -- it is simply a file edit. prompts/ is designed to be "freely editable, deletable, and rewritable."


Case Layer

7.4 use_skill Resolution Flow

The execution flow of the use_skill tool:

fn execute_use_skill(args: UseSkillArgs, context: &Context) -> Result<()> {
    let name = &args.name;

    // 1. Look up in skills/
    if let Some(skill) = context.registry.skills.get(name) {
        // Inject full skill content into current context
        context.inject(&skill.content);
        return Ok(());
    }

    // 2. Fall back to prompts/
    if let Some(prompt) = context.registry.prompts.get(name) {
        context.inject(&prompt.content);
        return Ok(());
    }

    // 3. Not found in either
    Err(format!("skill not found: {}", name))
}

context.inject() inserts the Skill content as a system-role message into the current context -- positioned before the user message and after the system prompt. This placement ensures the agent reads the Skill content before reading the user's message.

7.5 Division of Responsibilities Among Three Generation Tools

Three tools handle the creation and promotion of Skills and Prompts:

// generate_prompt: writes a draft to prompts/
fn generate_prompt(name: String, content: String) -> Result<()> {
    let path = prompts_dir().join(format!("{}.md", name));
    fs::write(&path, &content)?;
    // Register in Registry
    registry.prompts.insert(name, PromptEntry {
        path,
        content,
        created_at: now(),
    });
    Ok(())
}

// generate_skill: writes a formal Skill directly to skills/
fn generate_skill(name: String, content: String) -> Result<()> {
    let path = skills_dir().join(format!("{}.md", name));
    fs::write(&path, &content)?;
    registry.skills.insert(name, SkillEntry {
        path,
        content,
        promoted: false, // directly generated, not promoted
        source: None,
    });
    Ok(())
}

// promote_prompt: promotes a draft to a formal Skill
fn promote_prompt(name: String) -> Result<()> {
    let prompt_path = prompts_dir().join(format!("{}.md", &name));
    let skill_path = skills_dir().join(format!("{}.md", &name));

    // Check if a file with the same name already exists in skills/
    if skill_path.exists() {
        return Err("skill already exists with this name");
    }

    // Move from prompts/ to skills/
    fs::rename(&prompt_path, &skill_path)?;

    // Update Registry
    let prompt = registry.prompts.remove(&name).unwrap();
    registry.skills.insert(name, SkillEntry {
        path: skill_path,
        content: prompt.content,
        promoted: true, // marked as "promoted"
        source: prompt.source,
    });
    Ok(())
}

The division of responsibilities among the three tools reflects an important design principle: creation (generate) and promotion (promote) are separated. The agent cannot "write content and put it into the formal knowledge base" in a single operation. generate_prompt only writes drafts, promote_prompt only promotes -- the combination of these two operations requires the agent to validate value through actual use before actively triggering promotion.

generate_skill exists to support scenarios where known effective methodologies are imported directly as formal Skills. However, in actual CodeCoder usage, generate_skill is rarely called; the progressive path of generate_prompt -> trial -> promote_prompt is far more common.

7.6 SourceInfo Provenance

Every Skill and Prompt file in the Registry carries a SourceInfo:

struct SourceInfo {
    source: Source,        // Agent | User | Imported
    created_at: Instant,
    agent_reason: Option<String>, // the prompt or rationale at generation time
}

enum Source {
    Agent { session_id: String },
    User,
    Imported { url: Option<String> },
}

SourceInfo serves the purpose of provenance -- when a reader sees a file in skills/, they can look up "when was this Skill created, by whom, and for what reason."

For example:

---
source: Agent
session_id: "cc-session-20260715-a3b2c1"
reason: "After fixing three similar bugs, discovered they shared the same root cause pattern"
---
# Debug Causal Chain

When encountering a bug, follow this order...

SourceInfo itself is not a digital signature -- it is merely a metadata header. It cannot prevent forgery (the user can edit the header directly), but in a collaborative environment it provides a traceable context for "why this Skill exists here."

7.7 Sample Skill Source

Below is a simplified real Skill file, illustrating the writing style and structure of procedural knowledge:

---
name: debug-causal
source: Agent
session_id: "cc-session-20260710-9f8e7d"
reason: "After encountering the same panic pattern three times, generalized a universal debug workflow"
---

# Debug Causal Chain

## Applicable Scenarios

When encountering panics, test failures, or unexpected error output.

## Steps

### 1. Reproduce
Write a minimal input to trigger the bug.

### 2. Pinpoint
Use git bisect to find the commit that introduced the bug.

### 3. Verify
Confirm the root cause was not introduced elsewhere.

### 4. Fix
Write the minimum code to fix. No refactoring, no optimization -- just fix.

### 5. Cross-check
Before committing, confirm the fix does not break existing tests. Run the test suite.

## Not Applicable

- Performance regression: do not use this workflow; use the `perf-debug` skill instead
- Known bugs: if the root cause is already in the causal tree, update the status directly in the `reason` tool

Key characteristics of this Skill file:

  • Preconditions (Applicable/Not Applicable) help the agent decide when to use this Skill
  • Steps are numbered sequentially, one action per line
  • Each step includes a concrete operation, not just the word "fix" -- "fix" is followed by specific instructions
  • Non-applicable conditions prevent the agent from incorrectly applying the wrong workflow in inappropriate scenarios

ADR Deep Dive

From No Provenance to SourceInfo

CodeCoder's original Skill system contained no provenance information. A Skill file was just a .md file with no header metadata, no creator, no rationale for its existence.

The problem emerged when a skills/ directory grew to multiple files: maintainers could not tell "was this Skill generated by the agent or written by a human?", "why does this Skill exist?", or "is it still useful?"

The introduction of SourceInfo changed this. Each Skill file now automatically includes a metadata header section when generated, recording the source (Agent / User / Imported), creation time, and the agent's rationale at generation time. This is not a strict verification mechanism (the header metadata can be edited), but it provides traceable context -- especially when the agent creates a Skill, it records its own motivation in the "reason" field.

A concrete example: the agent reviewed Python code three times and found import ordering issues each time. It generates a draft, writing in the reason field "found the same import ordering issue across three reviews." When a human later reviews this file, the maintainer understands "why this Skill exists." If the maintainer does not consider import ordering an issue, they can simply delete the file, understanding that "this is a pattern the agent summarized from practice, not a hard rule from a design document."


The next chapter enters the Capability execution environment -- the three lifecycles OneShot, OnDemand, and Persistent, along with the three sandboxes Shell, Wasm, and Docker.