# Re-use your skills across all your coding agents

> Source: <https://blog.herlein.com/post/skills-mapper-sm/>
> Published: 2026-08-23 08:00:01+00:00

I use more than one coding agent. Not because I’m indecisive — because I’m testing different agents - and learning how they work - and the field is moving fast. Seems like a new one every week! But every single one of them wants skills in its own directory, and I got tired of `cp -r`

and stale copies. So I wrote a tool.

It’s called [ sm](https://github.com/gherlein/sm) — skills-mapper. It’s a small Go binary. It does exactly one thing: it takes your library of skills, keeps it fresh from git, and puts it where every agent you use expects to find it.

That’s it. That’s the whole tool. Let me explain why that’s worth writing.

## The Problem: The Files Are Portable, The Location Isn’t

A skill is not a complicated thing. It’s a directory with a `SKILL.md`

in it — YAML frontmatter with a required `name`

, an optional `description`

, then markdown. That’s the standard. Any agent that reads the format can use the file.

So the *content* is completely portable. Wonderfully so. You can hand a skill directory to Claude Code, to Copilot, to `pi`

, to a thing you wrote yourself last Tuesday, and they all understand it.

What is **not** portable is where it lives:

| Agent | Global directory |
|---|---|
`claude-code` |
`~/.claude/skills` |
`copilot` |
`~/.copilot/skills` |
`hax` |
`~/.config/hax/skills` |
`pi` |
`~/.pi-go/skills` |
`oh-my-pi` |
`~/.config/agents/skills` |

Five agents, five directories, five slightly different opinions about where your stuff goes. And that’s just the global scope — each of them also has a *project* directory (`.claude/skills`

, `.github/skills`

, `.agents/skills`

, and so on) for repo-local skills.

Now layer packaging on top of that. If Claude Code is all you use, Claude Code plugins are genuinely a great answer — I’ve [built and shipped one](https://blog.herlein.com/post/unifi-fixed-hosts-skill/) and I’d recommend them. But a Claude Code plugin does absolutely nothing for Copilot. Or `hax`

. Or the agent you’re writing yourself.

Every ecosystem grows its own packaging, and every packaging format is a wall.

## Why I Suddenly Have Five Agents

This wasn’t a hypothetical problem. I went looking for it.

Back in March I wrote that [tooling is the new model](https://blog.herlein.com/post/dot-agents/) — the models are good enough, the differentiator is the harness. If I actually believe that, then I should be trying harnesses. Which isn’t just the skills - it’s also the tool calling, and importanly, how the tools are called. So I set out to run new ones alongside Claude Code and `pi`

, and they are each interesting for completely different reasons:

** hax** — a terminal-native coding agent written in

**C**. A single native binary, small dependency set, starts instantly, uses almost no memory. It auto-discovers llama.cpp and other local servers without you configuring anything. It deliberately has

*no*plugin marketplace,

*no*IDE integration,

*no*permission prompts. It streams markdown and tool results while respecting your terminal scrollback, and it gives you an inspectable transcript so you can see exactly what got sent to the model. It’s Unix philosophy applied to agents, and if you’re running local models on constrained hardware it’s a serious option.

** oh-my-pi** (

`omp`

) — a fork of Mario Zechner’s `pi`

, about 80,000 lines of Rust and TypeScript. This one goes the *opposite*direction from hax: it’s “a coding agent with the IDE wired in.” Full LSP support so it actually understands code structure instead of grepping at it. Real DAP debugging — it drives

`lldb`

, `dlv`

, `debugpy`

rather than sprinkling print statements. 31 built-in tools, 60+ providers, subagent coordination with schema-validated results. It treats coding as an *IDE*problem instead of a text problem, which is a thesis I find hard to argue with. It’s a “batteries included” version of pi.

** fx** — a tiny native agent written in

**Zig**. 6.39 MB. Cold start in 10 microseconds. Shell-like sparse output instead of a heavy TUI, minimal system prompts to keep token cost down, model and provider agnostic, and it compiles to WebAssembly so you can embed it in a browser with pluggable networking. It bills itself as being for research and for embedding in larger systems, and that’s the part I’m poking at.

C, Rust, Zig, TypeScript, and one I dabbled with in go. Nobody is converging on anything, and I think that’s *healthy*. Different constraints produce different tools. I want to run all of them against the same problems and see what actually holds up. Oh, and then compare it to Claude Code.

But here’s the thing — I can’t do a fair comparison if each agent has a different, drifted, hand-copied set of my skills. The agent is the variable I’m testing. The skills need to be the constant.

## The Solution: Placement, Not Transformation

The open standard for distributing skills already exists and it’s boring: **a git repo of skill directories**. Nothing agent-specific. Clone it, and the files are right there.

Which means the only real problem left is the last mile — getting one library into every agent’s expected location and keeping it current.

So that’s all `sm`

does. You write a manifest declaring your *sources* and your *agents*:

```
[skills]
# A git repo of SKILL.md directories
gherlein = { git = "https://github.com/gherlein/skills", ref = "main" }

# Only a subdirectory of a repo
tools    = { git = "git@github.com:you/monorepo.git", ref = "v1.2.0", subdir = "skills" }

# A local dev checkout — symlinked, never cloned
local    = { path = "~/dev/my-skills" }

[agents]
claude-code = true
copilot     = true
hax         = true
pi          = true
oh-my-pi    = true

[options]
prefix_on_collision = false
```

Then you run `sm sync`

and it does the boring, correct thing.

Three commands is the whole workflow:

```
sm init                 # write the starter manifest
$EDITOR ~/.config/skills-mapper/skills.toml
sm sync                 # fetch sources, place skills
```

## What `sync`

Actually Does

Five steps, in order:

``` php
flowchart LR
    F[freshen<br/>clone / git pull sources] --> D[discover<br/>find SKILL.md dirs]
    D --> R[resolve<br/>map source to target names]
    R --> L[link<br/>symlink into each agent]
    L --> C[reconcile<br/>remove stale links sm created]
```

**freshen**— clone each git source, or`git pull`

if it’s already cached, and prune caches that aren’t in the manifest anymore. Skipped entirely on`--dry-run`

.**discover**— walk each source looking for directories that contain a standard-compliant`SKILL.md`

.`.git`

,`node_modules`

,`vendor`

,`dist`

,`build`

and friends get ignored.**resolve**— compute the`source → target`

name mapping.**link**— whole-directory symlinks into each enabled agent’s skills directory.** reconcile**— remove links`sm`

previously made that nobody wants anymore.

Note the word **symlink**, repeatedly. `sm`

never copies and it never transforms. There is one canonical copy of every skill, in the clone cache, and every agent points at it. Update the source repo, run `sync`

, and all five agents get the new version simultaneously because they were all looking at the same file the whole time.

That’s the entire reason drift stops being possible.

## The Opinionated Bits

A tool this small only earns its keep if it’s opinionated about the right things. Three decisions I’d defend:

**No plugin support. On purpose.** This one surprises people. `sm`

will not consume a Claude Code plugin as a source. The reason is that the entire point of the tool is that skills follow *one* standard every agent reads. If I let you bridge Claude plugins in, Claude users get to consume skills Copilot users can’t — and I’ve just re-created the exact drift the tool exists to kill. Not doing it.

There’s a practical version of this trap too, and it bites people. Claude Code caches plugins under a **versioned** path: `~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/skills/`

. If you point a source at that path, it works great right up until Claude Code updates the plugin — at which point it installs into a *new* directory and your manifest keeps happily linking the stale one. Silently. Forever. Point at the plugin’s source repo instead and use `subdir`

.

**Duplicate skill names fail loud.** If two sources both ship a skill called `debugging`

, `sm`

stops and tells you. It does not pick a winner, and it does not quietly alphabetize its way into a decision you’ll discover three weeks later when the wrong skill fires. If you actually want both, set `prefix_on_collision = true`

and be explicit about it.

Silent success is the worst failure mode in tooling. A tool that guesses is a tool that lies to you.

**It only ever touches links it made.** There’s a state file at `~/.local/share/skills-mapper/state.json`

tracking every link `sm`

created. If `sm`

finds a file in an agent’s skills directory that it didn’t put there, it skips it with a warning and **never** clobbers it. Your hand-written skills are safe. I have a healthy paranoia about tools that write into `~/.claude`

unsupervised, and I wasn’t about to ship one.

## Scope: Global and Project

Two scopes. **Global** is the manifest at `~/.config/skills-mapper/skills.toml`

, installing into each agent’s home directory. **Project** is a `skills.toml`

at your repo root, installing into the repo’s per-project agent directories.

Scope auto-detects — `sm`

walks up from your current directory looking for a `skills.toml`

, stopping before `$HOME`

. Find one, you’re in project scope. Don’t, you’re global. Force it with `--global`

or `--project`

when you need to.

The project scope is the one I’m most interested in long-term. A repo that ships its own `skills.toml`

teaches *any* agent that clones it how to work on that codebase. That’s a much better story than a README section titled “if you use Claude Code, do this.”

## What Doesn’t Work Yet

I built this over the last couple of days and the commit log says “interim commit” more often than I’d like. Being honest about the state of it:

`add`

, `remove`

, `update`

, and `link`

are **stubs**. They return a “not implemented” error. Right now you edit `skills.toml`

by hand and run `sm sync`

. That’s genuinely fine for how I use it — the manifest is six lines and I touch it maybe once a month — but it’s not done done.

What *does* work: `init`

, `sync`

(with `--dry-run`

and `-v`

), and `list`

. Which is the actual workflow. The rest is convenience.

The agent registry is also deliberately extensible — Codex, Amp, and OpenCode are the obvious next entries. Adding one is a table entry, not a refactor. That was the point.

## Why Go, Again

Because of course it’s Go. Single static binary, no runtime to install, no `npm i -g`

that fails E404 on some unpublished internal workspace package. (Ask me how I know. The prior-art tool in this space couldn’t be installed at all — unpublished scoped packages, a private registry token baked into its install script, and a TOML grammar living in a package that wasn’t checked out. Three separate installability failures before you get to whether the tool is any good.)

A tool whose entire job is “make setup easy across five agents” has no business being hard to install. `make build`

, one binary, done. I’ve made [this argument](https://blog.herlein.com/post/why-go-for-robotics/) before in a different context and I’ll keep making it.

Plus… I love go. ‘Nuff said.

## Conclusion

`sm`

is not clever. It’s a symlink manager with a manifest and a state file. The clever part — if there is one — is refusing to do more than that.

The bet underneath it is simple: skills as plain git repos of `SKILL.md`

directories is the standard that survives, because it’s the only one that doesn’t require every agent author to agree on anything beyond a file format they already agree on. Everything above that layer is packaging, and packaging is where lock-in lives.

I’ll report back once I’ve run hax, oh-my-pi, and fx against real work for a while — I have opinions forming about all three and they’re not the opinions I expected to have. But that’s a topic for another day.

If you’re running more than one coding agent, [go grab it](https://github.com/gherlein/sm). It’s MIT, it’s small, and the whole thing is readable in an afternoon. Tell me what agent I should add to the registry.

If this is useful to you — or if you think the no-plugins stance is wrong and want to tell me why — drop me a note on LinkedIn: [linkedin.com/in/gherlein](https://www.linkedin.com/in/gherlein/).
