multi-harness 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multi_harness-0.1.0/.claude/settings.local.json +32 -0
- multi_harness-0.1.0/.gitignore +9 -0
- multi_harness-0.1.0/CLAUDE.md +179 -0
- multi_harness-0.1.0/PKG-INFO +189 -0
- multi_harness-0.1.0/README.md +177 -0
- multi_harness-0.1.0/pyproject.toml +29 -0
- multi_harness-0.1.0/src/multi_harness/__init__.py +1 -0
- multi_harness-0.1.0/src/multi_harness/agents/__init__.py +13 -0
- multi_harness-0.1.0/src/multi_harness/agents/antigravity.py +12 -0
- multi_harness-0.1.0/src/multi_harness/agents/claude.py +12 -0
- multi_harness-0.1.0/src/multi_harness/agents/codex.py +12 -0
- multi_harness-0.1.0/src/multi_harness/agents/copilot.py +12 -0
- multi_harness-0.1.0/src/multi_harness/agents/opencode.py +12 -0
- multi_harness-0.1.0/src/multi_harness/agents/spec.py +14 -0
- multi_harness-0.1.0/src/multi_harness/cli.py +207 -0
- multi_harness-0.1.0/src/multi_harness/config.py +45 -0
- multi_harness-0.1.0/src/multi_harness/harness.py +254 -0
- multi_harness-0.1.0/src/multi_harness/status.py +60 -0
- multi_harness-0.1.0/src/multi_harness/symlinks.py +35 -0
- multi_harness-0.1.0/tests/__init__.py +0 -0
- multi_harness-0.1.0/tests/conftest.py +26 -0
- multi_harness-0.1.0/tests/test_add_remove.py +210 -0
- multi_harness-0.1.0/tests/test_init.py +205 -0
- multi_harness-0.1.0/tests/test_status.py +255 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebSearch",
|
|
5
|
+
"WebFetch(domain:github.com)",
|
|
6
|
+
"WebFetch(domain:docs.claude.com)",
|
|
7
|
+
"WebFetch(domain:docs.github.com)",
|
|
8
|
+
"WebFetch(domain:code.visualstudio.com)",
|
|
9
|
+
"WebFetch(domain:opencode.ai)",
|
|
10
|
+
"WebFetch(domain:antigravity.google)",
|
|
11
|
+
"WebFetch(domain:docs.google.com)",
|
|
12
|
+
"WebFetch(domain:docs.anthropic.com)",
|
|
13
|
+
"WebFetch(domain:developer.cursor.so)",
|
|
14
|
+
"WebFetch(domain:cursor.com)",
|
|
15
|
+
"WebFetch(domain:www.jetbrains.com)",
|
|
16
|
+
"WebFetch(domain:codeium.com)",
|
|
17
|
+
"WebFetch(domain:windsurf.com)",
|
|
18
|
+
"WebFetch(domain:docs.openai.com)",
|
|
19
|
+
"WebFetch(domain:github.blog)",
|
|
20
|
+
"WebFetch(domain:ai.google.dev)",
|
|
21
|
+
"WebFetch(domain:developers.google.com)",
|
|
22
|
+
"WebFetch(domain:copilot.github.com)",
|
|
23
|
+
"WebFetch(domain:developer.chrome.com)",
|
|
24
|
+
"WebFetch(domain:developers.openai.com)",
|
|
25
|
+
"Bash(.venv/bin/mh --help)",
|
|
26
|
+
"Bash(.venv/bin/mh init *)",
|
|
27
|
+
"Bash(.venv/bin/mh add *)",
|
|
28
|
+
"Bash(.venv/bin/mh remove *)",
|
|
29
|
+
"Bash(.venv/bin/mh status *)"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# multi-harness — project reference
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
`mh` is a Python CLI that lets a single project share skills, subagents, and project
|
|
6
|
+
instructions across multiple coding agents without duplication. It creates a canonical
|
|
7
|
+
`.harness/` directory and materializes per-agent symlinks so every agent sees its
|
|
8
|
+
expected paths.
|
|
9
|
+
|
|
10
|
+
Console script: `mh` (installed via `pip install -e .`)
|
|
11
|
+
Package: `multi_harness` — `src/` layout, `pyproject.toml` + hatchling.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/multi_harness/
|
|
19
|
+
├── cli.py Typer app (group mode via @app.callback()).
|
|
20
|
+
│ Entry point: mh init, future: mh add/remove/status/sync
|
|
21
|
+
├── harness.py Core logic: detect_configured_agents(), init().
|
|
22
|
+
│ Owns the HARNESS_DIR / AGENTS_MD path constants.
|
|
23
|
+
├── symlinks.py ensure_symlink(link, target) — relative, idempotent, never
|
|
24
|
+
│ overwrites real files. Returns "created"/"ok"/"replaced".
|
|
25
|
+
└── agents/
|
|
26
|
+
├── spec.py AgentSpec dataclass (frozen). Fields: name, display_name,
|
|
27
|
+
│ instructions_path (None if native AGENTS.md reader),
|
|
28
|
+
│ skills_path, subagents_path, detection_paths.
|
|
29
|
+
├── __init__.py AGENT_REGISTRY: dict[str, AgentSpec] — add a new file here
|
|
30
|
+
│ to support a new agent.
|
|
31
|
+
├── claude.py Claude Code
|
|
32
|
+
├── codex.py OpenAI Codex CLI
|
|
33
|
+
├── opencode.py OpenCode
|
|
34
|
+
├── copilot.py GitHub Copilot
|
|
35
|
+
└── antigravity.py Google Antigravity
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Tests live in `tests/test_init.py`. Run with `.venv/bin/pytest`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Canonical layout produced by `mh init`
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
AGENTS.md canonical project instructions (real file)
|
|
46
|
+
.harness/
|
|
47
|
+
skills/ real dir — shared skills
|
|
48
|
+
agents/ real dir — shared subagents
|
|
49
|
+
CLAUDE.md -> AGENTS.md
|
|
50
|
+
.claude/
|
|
51
|
+
skills -> ../.harness/skills
|
|
52
|
+
agents -> ../.harness/agents
|
|
53
|
+
settings.json (untouched — agent-specific)
|
|
54
|
+
.codex/
|
|
55
|
+
skills -> ../.harness/skills
|
|
56
|
+
agents -> ../.harness/agents
|
|
57
|
+
.opencode/
|
|
58
|
+
skills -> ../.harness/skills
|
|
59
|
+
agent -> ../.harness/agents (NB: singular)
|
|
60
|
+
.github/
|
|
61
|
+
copilot-instructions.md -> ../AGENTS.md
|
|
62
|
+
skills -> ../.harness/skills
|
|
63
|
+
agents -> ../.harness/agents
|
|
64
|
+
.agents/
|
|
65
|
+
skills -> ../.harness/skills
|
|
66
|
+
.subagents -> .harness/agents (Antigravity — root-level)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
All symlinks are **relative** so the tree is portable across machines and paths.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Per-agent path reference (as of 2026)
|
|
74
|
+
|
|
75
|
+
| Agent | instructions_path | skills_path | subagents_path | detection_paths |
|
|
76
|
+
|-------|-------------------|-------------|----------------|-----------------|
|
|
77
|
+
| claude | `CLAUDE.md` | `.claude/skills` | `.claude/agents` | `CLAUDE.md`, `.claude` |
|
|
78
|
+
| codex | *native AGENTS.md* | `.codex/skills` | `.codex/agents` | `.codex` |
|
|
79
|
+
| opencode | *native AGENTS.md* | `.opencode/skills` | `.opencode/agent` ⚠️ | `.opencode` |
|
|
80
|
+
| copilot | `.github/copilot-instructions.md` | `.github/skills` | `.github/agents` | `.github/copilot-instructions.md` |
|
|
81
|
+
| antigravity | *native AGENTS.md* | `.agents/skills` | `.subagents` ⚠️ | `.agents`, `.subagents` |
|
|
82
|
+
|
|
83
|
+
⚠️ opencode uses singular `agent/` (not `agents/`).
|
|
84
|
+
⚠️ Antigravity's subagent path `.subagents/` is root-level, inferred from docs as of Jun 2026 — may need revision.
|
|
85
|
+
|
|
86
|
+
**Adding a new agent:** create `src/multi_harness/agents/<name>.py` with a `SPEC`
|
|
87
|
+
`AgentSpec` instance, then add it to `AGENT_REGISTRY` in `agents/__init__.py`.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Key design decisions
|
|
92
|
+
|
|
93
|
+
**Detection skips symlinks.** `detect_configured_agents()` only fires when
|
|
94
|
+
`.harness/` does NOT yet exist. On `--force` re-init, detection is skipped entirely
|
|
95
|
+
— we never mistake our own symlinks for a natively-configured agent.
|
|
96
|
+
|
|
97
|
+
**`AGENTS.md` is not a detection signal.** Three agents (codex, opencode, antigravity)
|
|
98
|
+
natively read `AGENTS.md`, so its presence is ambiguous. Detection looks only at
|
|
99
|
+
agent-specific dirs/files (`.codex/`, `.opencode/`, `.github/copilot-instructions.md`,
|
|
100
|
+
`.agents/`, `.subagents/`, `CLAUDE.md`, `.claude/`).
|
|
101
|
+
|
|
102
|
+
**`instructions_path=None` means the agent is an AGENTS.md native.** No instruction
|
|
103
|
+
symlink is created for codex/opencode/antigravity — they already find the canonical
|
|
104
|
+
file. Only `CLAUDE.md` and `.github/copilot-instructions.md` need symlinks.
|
|
105
|
+
|
|
106
|
+
**`ensure_symlink` never deletes user data.** If the link target already exists as a
|
|
107
|
+
real file or directory, it raises `HarnessError` and the whole `init()` call aborts.
|
|
108
|
+
Migration happens before symlink creation, so migrated dirs are real dirs in `.harness/`
|
|
109
|
+
by the time symlinks are created.
|
|
110
|
+
|
|
111
|
+
**Migration is one-shot.** When exactly one agent is detected, its files are moved into
|
|
112
|
+
`.harness/` and symlinks replace them. There is no "dry-run" or partial migration.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Upcoming commands (planned)
|
|
117
|
+
|
|
118
|
+
All four commands will depend on a `.harness/config.toml` that records which agents are
|
|
119
|
+
registered for the project. `mh init` will write this file; subsequent commands read it.
|
|
120
|
+
|
|
121
|
+
### `mh add <agent> [--agents ...]`
|
|
122
|
+
Register one or more new agents to an existing harness: validate that `.harness/` exists,
|
|
123
|
+
create the symlinks for the new agent(s), update config.toml.
|
|
124
|
+
|
|
125
|
+
### `mh remove <agent> [--agents ...]`
|
|
126
|
+
Remove an agent's symlinks (unlink instructions, skills, subagents paths) without deleting
|
|
127
|
+
any `.harness/` content. Update config.toml.
|
|
128
|
+
|
|
129
|
+
### `mh status`
|
|
130
|
+
Read config.toml to know which agents are registered, then inspect each symlink:
|
|
131
|
+
- `ok` — symlink exists and points to the right place
|
|
132
|
+
- `missing` — symlink is absent (agent was added manually after init?)
|
|
133
|
+
- `broken` — symlink target doesn't resolve
|
|
134
|
+
- `detached` — real file/dir where a symlink should be
|
|
135
|
+
Report per-agent, per-link.
|
|
136
|
+
|
|
137
|
+
### `mh sync [--agents ...]`
|
|
138
|
+
Re-create any missing or broken symlinks for registered agents (like `--force` but
|
|
139
|
+
restricted to links that actually need repair). Reads config.toml, calls `ensure_symlink`
|
|
140
|
+
for each link, skips those that are already `"ok"`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## config.toml schema (planned)
|
|
145
|
+
|
|
146
|
+
File: `.harness/config.toml`
|
|
147
|
+
|
|
148
|
+
```toml
|
|
149
|
+
[harness]
|
|
150
|
+
version = 1
|
|
151
|
+
agents = ["claude", "codex", "opencode", "copilot", "antigravity"]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The `agents` list is the source of truth for which agents are registered. Commands that
|
|
155
|
+
need it (`status`, `sync`, `add`, `remove`) read it; `init` writes it.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Open questions / future considerations
|
|
160
|
+
|
|
161
|
+
- **Antigravity subagent path:** documented as `.subagents/` (root-level). Confirm when
|
|
162
|
+
official Antigravity docs clarify the subagents directory convention.
|
|
163
|
+
- **config.toml location:** `.harness/config.toml` keeps all harness state in one place;
|
|
164
|
+
an alternative is `multi-harness.toml` at the repo root (more visible).
|
|
165
|
+
- **Skill/agent file format validation:** should `mh` inspect SKILL.md frontmatter (name,
|
|
166
|
+
description fields) for correctness? Deferred for now.
|
|
167
|
+
- **Per-agent instruction overlays** (`.harness/instructions/<agent>.md`) are explicitly
|
|
168
|
+
out of scope for now — one shared AGENTS.md is the intent.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
176
|
+
.venv/bin/pytest # 13 tests, all scenarios
|
|
177
|
+
.venv/bin/mh --help
|
|
178
|
+
.venv/bin/mh init --help
|
|
179
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multi-harness
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Manage projects that target multiple coding agents from a single shared harness.
|
|
5
|
+
Author-email: jslarraz <jslarraz@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: typer>=0.12
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# multi-harness 🔗
|
|
14
|
+
|
|
15
|
+
> One project. Many coding agents. Zero duplication.
|
|
16
|
+
|
|
17
|
+
`mh` is a CLI that keeps your skills, subagents, and project instructions in a single
|
|
18
|
+
canonical `.harness/` directory and materialises per-agent symlinks so every agent sees
|
|
19
|
+
the paths it expects — no copy-paste, no drift.
|
|
20
|
+
|
|
21
|
+
**Supported agents:** Claude Code · OpenAI Codex CLI · OpenCode · GitHub Copilot · Google Antigravity
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ✨ Why multi-harness?
|
|
26
|
+
|
|
27
|
+
Each coding agent looks for instructions, skills, and subagents in different places:
|
|
28
|
+
`CLAUDE.md`, `.github/copilot-instructions.md`, `AGENTS.md`, `.claude/skills/`, `.codex/skills/` …
|
|
29
|
+
|
|
30
|
+
Without `mh` you either duplicate these files across every agent-specific location or
|
|
31
|
+
keep them in sync by hand. `mh` solves this by:
|
|
32
|
+
|
|
33
|
+
1. Creating one real `.harness/` directory with the canonical files.
|
|
34
|
+
2. Writing relative symlinks everywhere each agent expects to look.
|
|
35
|
+
3. Detecting an existing single-agent project and **migrating** it automatically.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 📦 Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install multi-harness
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Verify the install:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
mh --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🚀 Quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd my-project
|
|
57
|
+
mh init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That's it. `mh` detects which agents are already configured, migrates their files into
|
|
61
|
+
`.harness/`, and creates symlinks for all five supported agents.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📖 Commands
|
|
66
|
+
|
|
67
|
+
### `mh init` — bootstrap or migrate a project
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mh init # all five agents, current directory
|
|
71
|
+
mh init --agents claude,codex # register only two agents
|
|
72
|
+
mh init --template ./AGENTS_TEMPLATE.md # seed AGENTS.md from a template file
|
|
73
|
+
mh init --force # re-create symlinks idempotently (safe)
|
|
74
|
+
mh init /path/to/project # target a different directory
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
On first run `mh init`:
|
|
78
|
+
- Creates `AGENTS.md` (or uses your `--template`) as the shared project instructions file.
|
|
79
|
+
- Creates `.harness/skills/` and `.harness/agents/` as the shared canonical directories.
|
|
80
|
+
- Writes relative symlinks for every registered agent (see layout below).
|
|
81
|
+
- Records the registered agents in `.harness/config.toml`.
|
|
82
|
+
|
|
83
|
+
If exactly **one** agent is already configured, `mh init` migrates its files into
|
|
84
|
+
`.harness/` before creating symlinks — your existing work is preserved, not overwritten.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### `mh add` — register new agents
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mh add copilot # add a single agent
|
|
92
|
+
mh add opencode antigravity # add multiple agents at once
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Creates the symlinks for the new agent(s) and updates `.harness/config.toml`. Requires
|
|
96
|
+
`mh init` to have been run first.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `mh remove` — unregister agents
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
mh remove codex # remove one agent
|
|
104
|
+
mh remove opencode copilot # remove several at once
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Removes the agent's symlinks (instructions, skills, subagents) without touching any file
|
|
108
|
+
inside `.harness/`. Your shared content is never deleted.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### `mh status` — inspect symlink health
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
mh status # check current directory
|
|
116
|
+
mh status /path/to/project # check another directory
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Reports the state of every symlink for every registered agent:
|
|
120
|
+
|
|
121
|
+
| Status | Meaning |
|
|
122
|
+
|--------|---------|
|
|
123
|
+
| ✅ `ok` | Symlink exists and resolves correctly |
|
|
124
|
+
| ❌ `missing` | Symlink is absent |
|
|
125
|
+
| 💔 `broken` | Symlink exists but target doesn't resolve |
|
|
126
|
+
| ⚠️ `detached` | A real file/dir sits where a symlink should be |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🗂️ Layout produced by `mh init`
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
my-project/
|
|
134
|
+
├── AGENTS.md ← canonical shared instructions (real file)
|
|
135
|
+
├── CLAUDE.md → AGENTS.md
|
|
136
|
+
├── .harness/
|
|
137
|
+
│ ├── config.toml ← registered agents list
|
|
138
|
+
│ ├── skills/ ← shared skills (real dir)
|
|
139
|
+
│ └── agents/ ← shared subagents (real dir)
|
|
140
|
+
├── .claude/
|
|
141
|
+
│ ├── skills → ../.harness/skills
|
|
142
|
+
│ └── agents → ../.harness/agents
|
|
143
|
+
├── .codex/
|
|
144
|
+
│ ├── skills → ../.harness/skills
|
|
145
|
+
│ └── agents → ../.harness/agents
|
|
146
|
+
├── .opencode/
|
|
147
|
+
│ ├── skills → ../.harness/skills
|
|
148
|
+
│ └── agent → ../.harness/agents
|
|
149
|
+
├── .github/
|
|
150
|
+
│ ├── copilot-instructions.md → ../AGENTS.md
|
|
151
|
+
│ ├── skills → ../.harness/skills
|
|
152
|
+
│ └── agents → ../.harness/agents
|
|
153
|
+
├── .agents/
|
|
154
|
+
│ └── skills → ../.harness/skills
|
|
155
|
+
└── .subagents → .harness/agents
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
All symlinks are **relative**, so the tree is fully portable across machines and paths.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🔄 Typical workflow
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# 1. Bootstrap a new project
|
|
166
|
+
mh init --agents claude,codex
|
|
167
|
+
|
|
168
|
+
# 2. Add an agent later
|
|
169
|
+
mh add copilot
|
|
170
|
+
|
|
171
|
+
# 3. Check everything is wired up correctly
|
|
172
|
+
mh status
|
|
173
|
+
|
|
174
|
+
# 4. Remove an agent you no longer use
|
|
175
|
+
mh remove antigravity
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Write your instructions once in `AGENTS.md`, drop skills into `.harness/skills/`, and
|
|
179
|
+
place shared subagents in `.harness/agents/` — every registered agent picks them up
|
|
180
|
+
automatically.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 🛡️ Safety guarantees
|
|
185
|
+
|
|
186
|
+
- **Symlinks never overwrite real files.** If a real file or directory already exists
|
|
187
|
+
where a symlink should go, `mh` aborts with an error rather than deleting your data.
|
|
188
|
+
- **`--force` is safe.** It re-creates symlinks but still refuses to touch real files.
|
|
189
|
+
- **Migration is non-destructive.** Files are moved into `.harness/`; nothing is deleted.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# multi-harness 🔗
|
|
2
|
+
|
|
3
|
+
> One project. Many coding agents. Zero duplication.
|
|
4
|
+
|
|
5
|
+
`mh` is a CLI that keeps your skills, subagents, and project instructions in a single
|
|
6
|
+
canonical `.harness/` directory and materialises per-agent symlinks so every agent sees
|
|
7
|
+
the paths it expects — no copy-paste, no drift.
|
|
8
|
+
|
|
9
|
+
**Supported agents:** Claude Code · OpenAI Codex CLI · OpenCode · GitHub Copilot · Google Antigravity
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Why multi-harness?
|
|
14
|
+
|
|
15
|
+
Each coding agent looks for instructions, skills, and subagents in different places:
|
|
16
|
+
`CLAUDE.md`, `.github/copilot-instructions.md`, `AGENTS.md`, `.claude/skills/`, `.codex/skills/` …
|
|
17
|
+
|
|
18
|
+
Without `mh` you either duplicate these files across every agent-specific location or
|
|
19
|
+
keep them in sync by hand. `mh` solves this by:
|
|
20
|
+
|
|
21
|
+
1. Creating one real `.harness/` directory with the canonical files.
|
|
22
|
+
2. Writing relative symlinks everywhere each agent expects to look.
|
|
23
|
+
3. Detecting an existing single-agent project and **migrating** it automatically.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 📦 Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install multi-harness
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Verify the install:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mh --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🚀 Quick start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd my-project
|
|
45
|
+
mh init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That's it. `mh` detects which agents are already configured, migrates their files into
|
|
49
|
+
`.harness/`, and creates symlinks for all five supported agents.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 📖 Commands
|
|
54
|
+
|
|
55
|
+
### `mh init` — bootstrap or migrate a project
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
mh init # all five agents, current directory
|
|
59
|
+
mh init --agents claude,codex # register only two agents
|
|
60
|
+
mh init --template ./AGENTS_TEMPLATE.md # seed AGENTS.md from a template file
|
|
61
|
+
mh init --force # re-create symlinks idempotently (safe)
|
|
62
|
+
mh init /path/to/project # target a different directory
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
On first run `mh init`:
|
|
66
|
+
- Creates `AGENTS.md` (or uses your `--template`) as the shared project instructions file.
|
|
67
|
+
- Creates `.harness/skills/` and `.harness/agents/` as the shared canonical directories.
|
|
68
|
+
- Writes relative symlinks for every registered agent (see layout below).
|
|
69
|
+
- Records the registered agents in `.harness/config.toml`.
|
|
70
|
+
|
|
71
|
+
If exactly **one** agent is already configured, `mh init` migrates its files into
|
|
72
|
+
`.harness/` before creating symlinks — your existing work is preserved, not overwritten.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### `mh add` — register new agents
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
mh add copilot # add a single agent
|
|
80
|
+
mh add opencode antigravity # add multiple agents at once
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Creates the symlinks for the new agent(s) and updates `.harness/config.toml`. Requires
|
|
84
|
+
`mh init` to have been run first.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### `mh remove` — unregister agents
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mh remove codex # remove one agent
|
|
92
|
+
mh remove opencode copilot # remove several at once
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Removes the agent's symlinks (instructions, skills, subagents) without touching any file
|
|
96
|
+
inside `.harness/`. Your shared content is never deleted.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `mh status` — inspect symlink health
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
mh status # check current directory
|
|
104
|
+
mh status /path/to/project # check another directory
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Reports the state of every symlink for every registered agent:
|
|
108
|
+
|
|
109
|
+
| Status | Meaning |
|
|
110
|
+
|--------|---------|
|
|
111
|
+
| ✅ `ok` | Symlink exists and resolves correctly |
|
|
112
|
+
| ❌ `missing` | Symlink is absent |
|
|
113
|
+
| 💔 `broken` | Symlink exists but target doesn't resolve |
|
|
114
|
+
| ⚠️ `detached` | A real file/dir sits where a symlink should be |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🗂️ Layout produced by `mh init`
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
my-project/
|
|
122
|
+
├── AGENTS.md ← canonical shared instructions (real file)
|
|
123
|
+
├── CLAUDE.md → AGENTS.md
|
|
124
|
+
├── .harness/
|
|
125
|
+
│ ├── config.toml ← registered agents list
|
|
126
|
+
│ ├── skills/ ← shared skills (real dir)
|
|
127
|
+
│ └── agents/ ← shared subagents (real dir)
|
|
128
|
+
├── .claude/
|
|
129
|
+
│ ├── skills → ../.harness/skills
|
|
130
|
+
│ └── agents → ../.harness/agents
|
|
131
|
+
├── .codex/
|
|
132
|
+
│ ├── skills → ../.harness/skills
|
|
133
|
+
│ └── agents → ../.harness/agents
|
|
134
|
+
├── .opencode/
|
|
135
|
+
│ ├── skills → ../.harness/skills
|
|
136
|
+
│ └── agent → ../.harness/agents
|
|
137
|
+
├── .github/
|
|
138
|
+
│ ├── copilot-instructions.md → ../AGENTS.md
|
|
139
|
+
│ ├── skills → ../.harness/skills
|
|
140
|
+
│ └── agents → ../.harness/agents
|
|
141
|
+
├── .agents/
|
|
142
|
+
│ └── skills → ../.harness/skills
|
|
143
|
+
└── .subagents → .harness/agents
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
All symlinks are **relative**, so the tree is fully portable across machines and paths.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🔄 Typical workflow
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# 1. Bootstrap a new project
|
|
154
|
+
mh init --agents claude,codex
|
|
155
|
+
|
|
156
|
+
# 2. Add an agent later
|
|
157
|
+
mh add copilot
|
|
158
|
+
|
|
159
|
+
# 3. Check everything is wired up correctly
|
|
160
|
+
mh status
|
|
161
|
+
|
|
162
|
+
# 4. Remove an agent you no longer use
|
|
163
|
+
mh remove antigravity
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Write your instructions once in `AGENTS.md`, drop skills into `.harness/skills/`, and
|
|
167
|
+
place shared subagents in `.harness/agents/` — every registered agent picks them up
|
|
168
|
+
automatically.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🛡️ Safety guarantees
|
|
173
|
+
|
|
174
|
+
- **Symlinks never overwrite real files.** If a real file or directory already exists
|
|
175
|
+
where a symlink should go, `mh` aborts with an error rather than deleting your data.
|
|
176
|
+
- **`--force` is safe.** It re-creates symlinks but still refuses to touch real files.
|
|
177
|
+
- **Migration is non-destructive.** Files are moved into `.harness/`; nothing is deleted.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "multi-harness"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Manage projects that target multiple coding agents from a single shared harness."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "jslarraz", email = "jslarraz@gmail.com" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"typer>=0.12",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
mh = "multi_harness.cli:app"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/multi_harness"]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .antigravity import SPEC as _ANTIGRAVITY
|
|
2
|
+
from .claude import SPEC as _CLAUDE
|
|
3
|
+
from .codex import SPEC as _CODEX
|
|
4
|
+
from .copilot import SPEC as _COPILOT
|
|
5
|
+
from .opencode import SPEC as _OPENCODE
|
|
6
|
+
from .spec import AgentSpec
|
|
7
|
+
|
|
8
|
+
AGENT_REGISTRY: dict[str, AgentSpec] = {
|
|
9
|
+
spec.name: spec
|
|
10
|
+
for spec in (_CLAUDE, _CODEX, _OPENCODE, _COPILOT, _ANTIGRAVITY)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
__all__ = ["AGENT_REGISTRY", "AgentSpec"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .spec import AgentSpec
|
|
4
|
+
|
|
5
|
+
SPEC = AgentSpec(
|
|
6
|
+
name="antigravity",
|
|
7
|
+
display_name="Google Antigravity",
|
|
8
|
+
instructions_path=None,
|
|
9
|
+
skills_path=Path(".agents/skills"),
|
|
10
|
+
subagents_path=Path(".subagents"),
|
|
11
|
+
detection_paths=(Path(".agents"), Path(".subagents")),
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .spec import AgentSpec
|
|
4
|
+
|
|
5
|
+
SPEC = AgentSpec(
|
|
6
|
+
name="claude",
|
|
7
|
+
display_name="Claude Code",
|
|
8
|
+
instructions_path=Path("CLAUDE.md"),
|
|
9
|
+
skills_path=Path(".claude/skills"),
|
|
10
|
+
subagents_path=Path(".claude/agents"),
|
|
11
|
+
detection_paths=(Path("CLAUDE.md"), Path(".claude")),
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .spec import AgentSpec
|
|
4
|
+
|
|
5
|
+
SPEC = AgentSpec(
|
|
6
|
+
name="codex",
|
|
7
|
+
display_name="OpenAI Codex",
|
|
8
|
+
instructions_path=None,
|
|
9
|
+
skills_path=Path(".codex/skills"),
|
|
10
|
+
subagents_path=Path(".codex/agents"),
|
|
11
|
+
detection_paths=(Path(".codex"),),
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .spec import AgentSpec
|
|
4
|
+
|
|
5
|
+
SPEC = AgentSpec(
|
|
6
|
+
name="copilot",
|
|
7
|
+
display_name="GitHub Copilot",
|
|
8
|
+
instructions_path=Path(".github/copilot-instructions.md"),
|
|
9
|
+
skills_path=Path(".github/skills"),
|
|
10
|
+
subagents_path=Path(".github/agents"),
|
|
11
|
+
detection_paths=(Path(".github/copilot-instructions.md"),),
|
|
12
|
+
)
|