Skip to content

Author a plugin

Build a plugin on disk, import it, and get a working agent — then export it back out and keep it in source control.

This is the path to take when an agent configuration is something you want to review, version and promote between environments rather than click together once. See Plugins for what the format is; this page is how to actually produce one.

Terminal window
mkdir -p support-plugin/{agents,skills/web-research,instructions}

You should end up with this:

support-plugin/
plugin.json
agents/
support-bot.agent.md
workflows/
support-desk.workflow.yaml
skills/
web-research/
SKILL.md
instructions/
support-bot.md

Only plugin.json is required. Leave out any directory you do not need. The workflows/ directory is optional and is covered under Add a workflow below.

plugin.json:

{
"name": "support-plugin",
"description": "Support agents and the skills they rely on",
"trust_tier": "untrusted"
}

Use untrusted unless you are a platform operator — it is the only tier a tenant role can assign, and declaring a higher one gets the import refused. untrusted still permits retrieval, which is what a knowledge agent needs.

Do not add keys the format does not define. Unknown keys are rejected, so a stray "author" fails the import rather than being quietly dropped.

agents/support-bot.agent.md. The filename before .agent.md is the agent’s name, so this defines an agent called support-bot.

---
description: Answers support questions from the handbook, citing sources.
model: anthropic-haiku
tools: [search]
skills: [web-research]
collections: [handbook]
visibility: tenant
---

Everything after the closing --- is the system prompt:

You are the support assistant for Example Corp. Answer from the handbook
where it covers the question, and say plainly when it does not. Keep
answers to a few sentences unless asked for detail.

description is the only required field. collections must name collections that exist in the tenant you import into — see Ingest a document and query it if you have not created one yet.

skills/web-research/SKILL.md. The name must match the directory:

---
name: web-research
description: Structured approach to answering questions that need looking
something up rather than being answered from memory.
---

Then the body — the procedure itself:

# Web research
When a question needs current or specific information you do not already
know:
1. State clearly what you do not know and would need to look up.
2. If a search tool is available, use it with a focused query.
3. Synthesise findings into a direct answer, and cite the sources.

Spend your effort on the description. It is the only part an agent sees before deciding whether to open the skill, so it needs to say when the skill applies, not what it is called. See Skills.

instructions/support-bot.md is always-loaded context for the agent of that name. It is optional, and it should be short, because it costs context on every single run:

# support-bot notes
Always end responses with "Let me know if you need anything else."

Anything conditional belongs in a skill instead, where it is loaded only when relevant.

A workflow groups agents that answer one conversation together. Put it in workflows/<name>.workflow.yaml. The file’s name is the workflow’s name. This one needs a second agent: add agents/billing-bot.agent.md, written like support-bot in step 3 but about billing. Then save this as workflows/support-desk.workflow.yaml, one handoff between the two:

description: Answers, and passes billing questions on.
entry: support-bot
agents: [support-bot, billing-bot]
edges:
- from: support-bot
to: billing-bot
kind: transfer
when: invoices, payments, refunds

A member is named by its agent name when it is in the same plugin, and plugin/name when it is in another. A workflow that names a missing agent is rejected whole, like any other file in the plugin. See Workflows for edges, limits and approval, and Build your first workflow for a complete example.

Zip the directory and upload it on the Plugins screen.

Terminal window
cd support-plugin && zip -r ../support-plugin.zip .

Zip the directory’s contents, not the directory itself — plugin.json must be at the archive root.

The importer validates everything before writing anything, and reports failures per file and per field. A plugin that fails validation is rejected whole; nothing is partially imported, so you can fix and retry without cleaning up.

'name' must match the skill name — the name in SKILL.md frontmatter differs from its directory name. They are compared exactly.

unknown manifest key — a key in plugin.json outside name, description, trust_tier and version.

'description' is required — an agent or skill with no description. Both need one.

'description' exceeds 1024 characters — a skill description too long for the runtime to keep whole. Move the detail into the body.

A trust-tier refusal — the manifest declares a tier above your role’s ceiling. Change it to untrusted, or ask an operator to import it.

plugin path escapes the plugin root — the archive contains an absolute path, a .. segment, or a symlink. Some zip tools add these; rebuild the archive from a clean directory.

Once it works, export it from the Plugins screen and commit the archive. Export is the exact inverse of import: what comes out re-imports to the same content hashes, so the copy in source control is provably the configuration that ran, not an approximation of it.

That round-trip is what makes the promote-between-environments workflow honest. Export from staging, commit, import into production, and the two sides are the same agent.

Next: Attach knowledge to an agent.