Skip to content

Writing your own

You can describe the craft perfectly and still never see your Skill used. The agent loads one field from every Skill on every request, the routing, and decides from that alone which one to read properly. A Skill with brilliant composition rules and vague routing is a Skill that never fires.

The wizard walks the fields in order and confirms routing at the end, on step 5, before anything is saved. That ordering is deliberate: you write the craft while it is fresh, then decide how the agent should find it.

The three parts of good routing, in this order:

  1. A purpose sentence. The concrete deliverables that route here.
  2. A trigger sentence in the user’s own words. Use when the user says “…”, “…”, or …, in short phrases someone would actually type, not restatements of the category. Include a non-verbal trigger where one exists: attaching an asset, choosing a model that needs image input.
  3. At least one explicit boundary against a named neighbor. Not X (→ Neighbor), or Separated from X by <criterion>.

The worked example, from the built-in catalog:

Paid banner placements in standard ad slots. Use when the user says “banner ad”, “display ad”, “retargeting creative”, “300x250”, or names an ad network or IAB size. Separated from Social Graphics by three real constraints: fixed IAB pixel dimensions, adjacency to unrelated content, and a sub-second viewing budget.

Purpose, then real utterances, then a named boundary with the criteria that separate them.

Trigger phrases exist because taxonomy is not testable. Every one of the original built-in Skills was first written as a category (“Still imagery for social feeds and stories”), which tells a reader what the thing IS while naming no sentence that lands in it. You cannot check a category description against a real request. You can check whether “make me a story frame” routes correctly. That is the whole argument for part 2, and it is why it was retrofitted across the entire built-in catalog rather than applied only to new Skills.

Never put documentation apparatus in the routing field. No section numbers, no “see the note further down”, no editorial asides about whether the Skill earns its place. That field is loaded into the model’s context on every routing decision, so a pointer to a document the model cannot open is pure cost paid on every request. Substantive warnings that genuinely change routing, such as “No live model routes this today”, belong there and are worth their weight.

Failure modes are named, not listed. Each carries three parts: the failure, the mechanism that causes it, and the correction. The correction always states what to specify, never what to avoid: negation primes the failure, so telling a model “no motion blur” puts motion blur into its context.

The convention is enforced, not merely recommended. Agent-drafted routing is required to follow it, and the retrofitted built-ins are sent along as worked examples so the instruction and the examples cannot drift apart.

Work through these in order. They are ordered by how often each turns out to be the answer.

1. Is it enabled? Disabled Custom Skills are excluded everywhere by default.

2. Are you in the right mode? The picker is mode-scoped, so a video Skill is invisible from the image tab. /app/skills shows every Skill regardless of mode, which is the fastest way to confirm one exists at all.

3. Read your routing out loud and ask: does it contain a sentence someone would actually type? If it reads “Still imagery for social feeds and stories”, that is taxonomy. It tells a reader what the category is and names no utterance that lands in it. Add the trigger sentence: Use when the user says “…”, “…”, or …

4. Does it say what it is NOT? If two of your Skills could plausibly take the same request, both will lose some of the time. Name the neighbor and the criterion that separates them.

5. Only then look at the body. Composition rules, constraints and failure modes are read after routing has already chosen the Skill. They cannot cause a Skill not to fire.

Routing is testable and the rest is not. You cannot check a category description against a real request, which is why “describe the category” survives so long as a habit: nothing ever tells you it failed. Trigger phrases give you a test: take a sentence a real person sent, and see where it goes.

Non-verbal triggers count. Attaching an asset, or choosing a model that needs image input, is a routing signal as real as a phrase. A Skill that only ever fires on words will miss the request that arrived as a dragged-in file.

A warning that changes routing belongs in the field; a note about the Skill does not. “No live model routes this today” is worth its cost on every request. A section reference is not. The model cannot open it, and it is loaded on every routing decision for every Skill.

Your Skills live at /app/skills, in two tables: Personal and Team. Skills accumulate. The page exists because reviewing or deleting one used to mean switching to the right Studio mode (the picker is mode-scoped, so a video Skill is invisible from the image tab), opening the picker, selecting each Skill in turn and reading its detail card. There was no list, no count and no management surface anywhere.

Two tables. Personal and Team, with name, mode, when it was last updated, and what you can do with it. The team table also shows where each one came from.

Search, filter, sort. By name, by mode, by team; ordered by name or last updated.

Fork, not edit, for anything that isn’t yours. A teammate’s Skill shows a plain statement that it is theirs and offers you a copy. Editing it was never an option the server would have honored, so the row does not pretend otherwise.

Disable rather than delete when you want a Skill to stop being offered but not to disappear. Disabled Skills are excluded by default everywhere, so nothing has to remember to filter them.

Scope decides the table; ownership decides the controls. A Skill you own, scoped to a team, is visible to you through team membership, so it sits in the Team table rather than appearing in both. Then ownership decides what that row can do. Splitting on ownership instead would show you the same Skill twice and still have to answer the question about controls separately.

Search covers every Skill, not the page you are looking at. This page filters and paginates in the browser, which is the one place it departs from the pattern the Dashboard uses. The Dashboard paginates on the server and filters in the browser, so its search only ever searches the current page. This page exists because Skills become unmanageable at about fifteen, and a search that silently ignores pages two onward fails exactly the test the page was built to pass.

Enable/Disable is Custom Skills only, and owner-level. Built-in Skills have no owned record to carry the flag. That was settled before the control was built rather than discovered afterwards.