TZ Jay Zhang
Writing
AI AgentsDeveloper ToolsRustArchitecture

One library, every agent: syncing AI-agent skills across 15+ tools

Coding agents each store skills in their own folder, their own way. Skills Manager normalises all of them behind one central library — the architecture, and why it's a Rust filesystem program wearing a GUI.

Tianliang Zhang ·AI Engineer · ·5 min read
One library, every agent: syncing AI-agent skills across 15+ tools

Coding agents have converged on a good idea — skills: small, reusable units of capability you drop in a folder, and the agent picks them up. Claude Code reads ~/.claude/skills/. Cursor, Codex, Copilot, OpenCode and a dozen others each do their own version.

The problem is the folder. Every agent invented its own location and conventions. Write a useful skill and you copy it into Claude Code’s directory, then Cursor’s, then Codex’s. Update it, and you’re hand-editing five copies. They drift. There is no single source of truth.

Skills Manager (2,000+ stars) is the fix: one central library, a thin adapter per agent, and a sync engine. Edit a skill once; every agent sees the change.

Skills Manager architecture — one library, normalised through adapters, synced to every agent

The adapter: one struct per agent

A central library only helps if something absorbs the variation between agents — and they vary in every dimension: where skills live (~/.claude/skills/ vs the XDG ~/.config/opencode/skills/), global vs project paths, flat vs nested layouts (some agents nest skills inside category folders), and how each one is detected. Every supported tool is a ToolAdapter — a small, declarative description of exactly those conventions:

pub struct ToolAdapter {
    pub key: String,
    pub display_name: String,
    pub relative_skills_dir: String,        // relative, e.g. .claude/skills
    pub relative_detect_dir: String,        // how we know it's installed
    pub additional_scan_dirs: Vec<String>,  // plugin / marketplace dirs
    pub recursive_scan: bool,               // nested category dirs (Hermes)
    pub project_relative_skills_dir: Option<String>, // global ≠ project path
    // ...
}

Adding a fifteenth agent is usually just a new row, not a new code path. Each adapter’s relative dir is expanded into candidate paths — home, plus an XDG .config fallback — and resolution picks whichever one actually exists on this machine:

fn select_existing_or_default(paths: &[PathBuf]) -> PathBuf {
    paths.iter().find(|p| p.exists()).cloned()
        .unwrap_or_else(|| paths[0].clone())
}

The “15+ tools” aren’t 15 integrations. They’re 15 rows in a registry over one normalised model. That’s the whole design bet — and it’s why supporting a new agent is a data change, not an engineering project.

Once a skill and a target agent are normalised, deploying is one operation with two modes:

pub enum SyncMode { Symlink, Copy }

The default is symlink. The skill lives once in the central library; each agent’s directory holds a link to it. Edit the skill and every agent is updated at once — there are no copies to drift. Copy exists for environments where symlinks aren’t available.

The edge cases are where the real work hides. One example, straight from the source — a recursive copy that, left unguarded, eats its own output:

/// Refuse to copy when `dst` would land inside `src` — otherwise the recursive
/// copy walks into the freshly-created `dst`: <dst>/<dst>/<dst>/... forever. (#61)
fn ensure_dst_not_inside_src(src: &Path, dst: &Path) -> Result<()> { /* ... */ }

The kind of bug you only find by shipping to real machines.

The model: library, presets, workspaces

The sync engine is the how. The design is the what, and it rests on a deliberate split between the skills you have and where they’re deployed.

The conceptual model — Library to Preset to Global / Project Workspace to Agent

  • Library — the central store. Every skill exists once, git-backed: the source of truth.
  • Preset — a named, reusable group of skills, like “my Python stack” or “my code-review kit.” A preset is a template, not live state.
  • Workspaces — the deployment surfaces. A Global Workspace manages an agent’s user-level skills (~/.claude/skills/); a Project Workspace manages skills scoped to a single repo (<project>/.claude/skills/). The same preset can be stamped into either.
  • Agent — simply reads whatever sits in its skills folder.

The decision that makes presets work: applying one is a one-time action, not a live binding. Activate “my Python stack” on Claude Code and its current skills are deployed into the agent’s folder (each via the usual symlink-or-copy sync); later changes to the preset’s membership don’t reach back and rewrite what that agent already has. The preset is a curation label, not a subscription — which is the opposite of how an individual synced skill behaves. A synced skill is one source of truth you want tracked everywhere; a preset is a snapshot you compose once and stamp anywhere. Real workflows need both, so the tool ships both.

That separation is what lets it scale past “a folder of skills”: you assemble a capability set once and apply it across every agent and project with a click, instead of re-picking skills each time.

The Skills Manager library — Presets, Global Workspace (per agent) and Project Workspaces in the sidebar

The model, made concrete: the central library on the right, with Presets and per-agent Workspaces down the left.

Why Rust + Tauri

This is a filesystem program wearing a GUI. Symlinks (with a Windows fallback), recursive copies, git backup, content-hash freshness checks, watching directories for outside changes, and a per-deployment record of target path, actual mode, status and source hash so the UI shows what each agent really has — that is the job. So the backend is Rust (via Tauri), doing the filesystem work with real error handling; React just drives the interface. The UI is the easy part; the value is the engine underneath.

Takeaways

  • Normalise, don’t integrate. When you support N similar-but-different systems, find the one model that captures the variation and make each system a row, not a branch. A new agent is new data, not new code.
  • Separate the catalogue from the deployment. The library is what you have; presets and workspaces are where it goes. Keeping them apart is what lets the tool scale past a folder of skills.
  • Pick the binding per job. A symlinked skill is one source of truth you want tracked everywhere; a preset is a one-time stamp you compose once and apply anywhere. Ship both.

Skills Manager is open source — 2,000+ stars and counting.

Ask Jay

AI assistant · Gemini

Hi — I'm Jay's AI assistant. Ask about his experience, projects, or whether he's open to roles.