Telvine Learn

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:

ComponentPurpose
PluginThe installable invoice operations product
Skill: reconcile-invoicesMatch payments to invoices and report exceptions
Skill: draft-collection-emailsDraft follow-up emails for overdue invoices
Connector or MCP configRead invoices from the accounting system
HookEmit 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.

HarnessPlugin/package shapeWhat to ship
Microsoft 365 Copilot CoworkMicrosoft 365 app packageSkills, connectors, or both, with telemetry from backend and connector surfaces
OpenAI Codex.codex-plugin/plugin.json at the plugin rootskills/, hooks/, .app.json, .mcp.json, and assets/ as needed
Anthropic Claude Cowork / Claude Code.claude-plugin/plugin.json at the plugin rootSkills, commands, agents, hooks, MCP servers, output styles, LSP servers, settings, user config, dependencies, scripts, bins, themes, and monitors as supported
OpenClawOpenClaw plugin packageTools, Skills, channels, model providers, agent harness integrations, speech, media, web, generation, and runtime components as needed
Hermes AgentHermes plugin packageTools, hooks, slash commands, CLI commands, integrations, bundled Skills, memory providers, context engines, and model providers
GitHub CopilotSkill capability packagePlace Skills where Copilot loads SKILL.md; keep plugin identity and version in your release process
Cursor, Windsurf, Gemini CLI, AntigravitySkill capability packagePlace 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 surfaceTelvine component typeDirectoryManifest fieldTelemetry mode
.claude-plugin/plugin.jsonmanifest.claude-pluginunknowndeclaration_only
skills/*/SKILL.mdskillskillsskillsskill_events
commands/*.mdcommandcommandscommandscomponent_events or declaration_only
agents/*.mdagent or sub_agentagentsagentscomponent_events or declaration_only
hooks/hooks.json or inline hookshookhooks or inlinehookscomponent_events
.mcp.json or inline MCP configmcp_server or mcp_configmcp or inlinemcpServerscomponent_events
.lsp.json or inline LSP configlsp_serverlsp or inlinelspServerscomponent_events or host_unavailable
output-styles/output_styleoutput-stylesoutputStylesdeclaration_only
themes/themethemesexperimental.themesdeclaration_only
monitors/monitors.json or inline monitorsmonitormonitors or inlineexperimental.monitorscomponent_events
userConfiguser_config.claude-plugin or inlineuserConfigdeclaration_only
channelschannelinlinechannelscomponent_events or declaration_only
dependenciesdependency.claude-plugin or inlinedependenciesdeclaration_only
bin/executablebinbincomponent_events when wrapped
scripts/scriptscriptsscriptscomponent_events when invoked
settings.json or manifest settingssettingssettingssettingsdeclaration_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:

  1. Keep a source plugin folder with the plugin name, owner, version, changelog, Skills, scripts, references, and telemetry plan.
  2. Generate or copy the Skill capability into the harness-specific directory.
  3. Keep the plugin id and version in your telemetry events.
  4. 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:

  1. Generate or update the Codex package.
  2. Generate or update the Microsoft 365 Copilot Cowork app package.
  3. Generate or update the Claude plugin package.
  4. Generate or update the OpenClaw plugin package.
  5. Generate or update the Hermes Agent plugin package.
  6. Export direct SKILL.md packages for harnesses that do not yet expose full plugin manifests.
  7. Register the plugin version and component inventory in Telvine.
  8. 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.

Next steps