Windsurf / Devin Desktop (windsurf)
Output
AGENTS.md # shared pointer body (dedup with the other AGENTS.md consumers)
.devin/rules/<name>.md
<scope>/.devin/rules/<name>.md # one per scoped rule
.devin/agents/<name>.md # one per agent (custom subagent profile)
.agents/skills/<name>/SKILL.md # one folder per skill (shared tree with codex/amp/zed/crush/openhands)
.devinignore # when ignore entries exist
.devin/mcp_config.json # when MCP entries exist
.devin/hooks.v1.json # when hook entries exist
Windsurf became Devin Desktop (2026-06). Devin Desktop prefers .devin/rules/*.md and keeps .windsurf/rules/ as a backward-compat fallback (.windsurfrules is legacy), so rules now emit at the preferred path. The target keeps its windsurf name: existing outputs.windsurf.* keys and x-windsurf meta continue to work.
Set outputs.windsurf.rules-dir: .windsurf/rules to stay on the old layout; otherwise sync sweeps managed leftovers at the pre-rename path (hand-authored files survive). Devin also reads the cross-tool root AGENTS.md, so sync distributes the shared pointer body there (see the entry-point table above, #645).
Agents: one custom subagent profile per agent at
.devin/agents/<name>.md. Devin's docs call this "Custom subagents are defined as markdown files underagents/", project layout.devin/agents/, "Flat file:agents/<name>.md" (docs.devin.ai/cli/subagents). Frontmatter carriesname,description,model,allowed-tools, andmax-nesting; the body after the closing delimiter is the subagent's system prompt.- Agents used to flatten into
.devin/rules/agent-<name>.md, which reached the rules loader instead of the subagent loader and had no key for any of those five fields (target-audit 2026-08-27, #638). A managed copy at the old name is swept for every current agent. A scoped agent lands flat here, since Devin documents sub-directory discovery for rules only. allowed-toolstranslates agnostic-ai's Claude-style names onto the five Devin publishes as its complete set:read,edit,grep,glob,exec(docs.devin.ai/cli/reference/permissions).Read/Grep/Glob/Bashmap one-to-one ontoread/grep/glob/exec.WriteandEditboth collapse ontoedit, so an agent declaring onlyWritealso gains edit capability. Anmcp__<server>__<tool>name passes through untranslated. Anything else drops with a coverage note rather than shipping a name the vendor never documented.- Set
x-windsurf.allowed-toolsto write Devin's vocabulary directly, andx-windsurf.max-nestingfor the nesting override, which has no generic spec field.modelpasses through verbatim, since the vendor's own example pinsmodel: sonnet. The vendor caveat holds: "Custom subagents are experimental. The format, behavior, and configuration options may change in future releases."
- Agents used to flatten into
Scoped rules: a scoped rule lands at
<scope>/.devin/rules/<name>.md, not nested inside the root rules dir. Devin reads ".devin/rulesor.windsurf/rulesin any sub-directory of your workspace" (docs.devin.ai/desktop/cascade/memories) and globs each one single-level as.devin/rules/*.md(docs.devin.ai/cli/extensibility/rules). The old.devin/rules/<scope>/<name>.mdreached no documented discovery path (target-audit 2026-08-27, #628), so sync sweeps the old nested tree through the ledger.- With
outputs.windsurf.rules-dirset, the prefix follows it, so the legacy layout scopes to<scope>/.windsurf/rules/<name>.md. Devin CLI loads a sub-directory rules dir lazily, when the agent touches files there; Devin Desktop discovers every one of them at session start. The scope narrows what the CLI sees, not what Desktop sees.
- With
Rule activation: a rule that sets
alwaysApply: falsecarries atriggerfrontmatter key, the activation mode Devin reads.Rule has Emitted trigger globstrigger: glob(plus the pattern verbatim)descriptionalonetrigger: model_decisionneither trigger: manualAn always-on rule stays bare: Devin loads a file with no frontmatter as always-on, and its Always On mode puts the full body in the system prompt on every message, so a
descriptionhas no job there. Devin's fifth documented value,agent, has no counterpart in the spec format and is never emitted. Before this, no rule file carried frontmatter, soalwaysApply: falsewas silently promoted to always-on (#628).Skills: one folder per skill under
.agents/skills/<name>/SKILL.md. Devin documents.agents/skills/,.devin/skills/, and.windsurf/skills/as project paths. This adapter writes the shared first path so identical skills dedupe with Codex, Amp, Zed, Crush, OpenHands, Antigravity, Augment, and Kilo.- Native
triggersvalues move underx-windsurfin the source spec, then return to top-level frontmatter on sync. This preserves[user],[model], and combined invocation policy without leaking a Devin-only field to other targets. Sibling assets and modes survive every path.
- Native
Ignore: ignore specs emit as
.devinignore, gitignore syntax under a#provenance header: "you can add a.devinignorefile to your repo root, with the same syntax as .gitignore" (docs.devin.ai/desktop/context-awareness/windsurf-ignore). Devin Desktop also still respects the legacy.codeiumignorefilename and.windsurfignore, but this adapter only writes the current.devinignorepath; override viaoutputs.windsurf.ignore-fileto write one of the legacy names instead.MCP: merges into
.devin/mcp_config.jsonunder a rootmcpServersmap. This is the file Devin Local reads for project scope, not Cascade: Devin Desktop v3.9.19 removed Cascade, leaving Devin Local as the only agent (docs.devin.ai/desktop/changelog.md, target-audit 2026-09-09, #707). Cascade's own MCP page confirms this: "The MCP configuration on this page applies to the legacy Cascade agent only. The Devin Local agent ... configures MCP servers in the Devin CLI config files instead."- The schema carries
command/args/envfor local (stdio) servers, andurl/transport(httporsse)/headers/oauthClientId/oauthClientSecret/oauthResourcefor remote ones. Both acceptdisabled, whichdevin mcp enable|disablealso toggles on this file (seedisabledsupport by target). The field is spelledtransport, not thetypekey the sharedmcpServers-with-typebuilder writes for claude, cursor, and the rest, so this adapter holds its own schema. - The vendor documents that the file moved here in v3000.3 (Local 3.6);
mcpServersentries in the older.devin/config.jsonmigrate to this dedicated file automatically on startup, so this path is correct for both. Cascade's own MCP file,~/.codeium/windsurf/mcp_config.json, is user-tier and out of reach: agnostic-ai only emits project-tier files, so this is a new surface rather than a restored one. - Devin CLI also documents a third scope this adapter does not write:
.devin/mcp_config.local.json, saved there "by default" and "gitignored... use these for personal API keys" (docs.devin.ai/cli/extensibility/mcp/configuration), next to the project-committed.devin/mcp_config.jsonabove ("shared with your team via version control"). The vendor's local/committed split exists so a team can share servers while keeping personal secrets out of version control. agnostic-ai's own generated.devin/mcp_config.jsonis itself added to the managed.gitignoreblock by default (gitignore.enabled: true), so it is never committed either; the split's purpose does not apply to agnostic-ai's own output (target-audit 2026-08-11, #609). - A
type: wsspec emits no server and raises a coverage note because Devin documents onlyhttpandsseremote transports.
- The schema carries
Hooks: merge into
.devin/hooks.v1.json: "Create.devin/hooks.v1.jsonin your project" (docs.devin.ai/cli/extensibility/hooks/overview, #629). Eight events:PreToolUse,PostToolUse,PermissionRequest,UserPromptSubmit,Stop,PostCompaction,SessionStart,SessionEnd.- Unlike Claude Code, Codex, Gemini, and Qoder's shared
{"hooks": {...}}wrapper, "the hooks object is the entire file (no wrapper key needed)", so this adapter's document renders{"<Event>": [...]}at the top level with no wrapper. - Per entry:
type("command"runs a shell command, or"prompt"evaluates an LLM prompt instead; agnostic-ai's generic hook spec has nopromptfield, so that shape only reaches the file through a hand-authoredtype/promptMeta pair),command, and optionaltimeout(seconds).matcheris a regex on the event'stool_name, available onPreToolUse,PostToolUse, andPermissionRequest. - Devin CLI names its own tools in lowercase snake_case (
exec,edit,read,write,apply_patch,grep,glob,webfetch, ...), not Claude's PascalCase. A matcher carried over from a Claude spec parses as a valid regex and then matches nothing. That case surfaces a coverage note rather than a guessed rename, the same treatment OpenHands and Antigravity give their own mismatched vocabularies.
- Unlike Claude Code, Codex, Gemini, and Qoder's shared
outputs.windsurf.workflows-dir no longer emits anything. It used to write each agent as a Workflow, invokable in Cascade as /<name>, but Devin Desktop v3.9.19 ("September 8, 2026") removed Cascade, the only agent that ever read one: "Cascade has been removed. Devin Local is now the only agent available in Devin Desktop" (docs.devin.ai/desktop/changelog.md).
Devin Local does not pick the surface back up: "Workflows are not available with the Devin Local agent. Migrate your workflows to skills with the Devin: Open Cascade Migration Wizard command" (docs.devin.ai/desktop/devin-local, Limitations; target-audit 2026-09-09, #707). Setting the key now only prints a warning naming that migration path; the native .devin/agents/<name>.md emission happens either way, unaffected.
Config keys
| Key | Default | Notes |
|---|---|---|
outputs.windsurf.rules-dir | .devin/rules | |
outputs.windsurf.agents-dir | .devin/agents | |
outputs.windsurf.skills-dir | .agents/skills | |
outputs.windsurf.workflows-dir | empty | set, it only warns (see above) |
outputs.windsurf.ignore-file | .devinignore | |
outputs.windsurf.mcp-file | .devin/mcp_config.json | |
outputs.windsurf.hooks-file | .devin/hooks.v1.json |
Import
agnostic-ai import windsurf reads rules from .devin/rules/, falling back to legacy .windsurf/rules/, and reclassifies each file by filename prefix.
Skills import from .agents/skills/, .devin/skills/, then .windsurf/skills/. The first same-name skill wins. Bundled assets and executable modes survive, and native triggers move under x-windsurf as described under Skills.
Verify
- Install Devin Desktop from devin.ai (formerly windsurf.com).
- Check the tree:
ls .devin/rules/ .devin/agents/ .agents/skills/,grep "Generated by agnostic-ai" .devin/rules/*.mdfor the provenance header,test -f .agents/skills/*/SKILL.md,python -m json.tool .devin/mcp_config.json > /dev/nullwhen MCP specs exist,python -m json.tool .devin/hooks.v1.json > /dev/nullwhen hook specs exist. - Open the project. Devin Local loads every
.devin/rules/*.md; each appears in the Rules panel with no "failed to parse" warnings (docs.devin.ai/cli/extensibility/rules still documents this discovery path; only the agent that reads it changed with Cascade's removal, #707). Each.agents/skills/<name>/loads as a skill. With a scoped rule,<scope>/.devin/rules/<name>.mdappears in the same panel, and a rule withtrigger:frontmatter shows that activation mode rather than Always On. Ask Devin CLI to use a subagent by name (review this using the <name> subagent); each.devin/agents/<name>.mdprofile appears alongside the built-insubagent_exploreandsubagent_general, with no "profile skipped" warning. outputs.windsurf.workflows-dirhas nothing left to verify: no shipping Devin Desktop agent reads a Workflow file, so setting the key only prints a sync-time warning now (#707).- When ignore specs exist,
cat .devinignoreshows the concatenated patterns and indexing skips them. - Devin Local is the only agent in Devin Desktop (Cascade was removed in v3.9.19, #707); confirm each
mcpServers.<name>from.devin/mcp_config.jsonconnects, with a disabled spec showing as disabled. - Run
/hooksin Devin CLI to confirm each entry in.devin/hooks.v1.jsonloads, with the file listed as its source.