Skills
10. Skill arguments
View this stage's code on GitHub →A skill that always says the same thing isn't very useful — /deploy needs to know which environment. $ARGUMENTS captures everything typed after the skill name as one string; $ARGUMENTS[n]/$n pull out one positional argument by index. The substitution order matters more than it looks: $ARGUMENTS[0] is replaced before the bare $ARGUMENTS, because doing it the other way round would mangle $ARGUMENTS[0] into "<everything>[0]" the moment the bare pattern matched first.
Your task
- Parse everything after the skill name as whitespace-separated arguments.
- Substitute
$ARGUMENTS(all of it, joined) and$ARGUMENTS[n]/$n(one positional argument) in the body before sending it. - Replace indexed placeholders before the bare
$ARGUMENTS— doing it in the other order mangles$ARGUMENTS[0].
The solution
Try the task above first. When you want to compare, this is exactly what changed since stage 9 (just diff 9 10 shows the same).
Show the solutionHide the solution +64 −12 lines
.petite/skills/feed/SKILL.md+11 −0
⋯ ---name: feeddescription: Feeds an animal something, demonstrating skill arguments.--- Respond with exactly these two lines and nothing else: Feed the $0 some $1.Full request: $ARGUMENTS Do not call any tools.
.petite/skills/rabbit/SKILL.md+8 −0
⋯ ---name: rabbitdescription: Reports the burrow location for the project's rabbit mascot.--- Respond with exactly one word: burrow Do not add any other text, punctuation, or formatting.
skills.py+45 −12
⋯ """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.Stage 10: skill arguments ($ARGUMENTS, $0, $1, ...) A slash command can pass arguments after the skill name:"/feed rabbit carrots" invokes "feed" with args ["rabbit", "carrots"].The body can reference them with placeholders, substituted before thebody is sent to the model: $ARGUMENTS everything after the skill name, joined by spaces $ARGUMENTS[0], [1] a single argument, by position $0, $1, ... shorthand for the same thing Order matters when substituting: $ARGUMENTS[0] must be handled before thebare $ARGUMENTS, or a naive string replace would mangle it into"rabbit carrots[0]".""" import osimport re import yamlfrom pydantic import BaseModel, Field, field_validator⋯ def load_skill_body(name, skills_dir=SKILLS_DIR): 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. """If prompt starts with '/', resolve the first word as a skill name, substitute any argument placeholders in its body, and return that 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.⋯ def resolve_slash_command(prompt, skills_dir=SKILLS_DIR): if not rest: return None skill_name = rest.split()[0] return load_skill_body(skill_name, skills_dir) parts = rest.split() skill_name, args = parts[0], parts[1:] body = load_skill_body(skill_name, skills_dir) if body is None: return None return substitute_arguments(body, args) def substitute_arguments(body, args): """Replace $ARGUMENTS, $ARGUMENTS[n] and $n placeholders in a skill body with the given positional arguments. Missing indices substitute to an empty string rather than raising. """ def arg_at(index): return args[index] if 0 <= index < len(args) else "" # $ARGUMENTS[n] first — a bare $ARGUMENTS replace would otherwise # mangle "$ARGUMENTS[0]" into "<joined args>[0]". body = re.sub(r"\$ARGUMENTS\[(\d+)\]", lambda m: arg_at(int(m.group(1))), body) body = body.replace("$ARGUMENTS", " ".join(args)) body = re.sub(r"\$(\d+)", lambda m: arg_at(int(m.group(1))), body) return body
Try it
Run this stage's own code:
just stage 10
cd .stages/10
# bundled skill "feed", body: "Feed the $0 some $1." + "Full request: $ARGUMENTS"
just run "/feed rabbit carrots"
Expect: Feed the rabbit some carrots. and Full request: rabbit carrots — each arg lands in its slot ($0 = rabbit, $1 = carrots) and $ARGUMENTS gets them all
.stages/10 is a git worktree: your checkout stays on main, so
just keeps working there. Compare with the previous stage with just diff 9 10,
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.