Vendor-agnostic AI tooling — portable skills across coding agents
I use one AI coding agent most days. I want to be able to switch to a different one tomorrow without losing the skill library I’ve built up. Not because I expect to switch — but because the day a tool changes its pricing, its API, its behavior, or simply gets surpassed by something better, I want my work to come with me.
The pattern that gets me there is small enough to fit in a paragraph and stable enough that I haven’t had to revisit it. One canonical source folder. Symlinks from each tool’s expected directory back to that source. The same skill, rule, and subagent files work in every agent without forks, copies, or sync scripts.
This is a walkthrough of the layout, the few small details that make it work, and the reasoning for choosing portability over per-tool optimization.
The problem this solves
Every AI coding agent expects its configuration in a slightly different place.
- Cursor reads from
.cursor/(rules in.cursor/rules/, skills in.cursor/skills/, subagents in.cursor/agents/). - Claude Code reads from a top-level
CLAUDE.mdplus a.claude/directory (with similar subfolders). - Other agents have their own conventions, none of them harmonized.
The naive approach is to have one set of files in one tool’s folder, and accept that switching tools means rewriting everything. That’s how most projects start.
The slightly less naive approach is to maintain two copies — one in .cursor/, one in .claude/ — and keep them in sync manually. That works for about three skills before it falls apart. By the tenth skill, the two folders have drifted, and you’re spending more time reconciling than writing.
The actually-good approach is the one I’ll describe: pick one source-of-truth location, and have every tool’s expected directory symlink to it. One file edit, every tool sees the change.
The layout
In any project where I want this portability, the layout looks like this:
my-project/
├── src/
│ ├── rules/ ← canonical rules
│ ├── skills/ ← canonical skills
│ └── agents/ ← canonical subagents
├── .cursor/
│ ├── rules → ../src/rules
│ ├── skills → ../src/skills
│ └── agents → ../src/agents
└── .claude/
├── rules → ../src/rules
├── skills → ../src/skills
└── agents → ../src/agents
src/ is where the actual files live. The .cursor/ and .claude/ directories contain only symlinks back to src/. When the agent reads .cursor/skills/journal-capture-progress/SKILL.md, it’s transparently following the symlink to src/skills/journal-capture-progress/SKILL.md.
That’s the whole pattern.
Why src/ and not the tool folders directly
A few choices in this layout are doing more work than they look.
src/ is generic. It’s not named after any specific AI coding agent. If a new tool comes along that expects its config in .someother/, I add three more symlinks pointing at src/ and the new tool starts working without any file moving.
src/ is on its own at the project root. It sits as a sibling of the tool-specific folders, not inside any of them. That’s deliberate. If I deleted .cursor/ tomorrow — say I switched away from Cursor — src/ is unaffected. The canonical content has no dependency on any one tool’s directory existing.
The symlinks are at the directory level, not the file level. Each tool folder has three symlinks (rules, skills, agents), not one symlink per skill file. This means I never have to update the symlinks when I add a new skill — the symlink is already pointing at the parent directory, so the new skill is visible immediately.
The symlinks are relative. ../src/rules, not /Users/me/projects/foo/src/rules. Relative symlinks survive moving the project to a different folder, cloning to a new machine, or sharing with a teammate. Absolute paths would break the moment the project moved.
These four small choices add up to a setup that’s almost completely passive — once configured, it doesn’t need maintenance.
Setting it up
The setup is a few ln commands once. No script needed. From the project root:
mkdir -p src/rules src/skills src/agents
mkdir -p .cursor .claude
ln -s ../src/rules .cursor/rules
ln -s ../src/skills .cursor/skills
ln -s ../src/agents .cursor/agents
ln -s ../src/rules .claude/rules
ln -s ../src/skills .claude/skills
ln -s ../src/agents .claude/agents
That’s the whole setup. Six symlinks; one canonical source.
If you want to make this repeatable across projects, a tiny shell function does the trick:
setup_portable_ai_config() {
mkdir -p src/rules src/skills src/agents
mkdir -p .cursor .claude
for kind in rules skills agents; do
[ -L ".cursor/$kind" ] || ln -s "../src/$kind" ".cursor/$kind"
[ -L ".claude/$kind" ] || ln -s "../src/$kind" ".claude/$kind"
done
}
Idempotent (the -L check skips creating the link if one already exists). Stdlib-only. Safe to rerun.
What the skill files look like
The skill files themselves are tool-agnostic.
A SKILL.md written under src/skills/journal-capture-progress/SKILL.md works identically whether Cursor or Claude Code reads it. The format both tools use is the same: YAML frontmatter with name and description, body in Markdown, optional script files in a scripts/ subfolder.
---
name: journal-capture-progress
description: Capture daily progress into journaling/entries/YYYY-MM-DD.md.
Ask the user about style and which sections to fill before running.
---
# Journal: capture progress
Take loose end-of-day notes and produce a structured journal entry...
There’s nothing in this file that says “this is for Cursor” or “this is for Claude Code.” It’s just a procedure document. The tool-specificity is in the directory layout, not in the content.
This was the part I had to be deliberate about. Early on, I wrote skills that referenced specific tool features — “use the Cursor agent panel to…”, “in Claude Code’s slash menu…”. Those skills broke when I tried them in the other tool. The fix was to write skills that describe the task, not the interface. The interface is the agent’s job to figure out; the skill describes what the human wants done.
A simple test: read the skill body without knowing which tool will run it. If you can still tell what should happen, the skill is portable. If the body assumes a specific tool’s UI or commands, it’s coupled.
What about tool-specific quirks?
The honest answer: every tool has them, and you have to decide what to do at the edges.
Some things to expect:
Frontmatter fields differ slightly. Cursor’s rules use globs: and alwaysApply:; some other tools use different keys. Most tools ignore frontmatter fields they don’t recognize, which means a rule with both sets of fields works in both tools, just with different behavior. I usually pick one tool’s convention as the primary and accept that the other tool will treat the rule slightly differently.
Subagent definition formats differ slightly. The fields and prompt structure vary. For subagents, I sometimes maintain two definition files — one per tool — because the differences are big enough that one file can’t cleanly serve both. They live in src/agents/cursor/ and src/agents/claude/, and the symlinks in each tool’s directory point at the right one.
Glob patterns can vary. Some tools’ glob matchers behave differently for edge cases (deeply nested paths, hidden files). When this matters, I write the glob in the broader form and accept slight overmatching in one tool.
The pattern: share what’s truly shared, accept small forks at the genuine edges, and never let the edges spread into the shared parts.
What I gain from this setup
The pattern was originally about portability — if I have to switch tools, my library comes with me. That’s the headline benefit and it’s real.
But a few unexpected benefits showed up after I’d been running this for a while.
Diff review is cleaner. When I add a skill, the diff is one new file under src/skills/.... Not three or four duplicated files in different directories. The intent of the change is unambiguous.
Onboarding new tools is fast. The first time I tried Claude Code on a project that already had src/, the only setup step was creating the .claude/ directory and adding three symlinks. Total time: under a minute. Every existing skill was immediately available.
Cross-tool experiments become possible. Sometimes I want to compare how the same skill performs in two different agents. With this layout, “the same skill” is literally the same file. Any difference in behavior is the model or the runtime, not a skill drift.
The library outlives the tools. This is the long-tail benefit. AI tools change fast. A library that’s tightly coupled to one tool ages with that tool. A library that’s vendor-agnostic ages with the concepts, which age much more slowly. The skill that helped me debug a checkpoint last year is the same skill helping me today, even though the runtime around it has changed twice.
What this isn’t a substitute for
A few honest caveats.
This pattern doesn’t mean “all skills work equally well in all tools.” Some agents are better at picking the right skill from a description; some are better at executing scripts; some have richer tool-call ecosystems. The skill files are portable; the experience of using them isn’t perfectly uniform across tools. Don’t expect a skill that’s snappy in one agent to be snappy in every other agent.
It also doesn’t mean “switching tools is free.” There’s still onboarding cost — relearning the agent’s interaction patterns, retraining yourself on its strengths and weaknesses. The portable library reduces a major friction point, but it doesn’t reduce all of them.
And it doesn’t mean “you should be tool-agnostic from day one.” When you’re starting out with AI coding agents, picking one tool and going deep in it is the right move. The portability concern matters once you have enough skills accumulated that not protecting them feels reckless. Until then, it’s premature optimization.
A small philosophical note
I want to be explicit about the underlying conviction.
AI coding agents are early. The space is moving fast. New entrants will come, existing tools will change shape, and there’s no reason to believe the dominant tool of 2026 will be the dominant tool of 2030. In that environment, the portable artifact is the durable one. Skills, rules, and subagent definitions written in plain Markdown, decoupled from any one tool’s idiosyncrasies, will outlive any specific runtime.
That’s a wager about how the industry evolves. It might be wrong. If a single tool turns out to dominate forever and absorb every alternative, my hedge will have been unnecessary.
I’d still rather make the bet. The cost of portability is six symlinks. The cost of being locked in is being locked in.