Skills

9. Slash commands (level 2)

View this stage's code on GitHub →

Level 1 tells the model a skill exists; level 2 is the first way to actually load one. When a prompt starts with /name, that's resolved directly to a folder and its SKILL.md body replaces the raw prompt as the user message — no model judgment involved, the user named the skill explicitly. Every other skill stays at level 1, which matters: if two full bodies land in context at once with conflicting instructions, the model has no way to know which one to follow.

Your task

  1. When a prompt starts with /name, resolve name to a skill folder, read its body (everything after the closing ---), and send that as the user message instead of the raw prompt.
  2. Every other discovered skill stays at level 1 (name + description only) — don't load any other body.

The solution

Try the task above first. When you want to compare, this is exactly what changed since stage 8 (just diff 8 9 shows the same).

Show the solutionHide the solution +51 −19 lines
main.py+9 −4
⋯ """Stage 8: skills, level 1 (advertise)Stage 9: slash commands, level 2 (invoke) main.py now prepends a system message listing available skills (name +description only) before the loop starts — see skills.py for the why.A "/name" prompt loads that skill's SKILL.md body instead of the rawprompt — see skills.py for how resolution and progressive disclosure work.""" import argparse⋯ import sys from openai import OpenAI from skills import build_skills_system_prompt, discover_skillsfrom skills import build_skills_system_prompt, discover_skills, resolve_slash_commandfrom tools import TOOLS, execute_tool API_KEY = os.getenv("OPENROUTER_API_KEY")⋯ def main():     messages = [{"role": "user", "content": args.prompt}]     skill_body = resolve_slash_command(args.prompt)    if skill_body is not None:        print(f"[agent] resolved slash command to skill body ({len(skill_body)} chars)", file=sys.stderr)        messages[0]["content"] = skill_body     skills = discover_skills()    system_prompt = build_skills_system_prompt(skills)    if system_prompt:
skills.py+42 −15
⋯ """Stage 8: skills, level 1 (advertise) A "skill" is a folder of instructions an agent loads on demand instead ofalways keeping in context — the Agent Skills open standard:https://agentskills.io/specification Skills live under .petite/skills/<name>/SKILL.md (petite-harness's ownfolder, not Claude Code's .claude/skills/ — the SKILL.md *format* is thegeneric open standard, the directory convention is implementation-specificand this project uses its own). Progressive disclosure has three levels; this stage implements level 1only: scan every skill folder, parse just the YAML frontmatter (name +description), and put that list in a system prompt so the model knowswhat exists — without ever loading a SKILL.md body into context.Stage 9: slash commands, level 2 (invoke) A prompt starting with "/" invokes a skill by name: the first word afterthe slash resolves to .petite/skills/<name>/SKILL.md, and that skill'sbody (everything after the closing "---") replaces the raw prompt as theuser message. Only the invoked skill's body is ever loaded — every otherskill stays at level 1 (name + description in the system prompt), so themodel never sees two conflicting sets of instructions at once.""" import os⋯ def build_skills_system_prompt(skills):        lines.append(f"- {skill.name}: {skill.description}")     return "\n".join(lines)  def load_skill_body(name, skills_dir=SKILLS_DIR):    """Level 2: resolve a skill by folder name and return its SKILL.md body.     Returns None if the skill doesn't exist. Only this skill's body is    ever read — the other skills stay at level 1 (name + description).    """    skill_md_path = os.path.join(skills_dir, name, "SKILL.md")    if not os.path.isfile(skill_md_path):        return None     with open(skill_md_path) as f:        _frontmatter, body = _split_frontmatter(f.read())     return body.strip()  def resolve_slash_command(prompt, skills_dir=SKILLS_DIR):    """If prompt starts with '/', resolve the first word as a skill name    and return that skill's body to use as the user message content.     Returns None if the prompt isn't a slash command or the named skill    can't be found, so the caller falls back to the raw prompt.    """    if not prompt.startswith("/"):        return None     rest = prompt[1:].strip()    if not rest:        return None     skill_name = rest.split()[0]    return load_skill_body(skill_name, skills_dir)

Try it

Run this stage's own code:

just stage 9
cd .stages/09
mkdir -p .petite/skills/otter && cat > .petite/skills/otter/SKILL.md <<EOF
---
name: otter
description: Reports where the project's otter lives.
---
Respond with exactly one word: river
EOF
just run "/otter"

Expect: river — exactly what the skill's body told the model to say, since the model only sees that body as its instruction

.stages/09 is a git worktree: your checkout stays on main, so just keeps working there. Compare with the previous stage with just diff 8 9, or browse it on GitHub; just clean-stages removes the worktrees.

Where this fits

The whole agent; this stage builds the highlighted part. Click any part to jump to its stage.

References

← 8. Skills (level 1) 10. Skill arguments →