Skip to content
Conduit

mcp server

harness-asset-manager

One control plane for Skills, Agents, MCP servers, commands, hooks, and permissions across AI harnesses.

execsumo/harness-asset-managercommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "harness-asset-manager": {
      "command": "uvx",
      "args": [
        "harness-asset-manager"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

HAM

Harness Asset Manager

What HAM does

Every AI harness wants its own copy of the same things: Skills in one folder, agents in another, MCP servers in a config file, commands somewhere else. It works until you use more than one harness.

HAM turns that sprawl into one local source of truth. Manage an extension once, decide where it should be active, and let HAM handle the harness-specific plumbing. It normalizes what can be normalized, preserves native behavior where it matters, detects drift instead of hiding it, and keeps conflicts reviewable.

The goal is not another abstraction layer for its own sake. It's simple: configure your AI tooling once, then use the right harness for the job.

Single Source of Truth & Cross-Harness Sync

  • Edit Once, Active Everywhere: For file-based extensions (Skills and Agents), Harness Asset Manager uses managed symlinks to a single canonical store. Edit an asset in any enabled harness or in the shared store, and the changes are immediately active across every harness sharing the link.
  • Drift Detection & One-Click Sync: For native config-based and rendered extensions (MCP Servers, Slash Commands, Hooks, and Security Permissions), Harness Asset Manager actively monitors local harness state. When you edit a native config in one harness, Harness Asset Manager detects the drift, flags it for review, and lets you adopt or sync the changes across all your other harnesses in one step.
  • Safe Conflict Isolation: If multiple harnesses edit the same asset independently, Harness Asset Manager preserves both versions, prevents silent data loss, and lets you resolve conflicts safely.

Key Capabilities

Asset FamilyWhat HAM does
SkillsAdopt local Skill folders into one shared inventory, then enable or disable them per harness using managed symlinks.
AgentsStore subagents as Markdown files with YAML frontmatter, symlinked (or rendered for Codex) across harnesses with automated drift repair and safe conflict resolution.
MCP ServersManage normalized MCP server configurations and translate them into native harness shapes (JSON, TOML, YAML).
Slash CommandsMaintain a single reusable prompt library and sync rendered command files into supported harness formats.
HooksConfigure normalized event and tool category hook records, synced into native harness settings with drift detection and review for unmanaged entries.
PermissionsEnforce strict denylists across supported harnesses (Claude Code, Codex, Antigravity, and Cursor) to restrict shell commands, file paths, web domains, and MCP tools in a unified view.
Configs & AuditCarry harness preferences between machines in one portable manifest that excludes secrets, absolute paths, and other families' keys — backed by an append-only JSON Lines audit journal.
MarketplaceDiscover and preview Skills, MCP servers, and external CLI tools from marketplace hubs.
Headless / CLIDrive all features headlessly via CLI commands with --json output—ideal for VPS environments, containers, or Linux sandboxes with no browser required.

Extension Statuses

Each asset family has a single "In use" page listing everything in that family — managed and unadopted alike — with a status filter to narrow the view:

  • In use: Harness Asset Manager controls the item and enables or disables it across harnesses.
  • Needs review (filter): items Harness Asset Manager found in your harness configs but does not yet manage — unmanaged entries and configuration drift that require your decision. Adopt them inline from the same page; differing configs are resolved via a source-of-truth choice.
  • Discover: Browse marketplaces and preview external tools.

Supported Harnesses

Harness Asset Manager supports 8 AI agent harnesses across 6 asset families:

Harnesses appear in this canonical order everywhere in the app—Settings and every resource matrix. The order is declared in SUPPORTED_HARNESS_DEFINITIONS (harness_asset_manager/harness/catalog.py). Disabling a harness in Settings drops its column across all matrices.

Capability Matrix

HarnessSkillsAgentsMCP ServersSlash CommandsHooksPermissions
Claude CodeYesYesYesYesYesYes (Denylist)
Codex CLIYesYesYesYesYesYes (Denylist)
PiYesYes (extension)Not YetYesNot YetNo
Antigravity (agy)YesYesYesYesPartialYes (Denylist)
CursorYesYesYesYesYesYes (Denylist)
OpenCodeYesYesYesYesPartialNo
Hermes AgentYes (per-Bot¹)Yes (Bots/Profiles¹)YesYes² (Provisional)Not YetNo
Factory DroidYesYesYesYesNot YetNo

Factory Droid support is currently best-effort and targets the personal/global configuration under ~/.factory. See Factory Droid support for the supported asset paths, project-scope boundaries, and documentation basis.

Pi support is currently best-effort and targets the global agent store under ~/.pi/agent. See Pi support for the supported resource paths and extension boundary.


How to Use It

Install

Homebrew (macOS recommended)

brew tap execsumo/tap
brew install harness-asset-manager
harnessam start

brew install harnessam also works as an alias for the same formula.

If harnessam: command not found appears after installing, Homebrew's bin directory isn't on your PATH yet — run eval "$(brew shellenv)" and add that line to your shell profile (~/.zprofile or ~/.bash_profile).

From Source / Local Development

git clone https://github.com/execsumo/harness-asset-manager.git
cd harness-asset-manager
scripts/install-dev.sh

Quick Start

1. Web Control Surface

Launch the background daemon and open the browser interface:

harnessam start

Check on it, or bring it down, the same way:

harnessam status   # running? at what URL, which pid
harnessam stop     # stop the managed background instance

There's no separate "restart" command — stop, then start again, to pick up an upgrade or a config change:

harnessam stop && harnessam start

Or run the server directly in the foreground (for containers, systemd, or remote setups):

harnessam serve --no-open-browser --host 127.0.0.1 --port 8000

2. Headless CLI Quick Reference

Every asset family managed by the Web UI is also accessible via the CLI:

# Matrix Overview
harnessam skills list                      # skills × harness matrix
harnessam agents list                      # agents × harness matrix
harnessam mcp list                         # mcp servers × harness matrix
harnessam hooks list                       # hooks × harness matrix
harnessam permissions list                 # permissions × harness matrix
harnessam commands list                    # slash commands list

# Management & Binding
harnessam agents enable release-bot --harness claude
harnessam skills set-harnesses lint-rule --target enabled
harnessam commands sync deploy --target claude --target codex
harnessam hooks set-harnesses lint-gate --target enabled

# Permissions
harnessam permissions create --id no-force-push \
    --decision deny --scope shell --pattern 'git push --force'

# Marketplace Installation
harnessam mcp install exa

# Configs & Settings
harnessam configs capture
harnessam settings show

All CLI commands accept --json for script integration:

harnessam skills list --json | jq -r '.rows[] | select(.displayStatus=="Unmanaged") | .skillRef'

Product Tour

Overview

Start with the Active harnesses table: per-harness coverage across skills, slash commands, MCP servers, hooks, permissions, and agents, with a review column that totals everything waiting for a decision. Every cell deep-links into the filtered capability view (and its needs-review surface), so a glance turns directly into action, and an All-harnesses totals row jumps to each capability's full catalog.

Below it sit two secondary panels. Needs correcting lists one notice per asset that departs from the Agent Skills standard — naming the asset, saying what to fix, and linking straight to its detail drawer, rather than reporting a count you then have to go hunt through. Review to Adopt is a standing backlog of adoption and config work, styled recessed on purpose: it is a queue, not an alert, and it should not compete with the harness table for your attention.

Skills

Use Skills as shared local packages instead of maintaining separate copies per harness.

Typical flow:

  1. Review a Skill found in a harness, install one from the marketplace, or write a new one with Add Skill.
  2. Adopt it into the Harness Asset Manager inventory.
  3. Check what the package holds — scripts/, references/, assets/ — before enabling it.
  4. Enable it only where it should be available.
  5. Filter by Agent or bulk attach/detach selected Skills to adopted Agents when curating an agent's Skill set.
  6. Update, remove, or delete it from one place.

MCP Servers

Use MCP servers as one normalized config that can be written into each harness shape.

Typical flow:

  1. Review an MCP server found in a harness or install one from the marketplace.
  2. Adopt it into the Harness Asset Manager inventory.
  3. Enable it where the server should be available.
  4. Resolve config differences, disable harness bindings, or uninstall it from one place.

Slash Commands

Use slash commands as one shared prompt library instead of rewriting the same command in each harness-specific format.

Typical flow:

  1. Create a slash command with a name, description, and prompt.
  2. Use $ARGUMENTS where runtime input should be inserted.
  3. Sync it to supported harnesses.
  4. Review existing harness command files and adopt them into the shared library when needed.

Agents

Subagents you keep in one place instead of copy-pasting between harnesses.

Typical flow:

  1. Write an agent — a name, a description, and a system prompt — or adopt one Harness Asset Manager found in a harness.
  2. Turn it on for the harnesses that should have it.
  3. In the detail editor, attach adopted Skills to the agent; the full list of adopted Skills is offered, and typing narrows it.
  4. From the Skills page, filter by an Agent chip or select several managed Skills and attach/detach them to adopted Agents in one previewed bulk action.
  5. Review agents discovered in harness directories and adopt the ones worth keeping.

If a harness later edits an agent out from under Harness Asset Manager — some editors replace the link with their own copy — that edit is folded back in automatically, but only when it is provably the only edit. Conflicting edits are always left for you to resolve. See Agents below.

Marketplace

Marketplace is the discovery surface:

  • Skills Marketplace: browse and install Skills.
  • MCP Marketplace: browse and install MCP servers.
  • CLI Marketplace: preview external CLI tools from CLIs.dev. This is display-only; Harness Asset Manager does not install or manage CLIs.


How It Works (Deep Dive)

Store Layout

Harness Asset Manager keeps a flat store under its data directory: skills/ holds one directory per skill, agents/ holds one <slug>.md per agent, and each family's manifests sit alongside. Every binding into a harness points back here, so a resource is edited once and every harness that has it enabled follows.

Two earlier layouts are migrated one-time on first start — the pre-package shared/ directory and the later packages/local/ structure both fold into skills/ and agents/. The migration is locked, idempotent, and skipped once the flat layout exists.

All six asset families support the same sidecar tagging model. Tags are stored in data/asset-tags.json, never in asset documents or harness files; managed entries expose tags in list/detail views, and the starred tag is available through the matrix star control and URL-backed ?tag=starred filter. Tag filters compose with each family's status and harness filters. Unmanaged entries remain read-only for tagging.

Every tag editor suggests existing tags while you type, so a portfolio converges on a shared tag vocabulary instead of splintering into near-duplicates. Families whose managed rows carry select checkboxes (Skills, MCP Servers, Permissions) also support bulk tagging: select rows, then use Tag in the bulk action bar to apply one or more tags to all selected assets at once — tags are merged into each asset's existing set, never replaced.

Skills

Before adoption, each harness points at its own local skill folder. After adoption, Harness Asset Manager keeps one canonical package in its shared local store and exposes it to selected harnesses with local links. Disabling a harness removes that harness binding without deleting the package.

Harness Asset Manager treats managed Skills as portable by default: once a Skill is adopted into the shared store, it can be enabled for any supported harness. originHarness is retained only as provenance.

Hermes Agent Skills use the categorized Hermes layout under ~/.hermes/skills/<category>/<skill>/SKILL.md. Shared Skills enabled for Hermes are linked under the harnessam category by default. Each Hermes Bot additionally has its own independent Skill set under <hermes-root>/profiles/<bot>/skills/harnessam/, recorded as hermes:<profile>; a Skill a Bot created for itself is surfaced as an adoption candidate attributed to that Bot. See Hermes Bots (Profiles). The legacy harness-asset-manager category remains readable so existing links continue to work. Harness Asset Manager excludes bundled Skills tracked by .bundled_manifest and official/builtin optional Skills recorded in Hermes hub provenance. Other valid Hermes Skill directories—including local or self-learned Skills with no .hub/lock.json entry—are surfaced as unmanaged and can be adopted; external hub provenance is retained when available. Hermes-owned bundled and official optional folders remain untouched until explicitly adopted or managed.

Claude Code plugin skills from installed plugins in ~/.claude/plugins/installed_plugins.json are discovered directly from active plugin installation roots. Plugin skills are surfaced in the inventory as unmanaged with plugin provenance (claude:plugin:<id>@<version>:<skill>) and treated as strictly read-only external sources. Adopting a plugin skill copies the package into Harness Asset Manager's canonical store and links it into ~/.claude/skills/, leaving the underlying plugin installation cache completely untouched.

Add Skill on the Skills page authors a new package in the shared store without leaving Harness Asset Manager — the counterpart to Add Agent on the Agents page. The dialog asks for the frontmatter through the same structured editor the Skill detail view uses, raw-YAML toggle and free-form keys included, so the fields a Skill is created with and the fields it is edited with are one surface. The typed name is folded to the specification's form and used for both the package directory and name: — shown as <slug>/SKILL.md while you type — which is why a Skill created here opens with a clean standards check. A name that collides with a package already in the store is refused before the request. Harnesses are picked in the dialog and bound in the same call that writes the package, pre-selected from Settings → Auto-adopt; a harness that cannot take the link is named in the result rather than losing the Skill. Scripts and resources are added to the package folder afterwards.

Every managed Skill is checked against the Agent Skills specification — name charset and length, name matching its package directory, description presence and length. The results are advisory and never block anything: HAM keys Skills on their package directory and treats name as a display name, so enforcing the spec would retroactively invalidate Skills that work perfectly well. Departures appear as Standards check notes on the Skill's detail view and as notices on the Overview, each phrased as the correction to make. This is HAM's own check — there is no external validator and no extra dependency.

Adoption takes the whole folder, not just SKILL.md — scripts/, references/, assets/, and anything else the package ships travel with it, and harnesses bind to it with a directory symlink. The detail view's Package contents section says what is in there: each top-level entry with its file count, folders collapsed so a large package stays readable, and scripts/ badged as executable material so you can see whether a Skill ships code before enabling it somewhere that will run it.

Editing a Skill's document in place preserves its frontmatter exactly, including nested structures — metadata: maps, tags: lists, lists of maps, and literal | blocks — and re-quotes any scalar that would not be valid YAML unquoted. A frontmatter value spanning several lines is edited in a text area so its indentation stays intact.

Skills can be starred or assigned free-form tags from the matrix or detail view. Tag chips and filters use the shared sidecar store, while the star is surfaced as the pinned starred system tag.

Managed Skill rows also show Agents chips derived from adopted agents' skills: frontmatter. Clicking a chip filters the matrix with ?agent=, and the sibling Agent filter can combine several agents with OR semantics. Bulk selection offers Agents for managed Skills only: choose one or more adopted agents, review the agent files that will change plus any new harness bindings, then confirm. Detach removes the Skill slug from the agent frontmatter but deliberately leaves existing harness Skill bindings installed.

MCP Servers

MCP servers are stored as normalized Harness Asset Manager records, then translated into the config shape each harness expects:

  • Codex uses TOML under mcp_servers.
  • Claude Code and Cursor use mcpServers JSON entries.
  • OpenCode uses typed local/remote MCP entries.
  • Antigravity (agy) uses mcpServers JSON entries with serverUrl for HTTP transports and command/args/env for stdio.
  • Hermes Agent uses YAML under mcp_servers in ~/.hermes/config.yaml (or $HERMES_HOME/config.yaml).
  • Factory Droid uses JSON under mcpServers in ~/.factory/mcp.json.

When Harness Asset Manager finds different configs for the same MCP server, it asks you to resolve the source of truth first.

MCP servers support the shared managed-entry tagging controls: star a server in the matrix, edit tags in its detail view, or filter the page with ?tag=.

Slash Commands

Slash commands are stored as TOML records under Harness Asset Manager app storage, then rendered into each supported harness format:

  • Claude Code writes Markdown command files under ~/.claude/commands and invokes them with /.
  • Codex writes prompt files under ~/.codex/prompts and invokes them with /prompts:.
  • Cursor writes plain text command files under ~/.cursor/commands and invokes them with /.
  • OpenCode writes Markdown command files under ~/.config/opencode/commands and invokes them with /.
  • Antigravity (agy) writes Markdown command files under ~/.gemini/antigravity-cli/commands and invokes them with /.
  • Factory Droid writes Markdown command files under ~/.factory/commands and invokes them with /.

Disabling a harness in Settings removes its column here immediately, without a restart. Command files already written to that harness and their sync records are left alone — re-enabling it restores the column and its sync state unchanged.

Harness Asset Manager tracks target ownership with sync state and content hashes. With slash-command auto-adoption enabled, it adopts only equivalent unmanaged command files and never overwrites their contents. Otherwise it reports unmanaged, changed, or missing files for review. Review actions let you adopt unmanaged commands, restore managed content, adopt a changed harness command as the new source, or remove a broken binding while leaving the harness file untouched.

Slash commands support the shared managed-entry tagging controls, including matrix starring, detail tag chips, and URL-backed tag filters.

Hooks

Hooks are stored as normalized Harness Asset Manager records using canonical events (pre_tool_use, post_tool_use, user_prompt_submit, session_start, stop, pre_compact) and canonical tool categories (shell, file_read, file_write, mcp, web, any). Each harness codec translates a canonical record into that harness's native event names and config shape, and merges it into the harness's hook config:

  • Claude Code writes hook entries into ~/.claude/settings.json under the hooks key.
  • Codex writes inline [hooks] tables into ~/.codex/config.toml (same event schema as Claude).
  • Cursor writes ~/.cursor/hooks.json, expressing each tool category as its dedicated event (beforeShellExecution, afterFileEdit, beforeMCPExecution, and so on).
  • OpenCode writes experimental.hook entries in opencode.json — limited to file_edited (post-edit on write) and session_completed (stop), so coverage is partial.
  • Antigravity (agy) writes a name-keyed ~/.gemini/config/hooks.json (discovering entries across user-level ~/.gemini/*/hooks.json and project-level .agents/hooks.json), matching against tool names (run_command, view_file, …); it covers tool, stop, and (via PreInvocation) prompt-submit hooks, so coverage is partial.
  • Factory Droid hooks are not currently managed. Droid has user, project, enterprise, and legacy hook scopes with lifecycle and matcher semantics that do not map safely to HAM's canonical hook model.

Because harnesses differ, not every canonical event maps to every harness. Harness Asset Manager exposes a representability matrix showing where each hook can sync and where it cannot, including caveats — for example, an Antigravity user_prompt_submit hook maps to PreInvocation, which fires before every model invocation rather than only on prompt submit.

Harness Asset Manager owns only the specific hook entries it writes. It merges into each harness's config without disturbing hooks or other keys it does not manage, and it tracks ownership with content hashes. When a managed hook is edited outside Harness Asset Manager it is reported as drifted, and hooks found in a harness that Harness Asset Manager does not manage are reported as unmanaged for review.

Hooks support the shared managed-entry tagging controls, including matrix starring, detail tag chips, and URL-backed tag filters.

Agents

Agents are Markdown files with YAML frontmatter and the system prompt as the body. They live in Harness Asset Manager's store; enabling one for a harness symlinks it into that harness's agents directory, so editing the agent once updates it everywhere it is enabled.

Harness Asset Manager's standard agent frontmatter contract includes name, description, role, harness, model, effort, tools, disallowedTools, skills, memory, and maxTurns. The detail editor groups supported fields under Identity, Harness & Model, Capabilities and Execution. Keys outside that contract — including legacy mode, spawning, trust-project and deny-tools — are not structured fields, but a saved agent keeps them verbatim as custom frontmatter; only keys that are null or empty are dropped. The Create Agent dialog follows the same field order. skills accepts only adopted HAM Skills and offers them as suggestions. Saving an agent automatically enables each attached Skill on installed harnesses where that agent is enabled. Removing a Skill from the agent is non-destructive and does not disable existing Skill bindings.

effort is a fixed vocabulary — Claude Code's low, medium, high, xhigh, max, or empty to clear the key — so the editor offers a picker rather than a text box, and the API rejects anything else with a 400. That keeps a typo, a raw-YAML edit, or a hand-edited file from writing a value Claude Code does not understand. model is deliberately free text — its value set is open-ended. harness is a picker too, but of a discovered vocabulary rather than a fixed one: both the detail editor and the Create Agent dialog list every harness in the inventory and mark the ones not installed on this machine, since an agent is often authored here for a machine elsewhere.

Other frontmatter keys — Claude's permissionMode, mcpServers, hooks, memory, initialPrompt, and experimental; Cursor's readonly and is_background; Codex's sandbox_mode — remain custom configuration and are preserved on edit. The detail view lists those keys verbatim under Configuration, so a new harness field shows up without code changes here. Retired HAM-only keys are dropped on write because Claude Code does not load them.

The agents matrix shows the same harnesses as every other family — whichever you have enabled in Settings:

HarnessLocationHow it is installed
Claude Code~/.claude/agents/symlink
Cursor~/.cursor/agents/symlink
Antigravity~/.gemini/antigravity-cli/agents/symlink
Pi~/.pi/agent/agents/symlink
OpenCode$XDG_CONFIG_HOME/opencode/agents/symlink
Codex~/.codex/agents/rendered TOML
Hermes<hermes-root>/profiles/<bot>/ (a Bot/Profile)provisioned profile directory
Factory Droid~/.factory/droids/symlink

Most harnesses read the same Markdown format the store holds, so enabling one symlinks the store file into place — edit the agent once and every harness it is enabled for follows. Codex is the exception: it reads TOML with different keys (name, description, developer_instructions), so Harness Asset Manager renders a real file marked # harness-asset-manager:generated. Local edits to a rendered file are reported but never adopted — re-enabling overwrites them. Hermes is the other exception: an agent becomes a native Hermes Bot — a Profile directory HAM writes, with its own SOUL.md identity rendered from the store, its own model routing and its own Skill set. See Hermes Bots (Profiles).

Harness Asset Manager only ever removes files it owns — a symlink into its store, or a file carrying its generated marker. Anything else in a harness's agents directory is reported as unmanaged for review, never overwritten. Adopting one moves it into the store (converting Codex TOML to Markdown) and installs it back; when the donor is being promoted, an incomplete donor missing its name, description, or system prompt is rejected before mutation with guidance to edit and save it before retrying. If the name is already taken in the store, Harness Asset Manager refuses to guess and asks which version to keep.

Agents support the shared managed-entry tagging controls, including matrix starring, detail tag chips, and URL-backed tag filters.

Agent Skills

The agent detail editor can attach adopted Skills through the skills: frontmatter list. HAM validates each slug against the managed Skills inventory, suggests adopted Skills while typing, and automatically enables newly attached Skills on every installed harness where the agent is enabled. The Skills page is the bulk view over the same relation: its Agent chips and ?agent= filter are a derived reverse index over agent files, and Agents writes through the same agent update path after a dry-run review. Removing a Skill from an agent — from either the agent detail editor or the Skills bulk detach action — only changes that agent's frontmatter; it never removes a Skill binding that may be used independently.

When a harness breaks the link

Some editors save a file by writing a temporary file and renaming it over the target. Renaming over a symlink replaces the symlink with a regular file, so a harness that edits an agent through its own UI can silently turn a binding into an ordinary copy — and from then on the two versions drift apart. (Skills are immune to this: they bind as directory symlinks, and renaming a directory over one fails at the kernel.)

Harness Asset Manager records every binding it makes in bindings.json: which agent went to which harness, and what the store held at that moment. That record is what makes the difference between "a harness broke our link" and "an unrelated file happens to share this name" decidable — without it, the two are the same observation. The record is a cache, never a source of truth; if it disagrees with the filesystem, the filesystem wins. Deleting it costs you the automation, never your content.

On the next inventory load, each broken binding is classified and handled:

What is foundWhat happens
The copy is identical to the storeThe link is restored. There is no content decision to make.
The copy was edited, and the store has not changed since it was linkedThat copy holds the only edit in existence, so it is adopted into the store and every binding for that agent is restored.
The copy was edited and the store changed tooNothing. Both sides hold work; choosing either discards the other. Reported for you to resolve.
Several harnesses were edited differentlyNothing is adopted and nothing is deleted. Each version is preserved under agents/conflicts/, with one issue naming every side.
There is no record of a binding hereNothing. This is an ordinary name collision, and you are asked, as before.

Newest-file-wins is deliberately not a rule here — it silently discards the other harness's work, which is the exact failure this exists to prevent. Codex is excluded from automatic adoption entirely, because converting its TOML back to Markdown drops keys Harness Asset Manager does not model.

Every automatic action is appended to the Activity audit log: repair you cannot see is nearly as bad as breakage you cannot see. Agent-specific repairs are also shown under Recent automatic repairs on the agents page's Needs-review view. Each family has its own setting, and turning one off takes effect on the next load, not the next restart.

Hermes Bots (Profiles)

A Hermes Profile is a complete HERMES_HOME — its own config.yaml, SOUL.md, .env, skills/, sessions/ and memory — which Hermes discovers by scanning <hermes-root>/profiles/. Harness Asset Manager represents a HAM agent as one such profile, so a Bot gets its own identity, model routing, sessions and precisely the Skills chosen for it, without touching the default Hermes profile.

HAM writes the profile directory; it never runs the hermes CLI. Every other harness binding is pure filesystem manipulation and this one stays consistent with them, which keeps provisioning idempotent, offline and testable. Two effects of hermes profile create are deliberately not reproduced: HAM installs no PATH wrapper script and registers no gateway service. A HAM-managed Bot is addressed as hermes -p <name>.

What provisioning does write, and why each matters:

WrittenWhy
.env, empty, mode 0o600Without it the profile silently inherits shell API keys
SOUL.mdThe Bot's identity, rendered from the HAM agent. HAM owns and updates this one
config.yaml with _config_versionA stale version makes Hermes' doctor/desktop report the profile as outdated
.no-bundled-skillsThe Bot's skill set is intentional rather than inherited
skills/harnessam/The managed category HAM links into

The _config_version value is mirrored from the local install's own ~/.hermes/config.yaml, never hardcoded, so a Hermes schema bump does not start producing "outdated profile" warnings. A profile carrying a .deleted tombstone is never silently resurrected: it is reclaimable only when it is an identity-free empty shell, and the tombstone is cleared when it is.

Slug to profile name

HAM slugs and Hermes profile ids are different alphabets. Hermes requires ^[a-z0-9][a-z0-9_-]{0,63}$ — no dots, must start alphanumeric, 64 characters — and reserves both a fixed set of names and its own subcommands. HAM slugs allow dots and have no length cap. hermes_profile_name() maps between them, and refuses rather than rewrites when the result would be reserved, a subcommand, or empty. Two slugs that would map to the same profile are reported as a collision at enable time, never silently merged into a shared profile.

Per-Bot Skills, one canonical package

Each Bot has an independent enabled-Skill set, but Bots linked to the same Skill share one canonical HAM package:

<hermes-root>/profiles/<bot>/skills/harnessam/<skill> -> $HAM_DATA_DIR/skills/<skill>

Bindings are recorded as hermes:<profile> — a binding target is <harness> for the whole harness or <harness>:<scope> for one target within it. Existing harness-wide values keep working unchanged and need no migration. The Skills matrix still shows one Hermes column; the detail view names each Bot.

Because the content is shared, editing a Skill through one Bot's symlink edits the canonical package, and therefore every Bot bound to it. The UI says so where you can act on it.

Disabling a Bot removes only that Bot's HAM-owned links. The canonical package, the other Bots' links, and the default profile's links are untouched. Deleting a HAM agent never destroys a Hermes profile's sessions or memory — detachment is orphan-safe, and removing a profile stays hermes profile delete's job.

What Hermes can and cannot do to a binding

Verified against a real Hermes v0.21.0 install rather than assumed:

  • A hand-written profile directory is listed by hermes profile list, runs under hermes -p <name>, and deletes cleanly. A new symlink under skills/harnessam/ is picked up on the very next invocation — no restart, no registration.
  • Hermes cannot destroy a canonical HAM package through a Bot's link. Archiving relocates the symlink (Path.rename), leaving the target intact; Hermes' delete guard refuses to recurse through a symlink at all; and the hub installer refuses any install path that redirects through a symlink.
  • The autonomous curator only touches skills carrying created_by: agent provenance. A HAM binding does not carry it by default, so autonomous curation leaves it alone until you opt in with hermes curator adopt <name> — the same opt-in a Skill you wrote by hand needs. Once adopted, the curator maintains it like any native Skill and its edits land in the canonical HAM package. See Skills parity and the compatibility sidecar.
  • The residual risk is therefore a detached binding, not lost work: a foreground hermes skills archive inside a Bot moves HAM's link into skills/.archive/. HAM reports that as re-bindable, distinctly from a broken link (target gone) or a stale one (target outside the store).

Skills parity and the compatibility sidecar

A Skill that Harness Asset Manager enables for Hermes behaves like a Skill Hermes created itself: discovered and loaded into the prompt, listed in the Hermes Web UI, editable through Hermes' own editor, and — once you opt in — maintained by Hermes' curator. All of it runs through Hermes' ordinary code paths. There is no Hermes-specific mode and no second copy of the package.

Discovery always worked, because Hermes' canonical index walker (agent/skill_utils.py::iter_skill_index_files) follows symlinks. Four of Hermes' management paths did not: three scanned with Path.rglob(), which never descends a directory symlink, and one resolved a path before matching it. The practical effect was that a linked Skill was invisible to the curator and could not be opened or edited from the dashboard.

Hermes symbolDefectWhat it broke
tools/skill_usage.py::_iter_skill_mdsrglob does not descend a directory symlinkcurator could not see the Skill at all
tools/skill_manager_tool.py::_iter_skill_dirssameskill_view, skill_manage(patch), dashboard editor
tools/skill_manager_tool.py::_find_skillresolved before matching a categorised nameharnessam/<skill> never matched
tools/skills_tool.py::_log_security_warningstrust checked only the resolved pathspurious "outside the trusted skills directory" warning

HAM closes the gap automatically. On initialization and on every harnessam start, it installs a small compatibility sidecar into the Hermes virtualenv that repoints those four paths at Hermes' own canonical walker. It is best-effort and silent: if Hermes is not installed, or its virtualenv is not where HAM expects, nothing happens and nothing fails.

harnessam hermes compat status     # not-detected | absent | stale | current
harnessam hermes compat install    # install or refresh explicitly
harnessam hermes compat remove     # take it back out
harnessam start --no-hermes-compat # skip it for this launch

The sidecar lives in the virtualenv's site-packages, which Hermes gitignores. The Hermes source checkout is never modified — deliberately, because hermes update refuses to run on a dirty working tree, so patching it in place would permanently block Hermes updates. A Hermes plugin was also ruled out: plugins load lazily per entry point and are not loaded for hermes curator, which is exactly the path that has to work.

The same four fixes are proposed upstream in NousResearch/hermes-agent#112404. Once they ship, the sidecar becomes redundant and can retire.

What stays with HAM. Hermes gets read, use and edit. HAM keeps adoption, source updates, binding and deletion. HAM also does not point Hermes' skills.create_dir or skills.external_dirs at the store — either would expose every package in the store rather than the ones you selected for Hermes.

Provider and model

A Bot may select a configured Hermes provider; its model always comes from the shared agent model field. HAM writes that routing into its profile config.yaml:

model:
  provider: openai-codex
  default: gpt-5.6-luna

HAM writes the bare model id in model.default with the provider in its own key, matching the shape a working install actually uses; it never synthesises a <provider>/<id> string. Model ids are free text; provider choices come from the configured Hermes providers. The provider choice lives in a .<slug>.hermes.toml sidecar beside the agent, so Hermes-only settings never leak into the shared Markdown that Claude, Cursor and the rest read; an agent that sets no provider writes no sidecar.

Writes go through the same config_document round-trip every other config binding uses, so an existing profile config keeps its comments and formatting, and clearing a value removes only the keys HAM set. Provider availability and credentials remain Hermes' concern: a failed profile write is reported and never rolls back the HAM agent.

Actual execution through an external CLI backend — a Claude Code CLI provider, a generic CLI provider, or a separate Codex skill home — is explicitly out of scope. A Bot's Hermes profile Skills do not become Skills for a Codex app-server subprocess.

Permissions

Denylist rules restrict shell commands, file paths, web domains, and MCP tools across supported harnesses in a single unified view. Only --decision deny binds to harnesses today — Harness Asset Manager is denylist-only. Each rule carries a canonical scope (shell, file_read, file_write, web, mcp, any) and a pattern that is matched according to that scope:

  • shell → git push
  • file_read / file_write → ~/.zshrc
  • web → api.example.com
  • mcp → server/tool

Enabling the first rule for a harness also selects that harness's no-prompt execution mode, so unlisted actions proceed and only recorded deny rules are blocked; disabling the last rule restores the native default.

The permissions matrix behaves like every other family view: sortable rule, harness, and applied-count columns, plus a select checkbox on each row. Managed rules support bulk apply, remove, delete, and tag; untracked rules support bulk adopt.

Each harness codec translates rules into that harness's native deny surface:

  • Codex's native config currently supports HAM's file and web deny rules, but not shell-command or MCP deny rules.
  • Cursor maps rules onto deny tokens in ~/.cursor/cli-config.json — Shell(), Read(), Write(), WebFetch(), Mcp(). Multi-token shell patterns and bare-server MCP patterns are correctly reported unsupported, since Cursor's own tokens can't express either. Enabling a Cursor rule writes approvalMode = "unrestricted"; disabling the final rule cleans it up.

Permissions support the shared managed-entry tagging controls. Permission tags are keyed by the stable permission spec ID, so a rule can be starred, grouped with free-form tags, and found via ?tag= without changing its native deny configuration.

Only Cursor's separate CLI (cursor-agent) is targetable at all — its IDE Agent reads an entirely different permissions.json that is allowlist-only, with no deny/enforcement surface, so it stays permanently out of scope for this model.

Configs

Carry your harness preferences — model, theme, effort, and the rest — between machines. Harness Asset Manager extracts them from each harness's user-level config into one portable manifest at ~/.harnessam/configs/manifest.json, which syncs with the store. Managed from the Configs page, or harnessam configs list|enable|disable|capture|restore|diff.

The toggle means managed vs. not managed. Enabling a harness captures its preferences into the manifest; disabling drops that record and never writes to the harness's config file — it is left completely untouched.

Because the manifest travels between machines, a harness whose record exists but whose config file is missing here is not assumed to be stale: it may be managed on another machine. It shows as not managed, flags the record, and leaves removing it to you.

A key is carried only when it is genuinely portable. Anything that is owned by another family (permissions, hooks, mcpServers, …), looks like a secret, or contains an absolute path is left behind — and those two checks recurse, so a credential nested deep inside a provider map is dropped along with the key holding it. The result is that the manifest is safe to commit next to the rest of your dotfiles.

Restore is a merge, never a rewrite. Only the managed preference keys are written back; every other key in the file survives untouched — comments and formatting included, in every supported format — and restoring an unchanged capture leaves the file byte-for-byte identical.

Automatic capture is deliberately conservative: because the manifest travels between machines, a local file that has diverged from the manifest is left for you to resolve explicitly rather than being captured over the top of another machine's edit.

Every supported harness has an entry, read from its user-level config:

HarnessPreferences read from
Claude Code~/.claude/settings.json
Codex CLI~/.codex/config.toml
Pi~/.pi/agent/settings.json
Antigravity (agy)~/.gemini/antigravity-cli/settings.json
Cursor~/.cursor/cli-config.json
OpenCode~/.opencode/opencode.jsonc
Hermes Agent~/.hermes/config.yaml (or $HERMES_HOME/config.yaml)
Factory Droid~/.factory/settings.json

Where a harness keeps several families in one document, the other families' keys are named in the catalog and withheld — in each harness's own spelling, which is not always Claude's (OpenCode's servers live under mcp, Codex's under mcp_servers). A guard test pins that: any family binding into the same file as configs has to be excluded from it.

Mutation Audit Journal

Trace every Web UI, API, and CLI mutation in an append-only JSON Lines journal, including the operation, outcome, and filesystem paths changed—without recording prompts, config payloads, or secrets.


Local-first Safety

Harness Asset Manager is a local configuration-management tool. It runs on your machine and reads or writes local harness extension state.

Actions that can change local state include:

  • adopting a local skill folder
  • enabling or disabling a skill for a harness
  • updating a source-backed skill
  • removing or deleting a skill
  • installing an MCP server into a selected harness config
  • adopting an existing MCP config
  • enabling, disabling, resolving, or uninstalling an MCP server
  • creating, updating, syncing, importing, or deleting a slash command
  • creating, enabling, disabling, resolving, or deleting a hook binding
  • changing harness support settings
  • repairing a drifted agent binding, which can move an edit out of a harness file and into the store

Automatic adoption is opt-in per asset family, except for the existing safe Agent repair default. It is limited to equivalent observations or cases where a harness copy is provably the only edit, and every action is recorded in the Activity audit log.

FamilyDefaultAutomatic behavior when enabled
AgentsOnRepair provable drift; conflicting edits remain for review.
SkillsOffAdopt new, equivalent unmanaged local directories and replace them with store links.
Slash commandsOffRegister equivalent unmanaged files without overwriting their contents.
MCP, Hooks, PermissionsOffPromote equivalent unmanaged observations without choosing between conflicting configurations.

These checks run while reading the relevant inventory or detail view, so a setting change takes effect on the next read; there is no background watcher. Codex rendered-agent adoption remains excluded because its TOML-to-Markdown conversion is not lossless. When the UI is closed, run harnessam refresh for one read and reconciliation pass across all asset families; use harnessam refresh --json for automation.

App-owned files live under ~/.harnessam on macOS and Linux. Existing XDG harnessam and harness-asset-manager stores are migrated automatically and retained as compatibility aliases.


Headless and CLI Reference

Every asset family the Web UI manages is also available as a CLI command. Nothing needs to be running first: the CLI builds the same backend the server does and talks to the same stores, so it works on a VPS, in a container, or in a Linux sandbox with no browser and no daemon.

Command Reference

Every command takes --json and --state-dir. --harness names a harness id (claude, codex, agy, cursor, opencode, hermes, droid), and set-harnesses --target enabled|disabled applies one state to every interactive cell in that row. Run harnessam <group> <verb> --help for the full flag list.

skills — Skills inventory and the skills marketplace.

CommandWhat it does
skills listThe skills × harness matrix, plus a managed/unmanaged count
skills show <ref>Detail: status, per-harness cells, on-disk locations
skills enable|disable <ref> --harness <h>Bind or unbind one harness
skills set-harnesses <ref> --target <state>Apply one state everywhere
skills manage <ref> / skills manage-allTake unmanaged skills into the store
skills unmanage <ref>Stop managing, leaving the files in place
skills update <ref>Re-fetch a managed skill from its source
skills delete <ref> --yesDelete a managed skill and its bindings
skills search <query> / skills popularBrowse the marketplace; prints install tokens
skills install <install-token>Install a marketplace skill

agents — Subagents stored as markdown and symlinked into each harness.

CommandWhat it does
agents listThe agents × harness matrix, including unmanaged harness copies
agents show <ref>Detail: prompt, tools, per-harness path and install method
agents create --name --description --prompt|--prompt-file [--tool …]Create an agent in the store
agents update <ref> [--name] [--description] [--prompt] [--tool …]Change one or more fields
agents enable|disable <ref> --harness <h>Bind or unbind one harness
agents set-harnesses <ref> [--harness <h> …]Bind to exactly this set; omit all to unbind everywhere
agents adopt <ref> [--on-conflict keep_store|replace_store]Take a harness-owned agent into the store
agents adopt-allAdopt every unmanaged agent
agents delete <ref> --yesDelete an agent and its bindings

mcp — MCP servers and the MCP marketplace.

CommandWhat it does
mcp listThe servers × harness matrix
mcp show <name>Detail: transport, command/url, per-harness state
mcp install <qualified-name>Install from the marketplace
mcp uninstall <name> --yesRemove a managed server and its bindings
mcp enable|disable <name> --harness <h> [--config <json>]Bind or unbind one harness
mcp set-harnesses <name> --target <state> [--config <json>]Apply one state everywhere
mcp check <name>Probe availability; exits 1 when unavailable
mcp unmanagedServers found in harness configs that we do not own
mcp adopt <name> [--observed-harness <h>] [--harness <h> …]Take an unmanaged server into the store
mcp search <query> / mcp popularBrowse the marketplace

--config takes a JSON object, or @file / @- to read one from a file or stdin.

hooks — Normalized hook records synced into harness settings.

CommandWhat it does
hooks listThe hooks × harness matrix
hooks show <id>Detail: event, command, match, per-harness state and drift
hooks create --id --event --command [--match] [--timeout] [--description]Create a managed hook
hooks enable|disable <id> --harness <h>Bind or unbind one harness
hooks set-harnesses <id> --target <state>Apply one state everywhere
hooks promote <id> [--observed-harness <h>]Take a harness-owned hook into the store
hooks delete <id> --yesDelete a hook and its bindings

--event is one of pre_tool_use, post_tool_use, user_prompt_submit, session_start, stop, pre_compact. --match is a tool category — any, shell, file_read, file_write, mcp, web — not a harness tool name; each harness maps the category to its own matcher.

permissions — Denylist rules across supported harnesses.

CommandWhat it does
permissions listThe rules × harness matrix
permissions show <id>Detail: decision, scope, pattern, per-harness state
permissions create --id --decision --scope [--pattern] [--description]Create a managed rule
permissions enable|disable <id> --harness <h>Bind or unbind one harness
permissions set-harnesses <id> --target <state>Apply one state everywhere
permissions promote <id> [--observed-harness <h>]Take a harness-owned rule into the store
permissions delete <id> --yesDelete a rule and its bindings

commands — Slash commands and their per-target renders.

CommandWhat it does
commands listCommands with their synced targets, plus anything needing review
commands targetsThe available targets and whether each is enabled and available
commands show <name>Detail: per-target sync status, path, and the prompt body
commands create --name --description --prompt|--prompt-file [--target …]Create and sync
commands update <name> --description --prompt|--prompt-file [--target …]Update and re-sync
commands sync <name> [--target …]Re-render into the selected targets
commands delete <name> --yesDelete the command and its renders

settings, snapshots, health

CommandWhat it does
settings showStorage paths, per-harness support and install state, auto-adopt
settings harness <h> --enable|--disableTurn support for a harness on or off
settings auto-adopt <agents|skills|slash_commands|mcp|hooks|permissions> --enable|--disableControl opt-in automatic adoption and repair
bootstrap [--dry-run] [-y]Bootstrap synced store assets onto this device by creating local bindings
refresh [--sync-all]Run inventory pass; --sync-all enables auto-adoption & drift reconciliation across all asset families
configs listCaptured portable preferences per harness
configs enable <harness>Enable managing a harness's preferences
configs disable <harness>Disable managing a harness's preferences (does not touch harness config)
configs captureExtract preferences from every managed harness config into the manifest
configs diff <harness>Compare a harness's live config against the manifest
configs restore <harness>Merge managed preferences back into the live config
healthHealth summary — app, harness count, home dir; useful as a readiness probe

Scripting Guidelines

  • --json on any command prints the exact payload returned by the corresponding API route — stdout stays clean for jq, while errors go to stderr.
  • Exit codes: 0 success, 1 a refused or partly-applied mutation, 2 bad usage. A fan-out like set-harnesses exits 1 when any harness rejects the change; check succeeded/failed in JSON when partial application is acceptable.
  • Destructive commands (delete, uninstall) prompt when stdin is a terminal and refuse otherwise — pass --yes in scripts.
  • --state-dir isolates a run, including HAM's store, settings, runtime files, and catalog-resolved harness paths, ensuring CI or throwaway sandboxes do not touch the primary store or native harness configuration. It overrides HOME and the XDG base directories for that invocation.
harnessam skills list --json | jq -r '.rows[] | select(.displayStatus=="Unmanaged") | .skillRef'
harnessam agents set-harnesses release-bot --harness claude --harness codex --json | jq .failed

Running the CLI while the app is serving is completely safe — stores serialize writes with flock, and the server's read models refresh within a second.

Running the Server Headlessly

serve runs in the foreground (systemd, Docker, tmux); start daemonizes and records a pid that status and stop read back.

harnessam serve --no-open-browser --host 127.0.0.1 --port 8000

A missing frontend/dist is fine — the API serves normally and only the HTML shell is a placeholder.

Security & Remote Access

Harness Asset Manager enforces a clear security boundary:

  • Same-user local access: A request from a loopback peer carrying a loopback Host is trusted automatically with zero configuration — same-user local processes already share file and environment access, so defending against them buys nothing. Both halves matter: tailscale serve proxies from 127.0.0.1, so the Host header is what separates a local client from forwarded tailnet traffic.
  • Remote tailnet access (Tailscale Serve): Tailscale Serve injects a verified Tailscale-User-Login header into proxied requests and strips it from anything a client sends directly, so it cannot be forged from the tailnet — this is what makes remote access paste-free.
  • API Bearer Token: Remote scripts or clients that aren't going through Tailscale Serve authenticate with Authorization: Bearer <token>. The persistent token is stored in ~/.harnessam/api-token (mode 0600) and managed via harnessam token [--rotate]. HARNESSAM_API_TOKEN in the environment overrides it.
  • Reverse Proxy Support: --trusted-host <hostname> (or HARNESS_ASSET_MANAGER_TRUSTED_HOSTS, comma-separated) allows a proxy's Host and Origin headers while keeping DNS rebinding and CSRF guards fully active. --allow-remote is only for binding a non-loopback interface directly, which drops both guards entirely — prefer --trusted-host for anything fronted by a proxy.

Tailnet Launch

No flag needed for the common case of one machine serving its own tailnet:

harnessam start                       # binds loopback, and when tailscale is available:
                                       #   auto-trusts this device's *.ts.net name for Host/Origin
                                       #   auto-publishes itself via `tailscale serve` on :7443

Both are best-effort and skip themselves silently if the tailscale CLI or daemon isn't reachable — nothing about a launch without Tailscale changes. Disable the serve mapping with --no-tailnet, or change its port with --tailnet-port/$HAM_TAILNET_PORT. It is torn down again (that port only) on a clean harnessam stop; scripts/serve-tailnet.sh re-applies it by hand after tailscaled state loss or for an instance launched with --no-tailnet.

Host/Origin auto-detection only fires when nothing is configured explicitly, and only ever widens what Host/Origin values are accepted — it never weakens authentication, which still requires the Tailscale identity header or a bearer token. Set --trusted-host <hostname> or HARNESS_ASSET_MANAGER_TRUSTED_HOSTS to override the detected name or add another one (e.g. a non-Tailscale reverse proxy); either takes priority over auto-detection.

Never use --allow-remote for this — it disables the Host and Origin guards altogether to work around a hostname mismatch that --trusted-host (or auto-detection) solves precisely.


Configuration

On macOS and Linux, app-owned files live under ~/.harnessam. Explicit XDG overrides remain supported for isolated/test runs. Existing harnessam and harness-asset-manager directories are migrated automatically on first use.

Path Locations

Useful macOS paths:

  • API bearer token: ~/.harnessam/api-token
  • skills store: ~/.harnessam/skills
  • agents store: ~/.harnessam/agents
  • agent binding ledger: ~/.harnessam/bindings.json
  • agent repair audit log: ~/.harnessam/agents-audit.json
  • mutation audit journal: ~/.harnessam/audit.log (append-only JSON Lines)
  • preserved conflicting agent copies: ~/.harnessam/agents/conflicts
  • MCP manifest: ~/.harnessam/mcp/manifest.json
  • hooks manifest: ~/.harnessam/hooks/manifest.json
  • slash command library: ~/.harnessam/slash-commands/commands
  • slash command sync state: ~/.harnessam/slash-commands/sync-state.json
  • marketplace cache: ~/.harnessam/marketplace
  • app settings: ~/.harnessam/settings.json

Useful Linux paths:

  • API bearer token: ~/.harnessam/api-token
  • skills store: ~/.harnessam/skills
  • agents store: ~/.harnessam/agents
  • agent binding ledger: ~/.harnessam/bindings.json
  • agent repair audit log: ~/.harnessam/agents-audit.json
  • mutation audit journal: ~/.harnessam/audit.log
  • MCP manifest: ~/.harnessam/mcp/manifest.json
  • hooks manifest: ~/.harnessam/hooks/manifest.json
  • slash command library: ~/.harnessam/slash-commands/commands
  • slash command sync state: ~/.harnessam/slash-commands/sync-state.json
  • marketplace cache: ~/.harnessam/marketplace
  • app settings: ~/.harnessam/settings.json

Carrying the store between your own devices

The Harness Asset Manager store (~/.harnessam on macOS and Linux, or your explicit XDG roots — the same paths described above) is designed to be portable across your own machines using standard dotfile or folder synchronization tools (a Git dotfile repo, Syncthing, Dropbox, iCloud, Nextcloud, rsync). This is a supported workflow, not an accident: every persisted reference that names a file stores it home-relative (~/...), so the store survives different usernames, operating systems, and home locations (/Users/alice ↔ /home/bob).

Portable Paths

All persisted references (such as agent binding ledgers and slash command sync state) store home-relative paths (~/...). This allows your store to travel seamlessly between devices with different usernames, operating systems, or home locations (for example, /Users/alice on macOS and /home/bob on Linux).

What to Sync vs Ignore

HAM automatically creates a default .gitignore inside ~/.harnessam on first start to keep sync repositories clean:

  • Sync (portable assets & configuration):

    • skills/ and skills-manifest.json (Skill packages and metadata)
    • agents/ (Subagent Markdown definitions)
    • mcp/manifest.json (Normalized MCP server configurations)
    • hooks/manifest.json (Hook rules)
    • permissions/manifest.json (Denylist policies)
    • slash-commands/commands/ (Prompt library)
    • slash-commands/sync-state.json (Slash command sync state)
    • bindings.json (Agent binding cache)
    • settings.json (App preferences and harness support toggles)
  • Ignore (device-local & ephemeral state):

    • *.lock (File locks)
    • runtime.json (Local server PID and port info)
    • server.log (Local daemon log)
    • *-audit.json*, audit.log (Machine-local activity journals)
    • configs/* except configs/manifest.json (the portable preference manifest is synced; anything else under configs/ is machine-local)
    • agents/conflicts/ (Local reconciliation history)
    • cache/, tmp/, marketplace/ (Ephemeral downloads and caches)
    • .sync-conflict-*, *.sync-conflict-*, .syncthing.* (Sync-tool conflict artifacts)

Secrets travel with the store

MCP env/headers values and hook command strings live in the manifests, so they cross machines with everything else. Only sync the store to repositories and devices you control and trust — a public dotfile repo leaks credentials exactly like any other dotfile would.

Arrival on a New Machine

When you sync or copy your store to a new machine:

  1. HAM starts immediately and discovers all stored skills, agents, commands, and configs.
  2. Harnesses on the new machine initially show up as disabled (since symlinks and rendered target files have not been generated on the new machine yet), not in an error state.
  3. Run harnessam bootstrap in your terminal (or click "Review & Bootstrap" on the workspace banner in the web UI) to inspect and instantiate the bindings recorded in your synced store on the new device.
  4. Auto-adoption and integrity checks tolerate sync conflict files and unreadable artifacts without crashing or corrupting the store.

Environment Variable Overrides

If you manage skills in a custom environment, you can override individual skill roots with environment variables:

HarnessEnv varDefault Harness Asset Manager skill root
CodexHARNESS_ASSET_MANAGER_CODEX_ROOT~/.agents/skills
ClaudeHARNESS_ASSET_MANAGER_CLAUDE_ROOT~/.claude/skills
CursorHARNESS_ASSET_MANAGER_CURSOR_ROOT~/.cursor/skills
OpenCodeHARNESS_ASSET_MANAGER_OPENCODE_ROOT~/.config/opencode/skills
Hermes AgentHARNESS_ASSET_MANAGER_HERMES_ROOT${HERMES_HOME:-~/.hermes}/skills
Factory DroidHARNESS_ASSET_MANAGER_FACTORY_ROOT~/.factory/skills
Antigravity (agy)HARNESS_ASSET_MANAGER_AGY_ROOT~/.gemini/antigravity-cli/skills

Note: Legacy SKILL_MANAGER_* env var spellings are still read as fallbacks but are deprecated.


From Source

Requirements

  • Python 3.11+
  • Node.js 18+
  • npm

harness-asset-manager supports Python 3.11+. CI validates backend compatibility on Python 3.11 through 3.14, while packaging and release builds stay pinned to Python 3.11 for determinism.

Contributor Setup

scripts/install-dev.sh

Run Locally

scripts/start-dev.sh (aliased as npm run start:dev) builds the frontend and launches the app isolated in .artifacts/runtime — its own skills/agents/config store, entirely separate from your real harness configs (~/.claude, ~/.codex, …) and your real HAM store (~/.harnessam). It's for iterating on the app itself without touching anything real, and it will look empty — that's expected, not a bug:

scripts/start-dev.sh   # or: npm run start:dev
scripts/stop-dev.sh    # or: npm run stop:dev

To run the app for real from a source checkout — reading and managing your actual harness configs, same as an installed harnessam binary would — build once, then invoke the module directly with no --state-dir:

npm run build
.venv/bin/python -m harness_asset_manager start   # status / stop work the same way

The split dev flow is available when you want Vite hot reload (also isolated, same as start-dev.sh):

npm run dev
npm run dev:backend

Default local URLs:

  • Frontend: http://127.0.0.1:5173
  • Backend: http://127.0.0.1:8000
  • Health: http://127.0.0.1:8000/api/health

Validation suite:

scripts/install-dev.sh
.venv/bin/ruff check harness_asset_manager tests scripts
.venv/bin/pyright
npm run lint:frontend
npm run typecheck
bash scripts/test_backend.sh
npm test
npm run build

Troubleshooting

  • If Marketplace requests fail with Marketplace is temporarily unavailable, verify your network connection and try again.
  • If an MCP harness is shown as unavailable, Harness Asset Manager has detected that the local client is missing or does not support the required config surface.

More to Come

Extension Families

  • Hook support
  • Slash command support
  • Agent personas
  • Package-based storage (portable resource bundles)
  • Plugin support

Cross-device sync — carried by you, not by the app

No in-app sync transport is planned. The supported way to move your store between your own machines is standard dotfile/folder replication (a Git dotfile repo, Syncthing, rsync, iCloud, …) — see Carrying the store between your own devices. Every persisted reference in the store is home-relative, so a replicated store re-resolves its bindings on arrival and a machine without Cursor simply does not bind Cursor.


Community

↑↓ navigate openesc close