Agents and skills
Two things an extension can contribute, often confused, worth keeping apart.
An agent is a worker: a model, a system prompt, a tool set, and limits. It is selected — by the user, or by another agent delegating to it — and it runs a turn.
A skill is knowledge: a name, a one-line trigger description, and a body of instructions. Every skill’s name sits in the system prompt permanently; the body is loaded only when the model decides it needs it.
The distinction that matters in practice: an agent answers who is doing this, a skill answers how it is done. Neither needs a permission, and neither needs any JavaScript — an extension that is nothing but a manifest and two markdown files is a working extension.
Which one do you want
Section titled “Which one do you want”| You want to… | Ship |
|---|---|
| A reviewer with a narrow tool set and a strict prompt | An agent |
| A researcher on a cheaper, faster model | An agent |
| Work that runs on a schedule without a user | An agent with a trigger |
| A procedure any agent might need occasionally | A skill |
| Your product’s conventions, so the model follows them when relevant | A skill |
| Knowledge one specific agent should always have | An agent whose skills: names it |
The cost asymmetry is the deciding factor. An agent’s prompt is paid on every turn that agent runs. A skill’s description is paid on every turn of every agent; its body is paid only on the turns that load it. Long content that is only sometimes relevant belongs in a skill body.
Agents
Section titled “Agents”Author one
Section titled “Author one”Drop a markdown file in agents/. No manifest entry, no build step.
---name: Release Notes Writerdescription: Turns a commit range into user-facing release notes. Use when preparing a changelog or a release announcement.model: balancedtools: [file, terminal]deny_tools: [write_file, edit_file]max_turns: 20icon: scroll-textcolor_token: blue---
You write release notes for humans, not for other engineers.
Read the commit range you are given with `git log`, group changes by what auser would notice, and drop anything with no user-visible effect.
Output exactly three sections: **Added**, **Fixed**, **Changed**. One line peritem, present tense, no commit hashes, no author names. If a section would beempty, omit it.Everything below the frontmatter is the system prompt. The agent id is the
filename — agents/release-notes.md is the agent release-notes — and name
is only the display label. The directory is scanned one level deep, .md only.
Frontmatter
Section titled “Frontmatter”| Key | Type | Default | Notes |
|---|---|---|---|
name |
string | — | Required. A file without it is skipped with a warning. |
description |
string | '' |
Shown in pickers, and to other agents deciding whether to delegate here. Write it as a trigger. |
model |
string | inherits | Either a tier — fast, balanced, powerful — or a specific model id. |
tools |
string[] | every registered tool | Mixed list of tool categories and exact tool names. |
deny_tools |
string[] | — | Removed after categories expand. Applied last, so it always wins. |
skills |
string[] | — | Skill names whose bodies are inlined into this agent’s prompt on every run. |
max_turns |
number | 25 |
Ceiling on loop iterations. |
max_tokens |
number | model default | Output cap per turn, clamped to the model’s maximum. |
thinking |
none|low|medium|high |
— | Anything else warns and is dropped. |
budget |
mapping | — | { turns?, cost_usd?, time_minutes?, mode? }, mode is soft (default) or hard. |
icon |
string | — | A Lucide icon name. |
color_token |
string | — | blue, purple, emerald, pink, gray, indigo, amber, rose. |
triggers |
array | — | Scheduled or webhook invocation. See Scheduled work. |
An unrecognized key is dropped with a warning, and common wrong names get a
“did you mean” — max_iterations and max_steps point at max_turns, color
at color_token, allowed_tools at tools, and system_prompt or prompt at
“the markdown body below the frontmatter”, which is where the prompt actually
goes.
Choosing the model
Section titled “Choosing the model”model: balanced is usually right. The three tiers resolve through user-editable
slots, and the balanced slot defaults to following the chat model — so a
balanced agent tracks whatever the user has chosen, while fast and powerful
deliberately do not.
Naming a specific model id pins it. If that id is not in the catalogue, the host warns and falls back to the tier, or to the default agent slot. Pinning is worth it only when the agent genuinely depends on one model’s behavior.
Choosing the tool set
Section titled “Choosing the tool set”Each entry in tools is resolved as a category if the platform knows that
category name, and as an exact tool name otherwise. There is no glob syntax.
The categories: file, terminal, analysis, memory, delegation, mcp,
workflow, meta, docs, media, search, testing, storage — plus any
category an extension has introduced.
tools: [file, search, crm_create_contact] # two categories and one specific tooldeny_tools: [write_file, edit_file] # subtract the writes back outThree behaviors to know. Omitting tools entirely — or giving an empty array —
means every registered tool. Any agent that does narrow tools automatically
gets use_skill added, so it can still load skills. And a misspelled entry is
neither a category nor a tool, so it silently contributes nothing; the host logs
one validation line per agent naming the unresolvable entry, which is where to
look when an agent seems to be missing a tool.
The manifest alternative, and why to skip it
Section titled “The manifest alternative, and why to skip it”contributes.agents can declare an agent inline:
{ "contributes": { "agents": [{ "id": "data-analyst", "name": "Data Analyst", "tools": ["analyze_data"] }] } }It is a much weaker shape. There is nowhere to put a prompt, so the agent has
none; the id is sanitized to lowercase and hyphens; file and terminal are
added to whatever tools you listed; max_turns is fixed at 25; and the model
is whatever the default agent slot resolved to at load time. Use it only for an
agent that genuinely needs no prompt. Otherwise ship the markdown file.
Ids, collisions, and overriding
Section titled “Ids, collisions, and overriding”Agents live in four scopes, in increasing precedence: built-in, extension, user, workspace. Two agents with the same id in the same scope is an error — a second extension registration throws, naming the extension that got there first. The same id in a higher scope is a legitimate override, and it is a full replacement rather than a field-by-field merge: the winning definition’s prompt, tools, and model are used, and nothing is inherited from the one it shadows.
So pick ids a user is unlikely to collide with, and expect that a user can deliberately shadow your agent with one of their own.
How an agent gets used
Section titled “How an agent gets used”Three real paths:
- The user launches it. The Library lists every agent, including yours, and running one opens a new chat bound to that agent. Every turn in that chat goes to it.
- Another agent delegates to it. The default agent can spawn a sub-agent in
an isolated context, choosing from a catalogue of agent ids and descriptions
injected into its prompt. Your
descriptionis the entire basis for that choice, so write it as “use when…”, not as a title. - A trigger fires it. A
triggersblock runs the agent on a schedule or a webhook without a user present.
Typing @release-notes in the composer autocompletes from the same list, but be
clear on what it does: it inserts the text of the mention into your message. The
bound agent reads it and may delegate accordingly. It is a way to refer to an
agent, not a switch that rebinds the turn.
An extension can also start an agent run itself with ctx.api.agents.delegate,
and list what is available with pluginAPI.agents.list().
Skills
Section titled “Skills”Author one
Section titled “Author one”Two layouts. A single file, when the skill is only prose:
---name: invoice-conventionsdescription: How this product numbers, dates, and rounds invoices. Use before creating, editing, or explaining any invoice.---
# Invoice conventions
## Numbering
`INV-{YYYY}-{sequence}`, sequence padded to four digits, never reused, neverreset mid-year. A credit note uses the same sequence with a `CN-` prefix.
## Dates
`issuedAt` is the date the invoice was sent, not the date it was created. Adraft has no `issuedAt` at all — leave it null rather than guessing.
## Rounding
Round each line to two decimals, then sum. Never sum then round; it producestotals that disagree with the printed lines by a cent.Or a directory, when the skill carries files with it:
skills/ release-process/ SKILL.md references/ hotfix.md rollback.md scripts/ check-tags.shThe file must be named exactly SKILL.md. A subdirectory without one is ignored
silently. Only references/, scripts/, and assets/ are scanned for bundled
resources, one level deep, files only.
Declare the directory in the manifest:
{ "contributes": { "skills": true } }true scans skills/. A string names a different directory. An array names
several.
Frontmatter
Section titled “Frontmatter”name is required — a skill without one is skipped, and the warning says so
explicitly, because an unnamed skill has nothing to be invoked by. id is
accepted as an alias for name. description is optional but is the whole
trigger mechanism, so treat it as required.
Be aware that the other frontmatter keys you may see in skill files elsewhere —
version, tags, disable-model-invocation — currently have no effect on a
skill contributed by an extension. They are read for skills the user or
workspace provides, not for yours. Do not tune them expecting a result.
How a skill reaches the model
Section titled “How a skill reaches the model”Two layers, and understanding them is what makes a skill get used.
The listing. Every skill’s name appears in the system prompt under “Available Skills” with its description, permanently, for every agent. Names are never dropped — the listing has a token budget, and under pressure descriptions are shortened rather than skills being omitted, because a skill the model cannot see does not exist.
The load. When the model decides a skill applies, it calls
use_skill("invoice-conventions") and gets the body back. The call is stateless:
there is no activation, no unload, and no “currently active skill” — each call
just returns text. Bodies are never truncated, so a long body is returned whole,
and it is never dropped during context compaction while the model is following
it.
For a directory skill, the response ends with the list of bundled resources, and the model pulls one with a second call:
use_skill("release-process", "references/hotfix.md")There is also find_skill(query) for searching by intent when the listing is not
enough, and an agent can pre-load skill bodies into its prompt by naming them in
its skills: frontmatter — useful when the knowledge is not optional for that
worker.
Writing a skill that actually gets loaded
Section titled “Writing a skill that actually gets loaded”The description is a trigger, not a title. The model reads it to decide whether to spend a tool call. Say the situation, not the subject.
# Weak — a topic. The model has no idea when this applies.description: Information about invoices.
# Strong — a situation with verbs.description: How this product numbers, dates, and rounds invoices. Use before creating, editing, or explaining any invoice.Then, for the body:
- Write a procedure, not an essay. Steps, rules, tables, and the specific strings and formats to use. The model is going to act on this immediately.
- One skill, one job. A skill that covers three unrelated areas will be loaded for one of them and waste context on the other two. Split it.
- Say the non-obvious thing. The model already knows how to write TypeScript. It does not know your numbering scheme, your rounding rule, or which of two plausible approaches your codebase has settled on.
- Put long detail in
references/. KeepSKILL.mdas the map — when to use which resource — and let the model pull only the branch it needs. - Include the failure mode. “If X, the symptom is Y” saves more turns than any amount of correct-path prose.
Never point a skill at a path in another repository
Section titled “Never point a skill at a path in another repository”This is the rule that costs the most when broken, and it looks harmless.
<!-- Wrong. Do not do this. -->For the full type definitions, read`packages/extension-sdk/types/wamp-extension-sdk.d.ts`.The skill ships inside your extension, but the model’s working directory is the user’s project. A repository-relative path in a skill body is an instruction to go looking, and the model will: it globs the disk, finds some checkout of something with a similar-looking path, reads a stale copy, and proceeds confidently from the wrong information. In the incident that produced this rule, that pattern burned roughly fifteen turns before anyone noticed the file being read was not the file that shipped.
Three correct alternatives, in order of preference:
- Inline it. If the model needs the content, put the content in the body.
- Bundle it. Ship it under
references/and reference it the only way that resolves —use_skill("<name>", "references/types.md"), which reads from inside your extension and cannot pick up a stray copy. - Name it without pointing at it. If the model does not need to read a file but does need to know it exists, say so and say explicitly not to open it: “the platform typechecks every save, so trust the error rather than opening the declaration files.”
The same applies to absolute paths, ~-relative paths, and repository URLs. If
the body contains a path, ask what happens when that path exists on the user’s
machine and contains something else.
Name collisions
Section titled “Name collisions”Skills merge across five sources — built-in, user, extension, installed, and
workspace — keyed on name, with later sources winning. A workspace skill named
invoice-conventions shadows yours entirely. Prefix names that are likely to
clash.
A complete zero-JavaScript extension
Section titled “A complete zero-JavaScript extension”One agent and one skill, no main, no build.
{ "name": "Invoice Assistant", "version": "1.0.0", "engines": { "wamp": "^12.0.0" }, "description": "An invoice reviewer agent plus this product's invoice conventions.", "permissions": [], "contributes": { "skills": true }}---name: Invoice Reviewerdescription: Checks a draft invoice against this product's conventions and reports problems. Use before an invoice is sent.model: balancedtools: [file]skills: [invoice-conventions]max_turns: 15icon: receiptcolor_token: amber---
You review draft invoices and report problems. You do not fix them.
For each invoice you are given, check the number format, the issue date, theper-line rounding, and the total. Report every problem you find as a singleline: what is wrong, what it should be, and which line it is on. If everythingis correct, say so in one sentence and stop.
Never edit a file. Never send anything.skills/invoice-conventions.md is the file shown earlier, with one section added
at the end — the kind of content that saves the most turns:
## Common failureA total off by exactly one or two cents is almost always sum-then-round. Checkthe line values before looking anywhere else.The agent names the skill in skills:, so its body is in the prompt on every
run — the knowledge is not optional for this worker. The skill is also in the
general listing, so any other agent can load it when an invoice comes up.
- Contributing tools — the abilities an agent’s
tools:list selects from. - Scheduled work — the
triggersblock in full. - The manifest — every
contributeskey in one place.