Windsurf / Devin Desktop (windsurf)
Set up this tool
Windsurf became Devin Desktop (2026-06). The tool keeps its windsurf name, so existing outputs.windsurf.* keys and x-windsurf meta still work.
First rule
Install the client using the official setup guide, then open your project in it.
Select windsurf in agnostic-ai.yaml, then follow First rule to add the shared commit-prefix rule and sync it. With the default paths, it reaches .devin/rules/conventional-commits.md.
The target ID stays windsurf. It writes the current .devin/ paths; Desktop and CLI differ in when scoped rules load.
For existing files, check what import windsurf reads before syncing.
Verify
Run agnostic-ai sync --check and inspect .devin/rules/conventional-commits.md for the commit-prefix rule. Open the project in Devin Desktop or CLI and ask for a commit subject for a small fix; it should use fix:.
Next, add a skill.
Shared memory
With builtins: [memory], a Devin CLI SessionStart hook adds shared memory indexes. It needs POSIX sh. Native memory stays available. See shared memory for which agnostic-ai it runs.
Devin does not document the hook's default shell, working directory, timeout, or Windows execution. Local modeled checks do not establish native recall or Windows support; the memory rule remains the fallback when hooks do not run.
Output
AGENTS.md # shared pointer body, plus the rules block when another tool adds it
.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 folder)
.devinignore # when ignore entries exist (indexing and agent access)
.windsurfignore # when ignore entries exist (legacy name, older builds)
.devin/mcp_config.json # when MCP entries exist
.devin/hooks.v1.json # when hook entries exist
.devin/config.json # when settings entries carry permission rules, or with repo-mode memory
Native mappings
Field mappings, discovery, and compatibility
Rules
Devin prefers .devin/rules/*.md and still reads .windsurf/rules/ (.windsurfrules is legacy). Set outputs.windsurf.rules-dir: .windsurf/rules to keep the old layout. Otherwise sync removes the files it wrote there earlier and leaves hand-written files alone.
Entry point
Devin also reads the root AGENTS.md, so sync writes the shared instructions that point to source specs there.
- When Codex or another tool that copies rules into
AGENTS.mdadds the## Rulesblock, Devin loads each always-on rule twice, because every rule also keeps its.devin/rules/file. - Legacy Cascade limits each workspace rule file to 12,000 characters and reads
AGENTS.mdthe same way. The Devin CLI rules reference gives no size limit. Sync keeps the per-rule files for compatibility. See target behavior.
Agents
Custom subagent profiles (subagents docs) with name, description, model, allowed-tools, and max-nesting. The body is the system prompt. A scoped agent is written flat, because Devin looks in sub-folders for rules only.
allowed-toolsmaps Claude-style names to Devin's (permissions reference, CLI changelog):Read/Grep/Glob/Bashtoread/grep/glob/exec, andWrite/Edittowrite/edit.writeneeds Devin CLI v3000.11.1 or later.mcp__<server>__<tool>is copied as written. Anything else is dropped with a sync note.- Devin also reads
.agents/agents/, flat<name>.mdand nested<name>/agent.md. Profiles written for Antigravity, Goose, or OpenHands can therefore appear in Devin withoutallowed-toolsrestrictions, even when their specs excludewindsurf. A matching.devin/agents/profile can also produce a duplicate name. Check the discovered profiles; see target behavior. x-windsurf.allowed-toolswrites Devin's names directly.x-windsurf.max-nestingsets nesting, which has no portable field.modelis copied as written.- Devin marks custom subagents as experimental, so the format may change.
Scoped rules
Go to <scope>/.devin/rules/<name>.md, because Desktop finds .devin/rules or .windsurf/rules in any sub-folder (Desktop rules reference). Devin CLI v3000.11.1 also finds rules in nested folders under .devin/rules/ and .windsurf/rules/ (CLI changelog). That is unconfirmed for Desktop, so the layout stays.
- With
outputs.windsurf.rules-dirset, the prefix follows it:<scope>/.windsurf/rules/<name>.md. - Devin CLI loads a sub-folder's rules once the agent touches files there. Desktop loads all of them at start. So the scope narrows what only the CLI sees.
Rule activation
A rule gets a trigger frontmatter key when it sets alwaysApply: false, or sets globs other than a catch-all such as **/* without alwaysApply.
| Rule has | Trigger written |
|---|---|
globs | trigger: glob (plus the pattern as written) |
description alone | trigger: model_decision |
| neither | trigger: manual |
An always-on rule has no frontmatter. Devin treats a file without frontmatter as always-on and adds its body to every message, so a description would do nothing. Devin's fifth value, agent, has no portable counterpart, so sync never writes it.
Skills
.agents/skills/<name>/SKILL.md. Devin reads .agents/skills/, .devin/skills/, and .windsurf/skills/. This adapter writes the first, so a skill is written once even when other tools also write .agents/skills/. Files next to the skill and file modes are kept.
disable-model-invocation: trueis written astriggers: [user], which Devin documents to "prevent the agent from invoking a skill on its own" while/skill-namestill runs it (Devin skills). So each command reaches Devin as a skill the user runs by name (tools without commands). When another target also writes the skills folder, the files must match, so the field is dropped with a note and commands are skipped; setoutputs.windsurf.skills-dir: .devin/skillsto keep them. Import readstriggers: [user]back asdisable-model-invocation: true, so after import every selected tool treats that skill as user-only. Deletingtriggersand importing with--overwritelifts the restriction for Windsurf only when no other enabled target writes that folder. A shared folder keeps the restriction, because its files omittriggersduring sync too.- Other native
triggerslive underx-windsurfin the spec and go back to top-level frontmatter on sync. This keeps[model]and combined invocation settings, and other tools never see a Devin-only field. Anx-windsurf.triggersvalue wins overdisable-model-invocation.
Ignore
Ignore specs are written as .devinignore and .windsurfignore, in gitignore syntax under a # header comment. Devin skips matched paths when indexing and cannot view, edit, or create files there (.devinignore docs).
- Devin still enforces the legacy
.windsurfignoreand.codeiumignore.outputs.windsurf.ignore-file: .codeiumignorewrites that name instead of.devinignore. .windsurfignoreis for older Windsurf builds, where only it blocked agent file access. Sync always writes it, unlessoutputs.windsurf.ignore-filenames it. That key moves only the main file.
MCP
Servers are merged into .devin/mcp_config.json under mcpServers, which Devin Local reads for project scope. Cascade, with its own MCP file, was removed in Devin Desktop v3.9.19 (changelog, Cascade MCP page).
- The schema uses
command/args/envfor stdio andurl/transport(httporsse)/headers/oauthClientId/oauthClientSecret/oauthResourcefor remote. The key istransport, nottype, so this tool has its own format. - Both accept
disabled, also toggled bydevin mcp enable|disable(seedisabledsupport by target). - A
type: wsspec writes no server and gets a sync note. Devin remote servers use onlyhttpandsse. - Cascade's personal file
~/.codeium/windsurf/mcp_config.jsonis not written, since agnostic-ai writes project files only.
Hooks
Hooks are merged into .devin/hooks.v1.json (hooks docs). Ten events: PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop, PostCompaction, PreCompact, PostCompact, SessionStart, SessionEnd. The hooks pages still list eight; PreCompact and PostCompact come from the v3000.11.92 changelog, which says they run before and after context compaction and that a failing hook does not block it.
- The file is the hooks object,
{"<Event>": [...]}, with nohookswrapper (unlike Claude Code, Codex, Gemini, and Qoder). - Per entry:
type,command, and optionaltimeout(seconds).typeis"command", or"prompt"for an LLM prompt. A hook spec gets"prompt"only through a hand-writtentype/promptMeta pair. matcheronPreCompactandPostCompactismanualorauto, the changelog's two triggers. The built-inhandoff-pre-compacthook does not reach Devin, because its payload is undocumented.matcheris a regex ontool_nameforPreToolUse,PostToolUse, andPermissionRequest. Devin CLI names tools in lowercase snake_case (exec,edit,read,write,apply_patch,grep,glob,webfetch, ...), so a Claude-style matcher matches nothing. Sync adds a sync note and does not rename it.agnostic-ai hook runruns them with Devin CLI's event data on an assumed shell, working directory, and timeout.--editis listed as not run, because Devin does not document the edit tools' input.
Settings
Portable permissions are merged into .devin/config.json, the committed project policy (permissions reference).
- Project configs accept only
permissions,read_config_from, andhooks(config file reference). Sync sets onlypermissions, so other keys, and other entries inside it, are kept. - In
allow,deny, andask, sync owns only the rules your specs write. A rule you add by hand stays, and a rule leaves with its spec. - An
x-windsurfblock on a settings spec is merged into the same file, which is how you setread_config_fromandhooks. Itspermissionsare merged per spec and per list with the translated rules. - A portable
modelis not written and gets a sync note, because Devin'sagentblock works only in your personal config. - Translation:
Read(glob)andWrite(glob)are copied as written,Edit(glob)becomesWrite(glob),Bash(prefix:*)becomesExec(prefix),WebFetch(pattern)becomesFetch(pattern), andmcp__<server>__<tool>is copied as written. - Bare tool names map to Devin's own (
Bashtoexec, and so on), the same way as inallowed-tools.WebSearchbecomesweb_search, accepted since Devin CLI v3000.10.21 (CLI changelog), and imports back. A bareWebFetchis dropped, becausewebfetchis not a permissions name. - Devin's
Execmatches only the start of a command, so an exactBash(...)rule without:*would match more commands. Onallowandaskthat would approve commands you did not list, so the rule is dropped with a sync note. Ondenyit blocks more, which is safe:Bash(rm -rf /)becomesExec(rm -rf /). A command deny wins over a broaderaskorallow(Devin CLI v3000.10.31). x-windsurf.permissionswrites Devin's rules directly and replaces that spec's portable ones.- With
memory.personal: repo, sync addsWrite(<folder>/**)topermissions.allow, so Devin CLI saves to the personal memory folder without asking. Your own rules stay. A committed settings kind (gitignore.commit) keeps the rule out. A folder path with pattern characters, including parentheses, gets no rule and a note; moveAGNOSTIC_AI_HOMEor setmemory.personal: checkout. - With
handofforhandoff-hookenabled in a linked worktree, sync addsWrite(<store>/**)for the main checkout's ignored handoff store. Existing memory and user rules stay. A main checkout path with pattern characters, including parentheses, gets no rule. For a tracked, hand-written, or unmanaged file sync skips only this grant and reports the permission to add by hand (once you add it, it stays); the rest of its output is unchanged.
Workflows
outputs.windsurf.workflows-dir writes nothing. It only prints a warning about moving to skills. .devin/agents/<name>.md is written either way.
Older paths and removed features
outputs.windsurf.workflows-dirwrote each agent as a Cascade Workflow. Devin Desktop v3.9.19 removed Cascade (changelog). Devin Local does not support Workflows and points users to skills (Devin Local limitations).- Sync removes the
.devin/rules/agent-<name>.mdfiles it wrote for each current agent, and the old.devin/rules/<scope>/<name>.mdfolders. - The MCP file moved here in v3000.3 (Local 3.6). Devin moves
mcpServersfrom the older.devin/config.jsonon startup, so the path works for both. - Devin CLI also reads a gitignored
.devin/mcp_config.local.jsonfor personal secrets, which agnostic-ai does not write. The generated.devin/mcp_config.jsonis in the managed.gitignoreblock by default (gitignore.enabled: true), so you do not need that split.
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 | when set, it only warns (see above) |
outputs.windsurf.ignore-file | .devinignore | moves only the main ignore file; the legacy .windsurfignore copy follows the same spec |
outputs.windsurf.mcp-file | .devin/mcp_config.json | |
outputs.windsurf.hooks-file | .devin/hooks.v1.json | |
outputs.windsurf.conf-file | .devin/config.json | project config; sync sets only permissions |
Import
agnostic-ai import windsurf reads:
- Rules: from
outputs.windsurf.rules-dirwhen set, else.devin/rules/, then legacy.windsurf/rules/, sorted by filename prefix. Scoped copies (<scope>/<rules-dir>/*.md) become scoped rules again, including scopes like.githuborvendor. It skips everynode_modules/. - Permissions: from
.devin/config.json.Exechas no exact-command form, so it imports asBash(<cmd>:*), notBash(<cmd>). - Skills: from
outputs.windsurf.skills-dirwhen set; otherwise.agents/skills/,.devin/skills/, then.windsurf/skills/, where the first same-name skill wins. Bundled assets and executable file modes are kept.triggersmove underx-windsurf. - Ignore: from
.devinignore, or.windsurfignorewhen the first is absent. When both files have different hand-written patterns, combine them in the imported ignore spec, in that order, before you sync.
Protected paths
Not enforced. Sync cannot write an edit-blocking rule or hook for this tool, so it prints a protected-path coverage note.