Skip to content
Agent integrations

Agent integrations

You write the door once. multivac projects it into whatever harness the person in front of it happens to use.

The canonical door is AGENTS.md at the repo root — in the brain and in every declared repo multivac may write in (not managed: false, not a shallow clone). Everything else on this page is a projection of that one file. Adding a harness is an entry in src/adapters/registry.ts, shipped inside the package: doors and doctor dispatch on the entry’s kind, never on its name, so a new harness is data and nothing else. Every entry records the vendor doc it was verified against.

Select them in .multivac/config.yml:

doors: [agents, claude, cursor, opencode, codex, windsurf, gemini, copilot]

Then mvac doors. That is the whole adoption step: init keeps an existing config, so re-running it never picks up the new entry.

The four kinds

kindwhat doors writesentries
canonicalAGENTS.md itself — the source every other kind projects fromagents
nativenothing. The harness already reads AGENTS.md; a second file would be a paraphraseopencode, codex, windsurf, cursor
symlinka second name for the same bytes: <door> → AGENTS.mdclaude, gemini
stuba small file at the harness’s path: optional frontmatter on creation, then the managed block, pointing at AGENTS.mdcopilot

There is no kind for “cannot be owned”. A harness whose door multivac cannot write gets no entry, because an entry is how this tool says supported — it is listed in init --provider’s help, in the table above, and in the count of what multivac integrates with. aider had one for a while, carrying a note that explained at length why none of it applied; it read as support to anyone who did not open it. An unknown name already gets the list of what is supported, which is the answer that helps.

Cursor was a stub until multivac stopped projecting one: it reads AGENTS.md at the project root, so the rules file was a second door that could disagree with the canonical one. A target that retires a file it used to project takes it with it — one doors run, with cursor still in doors:, removes multivac’s block from .cursor/rules/multivac.mdc and deletes the file when nothing but the frontmatter multivac itself wrote is left, saying which of the two it did. Lines you added there survive, and the file stays with them, frontmatter (alwaysApply: true) included.

Everything multivac writes into a door file it does not fully own lands between <!-- multivac:begin --> and <!-- multivac:end -->. Content outside that block is yours and is never touched — including in a stub door, which reads the file before it writes it and adds its frontmatter only when creating the file. The stub kind used to write its file whole, which made that sentence false for .github/copilot-instructions.md and .cursor/rules/multivac.mdc.

What one run looks like

$ mvac doors
brain: door + hooks updated
brain: .multivac/flow.md — what your declarations oblige, sorted; generated, binds nothing
$ mvac doctor
doors      agents: AGENTS.md ok · claude: CLAUDE.md ok (symlink) · cursor: AGENTS.md ok (read natively) · opencode: AGENTS.md ok (read natively) · codex: AGENTS.md ok (read natively) · windsurf: AGENTS.md ok (read natively) · gemini: GEMINI.md ok (symlink) · copilot: .github/copilot-instructions.md ok

agents

file writtenAGENTS.md
kindcanonical
skill—
hook config—
sourcehttps://agents.md/

The canonical door. Every other target projects from this file, and doors writes it whether or not agents is in your list. In the brain it points at the law, the changes, the ritual and verify, and says the brain is empty while the law table has no row that is not retired; in a consumer repo it carries where the brain is mounted, what binds, and that a change may cross repos.

init and doors both write it: either creates it when it is missing and refreshes its managed block when it is not.

claude

file writtenCLAUDE.md
kindsymlink → AGENTS.md
skill.claude/skills/multivac/
hook config.claude/settings.json — hooks.SessionStart and hooks.PostToolUse → mvac verify, wrapped per event so a red run reaches the model
detected byan existing CLAUDE.md
sourcehttps://code.claude.com/docs/en/memory

Claude Code reads CLAUDE.md, not AGENTS.md; its own docs give ln -s AGENTS.md CLAUDE.md as the way to share one file, so that is exactly what doors creates.

This is the only entry with all three artifact classes. Besides the symlink, doors copies the packaged skill into .claude/skills/multivac/ and merges multivac’s hook entries into .claude/settings.json, preserving every key and entry it does not own:

{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "mvac verify 2>&1 || true" } ] }
    ],
    "PostToolUse": [
      {
        "hooks": [ { "type": "command", "command": "mvac verify >&2 || exit 2" } ],
        "matcher": "Edit|Write|MultiEdit"
      }
    ]
  }
}

SessionStart catches a lying brain before the agent conceives code on top of it, in one line when nothing is off and in full otherwise; PostToolUse re-checks after every edit, in the checkout of the file written when a brain governs it, and while it is green nothing reaches the model. Both run the default policy, not --strict, and neither command carries a switch: verify reads the harness’s own hook payload (see hooks).

