research phase 3 plugin targets - sinrimin/harness-nexus GitHub Wiki
Status: research complete (original scope). See the newer
research-phase-3-ecc-install-patterns.mdfor the install-pipeline architecture (adapter factory + plan/apply + install-state) and Codex's ground-truth format, which this earlier doc does not cover.Priority revisions since this doc was written:
- ZCode is out of install scope. The ZCode findings here relied on a single on-disk inspection that is no longer reproducible, and ECC (the new reference) has zero ZCode support.
zcodestays in the enum but has no adapter (409 TARGET_UNSUPPORTED). The "CC+ZCode share a writer" thesis is superseded — each target now gets its own adapter.- Codex is in (JSON manifest + TOML config + stdio-only MCP) — see the ECC research doc, which has ground-truth files.
- Hermes is priority 1 (was last). Its format details below remain medium-confidence pending
hermes_cli/verification.- The "CLI must run standalone" rule is rescinded. References below to a local-manifest install path are obsolete — the CLI now fetches profiles from a server via the SDK (a profile is a reference bundle; resource bodies and the aggregated
/mcpendpoint both require the server). See AGENTS.md architecture rule #6 (rewritten).Compares how Claude Code, ZCode, and Hermes package and install extensions (skills/hooks/sub-agents/MCP/commands), and maps our
Profileconcept onto each as an installable plugin. Retained for the per-target format detail; superseded on architecture by the ECC research doc.
We already have a Profile (a named, versioned bundle of resources — MCP
servers, skills, hooks, sub-agents, rules, commands — plus optional third-party
imports). Phase 3 is the install pipeline: one command that materializes a
profile into a target Agent tool's config directories so the tool loads
everything the bundle carries. This doc answers: what is the native "plugin"
concept of each target, what can a plugin bundle, and how do we emit one from a
profile?
Scope is the three targets named in the PRD: Claude Code, ZCode,
Hermes. (generic exists in agentTargetSchema for future targets.)
Claude Code and ZCode share an almost identical plugin spec. ZCode probes
the same manifest paths as Claude Code (.claude-plugin/plugin.json is
recognized as-is), expands the same ${CLAUDE_*} variables, and registers
Anthropic's own marketplace by default. One Claude-Code-format plugin works
on both with trivial tweaks (a .zcode-plugin/plugin.json twin and/or
${ZCODE_*} variables). Hermes is the outlier: it is a Python agent with
its own plugin.yaml + config.yaml conventions and only partially overlaps
(SKILL.md, AGENTS.md).
This means the install pipeline is two writers, not three:
Profile ──▶ PluginWriter (claude-code) ──▶ also-valid-for-zcode
──▶ PluginWriter (hermes) ──▶ Python-style bundle
| Aspect | Claude Code | ZCode | Hermes |
|---|---|---|---|
| Engine | Node/TypeScript | Node/TypeScript (GLM) | Python |
| Plugin concept | "Plugin" (directory + manifest) | "Plugin" (clone of CC) | "Plugin" (folder + plugin.yaml, Python code) and "Skill Bundle" (lighter, MD-only) |
| Manifest path | .claude-plugin/plugin.json |
.zcode-plugin/plugin.json (also probes .claude-plugin/) |
plugin.yaml |
| Manifest format | JSON | JSON (identical schema) | YAML |
| Marketplace | git-based marketplace.json
|
same, + Anthropic's marketplace registered by default | GitHub "taps" (hermes skills add <repo>); community hub |
| Bundle skills | skills/<name>/SKILL.md |
same |
skills/<name>/SKILL.md (SKILL.md-compatible) |
| Bundle commands | commands/*.md |
same | dynamic, from skill dirs + "quick commands" |
| Bundle hooks |
hooks/hooks.json (rich events) |
hooks/hooks.json (7 events only) |
3 hook systems; event hooks via config/code |
| Bundle MCP servers |
.mcp.json / manifest mcpServers
|
same |
mcp_servers: in config.yaml (YAML, not JSON) |
| Bundle sub-agents |
agents/*.md (frontmatter) |
manifest agents field recorded but NOT executed
|
runtime-only delegate_task (+ background); no file format (3.4-verified) |
| Bundle rules/memory | cannot (no CLAUDE.md auto-load) | cannot (AGENTS.md is not a plugin field) | drop AGENTS.md/.hermes.md at project root |
| Memory file | CLAUDE.md |
AGENTS.md |
.hermes.md / AGENTS.md / CLAUDE.md (all auto-injected) |
| Variables |
${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${user_config.*}
|
${ZCODE_*} (still expands ${CLAUDE_*})
|
n/a (Python) |
| Headless install CLI | ✅ claude plugin install …
|
❓ UI-driven ("Get" button); no confirmed zcode plugin install
|
✅ hermes skills add, Python entry-points |
| Confidence | High (official docs, authoritative) |
High (built-in zcode-guide plugin + on-disk evidence) |
Medium (docs partly AI-generated; verify plugin.yaml schema in-repo) |
Source of truth: the official docs. A plugin is a self-contained directory of components. Installation, manifest, and marketplace are all first-class and headless-scriptable.
Only plugin.json lives in .claude-plugin/; everything else sits at the
plugin root. The manifest is optional (components auto-discovered), but
we will always emit one for explicitness.
| Artifact | Location | Notes |
|---|---|---|
| Skills | skills/<name>/SKILL.md |
MD + YAML frontmatter (name, description, …). Namespaced plugin:skill. |
| Commands | commands/*.md |
Filename = command name. "Use skills/ for new plugins." |
| Sub-agents | agents/*.md |
Frontmatter (name, description, model, tools, …). hooks/mcpServers/permissionMode forbidden in plugin agents.
|
| Hooks | hooks/hooks.json |
Full event set (~30 events: SessionStart, PreToolUse, PostToolUse, Stop, PreCompact, …). |
| MCP servers |
.mcp.json or manifest mcpServers
|
Declared in the plugin, NOT written into host's ~/.claude.json. Uses ${CLAUDE_PLUGIN_ROOT}, ${user_config.KEY}. |
| Memory/rules | — |
CLAUDE.md at plugin root is NOT loaded. Wrap rules as a skill instead. |
- Installed plugins cache:
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/ - Persistent data:
~/.claude/plugins/data/<plugin-id>/(survives updates) - Marketplace registry:
~/.claude/plugins/known_marketplaces.json - Enablement:
enabledPluginsin the relevantsettings.json(user/project/local)
- Interactive:
/plugin install <name>@<marketplace>then pick scope - Headless:
claude plugin install <name>@<marketplace> [--scope user|project|local] - Dev sideload (one session):
claude --plugin-dir ./my-plugin(or.zip, or--plugin-url) - Marketplace add:
/plugin marketplace add owner/repo(git), local dir, file, tarball -
marketplace.jsonsourcekinds:github,url,git-subdir,npm, relative path string
Version resolution: plugin.json.version → marketplace entry version → git SHA
→ unknown. The version string is the cache key — bump it to push updates.
claude plugin update <plugin>; marketplaces default to autoUpdate (Anthropic
ones on, others off).
Sources: plugins-reference, plugin-marketplaces, discover-plugins, settings.
Source of truth: the built-in zcode-guide plugin (Z.ai-authored, the most
authoritative source — far more complete than the web docs) plus on-disk
inspection of ~/.zcode/. Verified on this machine.
-
Manifest probe order:
.zcode-plugin/plugin.json→.claude-plugin/plugin.json→.codex-plugin/plugin.json. A CC plugin's manifest is recognized as-is. -
known_marketplaces.jsonon disk containsclaude-plugins-official→{ "source": "github", "repo": "anthropics/claude-plugins-official" }. Anthropic's marketplace is addable by default. -
CLAUDE_*variables expanded at runtime (${CLAUDE_PLUGIN_ROOT}in plugin.mcp.jsonfiles, observed inios-simulator). -
Identical artifact structure:
skills/<name>/SKILL.md,commands/*.md,hooks/hooks.json,.mcp.json,cache/<mp>/<plugin>/<version>/.
| Component | Claude Code | ZCode | Impact on us |
|---|---|---|---|
| Sub-agents in plugins |
agents/*.md loaded |
manifest agents "recorded but not executed"
|
Cannot ship working sub-agents via a ZCode plugin. Fall back to user-scope ~/.zcode/agents/. |
| Hook events | ~30 |
7 only: SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, Stop
|
Filter the hooks we emit; drop unsupported events for ZCode. |
| Headless install | claude plugin install |
UI-driven ("Get" button on Discover tab); no confirmed zcode plugin install
|
ZCode install may require the user to add our marketplace in the UI, or we write to user-scope dirs directly. |
| MCP schema strictness | lenient | unknown key drops the whole server | Validate aggressively before emitting. |
~/.zcode/
├── AGENTS.md # memory (NOT a plugin field)
├── cli/
│ ├── config.json # mcp.servers, hooks, plugins.enabledPlugins
│ └── plugins/
│ ├── known_marketplaces.json
│ ├── marketplaces/<mp>/marketplace.json
│ └── cache/<marketplace>/<plugin>/<version>/ # installed contents
├── skills/<name>/SKILL.md # user-scope skills
├── agents/<name>.md # user sub-agents (the supported path)
└── server/ # the runtime
Workspace: <repo>/.zcode/config.json, <repo>/.zcode/skills/, <repo>/AGENTS.md.
Plugin servers are keyed plugin:<plugin>:<server> (base layer, overridable
by user/workspace config). Defined inline in mcpServers or via legacy
.mcp.json. Template expansion is plugin-only — config-file servers do NOT
expand ${...}, so if we ever write directly to config.json we must use
absolute paths. All scopes auto-connect at session start.
ZCode supports userConfig (referenced via ${user_config.KEY}) but "a
sensitive value cannot currently be entered in the interface or persisted" —
no secure credential store yet. For secrets we must inject resolved values
at install time rather than rely on prompted userConfig.
Sources: built-in zcode-guide plugin (on disk):
~/.zcode/cli/plugins/cache/zcode-plugins-official/zcode-guide/0.1.0/skills/{zcode-configuration-guide,diagnosing-mcp,diagnosing-skills,diagnosing-commands,diagnosing-hooks,diagnosing-plugins}/SKILL.md;
docs.z.ai; zai-org/zai-coding-plugins.
Source of truth: hermes-agent.nousresearch.com/docs/ + the GitHub repo
NousResearch/hermes-agent. Confidence: medium — the docs site is large but
partly AI-generated; exact plugin.yaml schema must be verified against
hermes_cli/plugins.py in the repo before implementation.
A self-improving, Python-based agent by Nous Research. Distinct from the
Hermes LLM models. It has skills, hooks (3 systems), sub-agents (delegate_task),
slash commands, context files, and MCP — but the packaging model differs from
CC/ZCode.
-
Plugin — a folder under
~/.hermes/plugins/<name>/withplugin.yaml+ Python code. Used for custom tools / lifecycle hooks / CLI commands via aPluginManager. Registration also via Python entry-points. -
Skill Bundle (newer, mid-2026) — a lighter, non-Python set of skills
- a triggering slash command. This is the closer analog to our profile.
Fields (from PluginManifest): name, version, description, author,
requires_env, provides_tools, provides_hooks, source
(user/project/entrypoint), path, and kind — standalone
(default; opt-in via plugins.enabled) / backend (pluggable backend for a
core tool) / exclusive (single provider per category, e.g. memory).
Correction (2026-09, 3.4 verification): the earlier note guessed an
entry_pointmanifest field routing to amodel-provider/web-searchloader — no such manifest field exists. "Entrypoint" is a source value (Python packaging entry-points,plugins.py:398-427); directory plugins are discovered by folder +plugin.yamland register viaregister(ctx)in__init__.py. The Phase 3.4 adapter's manifest (name/version/description/author/kind: standalone) matches the real schema.
| Artifact | Format | Location | Confidence |
|---|---|---|---|
| Skills |
SKILL.md (CC-compatible frontmatter) |
~/.hermes/skills/<name>/ |
High |
| Hooks | event hooks via config + code | config + ~/.hermes/
|
Medium (format partial) |
| MCP servers |
YAML mcp_servers: in config.yaml
|
~/.hermes/config.yaml |
High |
| Sub-agents |
runtime-only — delegate_task tool (+ delegate_task(background=true)); no file format |
runtime | High (3.4-verified) |
| Rules/memory |
AGENTS.md / .hermes.md / CLAUDE.md (all auto-injected) |
project root / global | High |
| Commands | dynamic from skill dirs + "quick commands" | user + project | Medium |
Hermes uses YAML, not JSON:
# ~/.hermes/config.yaml
mcp_servers:
myserver:
url: https://... # HTTP
another:
command: '...' # stdio
args: [...]There is no .mcp.json for Hermes's own servers (that file appears only
when registering Hermes as a server inside another client).
- Skills Hub:
hermes skills add <github-repo>; community hub atgithub.com/amanning3390/hermeshub. - Local folder drop-in →
~/.hermes/plugins/or~/.hermes/skills/. - Python package via entry-points.
- Tarball install: unverified.
- Different language/runtime (Python vs Node), different manifest format (YAML
plugin.yamlvs JSONplugin.json), different MCP config (YAMLconfig.yamlvs JSON.mcp.json), different distribution (GitHub taps + Python entry-points vs git marketplaces). The CC-format plugin we emit for CC/ZCode will not load in Hermes.
Sources: NousResearch/hermes-agent, Hermes docs, plugins, skills, mcp, context-files.
Our domain model (packages/core/src/domain/profile.ts):
Profile { id, name, description, version, scope, ownerId, entries: ProfileEntry[], imports? }
ProfileEntry { resourceId, kind: 'skill'|'hook'|'sub_agent'|'rule'|'mcp'|'command',
pinnedVersion?, installOptions? }Each entry.kind maps onto a plugin component:
Our ProfileEntry.kind
|
Claude Code plugin component | ZCode | Hermes |
|---|---|---|---|
mcp |
.mcp.json / manifest mcpServers
|
same (MCP block) |
config.yaml mcp_servers:
|
skill |
skills/<name>/SKILL.md |
same | ~/.hermes/skills/<name>/ |
command |
commands/*.md |
same | skill-dir command / quick command |
hook |
hooks/hooks.json (event-filtered) |
hooks/hooks.json (7 events) |
config/code hooks |
sub_agent |
agents/*.md |
not executable → fall back to ~/.zcode/agents/
|
no file format — runtime delegate_task only (3.4-verified) |
rule |
wrap as a skill (no CLAUDE.md auto-load) | wrap as a skill | drop AGENTS.md
|
A profile entry of kind: 'mcp' can resolve in two ways, and this is the
crux of Phase 3:
-
MCP server definition (the connection config from Phase 2.1) → write into
the plugin's MCP block so the tool dials the upstream directly. This is
the "standalone plugin" path: the generated plugin carries its own
.mcp.jsonand needs nothing else. -
MCP server = our proxy → the plugin's
.mcp.jsonhas a single entry pointing at our/mcp?profile=<id>endpoint with the user's PAT as a bearer header. The tool then sees the aggregated toolset, and we keep upstream connection management server-side (the central thesis of the product). This is the preferred path — it is what makes the profile more than a static bundle.
// .mcp.json in a profile-generated plugin (recommended: proxy mode)
{
"mcpServers": {
"harnessnexus-frontend-daily": {
"type": "streamable-http",
"url": "https://hnx.example.com/mcp?profile=<profileId>",
"headers": { "Authorization": "Bearer ${user_config.PAT}" },
},
},
}We should support both, selected per profile or per entry. Proxy mode is the default; direct mode is the escape hatch for offline / no-server installs (matches the CLI's "must run standalone" rule in AGENTS.md).
hnx install --profile <id> [--target claude-code|zcode|hermes|generic]
│
▼
ProfileResolver ── (local manifest OR SDK fetch) ──▶ resolved Profile + artifacts
│
▼
TargetWriter (one per target) ──▶ emits a plugin dir / config edits
├── ClaudeCodePluginWriter (also valid for ZCode)
├── ZCodePluginWriter (narrows CC output: 7 hook events, no plugin sub-agents, user-scope agents fallback)
└── HermesPluginWriter (YAML plugin.yaml, config.yaml mcp_servers, skill-bundle)
│
▼
Installer (per target)
├── claude-code: `claude plugin marketplace add <dir|repo>` + `claude plugin install`
├── zcode: add marketplace via UI (or write to ~/.zcode/ user-scope dirs directly)
└── hermes: `hermes skills add <repo>` (or drop into ~/.hermes/skills|plugins/)
-
CC/ZCode output diverges from Hermes output at the format level (JSON vs
YAML,
.mcp.jsonvsconfig.yaml). A single generic emitter would be a mess of conditionals. - CC → ZCode is narrowing, not rewriting: emit CC-format, then strip unsupported hook events and move sub-agents to user-scope. A thin ZCode writer that post-processes the CC output (or shares an emitter with a target flag) is cleaner than two independent ones.
- Hermes is genuinely separate and lower-confidence; isolate it so its rough edges don't contaminate the high-confidence CC/ZCode path.
<profile>-bundle/
├── .claude-plugin/
│ └── plugin.json # name, version, description, userConfig{PAT}
├── .zcode-plugin/ # twin (optional; CC manifest is auto-recognized)
│ └── plugin.json
├── .mcp.json # single entry → our proxy w/ ?profile=<id>
├── skills/
│ └── <name>/SKILL.md # one per kind=skill entry
├── commands/
│ └── <name>.md # one per kind=command entry
├── agents/
│ └── <name>.md # one per kind=sub_agent entry (CC only; ZCode ignores)
├── hooks/
│ └── hooks.json # from kind=hook entries (event-filtered per target)
└── rules-as-skill/ # kind=rule entries wrapped as a skill (CC/ZCode)
For standalone/direct mode, .mcp.json instead carries each profile MCP entry's
own connection (decrypted credential injected at install time, since the bundle
leaves our trust boundary).
- Proxy vs direct MCP mode as default. Recommend proxy (it is the product thesis: one aggregated endpoint). Confirm direct mode is still wanted for the offline CLI path.
-
Credential handling in direct mode. If we emit
.mcp.jsonwith live upstream connections, decrypted secrets must be injected at install time and the bundle must be treated as sensitive. Lean onuserConfig{PAT}in proxy mode to avoid shipping secrets at all. -
ZCode sub-agents. Not loadable from a plugin today. Decision: skip
sub-agents in ZCode plugins, or fall back to writing
~/.zcode/agents/*.mddirectly (user-scope) and warn the user. -
ZCode headless install. No confirmed
zcode plugin install. Decision: document the UI "add marketplace" step, or write to user-scope dirs (~/.zcode/skills|commands|agents/) directly, bypassing the plugin system. -
Hermes confidence. Verify
plugin.yamlschema + hooks config againsthermes_cli/plugins.pyand the bundled skillskills/autonomous-ai-agents/claude-code/SKILL.mdin the repo before writing the Hermes writer. Treat the Hermes writer as lower-priority / lagging CC+ZCode. -
Rules as skills. Neither CC nor ZCode auto-loads a memory file from a
plugin. We must wrap
kind=rulecontent into a generatedSKILL.md. Confirm that is acceptable vs. instructing users to paste rules into theirCLAUDE.md/AGENTS.mdmanually. -
Marketplace hosting. For
hnx installto be one-click, the generated bundle should be installable from a marketplace we host (git repo) or a local dir. Decide whether Harness Nexus auto-publishes per-profile marketplace entries or only emits a local dir the user adds.
-
3.1 — CC/ZCode writer + proxy mode.
hnx install --profile <id> --target claude-codeemits the CC-format plugin dir above with a single proxy MCP entry. Verify it loads in both Claude Code and ZCode. No sub-agents/hooks yet. - 3.2 — full artifact emission. skills, commands, hooks (event-filtered), rules-as-skill, sub-agents (CC; ZCode fallback). Direct MCP mode as an option.
-
3.3 — Hermes writer. After verifying
plugin.yaml/config.yamlschemas in-repo; skill-bundle style first, Python plugin only if needed. -
3.4 — ECC + Superpower import adapters (the
ProfileImportfield).
- Claude Code: plugins-reference, plugin-marketplaces, discover-plugins, settings, anthropics/claude-plugins-official
- ZCode: built-in
zcode-guideplugin (on-disk, Z.ai-authored), docs.z.ai, zai-org/zai-coding-plugins, Anthropic marketplace registered in ZCode - Hermes: NousResearch/hermes-agent, Hermes docs, plugins, skills, mcp
{ "name": "frontend-daily", // required, kebab-case, immutable "displayName": "Frontend Daily", "version": "1.2.0", // omit → uses git SHA (every push = update) "description": "Frontend toolchain profile", "author": { "name": "Harness Nexus" }, "skills": "./skills", // dir | array | inline "commands": ["./commands"], // legacy; prefer skills/ for new plugins "agents": ["./agents/reviewer.md"], "hooks": "./hooks/hooks.json", "mcpServers": "./mcp-config.json", // or inline mcpServers object "userConfig": { // prompted at enable; sensitive → keychain "apiToken": { "type": "string", "sensitive": true, "description": "…" }, }, "defaultEnabled": true, }