For builders

Build a skill, ship it to Duet

A skill is a Markdown file. Frontmatter declares what your skill needs from the agent — a recommended model, the toolkits it drives, the specific actions it's allowed to call. The body is the playbook the agent reads at runtime. Twenty minutes from blank file to installable.

01

What a skill is

A skill is a single folder — skills/<name>/SKILL.md plus optional companion .md reference files. The agent reads a short summary of every active skill in its system prompt and pulls the full body in on demand.

Two parts: frontmatter(YAML — the contract Duet reads) and body(Markdown — the playbook the agent reads). Everything outside the frontmatter is the body, exactly as written.

02

The shape of a SKILL.md

The frontmatter block at the top of the file declares everything Duet needs to install, route, and gate the skill at runtime. Below the closing ---, the body is the instructions the agent follows.

---
# Anthropic Skills protocol — required by the runtime.
name: support-triage                    # hyphenated, ≤64 chars
description: >                          # ≤1024 chars, used for skill auto-loading
  Triage inbound support emails: classify, draft replies in
  the team's voice, and label what's urgent.

# Duet manifest.
manifest_version: 1
publisher: acme                         # your publisher slug
slug: support_triage                    # underscore form of `name`
version: 1.0.0                          # semver, immutable per upload
display_name: Support Triage
tagline: Classifies, drafts, and labels support email so humans see what matters.
suggested_agent_name: Hank
default_autonomy: supervised            # supervised | semi | autonomous
recommended_model: anthropic/claude-sonnet-4.6
tags: [Support, Email]

pricing:
  type: free                            # free | one_time | monthly
  amount_cents: 0
  currency: USD

required_toolkits:
  - slug: gmail
    name: Gmail
    reason: I read inbound mail, draft replies, and label urgency.
    actions:
      [GMAIL_FETCH_EMAILS, GMAIL_CREATE_EMAIL_DRAFT, GMAIL_REPLY_TO_THREAD,
       GMAIL_LIST_LABELS, GMAIL_ADD_LABEL_TO_EMAIL]

optional_toolkits:
  - slug: slack
    name: Slack
    reason: Pings the on-call channel for anything tagged urgent.
    actions: [SLACK_SEND_MESSAGE, SLACK_FIND_CHANNELS]

capabilities:
  - Classify support email by intent and urgency
  - Draft replies in the team's voice for human approval
  - Label and triage threads by urgency

non_goals:
  - Won't send a reply without human approval

communication_style: |
  Concise. Quotes the user's words. Leads with the ask, then context.
---

You are operating in your Support Triage mode. Your job is to ...

03

Toolkits and actions

The harness only ships tools your skill explicitly names.Composio catalogs a thousand toolkits with tens of thousands of actions; Gmail alone ships ~50 actions, Shopify ships 200+. If we loaded everything by default, the tool definitions alone would burn through a small model's context window before the agent saw its first message.

Two rules:

  1. Every required_toolkits / optional_toolkits entry must include an actions: [...]list. A toolkit with no declared actions is treated as a misconfiguration and contributes zero tools at runtime — with a warning in the logs.
  2. Action slugs must match Composio's catalog exactly. Typos don't silently get dropped — seed-skills refuses to publish a row that references a slug that doesn't exist.

The two consequences worth internalizing: declarations are the source of truth for the runtime gate, and broader is not safer. A tighter action list lowers the agent's working context and makes its tool choices more predictable. Declare what you need; nothing more.

04

Finding the right action slugs

Duet ships a snapshot of Composio's full catalog at skills/.composio-catalog.json — ~1000 toolkits, ~40,000 actions. Grep it instead of guessing.

# Refresh the snapshot from Composio (run periodically)
pnpm dlx dotenv-cli -e .env.local -- \
  npx tsx scripts/snapshot-composio-catalog.ts

# Find every Gmail action
jq -r '.toolkits[] | select(.slug=="gmail") | .actions[]' \
  skills/.composio-catalog.json

# Find every action that mentions "thread"
jq -r '.toolkits[] | select(.slug=="gmail") | .actions[]' \
  skills/.composio-catalog.json | grep -i thread

05

Writing the body

The body is the agent's playbook, in your voice. There's no required structure — whatever the agent needs to follow your skill correctly. Patterns that hold up in practice:

  • State boundaries up front.What this skill is and isn't. What other skills it composes with. Where to hand off when the user asks for something out of scope.
  • Number your steps. Multi-step workflows read better when each step is a heading the agent can reference back to.
  • Encode hard rules separately.A “Hard rules” section that says NEVER do X without Y is much harder for the model to skip than the same constraint buried in prose.
  • Companion files for deep references. Put long lookup tables, dropdown guides, or schema docs in a sibling .md file and reference it from the body. The agent loads it on demand.

06

Validate and ship

Run the validator locally before submitting. It catches manifest errors, unknown toolkit slugs, and unknown action names — the same checks Duet runs on the publisher pipeline.

# Validate the manifest + check every action against Composio's catalog
pnpm dlx dotenv-cli -e .env.local -- \
  npx tsx scripts/seed-skills.ts

# Output:
#   using Composio catalog snapshot from 2026-05-05T19:09:26Z (993 toolkits)
#   ✓ support_triage

Today, Duet's catalog ships from this repo — first-party and design-partner skills land via pull request. The publisher pipeline (CLI, Builder UI, third-party submissions) is on the roadmap. If you want to publish a skill, email us and we'll get you set up.

Browse the catalog

See how the eight first-party skills are written. Every SKILL.md is on GitHub; the catalog page surfaces them with install counts and ratings.