The skill directory is a mirror, not an accretion: every run deletes anything under .claude/skills/multivac/ that the package no longer ships — including a file you put there yourself, because nothing on disk says who wrote it. Your own skills live beside it: doors never touches a sibling under .claude/skills/, only the one directory it writes. What multivac owns here is the individual command, matched exactly — never an entry that merely mentions it — and the set it owns is the three commands multivac has written, so a brain projected with the unwrapped command is upgraded in place rather than gaining a second gate beside the mute one. The wrapping differs per event because Claude Code reads each hook back on one channel only; the contract and its reasons are in hooks. mvac verify --strict is your hook and stays untouched, commands you add beside multivac’s stay in place, and the matcher on an entry is yours. The rule and the notice it prints are in hooks.

If CLAUDE.md already exists as a regular file, doors refuses to replace it and says what to do:

api: notice: CLAUDE.md exists as a regular file — merge it into AGENTS.md and remove it to get the symlink

Where the symlink cannot be created — Windows without developer mode, for one — doors makes no CLAUDE.md and prints symlink not permitted on this platform — read AGENTS.md directly, or enable developer mode to get CLAUDE.md.

cursor

file writtennothing — Cursor reads AGENTS.md natively
kindnative
detected byan existing .cursor/ directory
sourcehttps://cursor.com/docs/context/rules

Cursor reads AGENTS.md at the project root, so there is nothing to project: a rules file under .cursor/rules would be a second door that could disagree with the canonical one. The stub this target used to write is retired — see The four kinds for what one doors run does with it.

opencode

file writtennone — AGENTS.md is the integration
kindnative
detected byan existing opencode.json
sourcehttps://opencode.ai/docs/rules/

opencode reads AGENTS.md at the project root and up the tree. There is nothing to project: extra instruction files would go under instructions in opencode.json, which multivac does not own.

doors writes nothing for opencode. Declaring it changes what doctor reports, which is the canonical door on opencode’s behalf:

opencode: AGENTS.md ok (read natively)

The declaration also reaches the SDD tool’s own init: when spec-kit is declared and has not run yet, opencode as the first door it maps makes that init write .opencode/ (the scaffold).

If AGENTS.md is missing, doctor names multivac init . for a native entry, as it does for the canonical one. It never names init . for a symlink or stub entry: those get multivac doors when their own file is missing, the symlink points elsewhere, or the file lacks the managed block.

codex

file writtennone — AGENTS.md is the integration
kindnative
detected byan existing .codex/ directory
sourcehttps://learn.chatgpt.com/docs/agent-configuration/agents-md

Codex reads AGENTS.md from the git root down to the working directory, concatenated, nearest last. Nothing to project; its own config is .codex/config.toml, which multivac does not write.

windsurf

file writtennone — AGENTS.md is the integration
kindnative
detected byan existing .windsurf/ directory
sourcehttps://docs.windsurf.com/windsurf/cascade/agents-md

Cascade treats a root AGENTS.md as an always-on rule, and one in a subdirectory as a glob rule for that directory. The legacy .windsurf/rules/*.md mechanism still works but needs its own frontmatter, so multivac stays with the canonical file.

gemini

file writtenGEMINI.md
kindsymlink → AGENTS.md
detected byan existing .gemini/ directory
sourcehttps://geminicli.com/docs/cli/gemini-md/

Gemini CLI reads GEMINI.md by default. It can be pointed at AGENTS.md with context.fileName in .gemini/settings.json — but the symlink needs no settings file and no merge into someone else’s JSON, so that is what multivac projects.

copilot

file written.github/copilot-instructions.md
kindstub, no frontmatter
detected byan existing .github/copilot-instructions.md
sourcehttps://docs.github.com/en/copilot/reference/custom-instructions-support

Copilot reads AGENTS.md in some surfaces — the cloud agent, VS Code chat, Copilot CLI — but .github/copilot-instructions.md is the one path supported everywhere. It takes plain markdown with no frontmatter, so the stub is the managed block alone:

<!-- multivac:begin -->
Read `AGENTS.md` at the repo root — the multivac door: what is law here, where the brain lives. Run `multivac verify` before you commit.
<!-- multivac:end -->

Three artifact classes

Across all entries, multivac writes at most three kinds of thing — a door, hooks and a skill — and they differ by when the agent reads them: the table is on Distribution.

Only claude currently has all three. Git hooks are installed for every repo doors reaches, regardless of which harness entries you declared — see Hooks.

Detection

init probes for the detect path of each registry entry that has one and writes what it found, beyond what --provider already named, as a commented proposal, never as an enabled key:

doors: [agents]
# detected claude, cursor artifacts — to project the door there, use:
# doors: [agents, claude, cursor]

Detect, then ask. multivac never enables an integration because a directory happened to exist.