Universal Agents vs. Agent-Specific Symlinked Installations in the Skills CLI

Universal agents share a single skill directory and skip symlinks entirely, while agent-specific installations create symlinks from the canonical store to each agent's isolated folder.

The skills CLI from Vercel Labs manages AI skills across multiple agents using two distinct installation strategies. Understanding how universal agents versus agent-specific symlinked installations work helps you predict where files land, why some agents need symlinks, and how to troubleshoot installation issues.


How the Skills CLI Installs Skills

Every skill installation begins at the canonical location: .agents/skills/<skill-name> for project installs or $XDG_CONFIG_HOME/agents/skills/<skill-name> for global installs. From there, the CLI decides whether additional steps are needed based on the target agent's type.

This logic lives in src/installer.ts, which handles file writing, symlink creation, and fallback copying when symlinks aren't supported.


Universal agents read skills directly from the canonical .agents/skills directory. No per-agent symlinks are created because these agents are already configured to look in the shared location.

How Universal Agents Are Identified

In src/agents.ts, the getUniversalAgents() function filters the agent registry using two criteria:

// src/agents.ts
export function getUniversalAgents(): AgentType[] {
  return (Object.entries(agents) as [AgentType, AgentConfig][])
    .filter(
      ([_, config]) => config.skillsDir === '.agents/skills' && config.showInUniversalList !== false
    )
    .map(([type]) => type);
}

A universal agent must have:

  • skillsDir set to '.agents/skills' — the canonical shared path
  • showInUniversalList not explicitly set to false

Installation Behavior for Universal Agents

When installing to a universal agent, src/installer.ts returns early after writing to the canonical directory:

// src/installer.ts (symlink mode)
if (isGlobal && isUniversalAgent(agentType)) {
  return {
    success: true,
    path: canonicalDir,
    canonicalPath: canonicalDir,
    mode: 'symlink',
  };
}

The mode: 'symlink' here is slightly misleading — no actual symlink is created. The field indicates this path would be the symlink target for non-universal agents, but universal agents use the canonical path directly.


Agent-Specific Symlinked Installations: Isolated Folders with Symlinked Access

Non-universal agents maintain their own isolated skill directories. The CLI creates symlinks from the canonical store into each agent's private folder, preserving a single source of truth while respecting the agent's expected layout.

Installation Flow for Symlinked Agents

In src/installer.ts, when the target agent is not universal and the installation is global, the full symlink creation runs:

  1. Write skill files to the canonical directory
  2. Create the agent's skills directory if missing
  3. Create a symlink: ~/.agents/<agent>/skills/<skill-name> → ../../.agents/skills/<skill-name>
  4. Fall back to file copying if symlinks aren't supported
// src/installer.ts - conceptual flow for symlinked agents
const agentSkillsDir = path.join(AGENTS_DIR, agentType, 'skills');
const symlinkPath = path.join(agentSkillsDir, skillName);

// Create symlink to canonical location
fs.symlinkSync(relativePathToCanonical, symlinkPath);

Symlinks serve three purposes in the skills CLI architecture:

  • Single source of truth — Update or delete the canonical skill, and all symlinked agents see the change immediately
  • Disk efficiency — Skills aren't duplicated across every agent
  • Agent isolation — Agents that require specific folder structures still get their expected layout

When symlinks fail (Windows without developer mode, certain CI environments), the CLI transparently falls back to full file copies.


CLI Output: How the Skills CLI Shows the Difference

When you run skills add, the CLI groups agents visually to make the installation behavior transparent. In src/add.ts, the splitAgentsByType function separates universal and symlinked agents:

// src/add.ts - splitAgentsByType
function splitAgentsByType(agentTypes: AgentType[]) {
  const universal: string[] = [];
  const symlinked: string[] = [];

  for (const a of agentTypes) {
    if (isUniversalAgent(a)) {
      universal.push(agents[a].displayName);
    } else {
      symlinked.push(agents[a].displayName);
    }
  }
  return { universal, symlinked };
}

Typical CLI output shows:

$ skills add vercel-labs/github-copilot

Installing github-copilot...

✓ Installed to .agents/skills/github-copilot

Available to:
  universal: GitHub Copilot, Vercel CLI
  symlink →: ChatGPT, Claude Desktop

This output makes it immediately clear which agents use the skill directly versus which receive symlinks.


Comparing Universal and Symlinked Installations

Aspect Universal Agents Agent-Specific Symlinked
Skill directory Shared .agents/skills Private ~/.agents/<name>/skills
Symlink created? No Yes (with copy fallback)
Code path in installer.ts Early return after canonical write Full symlink creation logic
Identification skillsDir === '.agents/skills' and showInUniversalList !== false Any agent not matching universal criteria
Use case Agents designed for shared skill discovery Agents requiring isolated skill environments
Update propagation Immediate for all universal agents Follows symlinks from canonical source

Practical Examples

Installing a Skill for All Universal Agents Globally


# Install globally to universal agents only

skills add vercel-labs/github-copilot -g

Result: Files written to ~/.config/agents/skills/github-copilot. No symlinks created. Universal agents like GitHub Copilot and Vercel CLI see the skill immediately.

Installing a Skill for a Specific Non-Universal Agent


# Install to a specific agent that requires symlinked access

skills add my-custom-skill --agent chatgpt

Result: Files written to ./.agents/skills/my-custom-skill, then symlinked to ~/.agents/chatgpt/skills/my-custom-skill.

Checking Agent Type Programmatically

import { isUniversalAgent, getUniversalAgents } from 'skills/src/agents';

// Check if a specific agent is universal
console.log(isUniversalAgent('copilot')); // true

// List all universal agents
console.log(getUniversalAgents()); // ['copilot', 'vercel-cli', ...]

Summary

  • Universal agents share the canonical .agents/skills directory and require no symlinks — the skills CLI writes once and all universal agents immediately have access.
  • Agent-specific installations use symlinks to bridge the canonical store into private agent folders, maintaining single-source-of-truth while respecting agent isolation requirements.
  • The distinction is controlled by agent configuration in src/agents.ts, specifically skillsDir and showInUniversalList properties.
  • The src/installer.ts file implements the branching logic: early return for universal agents, full symlink creation for others.

Frequently Asked Questions

How do I know if an agent is universal or requires symlinked installation?

Check the agent's configuration in src/agents.ts. Universal agents have skillsDir === '.agents/skills' and showInUniversalList !== false. You can also run skills add with verbose output — the CLI groups agents into "universal" and "symlink →" sections.

No. The skills CLI intentionally skips symlinks for universal agents because they already read from the canonical directory. Creating symlinks would be redundant and could cause path resolution issues.

The installer in src/installer.ts falls back to copying files when symlink creation fails. This ensures skills work on Windows without developer mode, in CI environments, or on filesystems without symlink support — though updates require re-installation rather than automatically following the symlink.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →