Skip to content

Agent Capability matrix

What works, what’s lossy, and what’s deferred — per agent, per component. This is the honest-expectations page for the beta. If a translation is lossy or skipped, agentsync says so at apply time in the translation report; nothing is dropped silently.

Legend

SymbolMeaning
native — full fidelity; the agent has the concept directly
projected — translated with documented, reported loss
skipped — no honest translation; logged in the apply report
not yet implemented (adapter is registered but no-op)

AgentStatus (beta)Notes
Claude Code✅ AdapterMCP, memory, skills, subagents, commands, and hooks. Canonical LSP servers are skipped because Claude Code only reads LSP servers from plugin manifests, not settings.json; installed plugins + marketplaces are captured by import (read from enabledPlugins / extraKnownMarketplaces); on apply, each plugin’s components project to Claude’s native paths and the enablement keys themselves are deliberately left untouched (PluginIngester is read-only — see the shared invariant below).
OpenCode✅ Adapter (some components projected/skipped)MCP, memory, skills, subagents, commands. Hooks and LSP are skipped with a warning. No native plugin/marketplace concept, so nothing for plugin import to capture; it still receives plugin-projected components (skills, MCP, …) on apply.
Codex CLI✅ Adapter (some components projected)MCP, memory, skills, subagents, slash commands, and hooks. MCP servers and hooks both merge into the TOML ~/.codex/config.toml (as [mcp_servers.*] / inline [hooks.*] tables — Codex’s documented equivalent to a separate hooks.json), so config.toml is the adapter’s single key-merge file and the user’s other keys (model, sandbox_mode, [plugins.*], …) are preserved; subagents project to Codex’s TOML agent format and slash commands to global-only custom prompts (both ◐). Codex has a native plugin system1 with enable-state in ~/.codex/config.toml as [plugins."<name>@<source>"] enabled = …, so the adapter implements PluginIngester: import codex:plugin captures that enable-state. The render never re-emits those tables (same invariant as Claude). Codex records no marketplace fetch source in a documented config location, so each plugin’s marketplace is resolved from agentsync’s own registered marketplaces (agentsync marketplace add <source> first), warning + skipping any it can’t — exactly how Claude’s auto-available built-in marketplace is handled.
Cursor✅ Adapter (some components projected)MCP, memory, skills, subagents, slash commands, and hooks. MCP lands in .cursor/mcp.json (the same mcpServers shape as Claude — full fidelity) and hooks in .cursor/hooks.json (Claude’s lifecycle events remapped to Cursor’s camelCase names; the required top-level version is asserted when missing, never overwriting a user-set value; events with no Cursor equivalent are dropped with a report). Memory projects to the repo-root AGENTS.md at project scope only — Cursor keeps user-level rules in app-local storage, so user-scope memory has no filesystem target (reported as a skip). Subagents project to .cursor/agents/<name>.md (Claude’s tools/color have no Cursor field and drop); slash commands to .cursor/commands/<name>.md (plain markdown — frontmatter drops). Only LSP is unsupported (Cursor has no LSP concept). Cursor has a native plugin system2, but where it records local enable-state is undocumented, so the adapter implements no PluginIngester yet (plugin discovery on import is deferred); it still receives plugin-projected components (skills, MCP, …) on apply like any agent.
Gemini CLI✅ Adapter (some components projected)MCP, memory, subagents, slash commands, and hooks. MCP and hooks both merge into .gemini/settings.json (MCP mcpServers with Gemini’s url/httpUrl transport split (Gemini expands $VAR/${VAR} in every settings.json string with no escape, so a resolved value containing $ is written verbatim and flagged with a report); hooks under hooks, the same nested shape as Claude, with events remapped to Gemini’s BeforeTool/AfterTool/… and unmapped ones dropped; consecutive handlers sharing an event+matcher render as one multi-handler group, an empty hook type is omitted rather than emitted as "type":"", and a matcher on an always-fire event is dropped with a report) — so settings.json is the adapter’s single key-merge file and the user’s other keys (theme, model, …) are preserved. Gemini reads settings.json as JSONC, so the merge is JSONC-tolerant (merge-jsonc-keys): a commented file’s foreign keys are preserved, with comments stripped on the first agentsync write (documented in Known limits). On import, a hook event agentsync can’t fully represent — a Gemini-only event (BeforeModel, …) or a handler with unmodeled fields (timeout, name, sequential) — is left uncaptured with a warning, so a later apply never owns an array it would lossily rewrite. Memory projects to GEMINI.md (~/.gemini/GEMINI.md at user scope, repo-root GEMINI.md at project scope — full fidelity). Slash commands become .gemini/commands/<name>.toml (description + prompt; argument-hint/allowed-tools drop; subdirectory namespaces like git/commit.toml/git:commit round-trip native→native, though the flat canonical source can’t carry the /-bearing name), subagents .gemini/agents/<name>.md (Claude’s tools vocabulary differs from Gemini’s, so it and color drop). Skills (Gemini uses extensions, not Agent Skills) and LSP have no Gemini concept and are skipped. Gemini has no native plugin enable-state agentsync models, so there is no PluginIngester; it still receives plugin-projected components on apply.
Continue✅ Adapter (some components projected)MCP, memory, and slash commands — projected as Continue “blocks” (one file per item under .continue/, so the adapter owns no shared key-merge file). MCP servers each become a .continue/mcpServers/<id>.yaml block (stdio command/args/env; remote streamable-http/sse + url with auth headers under requestOptions.headers; other requestOptions subkeys a native block carries — timeout, verifySsl, … — are preserved through import/apply via Extra passthrough — full fidelity). Memory projects to .continue/rules/agentsync.md, a frontmatter-less rule Continue always applies (so it behaves as persistent memory; byte-clean round-trip). Slash commands become .continue/prompts/<name>.md prompt blocks (name + description + invokable; argument-hint/allowed-tools drop). On import, only prompts with invokable: true are captured as slash commands (a plain prompt is left alone, with a warning — re-applying it would otherwise force it invokable). Skills, hooks, and LSP have no Continue concept, and Continue’s “agents” are top-level assistants rather than per-file subagents, so all four are skipped with a report. No PluginIngester (Continue composes blocks from its Hub + local files); it still receives plugin-projected components on apply.
Windsurf✅ Adapter (scope-asymmetric MCP)MCP, memory, and slash commands. MCP is global-only (~/.codeium/windsurf/mcp_config.json, JSON mcpServers; stdio command/args/env, remote serverUrl + headers — a native url key ingests but re-renders as serverUrl), so it renders at user scope and is skipped (reported) at project scope. Memory renders at both scopes: project → .windsurf/rules/agentsync.md carrying the documented trigger: always_on activation frontmatter (workspace rules declare their trigger in frontmatter; ingest strips it, so the canonical body round-trips byte-clean); user → the single global rules file ~/.codeium/windsurf/memories/global_rules.md (always-on, frontmatter-less, verbatim; Windsurf documents a 6,000-character limit it enforces itself). Commands render at both scopes as plain-markdown workflows invoked as /<name>: project .windsurf/workflows/, user ~/.codeium/windsurf/global_workflows/ (command frontmatter drops). Upstream now prefers `.devin/rules
Roo Code✅ Adapter (some components projected)MCP, memory, and slash commands — clean filesystem .roo/ paths (rulesync and ruler converged on these). MCP → .roo/mcp.json (project-level, mcpServers with explicit type: streamable-http/sse for remote + url/headers; merge-by-server-name preserves foreign servers). Roo’s global MCP lives in VS Code globalStorage (OS/editor-specific), which agentsync intentionally does not target — so user-scope MCP is reported as a skip. Memory → .roo/rules/agentsync.md (plain-markdown always-applied rule) and commands → .roo/commands/<name>.md (markdown + frontmatter — Roo keeps both description and argument-hint; only allowed-tools drops), both at user and project scope (~/.roo/ + <repo>/.roo/). Skills, hooks, and LSP have no Roo concept, and Roo’s “custom modes” are not per-file subagents, so all four are skipped. No PluginIngester; it still receives plugin-projected components on apply.
Cline✅ Adapter (scope-asymmetric)MCP, memory, and slash commands. MCP → ~/.cline/mcp.json at user scope — the Cline CLI’s clean config (mcpServers, transport inferred: stdio command/args/env, remote url + headers). Cline has no project MCP file, and its VS Code-extension MCP lives in OS/editor-specific globalStorage no config-sync tool writes, so project-scope MCP is reported as a skip. Memory → .clinerules/agentsync.md (plain markdown — Cline concatenates .clinerules/) and commands → .clinerules/workflows/<name>.md (plain markdown workflows invoked as /<name>.md; command frontmatter drops), both at project scope (Cline’s global rules and workflows live in ~/Documents/Cline/, a non-XDG app path agentsync deliberately does not target). Skills, subagents, hooks, and LSP have no Cline concept and are skipped. No PluginIngester; it still receives plugin-projected components on apply.
Breadth tier (22 agents)✅ Generic adapter (memory + MCP + skills)A long tail of agents supported by one data-driven generic adaptermemory (rules file) for all, MCP where the agent reads a JSON server-map agentsync can express (15 of 22), and Agent Skills where the agent natively scans a SKILL.md directory (18 of 22). Each is a verified spec, not a hand-written package; see the Breadth tier table for per-agent coverage. They flow through the normal apply/import pipeline (drift, secrets, capture), unlike a one-way rules dump.

The rule is the same for every adapter, present and future:

import reads the agent’s plugin enable-state for discovery; apply never writes it back. Apply fans out the plugin’s components, not the plugin itself.

agentsync’s Adapter interface has a Render (canonical → native components) and the optional PluginIngester extension is read-only (IngestPlugins — native plugin enable-state → canonical). There is no RenderPlugins. Each adapter handles the asymmetry the same way:

Adapterreads on import (PluginIngester)writes on apply (Render)
Claudesettings.json#/enabledPlugins, …#/extraKnownMarketplacescomponents only (skills, MCP, commands, …); enable-state keys left untouched
Codex~/.codex/config.toml [plugins."<name>@<source>"]components only (MCP, hooks, memory, skills, …); [plugins.*] left untouched
OpenCode— (no native plugin concept)components only (receives plugin-projected components like any user-authored component)
Cursor— (PluginIngester deferred; native enable-state location undocumented)components only (skills, MCP, commands, …)
Gemini— (no native plugin enable-state agentsync models; uses extensions)components only (MCP, memory, commands, subagents, hooks)
Continue— (no native plugin enable-state agentsync models; composes Hub + local blocks)components only (MCP, memory, commands)
Windsurf— (no native plugin enable-state agentsync models)components only (MCP, memory, commands)
Roo Code— (no native plugin enable-state agentsync models)components only (MCP, memory, commands)
Cline— (no native plugin enable-state agentsync models)components only (MCP, memory, commands)

Once a plugin’s components materialise at native paths (~/.claude/skills/<name>/, mcpServers in the agent’s config, ~/.codex/AGENTS.md, …), the consumer agent reads them through the same code path it uses for hand-authored components. Plugin attribution is purely agentsync’s internal bookkeeping. The write-back is omitted on purpose: it would pick a fight with the agent’s own /plugin disable UI (ping-pong on every apply), blur ownership between agentsync and the agent’s plugin manager, and double-install with the agent’s own per-plugin install dir. See architecture.md § PluginIngester (read-only).

Those native paths are flat, so a plugin’s subagents, skills, and commands are namespaced by their plugin: feature-dev’s code-reviewer lands as ~/.claude/agents/feature-dev-code-reviewer.md. Without it, two plugins shipping one component name would render two files at one path. Components you hand-author in ~/.agentsync/ are never renamed. MCP and LSP servers keep their ids — a same-id divergence across sources is refused rather than renamed apart, because it can be a silent endpoint hijack. See architecture.md § Plugin component namespacing.


Component support across agents.

ComponentClaudeOpenCodeCodexCursorGeminiContinueWindsurfRooCline
MCP server~/.claude.json (user) · .mcp.json (project)opencode.jsonconfig.toml.cursor/mcp.json.gemini/settings.json.continue/mcpServers/mcp_config.json (user-only).roo/mcp.json (project-only)~/.cline/mcp.json (user-only)
MemoryCLAUDE.mdAGENTS.md~/.codex/AGENTS.mdAGENTS.mdGEMINI.md.continue/rules/.windsurf/rules/ + global_rules.md.roo/rules/.clinerules/ (project-only)
Skill~/.claude/skills/X/ (dir)✓ shared .claude/skills/~/.agents/skills/.cursor/skills/✗ no skills concept✗ no skills concept✗ no skills concept✗ no skills concept✗ no skills concept
Subagent~/.claude/agents/X.md◐ frontmatter munged◐ markdown → TOML.cursor/agents/.gemini/agents/✗ top-level assistants only✗ no subagent concept✗ custom modes only✗ no subagent concept
Slash command~/.claude/commands/X.mdargument-hint dropped~/.codex/prompts/.cursor/commands/.gemini/commands/ (TOML, namespaced).continue/prompts/.windsurf/workflows/ + global_workflows/.roo/commands/ (allowed-tools dropped).clinerules/workflows/ (project-only)
Hook◐ JSON in settings (command hooks; other handler types/fields reported)✗ skip (JS/TS plugins)config.toml [hooks.*].cursor/hooks.jsonsettings.json hooks✗ no hook concept✗ no hook concept✗ no hook concept✗ no hook concept
LSP server✗ skip (Claude loads LSP only from plugin manifests)✗ skip (deferred)✗ no LSP concept✗ no LSP config✗ no LSP concept✗ no LSP concept✗ no LSP concept✗ no LSP concept✗ no LSP concept

The ◐/✗ cells are features, not bugs: agentsync refuses to invent a translation that would mislead you. Every ◐ and ✗ is printed in the apply report and queryable with agentsync plugin explain <plugin> --json.

The nine adapters above are deep, agent-specific packages. Beyond them, agentsync covers a long tail of agents through a single data-driven generic adapter (internal/adapter/generic): each agent is a verified Spec — a row in a table — rather than a hand-written package. The generic tier deliberately projects memory (the agent’s rules/instructions file), MCP where the agent reads a JSON server-map agentsync can express, and Agent Skills where the agent natively scans a SKILL.md directory; every other component is reported as a skip. Breadth agents run through the same apply/import pipeline as the deep ones, so they get drift detection, secret resolution, and capture — not a one-way rules dump.

MCP coverage (15 of 22). The MCP merge is JSONC-tolerant (hujson), so a commented settings file (Zed, Copilot’s .vscode/mcp.json, Amp) is parsed and its foreign keys and values preserved rather than clobbered. The comments themselves are NOT preserved: the file is re-emitted as plain JSON on the first agentsync write, with keys re-sorted (the original is backed up) — for Zed that file is the user’s whole editor settings.json, so expect the reformat (see Known limits). Per-agent dialect knobs cover the variance: top-level key (mcpServers / servers / mcp / context_servers / the flat namespaced amp.mcpServers), transport field (type / transport / inferred), stdio value (stdio / local), and remote URL key (url / httpUrl / serverUrl). The seven memory-only agents are the ones whose MCP is genuinely not a JSON server-map — array (Trae), YAML (Goose), TOML (OpenHands, Mistral), IDE app-storage (JetBrains, AugmentCode), or cloud-dashboard (Jules) — where the generic engine refuses to invent a shape and reports a skip.

MCP transport normalization. A type-keyed dialect (copilot, copilot-cli, factory, crush — those with a type/transport field) records sse explicitly, so an SSE server round-trips sse → sse. A transport-keyless dialect (antigravity, zed, warp, junie, kiro, amazonq, pi, amp — no transport field; transport is inferred from which url key is present) has nowhere to record it, so a canonical sse server is written with just its url and canonicalizes back as http if later captured via import/reconcile — the same acknowledged sse → http flip the deep OpenCode/Windsurf/Cline adapters carry (an apply-only flow is unaffected). The Gemini-lineage qwen dialect is the exception: it splits the two remote transports across two url keys (httpUrl = streamable HTTP, url = SSE), so it preserves sse.

Detection. Detect is informational only (it drives doctor’s per-agent line; it never gates apply), and most breadth agents are detected by a binary on PATH or a user-home config dir. Two are not auto-detectable and are only ever active when enabled explicitly: copilot (a VS Code extension with no DetectBin and no stable user-home marker — its config is per-project .github//.vscode/), and jetbrains (its .aiassistant/ is a project-relative rules dir, not a user-home install marker — JetBrains keeps its own state under ~/.config/JetBrains/…). Trae is detected: ~/.trae is Trae Desktop’s user-home marker.

Skills coverage (18 of 22). Agent Skills are the open agentskills.io spec — a skill is a directory (SKILL.md frontmatter + body, plus bundled scripts//references//assets/). Unlike MCP, the on-disk format is uniform across agents (there is no dialect to model); only the scanned directory varies, so the breadth tier reuses the same projection the deep adapters use (claude.SkillFileOps) — bundled files and executable bits survive byte-for-byte. Most agents read the cross-vendor .agents/skills/ convention (the same directory Codex targets, so the render pipeline dedupes the byte-identical ops rather than fighting over the path); a few scan only their own .<agent>/skills/ (Qwen, Junie, Kiro, Factory, Copilot’s .github/skills/). The four without skills are the ones that don’t natively scan a SKILL.md directory: Jules and Firebase Studio publish skills for other agents, Amazon Q consumes skills only through an MCP server (a different component), and OpenHands loads skills programmatically with no auto-scanned directory — each leaves Skills empty and reports a skip. Skills carry no secrets, so the projection is entirely off the secret-resolution path.

Every path below was cross-referenced against the agent’s upstream docs and prior-art config-sync tools (ruler, rulesync) before inclusion. A scope or component with no verified target is left out (and reported as a skip), never guessed.

AgentMemory (rules)MCPSkills (SKILL.md dir)
ampAGENTS.md · ~/.config/amp/AGENTS.md~/.config/amp/settings.json (namespaced amp.mcpServers key).agents/skills/ · ~/.config/agents/skills/
goose.goosehints✗ YAML ~/.config/goose/config.yaml.agents/skills/ (+ user)
qwenQWEN.md · ~/.qwen/QWEN.md.qwen/settings.json (dual-URL split: httpUrl = streamable HTTP, url = SSE).qwen/skills/ (+ user)
warpWARP.md.warp/.mcp.json · ~/.warp/.mcp.json.agents/skills/ (+ user)
julesAGENTS.md✗ dashboard-only (cloud)✗ publishes skills for other agents
junieAGENTS.md (project; JetBrains documents no global guidelines file).junie/mcp/mcp.json (+ user).junie/skills/ (+ user)
openhandsAGENTS.md✗ TOML [mcp] arrays✗ programmatic load (no auto-scanned dir)
amazonq.amazonq/rules/.amazonq/mcp.json · ~/.aws/amazonq/mcp.json✗ skills only via an MCP server
zedAGENTS.md · ~/.config/zed/AGENTS.md✓ settings.json context_servers.agents/skills/ (+ user)
kilocode.kilocode/rules/.kilocode/mcp.json (project).agents/skills/ · ~/.kilo/skills/
kiro.kiro/steering/ (+ user).kiro/settings/mcp.json (+ user).kiro/skills/ (+ user)
trae.trae/rules/project_rules.md✗ non-standard array shape.agents/skills/ (project only)
jetbrains.aiassistant/rules/✗ IDE app-storage.agents/skills/ (project; via configured agent)
firebase.idx/airules.md.idx/mcp.json (project)✗ publishes skills for other agents
antigravityAGENTS.md~/.gemini/config/mcp_config.json (remote key serverUrl; the shared IDE+CLI path is codelab-sourced — older installs read ~/.gemini/antigravity-cli/mcp_config.json, where this write is a no-op; re-verify when Antigravity’s docs firm up. Lives inside ~/.gemini/, the deep Gemini adapter’s directory, but a different file — no collision).agents/skills/ (project; global path disputed, omitted)
augmentcode.augment/rules/ (+ user)✗ IDE app-storage.agents/skills/ (+ user)
copilot.github/copilot-instructions.md.vscode/mcp.json (servers key, type).github/skills/ · ~/.copilot/skills/
copilot-cliAGENTS.md~/.copilot/mcp-config.json (type, stdio=“local”).agents/skills/ (+ user)
crushAGENTS.md✓ crush.json mcp key (+ user).agents/skills/ · ~/.config/crush/skills/
factoryAGENTS.md.factory/mcp.json · ~/.factory/mcp.json (type).factory/skills/ (+ user)
piAGENTS.md · ~/.pi/agent/AGENTS.md~/.pi/agent/mcp.json.agents/skills/ (+ user)
mistralAGENTS.md✗ TOML .vibe/config.toml.agents/skills/ · ~/.vibe/skills/

Deliberate exclusions: Aider (no native MCP; memory only via an .aider.conf.yml read: pointer — needs a content+config-pointer adapter, out of the generic tier) and Firebender (single-sourced JSON-manifest model) are not included pending a faithful implementation. Replit/Rovo Dev are likewise deferred (cloud / config-string memory).

Every apply and plugin explain ends with a coverage report — per plugin, per agent — using the same three marks (check does not print one; it only schema-lints the source and validates secrets):

plugin: atlassian@anthropic
claude ✓ full (1 mcp, 5 commands)
opencode ◐ partial (1 mcp; 5 commands → projected)
  • ✓ native — the component landed with full fidelity.
  • ◐ projected — it landed, but with the documented loss below (e.g. an OpenCode slash command drops its argument-hint).
  • ✗ skipped — no honest translation exists, so nothing was written; the skip is logged, never silent.

Every projected (◐) cell above is a deliberate, reported translation. Here’s what doesn’t carry over.

Note that MCP/LSP capture is not field-lossy: native server fields agentsync doesn’t model (e.g. timeout, disabled, cwd) are preserved verbatim through a passthrough [server.extra] table on import/reconcile and re-rendered on apply, rather than dropped. (Extra is verbatim only — ${secret:…} there is written literally, never resolved.)

Claude

  • Hook — agentsync models only command hooks (matcher + command), which round-trip losslessly. A Claude hook that uses a non-command handler type, or carries a field agentsync doesn’t model (e.g. timeout), is reported, not silently dropped: on render a non-command handler surfaces as a Skip and is never emitted (so agentsync’s owned-array write can’t clobber a native handler), and on ingest the whole event is left uncaptured with a warning. If agentsync previously captured the event (it was clean then, enriched natively since), import also retires the now-stale canonical hooks/<event>.toml — so the next apply never owns, and therefore never rewrites, your richer native entry. Canonical hooks are shared, so a retirement hands the event back to every hook-rendering agent at that scope (each native entry frozen as-is); a structurally-malformed native shape warns but never triggers retirement. This guard-and-warn behavior is anchored by the artifact-anchored TestIngest_HookArtifactRoundTrip (internal/adapter/claude).

OpenCode

  • Subagent — OpenCode-supported frontmatter keys agentsync doesn’t model explicitly (temperature, top_p, permission, disable, prompt, steps) pass through verbatim; the agent mode (primary/all/subagent) is preserved across an import/reconcileapply round-trip (a native primary/all agent is no longer demoted to subagent; a Claude-shaped subagent with no mode still defaults to subagent). Claude-only keys with no OpenCode home — tools (its allowlist has no clean projection onto OpenCode’s permission model) and color — are dropped with a reported Skip, never silently.
  • Slash command — OpenCode-supported command keys agentsync doesn’t model (agent, subtask) pass through verbatim; Claude’s argument-hint has no OpenCode field and is dropped with a reported Skip (there’s no command-level allowed-tools; scoping is per-agent instead). No frontmatter key is dropped silently — every one is either rendered or surfaced as a Skip.
  • Ingest ownershipimport captures only agentsync-owned agents/commands from the shared agents//commands/ directories (ownership is read from apply state); a user’s hand-authored files alongside them are left untouched and never pulled into the canonical source.

Codex

  • Subagent — Codex custom agents are TOML, not markdown: the prose body becomes developer_instructions, and the name (required by Codex), description, and model frontmatter round-trip in both directions — a frontmatter name that deliberately diverges from the file stem survives an applyimport/reconcile round-trip, because ingest re-populates the frontmatter name from the TOML name rather than silently rederiving it from the filename. Two subagents whose effective names collide are refused at render with an error naming both, not silently merged. There is no per-agent tools allowlist (tool scoping is only expressible via [mcp_servers] / skill toggles), so tools (and color) are dropped with a reported skip. Codex-only agent keys agentsync has no canonical source for (model_reasoning_effort, sandbox_mode, nickname_candidates, …) are simply not emitted.
  • Slash command — maps to Codex custom prompts (~/.codex/prompts/*.md), which do preserve description + argument-hint, but they’re global-only, so a project-scope command has no target and is skipped; they also can’t be namespaced in subdirectories, and the feature is deprecated in favor of skills.
  • Hook — Codex mirrors Claude’s declarative hook schema as inline [hooks.*] tables in ~/.codex/config.toml (Codex reads hooks from either a hooks.json or inline [hooks] tables; agentsync uses the config.toml form so the adapter has a single key-merge file), but recognizes a fixed set of lifecycle events (SessionStart, SubagentStart, PreToolUse, PermissionRequest, PostToolUse, Pre/PostCompact, UserPromptSubmit, SubagentStop, Stop); Claude events outside that set (e.g. SessionEnd, Notification) have no target and drop. Ingest has the same guard-and-warn posture as the other hook adapters — an event whose tables carry fields agentsync doesn’t model is refused whole, never captured lossily — and the adapter implements HookIngestGuard, so a natively-enriched event triggers import’s stale-hook retirement. One deliberate divergence: a non-command handler type is not refused — Codex parses-and-skips unknown types at runtime and agentsync re-renders the type verbatim (with a reported reduced Skip), so it round-trips losslessly.

Cursor

  • Memory — project memory lands as AGENTS.md, but Cursor keeps user-level rules in app-local storage (not the filesystem), so user-scope memory has no projection target (it is reported as a skip).
  • Subagent — markdown under .cursor/agents/. Cursor recognizes name/description/model/readonly/is_background; Claude’s tools allowlist and color have no Cursor field and are dropped with a report.
  • Slash command — Cursor commands (.cursor/commands/*.md) are plain markdown with no frontmatter, so argument-hint, description, and allowed-tools are all dropped — only the prompt body survives.
  • Hook — Cursor uses a declarative .cursor/hooks.json, but with its own camelCase event names and a flat entry shape, so Claude’s events are remapped (PreToolUsepreToolUse, UserPromptSubmitbeforeSubmitPrompt, …) and any with no Cursor equivalent (e.g. Notification, PostCompact) are dropped with a report. The required top-level version is asserted if missing and a user-set value is preserved. agentsync models only Cursor’s command hooks and only the command/matcher/type entry fields; on import, a Cursor-native event agentsync can’t render (afterFileEdit, beforeShellExecution, …) — or an event containing an entry it can’t fully represent (a prompt-type hook, or fields like timeout/failClosed) — is left uncaptured with a warning. And because Cursor implements HookIngestGuard, an event that was captured while clean and later enriched natively triggers import’s stale-hook retirement (reported under its canonical name — preToolUse retires hooks/PreToolUse.toml), so a later apply never keeps ownership of an array it would lossily rewrite; a structurally-malformed hooks.json shape warns but never triggers retirement.

Gemini CLI

  • Subagent — markdown under .gemini/agents/. Captured frontmatter is passed through verbatim, so Gemini’s own native fields — kind/temperature/max_turns/ timeout_mins/mcpServers (and any other native key) — survive apply instead of being stripped: a key that import/reconcile captured into canonical is re-emitted on the next render, not clipped by the whole-file replace. Only Claude’s tools list (its tool vocabulary differs from Gemini’s — read_file/grep_search, not Read/Grep, so copying it verbatim would name tools Gemini doesn’t have) and color (no Gemini agent field) are dropped with a report, and the reported Skip lists only those keys. name is defaulted to the filename when absent (Gemini requires it). The passthrough is a deliberate secret-machinery exception (like the MCP extra passthrough): subagent frontmatter — including a command/env-shaped mcpServers block — is never secret-resolved and never re-referenced, so a ${secret:…} written there stays a literal string, and a live secret hand-pasted into native frontmatter is captured verbatim like any other hand-authored text component — keep your dotfiles repo private.
  • Slash command — Gemini commands are TOML (.gemini/commands/*.toml) with description + prompt. The body becomes prompt and description carries over; argument-hint/allowed-tools have no Gemini field and drop. Gemini’s argument placeholder is {{args}} (not Claude’s $ARGUMENTS/$1); the body is written verbatim, so placeholder syntax is not auto-translated. Gemini namespaces commands by subdirectory — commands/git/commit.toml is /git:commit — so ingest walks the tree recursively and encodes the subdir path into Command.Name as a forward-slash relpath (git/commit), which render inverts back to the subdirectory file (byte-stable native→native round-trip). This is scoped to the adapter round-trip: the flat canonical loader/writer (source.ValidateComponentID rejects /) can’t carry a namespaced name, so a namespaced command does not survive a full import→canonical-source→apply cycle — a bulk import skips it with a warning (it never aborts the rest of the run, and a named single-item import fails loudly), and the namespace is preserved on disk, never silently truncated.
  • Hook — Gemini hooks live in settings.json under hooks in the same nested shape as Claude, so only the event name is remapped (PreToolUseBeforeTool, PostToolUseAfterTool, UserPromptSubmitBeforeAgent, StopAfterAgent, PreCompactPreCompress); events with no Gemini equivalent (SubagentStart/ SubagentStop/PostCompact/PermissionRequest) are dropped with a report. The on-disk group shape is preserved: consecutive canonical Hooks sharing an (event, matcher) coalesce into ONE group with a multi-handler hooks array (the faithful inverse of ingest flattening it), so an ingest→render round-trip is idempotent instead of exploding a hand-authored multi-handler group into N single-handler ones. An empty hook type is omitted (never a stray "type":""), and because only the tool events BeforeTool/AfterTool accept a matcher while every lifecycle event (BeforeAgent/AfterAgent/Session*/PreCompress/ Notification) is always-fire, a non-empty matcher on an always-fire event is dropped with a reported SkipReduced rather than emitted where Gemini ignores it. Ingest has the same guard-and-warn posture as Claude’s — an event carrying unmodeled fields (sequential, name, timeout) or a non-command handler is refused whole, never captured lossily — and the adapter implements HookIngestGuard, so a natively-enriched event triggers import’s stale-hook retirement (reported under its canonical name) exactly as it does for Claude.

Continue

  • Slash command — Continue prompt blocks (.continue/prompts/*.md) carry a name + optional description + invokable; the body becomes the prompt. argument-hint/allowed-tools have no Continue field and drop.
  • Subagent / Hook / Skill / LSP — Continue has no per-file subagent (its “agents” are top-level assistants), no declarative hook concept, no Agent Skills (it uses Hub blocks/extensions), and no LSP config, so each is skipped with a report rather than given a misleading translation.

Windsurf

  • Scope split — Windsurf’s MCP config is global-only (~/.codeium/windsurf/mcp_config.json): MCP renders at user scope only (skipped + reported at project scope). Memory and commands render at both scopes (project .windsurf/ tree; user ~/.codeium/windsurf/).
  • Memory — at project scope, .windsurf/rules/agentsync.md with the documented trigger: always_on activation frontmatter (workspace rules declare their trigger in frontmatter — a frontmatter-less rule’s activation is undefined); ingest strips any leading trigger: frontmatter fence so a re-apply never double-fences the rule. The exact agentsync always_on block round-trips byte-clean; a hand-changed non-always_on trigger is stripped and warned (its activation mode has no canonical home, so it is not captured). At user scope, the single global rules file ~/.codeium/windsurf/memories/global_rules.md — always-on and frontmatter-less, written verbatim. It is whole-file owned like Claude’s ~/.claude/CLAUDE.md (a pre-existing hand-authored copy is backed up on first apply), and Windsurf enforces its documented 6,000-character limit itself. Workspace rules (.windsurf/rules/) have a separate, larger documented limit — 12,000 characters per file — which agentsync also leaves to Windsurf: it writes the rule body verbatim and neither truncates nor flags it.
  • Slash command — Windsurf workflows are plain markdown invoked as /<name> (project .windsurf/workflows/*.md, user ~/.codeium/windsurf/global_workflows/*.md), so command description/ argument-hint/allowed-tools frontmatter drops — only the body survives. Windsurf documents a 12,000-character-per-file limit for workflows; agentsync writes the body verbatim and leaves that enforcement to Windsurf.
  • MCP remote — Windsurf does not distinguish SSE vs streamable-HTTP in the config (just serverUrl), so a canonical sse server normalizes to http if later captured back via import/reconcile. A hand-authored server using the alternate native url key (which ingest also reads) is likewise canonicalized to serverUrl on re-render — benign, since Windsurf/Devin accepts both keys.

Roo Code

  • MCP scope.roo/mcp.json is project-level; Roo’s global MCP lives in VS Code globalStorage (OS/editor-specific), which agentsync does not target, so user-scope MCP is reported as a skip. (rulesync and ruler make the same call.)
  • MCP remote / transport normalization — Roo records a remote server’s transport in an explicit type (streamable-http for HTTP, sse for SSE), so a captured sse server round-trips sse → sse losslessly. A canonical server with NO transport — type = "" with a url and no command — is treated as remote and rendered as type: streamable-http, so it normalizes to http on capture and is stable thereafter (http → streamable-http → http); a hand-authored native entry carrying only a url canonicalizes the same way. So the rule, in both directions, is “a url-bearing, command-less server with no explicit transport is remote http”. stdio carries no type key.
  • Slash command.roo/commands/*.md keep description AND argument-hint (Roo supports both); only allowed-tools (and any other unmodeled key) drops.
  • Subagent / Hook / Skill / LSP — Roo’s “custom modes” are not per-file subagents, and Roo has no hook, Agent-Skills, or LSP concept, so each is skipped with a report.

Cline

  • Scope split — Cline has no project MCP file (its VS Code-extension MCP is OS/editor-specific globalStorage no tool writes), but its CLI reads a clean ~/.cline/mcp.json, so MCP renders at user scope (skipped + reported at project scope). Memory + commands render at project scope (.clinerules/); Cline’s global rules live in ~/Documents/Cline/, a non-XDG app path agentsync does not target, so user-scope memory/commands are skipped + reported.
  • Memory — a plain .clinerules/agentsync.md rule (Cline concatenates .clinerules/ markdown), byte-clean round-trip.
  • Slash command — Cline workflows (.clinerules/workflows/*.md) are plain markdown invoked as /<name>, so command frontmatter drops — only the body survives. Each rendered workflow carries a leading reversible ownership marker (an inert HTML comment), so import/reconcile capture only agentsync-owned workflows and leave a human-authored workflow in that directory untouched — the same ownership scoping memory gets from its fixed agentsync.md filename.
  • MCP remote / transport normalization — Cline infers transport from which keys are present (there is no type field): stdio keeps command/args/env, a remote server uses url + headers. Because no transport is recorded, a canonical sse server normalizes to http if later captured back via import/reconcile (an apply-only flow is unaffected).
  • Hooks — OpenCode hooks are JS/TS plugins that subscribe to events, not declarative shell commands like Claude’s. There is no mechanical translation; hand-author a small plugin if you need a hook on OpenCode.
  • LSP — OpenCode does have a native lsp config, but agentsync defers projecting LSP servers beyond Claude to a later release; today you’ll see lsp server X skipped in the report on non-Claude agents.

OpenCode’s native MCP schema differs from the canonical model, so the adapter translates rather than copying fields verbatim:

  • transport — canonical type = "stdio" → OpenCode "type": "local"; "http"/"sse""type": "remote". OpenCode has no separate SSE transport, so a sse server normalizes to http if it is later captured back via import/reconcile (an apply-only flow is unaffected).
  • command — canonical command + args are flattened into OpenCode’s single command string array (["npx", "-y", "pkg"]), and split back on ingest.
  • environment — canonical env is written under OpenCode’s environment key (not env).
  • remoteurl and headers carry through unchanged.

Full-fidelity projections (✓ with a transform)

Section titled “Full-fidelity projections (✓ with a transform)”

A few ✓ cells still change shape on the way out — same content, no loss:

  • Codex MCP — Claude’s JSON mcpServers become TOML [mcp_servers.X] (stdio and streamable-HTTP both representable).
  • Cursor MCP.cursor/mcp.json mcpServers. A stdio server matches Claude’s shape (type/command/args/env, down to ${env:…} references); a remote server follows Cursor’s documented remote schema — url + headers with no type key — so agentsync does not write a type on remote servers, and a remote server’s transport label normalizes away on capture (Cursor infers “remote” from the url; the url/headers still round-trip). Ingest stays tolerant of a type key on read. (Open upstream question #164: whether real Cursor rejects or silently ignores an unknown remote type — dropping it is the conservative, spec-matching choice pending that test.)
  • Gemini MCP.gemini/settings.json mcpServers: stdio keeps command/args/env; a remote server uses Gemini’s transport split — url for SSE, httpUrl for HTTP streaming — both round-tripping the canonical type. Gemini applies $VAR/${VAR}/${VAR:-default} variable expansion to every settings.json string (env, headers, and url/httpUrl) and offers no escape for a literal $ (upstream envVarResolver.ts; there is no $$). agentsync therefore writes a resolved value verbatim — a fabricated escape would itself corrupt it — and reports a SkipReduced naming any env/headers/url value whose $ Gemini would expand, so the (data-dependent) read-time corruption is surfaced, not silent. This is value corruption at the destination, not a cleartext-persistence leak: the canonical source still holds the ${secret:…} reference.
  • Continue MCP — one .continue/mcpServers/<id>.yaml block per server: stdio keeps command/args/env; a remote server uses Continue’s streamable-http/sse type + url, with auth headers under requestOptions.headers. The block’s required name/version/schema header round-trips — a hand-authored non-default version/schema is preserved (via reserved Extra keys) rather than regenerated to the 0.0.1/v1 defaults. A canonical server carrying both a command and a url with no explicit type is ambiguous (a Continue block is single-transport): agentsync renders it as stdio (command wins) and reports the dropped url via a reduced Skip rather than dropping it silently; an explicitly-typed stdio server that also carries a url drops the unused url with the same report.
  • Continue memory — the body lands as .continue/rules/agentsync.md, a frontmatter-less rule Continue always applies (byte-clean round-trip).
  • Windsurf MCP~/.codeium/windsurf/mcp_config.json mcpServers: stdio command/args/env; remote serverUrl + headers (a native url key also ingests, but re-renders as serverUrl — both are accepted upstream).
  • Roo MCP.roo/mcp.json mcpServers: stdio command/args/env; remote explicit type: streamable-http/sse + url + headers (merge-by-server-name preserves the user’s own servers).
  • Roo memory — the body lands as .roo/rules/agentsync.md, a plain rule Roo applies recursively (byte-clean round-trip), at user or project scope.
  • Cline MCP — the Cline CLI’s ~/.cline/mcp.json mcpServers: stdio command/args/env; remote url + headers (transport inferred, no type key).
  • Codex & Gemini memory — the same markdown lands at ~/.codex/AGENTS.md / ~/.gemini/GEMINI.md (repo-root GEMINI.md at project scope).
  • Skills (Codex, Cursor & 18 breadth-tier agents) — the same skill directory per the Agent Skills spec: SKILL.md (name + description) plus any bundled scripts//references//assets/ and nested files, all carried verbatim (binary included, executable bit preserved) on apply, import, and reconcile — agentsync is not lossy for anything but the directory itself. Removing a skill (or one bundled file) from the source reclaims it from each destination on the next apply (drifted files backed up first; empty dirs pruned). Codex installs them under ~/.agents/skills/ (enabled by default — no feature flag), Cursor under .cursor/skills/, and both also read the shared .claude/skills/. The breadth tier projects the identical directory (via the shared claude.SkillFileOps) to each agent’s verified skills path — most to the cross-vendor .agents/skills/, which dedupes byte-for-byte against Codex; see the Breadth tier table for the per-agent path.

You control fan-out explicitly:

  • agents = ["claude", "opencode"] on an MCP server or plugin entry → fan out only to those agents.
  • native_agents = ["claude"] on a plugin entry → those agents install the plugin through their OWN plugin manager, so agentsync does not project its components there. Without it you get two of every skill, subagent and command and every hook fires twice, because apply never disables a plugin inside another tool’s plugin manager. import offers to record this for you.

(A per-component [plugin.overrides.<agent>] skip was specced but is not wired in v1 — the projector does not consult it. Use the keys above.)


These are documented trade-offs, not regressions. The authoritative list lives in the README; the highlights:

  • Comment preservation — comments in mcp/*.toml, in opencode.json, in Gemini’s .gemini/settings.json, in the breadth tier’s JSONC settings files (Zed/Amp settings.json, Copilot .vscode/mcp.json), and in Codex’s ~/.codex/config.toml are not preserved across a write-back/import round-trip; the JSONC files are re-emitted as plain JSON (foreign keys preserved, comments stripped, original backed up).
  • Owned-key hand-edits — if you hand-edit an agentsync-owned key in a shared file, the next apply overwrites it with no backup (agentsync considers it its own). Run agentsync reconcile first to capture the edit.
  • Insecure sourceshttp:// and git:// plugin/marketplace sources are rejected by default (MITM protection); override with AGENTSYNC_ALLOW_INSECURE_URLS=1.
  • Symlinked destinations are rejected by default; override with AGENTSYNC_ALLOW_SYMLINK_DEST=1.
  • Planned / deferred: Aider and Firebender (see Breadth tier § “Deliberate exclusions”).

See the user guide to put this into practice.

  1. Codex plugin system: developers.openai.com/codex/plugins. Enable-state lives in ~/.codex/config.toml under [plugins."<name>@<source>"] tables (an enabled bool) — the same name@source shape as Claude’s enabledPlugins, so a future Codex PluginIngester parses those tables (plus its marketplace sources) into the same NativeMarketplace / NativePlugin descriptors import already consumes.

  2. Cursor plugin system: cursor.com/docs/reference/plugins. Plugins bundle rules, skills (SKILL.md), agents, commands, hooks, and MCP servers; the manifest is .cursor-plugin/plugin.json and multi-plugin repos use .cursor-plugin/marketplace.json — nearly identical to Claude’s .claude-plugin/*, so agentsync’s projection layer largely transfers. Not yet documented: where Cursor records which plugins are installed/enabled locally (the enabledPlugins-equivalent a PluginIngester would read); given Cursor keeps user rules in app-local storage, this may not be a plain config file. The Cursor adapter therefore ships without a PluginIngester (plugin discovery on import is deferred until that location is documented); it still fans out plugin components on apply like every other adapter.