harness-sync

One canonical source of truth for skills, agents, MCP servers, LLM endpoints, and global instructions across multiple LLM harnesses. Edit once at ~/.config/harness-sync/; propagate everywhere.

Supported harnesses

Harness Config path What harness-sync writes
claude-code ~/.claude/ skills/ (symlink), agents/ (symlink), CLAUDE.md, settings.json mcpServers (merged)
crush ~/.config/crush/ skills/ (symlink), crush.json providers/default_model/mcp (merged)
kilo ~/.config/kilo/ agent/ (symlink), kilo.json model/mcp (merged)
opencode ~/.config/opencode/ opencode.jsonc provider/model/mcp (merged), AGENTS.md
goose ~/.config/goose/ config.yaml GOOSE_PROVIDER/GOOSE_MODEL/extensions (merged)
cagent ~/.config/cagent/ starter default.yaml (per-run config)
zed ~/.config/zed/ settings.json agent/context_servers (merged — other keys preserved)

Adding a new harness: drop a Go package under internal/adapters/<name>/ implementing adapter.Adapter, register it in cmd/harness-sync/main.go.

Install

make build && make install   # installs to ~/.local/bin/harness-sync

First run

harness-sync detect          # see which harnesses are installed
harness-sync init            # pick which to import from; canonical tree created at ~/.config/harness-sync/
harness-sync apply           # propagate canonical to all detected harnesses

init opens an interactive multi-select of detected harnesses. Use --no-prompt to import from all, or --from claude-code,crush to be explicit.

Day-to-day

harness-sync show              # list files each adapter would write
harness-sync diff              # apply --dry-run shorthand
harness-sync apply             # propagate canonical
harness-sync apply crush       # propagate to one harness only
harness-sync profile use work  # switch active profile and reapply
harness-sync rollback 1        # revert last apply via git revert
harness-sync adapter list      # print registered adapters

Canonical layout

~/.config/harness-sync/
├── config.yaml                # enabled_harnesses, active_profile
├── profiles/
│   ├── home.yaml              # gateway URL + token + model allowlist + upstreams
│   └── work.yaml
├── instructions/
│   ├── global.md              # rendered to CLAUDE.md / AGENTS.md / etc.
│   └── per-harness/<name>.md  # optional override
├── skills/<name>/SKILL.md     # markdown + frontmatter; symlinked into harnesses that accept it
├── agents/<name>.md           # markdown + frontmatter
├── mcp.yaml                   # canonical MCP server registry
├── state/<harness>/...        # last-rendered snapshots (git-tracked, do not hand-edit)
└── backups/                   # pre-apply backups; git-ignored

The whole tree is a git repository. Push it to a private remote for cross-machine sync.

Profiles

A profile bundles the LLM stack. Switching profiles re-renders every harness's LLM config in one command.

# profiles/home.yaml
name: home
gateway:
  url: https://gateway.lan
  token: dummy-local-token
  default_model: claude-sonnet-4-6
upstreams:
  - name: anthropic
    api_key: ${ANTHROPIC_API_KEY}
  - name: openai
    api_key: ${OPENAI_API_KEY}
models:
  - id: claude-sonnet-4-6
    alias: sonnet
  - id: claude-opus-4-7
    alias: opus

Secrets use ${VAR} for env-var substitution at render time. The dummy gateway token may be plaintext (it has no value if leaked); real provider keys must be env-var references.

Conflict resolution

apply performs git-style three-way merge on every rendered file. The base is the previous render snapshot from state/<harness>/; "ours" is the new render; "theirs" is the current target file. When the merge is clean, the target is updated. When it conflicts, harness-sync writes the merged-with-markers result next to the target as <file>.rej, leaves the original file untouched, and exits non-zero. Resolve the conflict (edit the target, delete .rej), then run apply again.

The same canonical repo holds the state snapshots. rollback N calls git revert on the last N commits and reapplies.

Design and implementation

License

Same as the surrounding repo.

S
Description
No description provided
Readme MIT
548 KiB
Languages
Go 98.1%
Shell 1.7%
Makefile 0.2%