The Multi-Agent Skill Fragmentation Problem
If you use more than one AI coding agent in 2026, you've almost certainly encountered this: you refine a skill in Claude Code, then realize Codex doesn't have it. You copy it over. Then you update the original. Now Codex is stale. Then you remember Perplexity Computer needs it too, but that requires uploading through a web dashboard. Multiply this by 20+ skills across 5 platforms and you've built yourself a maintenance nightmare that feels eerily similar to the pre-package-manager era of vendoring dependencies.
This isn't a hypothetical pain point. Developer forums and communities are full of threads describing exactly this workflow breakdown. The root cause is structural: each AI coding agent stores skills in a different location, using different formats, and some platforms (Claude Desktop, Perplexity Computer) don't even have local file access — they require browser-based uploads to cloud dashboards.
The common workarounds developers reach for all have significant drawbacks:
- Manual copy-paste: The most common approach. Works initially, becomes unmaintainable as skill count grows. Any update requires N copy operations across N agents.
-
Symlinks only: Solves the local agent problem elegantly (Claude Code reads from
~/.claude/skills/, Codex from~/.codex/skills/), but completely fails for cloud-based agents like Perplexity Computer and Claude Desktop that require dashboard uploads. -
Per-project skills: Checking skills into each repository's
.agents/skills/directory. Creates massive duplication across repos and makes global skill updates nearly impossible.
The Architecture: Symlinks + Browser Automation
The opensite-skills repository implements a two-layer solution that addresses both local and cloud agents from a single source of truth.
Layer 1: Local Agent Sync via Symlinks
The setup.sh script detects which local agents are installed (Claude Code, Codex, Cursor) and creates symlinks from each agent's skill directory back to the shared repo:
# What setup.sh does for each detected platform:
mkdir -p "$HOME/.claude/skills"
for skill in "$SKILLS_DIR"/*/; do
skill_name="$(basename "$skill")"
[[ -f "$skill_path/SKILL.md" ]] || return 0 # skip non-skill dirs
ln -sfn "$skill" "$HOME/.claude/skills/$skill_name"
done
The script auto-detects installed platforms by checking for either the CLI binary or the config directory. If Claude Code isn't installed, it skips it and moves on. If Codex is present, it links to ~/.codex/skills/. Cursor gets ~/.cursor/skills/. The result: edit a skill file once in the repo, and every local agent sees the change immediately with zero reinstall.
The key design decision here is using ln -sfn (symbolic link, force, no-dereference) rather than copying files. This means the skill directories in each agent's config are pointers, not duplicates. git pull on the shared repo instantly propagates changes to all local agents.
Layer 2: Cloud Agent Sync via Playwright Browser Automation
Neither Perplexity Computer nor Claude Desktop expose a public API for skill management. Both require authenticated browser sessions to upload skills through their web dashboards. This is where the architecture gets interesting.
The sync-perplexity.sh and sync-claude.sh scripts implement full browser automation using Playwright driving a real Brave browser instance. The flow for each skill:
- Zip the skill directory (SKILL.md + references/ + templates/ + agents/)
- Launch Brave with a fresh browser context (not your profile)
- Inject the session cookie to authenticate without storing credentials
-
Navigate to the skills dashboard (e.g.,
perplexity.ai/account/org/skills) - Check if the skill exists by searching the skill list
- Upload or update via the file chooser intercept pattern
- Confirm success by verifying the modal closes
The scripts support three invocation modes:
./sync-perplexity.sh # sync all skills
./sync-perplexity.sh --changed-only # only git-modified skills since last commit
./sync-perplexity.sh octane-rust-axum # one specific skill by name
The --changed-only flag uses git diff --name-only HEAD~1 HEAD to detect which skill directories have changes, then only uploads those. This is critical for CI/CD integration where you don't want to re-upload 20 unchanged skills on every commit.
Why Brave Instead of Headless Chromium?
This is one of the more practical engineering decisions in the system. Playwright ships with its own Chromium binary optimized for testing, but both Cloudflare (protecting perplexity.ai) and Claude's infrastructure detect and block it. The detection vectors include:
-
navigator.webdriverflag: Headless Chromium sets this totrue. The scripts disable this with--disable-blink-features=AutomationControlled. - Browser fingerprint: Playwright's bundled Chromium has a distinct fingerprint that Cloudflare's bot detection recognizes.
- Window visibility: Headless mode triggers additional bot detection heuristics.
By launching the real Brave binary at /Applications/Brave Browser.app in headed (visible) mode, the automation passes Cloudflare's checks because it IS a real browser. The scripts set headless: false and use a matching user agent string.
Session Cookie Authentication: Security Without Credential Storage
Rather than storing usernames and passwords (which would require handling MFA flows, password rotation, and create a significant security surface), the scripts use session cookie injection. The developer:
- Logs into the platform normally in their browser
- Opens DevTools (F12) > Application > Cookies
- Copies the session token value
- Adds it to a gitignored
.envfile
For Perplexity, the cookie is __Secure-next-auth.session-token. For Claude, it's sessionKey. The scripts inject these into a fresh browser context:
await context.addCookies([{
name: '__Secure-next-auth.session-token',
value: SESSION_COOKIE,
domain: 'www.perplexity.ai',
path: '/',
httpOnly: true,
secure: true,
sameSite: 'Lax',
}]);
This approach inherits whatever MFA trust the original login established, avoids storing long-lived credentials, and uses ephemeral tokens that the developer can rotate at will.
The React File Upload Problem
A subtle but important implementation detail: the scripts use Playwright's filechooser event intercept rather than directly setting input.files on the DOM. This matters because React's synthetic event system doesn't fire onChange when you programmatically set file input values through the DOM API. The upload would appear to work (the file reference gets set) but React's state never updates, so the upload silently fails.
The correct pattern intercepts the native file chooser dialog before it opens:
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser', { timeout: 8000 }),
dropZone.click(),
]);
await fileChooser.setFiles(zipPath);
This triggers React's onChange correctly because it flows through the same event path as a real user interaction.
Platform Compatibility Matrix
The skill format follows the Agent Skills open standard (agentskills.io) with per-platform metadata:
| Platform | Skill Location | Sync Method | Auto-detect |
|---|---|---|---|
| Claude Code | ~/.claude/skills/ |
Symlink via setup.sh | CLI or ~/.claude/ dir |
| Codex | ~/.codex/skills/ |
Symlink via setup.sh | CLI or ~/.codex/ dir |
| Cursor | ~/.cursor/skills/ |
Symlink via setup.sh | CLI or ~/.cursor/ dir |
| Perplexity Computer | Cloud dashboard | Browser automation | N/A (manual trigger) |
| Claude Desktop | Cloud dashboard | Browser automation | N/A (manual trigger) |
Each skill directory contains:
-
SKILL.md— Main instructions with YAML frontmatter -
agents/openai.yaml— Codex/OpenAI UI metadata -
references/activation.md— Portable activation guide - Optional:
templates/,examples/,scripts/
Claude Code-specific frontmatter fields (like context: fork for subagent execution) are silently ignored by other platforms, so a single SKILL.md works everywhere.
The Codex Auto-Deepening Feedback Loop
One of the more elegant workflow patterns: Codex has a built-in feature that periodically analyzes work history and refines skills. Because the symlinks point back to the shared git repo, Codex's deepening writes directly into the repo's skill files. The workflow becomes:
- Codex deepens a skill based on usage patterns
- The change is immediately visible to Claude Code and Cursor (via symlinks)
- Developer commits and pushes the change
-
./sync-perplexity.sh --changed-onlyand./sync-claude.sh --changed-onlyupload only the modified skills to cloud platforms
This creates a continuous improvement loop where AI-generated skill refinements propagate to all agents through a single git commit.
Getting Started
# 1. Clone the repo
git clone git@github.com:opensite-ai/opensite-skills.git ~/opensite-skills
cd ~/opensite-skills
# 2. Set up local agents (symlinks)
./setup.sh
# 3. Configure cloud sync (optional)
cp .env.example .env
# Add your session cookies to .env
# 4. Sync to cloud platforms
./sync-perplexity.sh
./sync-claude.sh
The repo ships with 20+ production-tested skills covering Rust/Axum patterns, Rails query optimization, React rendering performance, PostgreSQL engineering, Tailwind v4 + ShadCN, and more. Each skill is non-trivial — these aren't hello-world templates but deeply tuned instructions developed through real production use on the OpenSite AI platform.
Conclusion
The multi-agent skill sync problem is a natural consequence of the AI coding agent ecosystem fragmenting across multiple platforms. The approach implemented in opensite-skills — symlinks for local agents, Playwright browser automation for cloud agents, session cookie auth to avoid credential storage — provides a practical, production-tested solution. One repo, one git pull, and every agent is in sync.
opensite-skills on GitHub | Agent Skills Standard | OpenSite AI
Top comments (0)