Random Thoughts

Developer tooling

Vendor-agnostic AI tooling — portable skills across coding agents

Tuesday, May 5, 2026

  • ai-assisted
  • #ai
  • #ai-agents
  • #vibecoding
  • #architecture
  • #developer-experience
  • #cursor
  • #claude
  • #vscode
  • #bash
  • #symlinks
Mid-century modern flat-design illustration on a soft cream-eggshell paper background, with the warm muted palette and friendly organic geometry of 1950s-1960s Eames-era graphic design — soft mustard yellow, dusty teal, warm terracotta, soft brick-red, with no shading or gradients, only flat color planes. Two abstract device-silhouettes sit at the top of the composition, side by side, each rendered as a different rounded geometric shape: the left one is a small flat dusty-teal box with two small offset rectangular features and a single thin terracotta stripe across its face; the right one is a slightly different rounded mustard form with a small flat starburst motif at its center. From each device, a curved organic flat-color line in cream flows downward across the page toward a central generous oval cloud-shape rendered in soft mustard yellow. Inside the central oval, three small flat shapes are nestled: a soft terracotta leaf, a small cream seed-pod, and a small dusty-teal abstracted bird. Where each device's curved line meets the oval, a small flat chain-link icon — drawn as two interlocking rounded loops in cream — sits at the entry point. Around the composition, scattered cream-colored mid-century starburst dots and small geometric specks add to the era's visual vocabulary. Strict flat color, friendly organic geometry, no readable text or letters anywhere in the composition.
Two devices, one source. The links let each device find its things in the place it expects, without duplicating any content.

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.md plus 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.

Mid-century modern flat-design illustration on a soft cream-eggshell paper background, with the warm muted palette and friendly organic geometry of 1950s-1960s Eames-era graphic design — soft mustard yellow, dusty teal, warm terracotta, brick-red, with no shading or gradients, only flat color planes. The composition is structured like a stylized family tree of folders. At the top center sits a single rounded organic folder-shape in dusty teal, with a small upper tab and a soft curved bottom edge. From it, three thin flat-color curved lines descend to three child folders arranged in a horizontal row below. The left child is a smaller terracotta rounded folder; the center child is a slightly larger soft mustard rounded folder (clearly the central source); the right child is a cream rounded folder. Inside the central mustard folder, three small flat sub-shapes are nestled in a row: a small soft terracotta leaf, a small cream seed-pod, and a small dusty-teal abstracted bird. From each of the left and right folders, three small flat chain-link icons (each drawn as two interlocking rounded loops in cream) emerge and reach across via thin flat-color curved lines that gracefully loop toward the corresponding sub-shapes inside the central mustard folder. Around the margins, scattered cream-colored mid-century starburst dots and small geometric specks. Strict flat color, friendly organic geometry, no readable text or letters anywhere in the composition.
Two device folders. One source. The arrows are links the file system follows transparently.

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.

Further reading