How to Build an Agent Plugin for Any Harness
Build an Agent Plugin for Microsoft 365 Copilot Cowork, Codex, Claude Cowork, Claude Code, OpenClaw, Hermes Agent, and SKILL.md-based harnesses: define the product, package Skills and components, adapt it per harness, and measure it.
This guide shows how to build an Agent Plugin for XYZ - any domain-specific capability you want users to install in Microsoft 365 Copilot Cowork, Codex, Claude Cowork, Claude Code, OpenClaw, Hermes Agent, or a harness that supports SKILL.md.
Start with the plugin, not the harness. The plugin is the installed product users choose, version, and trust. A Skill is one task-specific capability inside that plugin. A useful plugin may also contain MCP config, connectors, hooks, agents, commands, assets, or app integrations.
The goal is to build the plugin once, then deploy it through harness-specific adapters. Codex wants a .codex-plugin/ package. Microsoft 365 Copilot Cowork wants a Microsoft 365 app package. Claude wants a .claude-plugin/ package with a manifest plus default component directories. OpenClaw and Hermes Agent expose richer plugin extension points for tools, Skills, providers, hooks, commands, integrations, and runtime components. Other tools may only expose SKILL.md directories. Telvine should be the source-of-truth workspace that keeps the product, components, versions, directories, and telemetry consistent across all of them.
1. Pick the plugin product
Define the plugin as a product before writing harness files:
- User: who installs it?
- Job: what work should the plugin make possible?
- Surfaces: which harnesses should it run in?
- Capabilities: which Skills, connectors, hooks, MCP config, or agents belong inside it?
- Telemetry: what package-level and capability-level events should prove it works?
For example, an invoice-ops plugin might include:
| Component | Purpose |
|---|---|
| Plugin | The installable invoice operations product |
Skill: reconcile-invoices | Match payments to invoices and report exceptions |
Skill: draft-collection-emails | Draft follow-up emails for overdue invoices |
| Connector or MCP config | Read invoices from the accounting system |
| Hook | Emit a completion or failure event after each run |
2. Create the source plugin workspace
Keep one canonical plugin workspace before generating platform-specific packages:
invoice-ops/
├── telvine.plugin.json
├── skills/
│ └── reconcile-invoices/
│ └── SKILL.md
├── connectors/
│ └── accounting-api/
├── hooks/
│ └── after-run/
├── assets/
│ └── logo.png
└── adapters/
├── codex/
├── copilot-cowork/
├── claude/
├── openclaw/
└── hermes/
Think of telvine.plugin.json as the product record: name, owner, version, component inventory, target harnesses, and telemetry policy. The adapters/ folders are generated or maintained outputs for each platform.
3. Build the first Skill capability
Most plugins should start with one excellent SKILL.md. Keep it narrow and measurable:
---
name: reconcile-invoices
description: >
Use this skill when the user asks to "reconcile invoices",
"match payments to invoices", or "find unmatched payments".
Reconciles payments against open invoices and reports exceptions.
---
# Reconcile invoices
## Steps
1. Confirm the accounting period and locate the invoice and payment data.
2. Match payments to invoices by customer, reference, date, and amount.
3. List unmatched payments, partial matches, and suspected duplicates.
4. Produce a review table. Never modify source records.
See the SKILL.md template for a fuller scaffold.
4. Package it for each harness
The portable core is your plugin source: the Skills, scripts, references, connectors, assets, and telemetry conventions. Each harness then gets an adapter.
| Harness | Plugin/package shape | What to ship |
|---|---|---|
| Microsoft 365 Copilot Cowork | Microsoft 365 app package | Skills, connectors, or both, with telemetry from backend and connector surfaces |
| OpenAI Codex | .codex-plugin/plugin.json at the plugin root | skills/, hooks/, .app.json, .mcp.json, and assets/ as needed |
| Anthropic Claude Cowork / Claude Code | .claude-plugin/plugin.json at the plugin root | Skills, commands, agents, hooks, MCP servers, output styles, LSP servers, settings, user config, dependencies, scripts, bins, themes, and monitors as supported |
| OpenClaw | OpenClaw plugin package | Tools, Skills, channels, model providers, agent harness integrations, speech, media, web, generation, and runtime components as needed |
| Hermes Agent | Hermes plugin package | Tools, hooks, slash commands, CLI commands, integrations, bundled Skills, memory providers, context engines, and model providers |
| GitHub Copilot | Skill capability package | Place Skills where Copilot loads SKILL.md; keep plugin identity and version in your release process |
| Cursor, Windsurf, Gemini CLI, Antigravity | Skill capability package | Place the same SKILL.md in the harness-specific Skills directory and preserve plugin-level versioning outside the harness |
The important rule: do not let a harness adapter become the product model. Your plugin id, version, component inventory, release history, and telemetry contract should stay stable even when the folder layout changes per harness.
5. Microsoft 365 Copilot Cowork layout
For Microsoft 365 Copilot Cowork, package the plugin as a Microsoft 365 app. Cowork plugins can contain Skills, connectors, or both, and Microsoft currently documents the feature as Frontier functionality.
invoice-ops/
├── manifest.json
├── skills/
│ └── reconcile-invoices/
│ └── instructions.md
├── connectors/
│ └── accounting-api/
└── telemetry/
└── telvine-events.md
Use Telvine to keep a stable plugin id, component inventory, and version history even if Microsoft changes preview-era packaging details. See How to Build a Microsoft 365 Copilot Cowork Plugin for the dedicated guide.
6. Codex plugin layout
For Codex, create a plugin folder with a required .codex-plugin/plugin.json manifest:
invoice-ops/
├── .codex-plugin/
│ └── plugin.json
├── skills/
│ └── reconcile-invoices/
│ └── SKILL.md
├── hooks/
│ └── hooks.json
├── .mcp.json
└── assets/
└── logo.png
Keep plugin.json inside .codex-plugin/. Keep skills/, hooks/, .mcp.json, .app.json, and assets/ at the plugin root.
7. Anthropic Claude Cowork and Claude Code layout
For Anthropic Claude, package the same plugin product with a .claude-plugin/plugin.json manifest:
invoice-ops/
├── .claude-plugin/
│ └── plugin.json
├── skills/
│ └── reconcile-invoices/
│ └── SKILL.md
├── commands/
│ └── invoice-status.md
├── agents/
│ └── collections-reviewer.md
├── hooks/
│ └── hooks.json
├── output-styles/
│ └── finance-summary.md
├── monitors/
│ └── close-health.json
├── themes/
│ └── finance-dark.json
├── bin/
│ └── invoice-check
├── scripts/
│ └── poll-deploy.sh
├── settings.json
├── .mcp.json
└── .lsp.json
Claude's manifest schema covers product metadata and component declarations:
skills, commands, agents, hooks, mcpServers, outputStyles,
lspServers, userConfig, channels, dependencies, and experimental
themes and monitors. Claude also documents root package locations such as
bin/, scripts/, and settings.json. Mirror those into the Telvine
component inventory with component_path, component_directory, and
manifest_field so usage and failures can be analyzed by directory.
Map Claude surfaces into Telvine components like this:
| Claude surface | Telvine component type | Directory | Manifest field | Telemetry mode |
|---|---|---|---|---|
.claude-plugin/plugin.json | manifest | .claude-plugin | unknown | declaration_only |
skills/*/SKILL.md | skill | skills | skills | skill_events |
commands/*.md | command | commands | commands | component_events or declaration_only |
agents/*.md | agent or sub_agent | agents | agents | component_events or declaration_only |
hooks/hooks.json or inline hooks | hook | hooks or inline | hooks | component_events |
.mcp.json or inline MCP config | mcp_server or mcp_config | mcp or inline | mcpServers | component_events |
.lsp.json or inline LSP config | lsp_server | lsp or inline | lspServers | component_events or host_unavailable |
output-styles/ | output_style | output-styles | outputStyles | declaration_only |
themes/ | theme | themes | experimental.themes | declaration_only |
monitors/monitors.json or inline monitors | monitor | monitors or inline | experimental.monitors | component_events |
userConfig | user_config | .claude-plugin or inline | userConfig | declaration_only |
channels | channel | inline | channels | component_events or declaration_only |
dependencies | dependency | .claude-plugin or inline | dependencies | declaration_only |
bin/ | executable | bin | bin | component_events when wrapped |
scripts/ | script | scripts | scripts | component_events when invoked |
settings.json or manifest settings | settings | settings | settings | declaration_only |
Example inventory entries:
[
{
"component_type": "manifest",
"name": "plugin-json",
"telemetry_mode": "declaration_only",
"component_path": ".claude-plugin/plugin.json",
"component_directory": ".claude-plugin",
"manifest_field": "unknown",
"source": "default_directory"
},
{
"component_type": "skill",
"name": "reconcile-invoices",
"telemetry_mode": "skill_events",
"component_path": "skills/reconcile-invoices/SKILL.md",
"component_directory": "skills",
"manifest_field": "skills",
"source": "default_directory"
},
{
"component_type": "command",
"name": "invoice-status",
"telemetry_mode": "component_events",
"component_path": "commands/invoice-status.md",
"component_directory": "commands",
"manifest_field": "commands",
"source": "default_directory"
},
{
"component_type": "agent",
"name": "collections-reviewer",
"telemetry_mode": "component_events",
"component_path": "agents/collections-reviewer.md",
"component_directory": "agents",
"manifest_field": "agents",
"source": "default_directory"
},
{
"component_type": "hook",
"name": "after-run",
"telemetry_mode": "component_events",
"component_path": "hooks/hooks.json",
"component_directory": "hooks",
"manifest_field": "hooks",
"source": "default_directory"
},
{
"component_type": "mcp_server",
"name": "accounting",
"telemetry_mode": "component_events",
"component_path": ".mcp.json",
"component_directory": "mcp",
"manifest_field": "mcpServers",
"source": "default_directory"
},
{
"component_type": "lsp_server",
"name": "typescript",
"telemetry_mode": "host_unavailable",
"component_path": ".lsp.json",
"component_directory": "lsp",
"manifest_field": "lspServers",
"source": "default_directory"
},
{
"component_type": "output_style",
"name": "finance-summary",
"telemetry_mode": "declaration_only",
"component_path": "output-styles/finance-summary.md",
"component_directory": "output-styles",
"manifest_field": "outputStyles",
"source": "default_directory"
},
{
"component_type": "user_config",
"name": "api_token",
"telemetry_mode": "declaration_only",
"component_path": ".claude-plugin/plugin.json",
"component_directory": ".claude-plugin",
"manifest_field": "userConfig",
"source": "inline_manifest"
},
{
"component_type": "channel",
"name": "slack",
"telemetry_mode": "component_events",
"component_path": ".claude-plugin/plugin.json",
"component_directory": "inline",
"manifest_field": "channels",
"source": "inline_manifest"
},
{
"component_type": "dependency",
"name": "secrets-vault",
"telemetry_mode": "declaration_only",
"component_path": ".claude-plugin/plugin.json",
"component_directory": ".claude-plugin",
"manifest_field": "dependencies",
"source": "inline_manifest"
},
{
"component_type": "script",
"name": "poll-deploy",
"telemetry_mode": "component_events",
"component_path": "scripts/poll-deploy.sh",
"component_directory": "scripts",
"manifest_field": "scripts",
"source": "default_directory"
},
{
"component_type": "executable",
"name": "invoice-check",
"telemetry_mode": "component_events",
"component_path": "bin/invoice-check",
"component_directory": "bin",
"manifest_field": "bin",
"source": "default_directory"
},
{
"component_type": "theme",
"name": "finance-dark",
"telemetry_mode": "declaration_only",
"component_path": "themes/finance-dark.json",
"component_directory": "themes",
"manifest_field": "experimental.themes",
"source": "default_directory",
"experimental": true
},
{
"component_type": "monitor",
"name": "close-health",
"telemetry_mode": "component_events",
"component_path": "monitors/close-health.json",
"component_directory": "monitors",
"manifest_field": "experimental.monitors",
"source": "default_directory",
"experimental": true
},
{
"component_type": "settings",
"name": "settings",
"telemetry_mode": "declaration_only",
"component_path": "settings.json",
"component_directory": "settings",
"manifest_field": "settings",
"source": "default_directory"
}
]
For Claude fields that accept a string or array path, resolve the path into
individual Telvine components. For example,
agents: "./custom/agents/reviewer.md" becomes an agent component with
component_path: "custom/agents/reviewer.md",
component_directory: "agents", manifest_field: "agents", and
source: "manifest_path". Claude manifest paths include ./; Telvine stores
the normalized plugin-relative package path without collecting absolute local
paths or user file paths.
commands and outputStyles replace their default directories when specified;
skills adds custom directories alongside default skills/; hooks, MCP, and
LSP servers follow their own merge rules.
Claude Cowork is the knowledge-work surface; Claude Code is the developer surface. Treat them as different install targets for the same plugin product when the underlying capability is shared. Use plugin components for the full Claude-to-Telvine mapping.
8. OpenClaw plugin layout
For OpenClaw, package the same plugin product with an OpenClaw adapter:
invoice-ops/
├── adapters/
│ └── openclaw/
│ ├── openclaw.plugin.json
│ ├── package.json
│ ├── src/
│ │ └── tools/
│ │ └── accounting-lookup.ts
│ └── skills/
│ └── reconcile-invoices/
│ └── SKILL.md
├── connectors/
│ └── accounting-api/
└── telemetry/
└── events.md
OpenClaw plugins can cover more than Skills. Treat tools, channels, providers, agent harness adapters, speech, media, web, and generation components as plugin components with their own telemetry. See How to Build an OpenClaw Plugin for the dedicated guide.
9. Hermes Agent plugin layout
For Hermes Agent, package the plugin product with a Hermes adapter:
invoice-ops/
├── adapters/
│ └── hermes/
│ ├── plugin.yaml
│ ├── __init__.py
│ ├── schemas.py
│ ├── tools.py
│ ├── hooks.py
│ └── skills/
│ └── reconcile-invoices/
│ └── SKILL.md
├── connectors/
│ └── accounting-api/
└── telemetry/
└── events.md
Hermes plugins can include tools, hooks, commands, integrations, bundled Skills, memory providers, context engines, and model providers. Keep each one in the Telvine component inventory so the product can be compared against Codex, Claude, OpenClaw, and Copilot Cowork. See How to Build a Hermes Agent Plugin for the dedicated guide.
10. Harnesses that expose Skills directly
Some harnesses expose SKILL.md before they expose a full plugin manifest. Still think in plugins:
- Keep a source plugin folder with the plugin name, owner, version, changelog, Skills, scripts, references, and telemetry plan.
- Generate or copy the Skill capability into the harness-specific directory.
- Keep the plugin id and version in your telemetry events.
- Measure the capability as part of the plugin, not as a detached file.
That lets the same reconcile-invoices Skill run in Cursor, Windsurf, Gemini CLI, Copilot, or Antigravity without losing the plugin-level product view.
11. Measure plugin and capability behavior
Measure both layers:
- Plugin events: installed, updated, enabled, disabled, component inventory registered.
- Skill events: invoked, completed, errored, rated, version compared.
- Component events: connector invoked, hook executed, MCP operation wrapped, app action completed, or plugin directory scanned.
For Telvine-backed distribution and metrics, publish the plugin:
npm i -g @telvine/cli@0.1.0
telvine login
telvine publish ./my-plugin
Use plugin components to register non-Skill components and decide whether each component emits runtime events or is inventory-only.
For package or adapter work, emit directory-level telemetry with
component_type: "directory" and a plugin-relative component_path, such as
hooks/hooks.json or monitors/close-health.json. Do not send absolute local
paths, prompts, file contents, connector payloads, tool arguments, or model
outputs.
12. Release through adapters
When a plugin version is ready, release the same source plugin through each target adapter:
- Generate or update the Codex package.
- Generate or update the Microsoft 365 Copilot Cowork app package.
- Generate or update the Claude plugin package.
- Generate or update the OpenClaw plugin package.
- Generate or update the Hermes Agent plugin package.
- Export direct
SKILL.mdpackages for harnesses that do not yet expose full plugin manifests. - Register the plugin version and component inventory in Telvine.
- Compare telemetry across harnesses after rollout.
The cross-harness question is not "did this Skill run?" It is "does this plugin product work across the surfaces where our customers use agents?"
Checklist
- The plugin has a stable name, owner, version, and install target.
- The source plugin workspace exists before any harness-specific adapter.
- The first Skill has a precise trigger description.
- Deterministic work lives in scripts, not prose.
- Each harness has an adapter, but the plugin identity stays stable.
- Telemetry records plugin version, capability version, component directory, and manifest field where relevant.
- Events use typed metadata only: no prompts, file contents, connector payloads, or tool arguments.