mcp server
harness-asset-manager
One control plane for Skills, Agents, MCP servers, commands, hooks, and permissions across AI harnesses.
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`)
{
"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, 2026HAM
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 Family | What HAM does |
|---|---|
| Skills | Adopt local Skill folders into one shared inventory, then enable or disable them per harness using managed symlinks. |
| Agents | Store subagents as Markdown files with YAML frontmatter, symlinked (or rendered for Codex) across harnesses with automated drift repair and safe conflict resolution. |
| MCP Servers | Manage normalized MCP server configurations and translate them into native harness shapes (JSON, TOML, YAML). |
| Slash Commands | Maintain a single reusable prompt library and sync rendered command files into supported harness formats. |
| Hooks | Configure normalized event and tool category hook records, synced into native harness settings with drift detection and review for unmanaged entries. |
| Permissions | Enforce 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 & Audit | Carry 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. |
| Marketplace | Discover and preview Skills, MCP servers, and external CLI tools from marketplace hubs. |
| Headless / CLI | Drive 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
| Harness | Skills | Agents | MCP Servers | Slash Commands | Hooks | Permissions |
|---|---|---|---|---|---|---|
| Claude Code | Yes | Yes | Yes | Yes | Yes | Yes (Denylist) |
| Codex CLI | Yes | Yes | Yes | Yes | Yes | Yes (Denylist) |
| Pi | Yes | Yes (extension) | Not Yet | Yes | Not Yet | No |
| Antigravity (agy) | Yes | Yes | Yes | Yes | Partial | Yes (Denylist) |
| Cursor | Yes | Yes | Yes | Yes | Yes | Yes (Denylist) |
| OpenCode | Yes | Yes | Yes | Yes | Partial | No |
| Hermes Agent | Yes (per-Bot¹) | Yes (Bots/Profiles¹) | Yes | Yes² (Provisional) | Not Yet | No |
| Factory Droid | Yes | Yes | Yes | Yes | Not Yet | No |
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:
- Review a Skill found in a harness, install one from the marketplace, or write a new one with Add Skill.
- Adopt it into the Harness Asset Manager inventory.
- Check what the package holds —
scripts/,references/,assets/— before enabling it. - Enable it only where it should be available.
- Filter by Agent or bulk attach/detach selected Skills to adopted Agents when curating an agent's Skill set.
- 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:
- Review an MCP server found in a harness or install one from the marketplace.
- Adopt it into the Harness Asset Manager inventory.
- Enable it where the server should be available.
- 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:
- Create a slash command with a name, description, and prompt.
- Use
$ARGUMENTSwhere runtime input should be inserted. - Sync it to supported harnesses.
- 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:
- Write an agent — a name, a description, and a system prompt — or adopt one Harness Asset Manager found in a harness.
- Turn it on for the harnesses that should have it.
- In the detail editor, attach adopted Skills to the agent; the full list of adopted Skills is offered, and typing narrows it.
- 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.
- 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
mcpServersJSON entries. - OpenCode uses typed local/remote MCP entries.
- Antigravity (agy) uses
mcpServersJSON entries withserverUrlfor HTTP transports andcommand/args/envfor stdio. - Hermes Agent uses YAML under
mcp_serversin~/.hermes/config.yaml(or$HERMES_HOME/config.yaml). - Factory Droid uses JSON under
mcpServersin~/.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/commandsand invokes them with/. - Codex writes prompt files under
~/.codex/promptsand invokes them with/prompts:. - Cursor writes plain text command files under
~/.cursor/commandsand invokes them with/. - OpenCode writes Markdown command files under
~/.config/opencode/commandsand invokes them with/. - Antigravity (agy) writes Markdown command files under
~/.gemini/antigravity-cli/commandsand invokes them with/. - Factory Droid writes Markdown command files under
~/.factory/commandsand 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.jsonunder thehookskey. - 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.hookentries inopencode.json— limited tofile_edited(post-edit on write) andsession_completed(stop), so coverage is partial. - Antigravity (agy) writes a name-keyed
~/.gemini/config/hooks.json(discovering entries across user-level~/.gemini/*/hooks.jsonand project-level.agents/hooks.json), matching against tool names (run_command,view_file, …); it covers tool, stop, and (viaPreInvocation) 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:
| Harness | Location | How 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 found | What happens |
|---|---|
| The copy is identical to the store | The link is restored. There is no content decision to make. |
| The copy was edited, and the store has not changed since it was linked | That 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 too | Nothing. Both sides hold work; choosing either discards the other. Reported for you to resolve. |
| Several harnesses were edited differently | Nothing 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 here | Nothing. 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:
| Written | Why |
|---|---|
.env, empty, mode 0o600 | Without it the profile silently inherits shell API keys |
SOUL.md | The Bot's identity, rendered from the HAM agent. HAM owns and updates this one |
config.yaml with _config_version | A stale version makes Hermes' doctor/desktop report the profile as outdated |
.no-bundled-skills | The 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 underhermes -p <name>, and deletes cleanly. A new symlink underskills/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: agentprovenance. A HAM binding does not carry it by default, so autonomous curation leaves it alone until you opt in withhermes 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 archiveinside a Bot moves HAM's link intoskills/.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 symbol | Defect | What it broke |
|---|---|---|
tools/skill_usage.py::_iter_skill_mds | rglob does not descend a directory symlink | curator could not see the Skill at all |
tools/skill_manager_tool.py::_iter_skill_dirs | same | skill_view, skill_manage(patch), dashboard editor |
tools/skill_manager_tool.py::_find_skill | resolved before matching a categorised name | harnessam/<skill> never matched |
tools/skills_tool.py::_log_security_warnings | trust checked only the resolved path | spurious "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 pushfile_read/file_write→~/.zshrcweb→api.example.commcp→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 writesapprovalMode = "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:
| Harness | Preferences 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.
| Family | Default | Automatic behavior when enabled |
|---|---|---|
| Agents | On | Repair provable drift; conflicting edits remain for review. |
| Skills | Off | Adopt new, equivalent unmanaged local directories and replace them with store links. |
| Slash commands | Off | Register equivalent unmanaged files without overwriting their contents. |
| MCP, Hooks, Permissions | Off | Promote 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.
| Command | What it does |
|---|---|
skills list | The 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-all | Take 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> --yes | Delete a managed skill and its bindings |
skills search <query> / skills popular | Browse the marketplace; prints install tokens |
skills install <install-token> | Install a marketplace skill |
agents — Subagents stored as markdown and symlinked into each harness.
| Command | What it does |
|---|---|
agents list | The 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-all | Adopt every unmanaged agent |
agents delete <ref> --yes | Delete an agent and its bindings |
mcp — MCP servers and the MCP marketplace.
| Command | What it does |
|---|---|
mcp list | The servers × harness matrix |
mcp show <name> | Detail: transport, command/url, per-harness state |
mcp install <qualified-name> | Install from the marketplace |
mcp uninstall <name> --yes | Remove 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 unmanaged | Servers 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 popular | Browse 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.
| Command | What it does |
|---|---|
hooks list | The 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> --yes | Delete 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.
| Command | What it does |
|---|---|
permissions list | The 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> --yes | Delete a rule and its bindings |
commands — Slash commands and their per-target renders.
| Command | What it does |
|---|---|
commands list | Commands with their synced targets, plus anything needing review |
commands targets | The 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> --yes | Delete the command and its renders |
settings, snapshots, health
| Command | What it does |
|---|---|
settings show | Storage paths, per-harness support and install state, auto-adopt |
settings harness <h> --enable|--disable | Turn support for a harness on or off |
settings auto-adopt <agents|skills|slash_commands|mcp|hooks|permissions> --enable|--disable | Control 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 list | Captured 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 capture | Extract 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 |
health | Health summary — app, harness count, home dir; useful as a readiness probe |
Scripting Guidelines
--jsonon any command prints the exact payload returned by the corresponding API route — stdout stays clean forjq, while errors go to stderr.- Exit codes:
0success,1a refused or partly-applied mutation,2bad usage. A fan-out likeset-harnessesexits1when any harness rejects the change; checksucceeded/failedin JSON when partial application is acceptable. - Destructive commands (
delete,uninstall) prompt when stdin is a terminal and refuse otherwise — pass--yesin scripts. --state-dirisolates 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 overridesHOMEand 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
Hostis 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 serveproxies from127.0.0.1, so theHostheader is what separates a local client from forwarded tailnet traffic. - Remote tailnet access (Tailscale Serve): Tailscale Serve injects a verified
Tailscale-User-Loginheader 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(mode0600) and managed viaharnessam token [--rotate].HARNESSAM_API_TOKENin the environment overrides it. - Reverse Proxy Support:
--trusted-host <hostname>(orHARNESS_ASSET_MANAGER_TRUSTED_HOSTS, comma-separated) allows a proxy'sHostandOriginheaders while keeping DNS rebinding and CSRF guards fully active.--allow-remoteis only for binding a non-loopback interface directly, which drops both guards entirely — prefer--trusted-hostfor 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/andskills-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/*exceptconfigs/manifest.json(the portable preference manifest is synced; anything else underconfigs/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:
- HAM starts immediately and discovers all stored skills, agents, commands, and configs.
- 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.
- Run
harnessam bootstrapin 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. - 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:
| Harness | Env var | Default Harness Asset Manager skill root |
|---|---|---|
| Codex | HARNESS_ASSET_MANAGER_CODEX_ROOT | ~/.agents/skills |
| Claude | HARNESS_ASSET_MANAGER_CLAUDE_ROOT | ~/.claude/skills |
| Cursor | HARNESS_ASSET_MANAGER_CURSOR_ROOT | ~/.cursor/skills |
| OpenCode | HARNESS_ASSET_MANAGER_OPENCODE_ROOT | ~/.config/opencode/skills |
| Hermes Agent | HARNESS_ASSET_MANAGER_HERMES_ROOT | ${HERMES_HOME:-~/.hermes}/skills |
| Factory Droid | HARNESS_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
- See CONTRIBUTING.md for contribution guidelines.
- See SECURITY.md to report vulnerabilities privately.