feat: multi-agent support — harness selection, instructions, skills & hooks (v0.16.0)#9
Conversation
Lifts the Claude-only coupling (ADR-004): project instructions are written once
to a canonical AGENTS.md and shared across agents via an extensible adapter
registry. (ADR-016)
- src/setup/agents.sh: registry (claude, codex, opencode, gemini, copilot,
cursor). Codex/opencode/Copilot/Cursor read AGENTS.md natively; Claude
(CLAUDE.md) and Gemini (GEMINI.md) get thin @AGENTS.md import stubs.
- templates: AGENTS.md now canonical; CLAUDE.md/GEMINI.md are import stubs.
- init: detect installed agents and emit matching files; report detection.
- update: content-preserving migration of legacy CLAUDE.md -> AGENTS.md, plus
GEMINI.md stub when Gemini CLI is present.
- --version --json/--features: multiagent:true + agents list from registry.
- doctor: AGENTS.md-aware instructions check + detected-agents line.
- docs: README multi-agent section + structure tree.
Commands/skills stay Claude-native; per-agent ports + ADR-005 v2.0 ({{ASK}})
are a later phase.
|
Warning Review limit reached
More reviews will be available in 50 minutes and 37 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughThis PR implements a multi-agent instruction harness that consolidates project instructions into a single canonical ChangesMulti-Agent Instruction Harness (Phase 2a)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.context/decisions/016-multi-agent-harness.md:
- Line 16: The spec reference string "github/spec-kit" should use correct
product casing; replace the lowercase occurrence with "GitHub/spec-kit" in the
Markdown content (look for the literal "github/spec-kit" in the decision
document and update it to "GitHub/spec-kit").
In `@dotcontext`:
- Around line 388-400: The bundled file dotcontext is stale and must be
regenerated from the source modules: make your edits in the src/ modules
(including the colon-parsing fix you applied), ensure SOURCES reflects any
new/changed files if needed, run make build to regenerate dotcontext, verify the
lint step passes, and commit the updated dotcontext so the bundled output
matches src/.
- Around line 547-549: The split in cmd_init breaks URLs containing ":" because
it uses the first-colon expansions (`${mapping%%:*}`/`${mapping#*:}`); change
the split to use the last-colon expansions so url=everything up to the final
colon and target=everything after it (use `${mapping%:*}` for the URL and
`${mapping##*:}` for the target) in the cmd_init loop that calls
download_if_missing (and ensure agent_instruction_seeds still emits
"url:target"); update the mapping handling in src/commands/init.sh’s cmd_init
and any similar use in src/setup/agents.sh (referencing the mapping variable and
download_if_missing/download functions) and then run make build.
In `@README.md`:
- Line 129: The README claims CLAUDE.md is "added when Claude is detected" but
init logic in src/commands/init.sh always appends Claude to selected_agents
before seeding; update wording to match behavior. Change the README line about
CLAUDE.md to state it is always added (e.g., "always added for compatibility")
or modify init.sh to conditionally add "claude" to selected_agents based on
actual detection logic; reference the README entry "CLAUDE.md" and the init.sh
symbol selected_agents when making the change.
In `@src/commands/init.sh`:
- Around line 80-82: The loop in init.sh incorrectly splits url:target on the
first colon causing URLs like "https://..." to be truncated; update the for-loop
that iterates over agent_instruction_seeds so it passes the full URL and the
filename to download_if_missing by using splitting on the last colon instead of
the first (use the expansions that take everything before the last colon and
everything after the last colon) for the variable mapping; modify the invocation
around mapping in the loop that calls download_if_missing (the mapping variable,
agent_instruction_seeds, and download_if_missing) so download_if_missing
receives the complete URL as the first argument and the target filename as the
second.
In `@src/setup/agents.sh`:
- Around line 70-80: The seed emitter agent_instruction_seeds uses ":" as a
separator which conflicts with URLs in BASE_URL; change the emitted separator to
a character not allowed in URLs (e.g., "|" or "\t") and update the two echo
lines to emit "${BASE_URL}/templates/${target}|${target}" (and the first echo to
"${BASE_URL}/templates/AGENTS.md|AGENTS.md"); ensure downstream consumers (e.g.,
code reading output in init.sh) split on that new separator or on the last
delimiter accordingly; keep references to agent_instruction_seeds, BASE_URL,
agent_emit_mode, agent_instructions_file, target, and seen when making the
change so the behavior and dedup logic remain identical.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9406bf04-2526-4314-8989-54e9a66f78c6
📒 Files selected for processing (14)
.context/decisions/016-multi-agent-harness.mdCHANGELOG.mdMakefileREADME.mddotcontextsrc/commands/doctor.shsrc/commands/help.shsrc/commands/init.shsrc/commands/update.shsrc/header.shsrc/setup/agents.shtemplates/AGENTS.mdtemplates/CLAUDE.mdtemplates/GEMINI.md
- init.sh/agents.sh: the "url:target" mapping was split on the FIRST colon,
which broke on the URL scheme (https:) — url became "https", breaking every
init download. Switched the separator to " |" (cannot appear in a URL or
filename) and split with ${mapping%|*} / ${mapping##*|}. Unambiguous by
construction, no reliance on "targets never contain a colon".
- README: CLAUDE.md is always added (not only when Claude is detected).
- ADR-016: capitalize "GitHub/spec-kit".
4e41fbd to
646921b
Compare
Only emit files for the chosen harness(es) — no .claude/ junk for a Codex-only user. init resolves agents interactively (default = detected), via --agents claude,codex, or --yes (all detected); gates all .claude/ + CLAUDE.md on Claude being selected. - skills: shared SKILL.md emitted to .agents/skills (Codex/opencode/Gemini/ Copilot/Cursor) mirrored to .claude/skills (Claude) via symlink+copy fallback. - hooks: notification wired per selected harness in native format (.codex, .gemini, .github/hooks, .cursor, opencode JS plugin) pointing at a shared arg-driven notify.sh; Claude keeps the tool-failure guard. - registry: resolve_selected_agents, link_or_copy_dir, setup_agent_hooks. - update: mirror skills to .agents/skills when a non-Claude agent is present. - --version --json: + skills:true; help/completion document --agents. - ADR-016 corrected (all 6 agents have structured-question tools + hooks); new ADR-017 (harness selection, skills, hooks, scope limits). Commands stay Claude-native (next phase). Non-Claude hook configs are artifact-validated (JSON/TOML/JS) but best-effort, not runtime-tested per agent.
Scope expanded (per discussion)This PR now delivers full multi-agent support, not just the instructions file:
Scope limits (honest): slash commands stay Claude-native (next phase, with ADR-005 v2.0 |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
dotcontext (1)
1-1816:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winCritical: Regenerate bundled CLI from source modules.
The CI lint step is failing because the bundled
dotcontextscript is out of sync withsrc/. After making changes to source modules (src/setup/agents.sh,src/commands/init.sh,src/commands/update.sh), you must regenerate the bundle:make build git add dotcontext git commit -m "build: regenerate bundled CLI"This is a critical blocker that prevents the CI pipeline from passing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dotcontext` around lines 1 - 1816, The bundled CLI script dotcontext is out of sync with recent changes in source modules (e.g. src/setup/agents.sh, src/commands/init.sh, src/commands/update.sh) causing CI lint failures; fix by regenerating the bundled CLI with the build target (run `make build`), stage the updated dotcontext, and commit with a clear message (e.g. "build: regenerate bundled CLI") so the repo's dotcontext matches the src modules.
🧹 Nitpick comments (1)
src/setup/agents.sh (1)
139-147: ⚡ Quick winHardcoded relative symlink assumes sibling directories.
The
ln -s "../$src"on line 146 assumes$srcand$dstare siblings (same parent directory). This works for the current usage (.claude/skills→.agents/skills), but the function will create broken symlinks if called with paths at different nesting levels.Consider computing the relative path dynamically or documenting that this function only supports sibling directories in its comment.
♻️ Example: Document the constraint
-# Create $2 as a symlink to $1 (relative), or copy if symlinks aren't available. -# No-op if $2 already exists. Used to mirror the skills dir across ecosystems. +# Create $2 as a symlink to $1 (relative), or copy if symlinks aren't available. +# LIMITATION: $1 and $2 must be sibling directories (same parent) for the symlink to work. +# No-op if $2 already exists. Used to mirror the skills dir across ecosystems. link_or_copy_dir() {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/setup/agents.sh` around lines 139 - 147, The function link_or_copy_dir uses a hardcoded ln -s "../$src" which assumes src and dst are sibling dirs and can produce broken symlinks; update link creation to compute a correct relative path from the destination directory to the source (or fall back to an absolute path) before calling ln -s. Specifically, inside link_or_copy_dir replace the literal "../$src" with a computed value such as: rel="$(realpath --relative-to="$(dirname "$dst")" "$src" 2>/dev/null || python -c 'import os,sys;print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$src" "$(dirname "$dst")" 2>/dev/null || printf "%s" "$src")" and then call ln -s "$rel" "$dst" 2>/dev/null || cp -r "$src" "$dst"; keep the existing checks ([ -d "$src" ], [ -e "$dst" ]/ -L) and mkdir -p "$(dirname "$dst")" as-is to locate the change in the link_or_copy_dir function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.context/decisions/017-skills-and-hooks-portability.md:
- Line 60: Update the example sentence that lists hook paths so “Copilot” is
written as “GitHub Copilot” and “GitHub” is capitalized consistently;
specifically edit the line that reads "Codex → `.codex/hooks.json`; Gemini →
`.gemini/settings.json`; Copilot → `.github/hooks/dotcontext-notify.json`;" to
use "GitHub Copilot → `.github/hooks/dotcontext-notify.json`" (ensure the word
“GitHub” is capitalized exactly as elsewhere in the docs).
In `@CHANGELOG.md`:
- Line 12: Update the release-note bullet that mentions ".github/hooks/" to use
the official capitalization "GitHub" instead of "github" — edit the line
containing `.github/hooks/` so it reads `.GitHub/hooks/` (and ensure surrounding
text keeps the same punctuation and casing).
In `@README.md`:
- Line 164: Update the README to remove the conflicting statement by making the
file-tree annotation match the behavior described: mark CLAUDE.md as emitted
only when Claude is selected (like `.claude/`), rather than saying it is always
added; specifically edit the tree annotation that currently shows CLAUDE.md as
always present so it indicates CLAUDE.md is conditional, leaving AGENTS.md and
`.context/` as always written and keeping GEMINI.md and `.agents/skills/`
behavior unchanged.
- Line 197: In the README table row for "Hooks" update the GitHub mention to use
the official capitalization by replacing `.github/hooks/` with `GitHub/hooks/`
(i.e., change the lowercase `.github` token in that row to "GitHub") so the
user-facing table displays "GitHub" consistently; edit the Hooks table cell text
that currently contains `.codex/, .gemini/, .github/hooks/, .cursor/, opencode
plugin` accordingly.
---
Outside diff comments:
In `@dotcontext`:
- Around line 1-1816: The bundled CLI script dotcontext is out of sync with
recent changes in source modules (e.g. src/setup/agents.sh,
src/commands/init.sh, src/commands/update.sh) causing CI lint failures; fix by
regenerating the bundled CLI with the build target (run `make build`), stage the
updated dotcontext, and commit with a clear message (e.g. "build: regenerate
bundled CLI") so the repo's dotcontext matches the src modules.
---
Nitpick comments:
In `@src/setup/agents.sh`:
- Around line 139-147: The function link_or_copy_dir uses a hardcoded ln -s
"../$src" which assumes src and dst are sibling dirs and can produce broken
symlinks; update link creation to compute a correct relative path from the
destination directory to the source (or fall back to an absolute path) before
calling ln -s. Specifically, inside link_or_copy_dir replace the literal
"../$src" with a computed value such as: rel="$(realpath
--relative-to="$(dirname "$dst")" "$src" 2>/dev/null || python -c 'import
os,sys;print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$src" "$(dirname
"$dst")" 2>/dev/null || printf "%s" "$src")" and then call ln -s "$rel" "$dst"
2>/dev/null || cp -r "$src" "$dst"; keep the existing checks ([ -d "$src" ], [
-e "$dst" ]/ -L) and mkdir -p "$(dirname "$dst")" as-is to locate the change in
the link_or_copy_dir function.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 661297c6-5f2a-45a1-b796-ae1370c93f79
📒 Files selected for processing (10)
.context/decisions/016-multi-agent-harness.md.context/decisions/017-skills-and-hooks-portability.mdCHANGELOG.mdREADME.mddotcontextsrc/commands/completion.shsrc/commands/help.shsrc/commands/init.shsrc/commands/update.shsrc/setup/agents.sh
✅ Files skipped from review due to trivial changes (1)
- .context/decisions/016-multi-agent-harness.md
🚧 Files skipped from review as they are similar to previous changes (1)
- src/commands/help.sh
|
|
||
| Each selected harness gets a "task finished / needs attention" notification wired in its native config: | ||
| - Claude → `.claude/settings.json` (existing: notify + tool-failure-guard). | ||
| - Codex → `.codex/hooks.json`; Gemini → `.gemini/settings.json`; Copilot → `.github/hooks/dotcontext-notify.json`; |
There was a problem hiding this comment.
Capitalize “GitHub” consistently in hook path examples.
Use “GitHub Copilot” capitalization in this sentence for consistency with the rest of the docs.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~60-~60: The official name of this software platform is spelled with a capital “H”.
Context: ...ni → .gemini/settings.json; Copilot → .github/hooks/dotcontext-notify.json; Cursor...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.context/decisions/017-skills-and-hooks-portability.md at line 60, Update
the example sentence that lists hook paths so “Copilot” is written as “GitHub
Copilot” and “GitHub” is capitalized consistently; specifically edit the line
that reads "Codex → `.codex/hooks.json`; Gemini → `.gemini/settings.json`;
Copilot → `.github/hooks/dotcontext-notify.json`;" to use "GitHub Copilot →
`.github/hooks/dotcontext-notify.json`" (ensure the word “GitHub” is capitalized
exactly as elsewhere in the docs).
| * **harness selection — only emit what you choose** — `init` confirms each detected agent interactively, or takes `--agents claude,codex` (non-interactive), or `--yes` (all detected). A Codex-only project gets **no `.claude/`** — no junk | ||
| * **instructions** — canonical **`AGENTS.md`** read natively by Codex/opencode/Copilot/Cursor; **Claude** (`CLAUDE.md`) and **Gemini** (`GEMINI.md`) via thin `@AGENTS.md` import stubs. Single source, no duplication | ||
| * **skills** — shared `SKILL.md` content emitted to `.agents/skills/` (Codex/opencode/Gemini/Copilot/Cursor) mirrored to `.claude/skills/` (Claude) via symlink (copy fallback) | ||
| * **hooks** — a "task finished / needs attention" notification wired per selected harness in its native config (`.codex/hooks.json`, `.gemini/settings.json`, `.github/hooks/`, `.cursor/hooks.json`, opencode JS plugin); Claude also keeps the tool-failure guard |
There was a problem hiding this comment.
Use official “GitHub” capitalization in this bullet.
Tiny wording fix, but it avoids inconsistency in release notes.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~12-~12: The official name of this software platform is spelled with a capital “H”.
Context: ...x/hooks.json, .gemini/settings.json, .github/hooks/, .cursor/hooks.json`, opencode...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` at line 12, Update the release-note bullet that mentions
".github/hooks/" to use the official capitalization "GitHub" instead of "github"
— edit the line containing `.github/hooks/` so it reads `.GitHub/hooks/` (and
ensure surrounding text keeps the same punctuation and casing).
| └── statusline.sh # StatusLine: model, dir, git, ctx-usage bar, cost/time/lines | ||
| ``` | ||
|
|
||
| > Files are emitted per chosen harness (see [Multi-Agent Support](#multi-agent-support)): `AGENTS.md` and `.context/` are always written; the full `.claude/` tree and `CLAUDE.md` appear only when **Claude** is selected, `GEMINI.md` only for **Gemini**, and `.agents/skills/` only when a non-Claude agent is selected. |
There was a problem hiding this comment.
README has conflicting CLAUDE.md behavior statements.
This line says CLAUDE.md is emitted only when Claude is selected, but the file tree above still says it is always added. Please align the tree annotation with this behavior to avoid user confusion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 164, Update the README to remove the conflicting statement
by making the file-tree annotation match the behavior described: mark CLAUDE.md
as emitted only when Claude is selected (like `.claude/`), rather than saying it
is always added; specifically edit the tree annotation that currently shows
CLAUDE.md as always present so it indicates CLAUDE.md is conditional, leaving
AGENTS.md and `.context/` as always written and keeping GEMINI.md and
`.agents/skills/` behavior unchanged.
| | --- | --- | | ||
| | **Instructions** — canonical `AGENTS.md` | Codex/opencode/Copilot/Cursor read it natively; Claude (`CLAUDE.md`) and Gemini (`GEMINI.md`) via `@AGENTS.md` import | | ||
| | **Skills** — shared `SKILL.md` | `.agents/skills/` (Codex/opencode/Gemini/Copilot/Cursor) mirrored to `.claude/skills/` (Claude) | | ||
| | **Hooks** — notification on finish | native config per harness (`.codex/`, `.gemini/`, `.github/hooks/`, `.cursor/`, opencode plugin); Claude also gets the tool-failure guard | |
There was a problem hiding this comment.
Capitalize “GitHub” consistently in the hooks row.
Use the official capitalization in this user-facing table.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~197-~197: The official name of this software platform is spelled with a capital “H”.
Context: ...fig per harness (.codex/, .gemini/, .github/hooks/, .cursor/, opencode plugin); ...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 197, In the README table row for "Hooks" update the GitHub
mention to use the official capitalization by replacing `.github/hooks/` with
`GitHub/hooks/` (i.e., change the lowercase `.github` token in that row to
"GitHub") so the user-facing table displays "GitHub" consistently; edit the
Hooks table cell text that currently contains `.codex/, .gemini/,
.github/hooks/, .cursor/, opencode plugin` accordingly.
… plan)
Records the skill-vs-command classification (knowledge=skill, action=command):
code-review + deep-context promoted to skill mode; the 9 side-effecting/
deliberate commands stay explicit-only; fix-bug stays command. Documents the
portable {{ASK}} that renders to each agent's native structured-question tool
(satisfies ADR-005 cross-agent, no free-text degrade). Status: Proposed.
Fixes stale "ADR-005 v2.0" references in ADR-016/017 and CHANGELOG — ADR-005 is
already v2.0 (clarity assessment); the {{ASK}} work is ADR-018.
Summary
Phase 2a of multi-agent support. Lifts the Claude-only coupling (ADR-004's accepted "won't work with other AI tools" tradeoff): project instructions are written once to a canonical
AGENTS.mdand shared across agents through an extensible adapter registry. (ADR-016)This phase covers the instructions file only — slash commands and skills stay Claude-native for now.
How it works
cursor-agent)AGENTS.mdnativelyCLAUDE.md→@AGENTS.mdimportGEMINI.md→@AGENTS.mdimportSingle source, no duplication, no drift.
Changes
src/setup/agents.sh— adapter registry (bash 3.2, no assoc arrays). One entry per agent:id, name,agent_detect(CLI on PATH),agent_instructions_file,agent_emit_mode. Adding an agent = one case arm.AGENTS.mdis now canonical (formerCLAUDE.mdcontent);CLAUDE.md+ newGEMINI.mdare thin@AGENTS.mdimport stubs.init— detects installed agent CLIs, emits the matching files (AGENTS.md always; CLAUDE.md always for back-compat/first-class; GEMINI.md when detected), substitutes{{projectName}}across all of them, and reports detected agents. No new flags (ADR-007).update— content- & behavior-preserving migration of a legacy single-fileCLAUDE.md→AGENTS.md(+ CLAUDE.md import stub), interactive with--yes/--dry-runhonored; adds aGEMINI.mdstub when the Gemini CLI is present. SeedsAGENTS.md.--version --json/--features—multiagent: true+agentslist built from the registry.doctor— instructions check isAGENTS.md-aware; new "Agents detected" line.New ADR
Test plan
make buildclean;bash -non all modules + binary; build in-syncagent_instruction_seeds,detect_agents, emit modes verified locally--version --json/--featuresshow multiagent + 6 agentsv0.16.0→ release.ymlinitin a fresh project once templates/AGENTS.md is onmain(init pulls templates from the main branch, so full end-to-end requires the merge)Decision flagged
Making
AGENTS.mdcanonical (andCLAUDE.mdan import stub) is the notable architectural call — it's the only single-source option that covers Codex/Copilot/Cursor, which don't resolve@import. Rationale and alternatives are in ADR-016. Easy to revisit before merge if you'd rather keepCLAUDE.mdcanonical.Next phases
{{ASK}}since Codex/Gemini/etc. have no AskUserQuestion)..agents/skills/,.opencode/skills/, etc.).Summary by CodeRabbit
New Features
Documentation