🧩 Canonical skills with polyskill
One source, N runtimes. Hand-copying a skill to a second executor works on day one and rots by month two. Here you turn a SKILL.md into a portable definition, generate the copies through a build, install them into both runtimes with a backup alongside, and measure the drift — the gap between what you wrote and what is installed.
🧬 Why manual copies diverge
This case is real and documented on this machine. To give skills to dsh-sandbox (a third executor, with a model running in a container), three skills were hand-copied to ~/projetos/dsh-skills: formato-curso-v2, formato-curso-v5 and capa-inema, which the other two depend on. The documentation warns in plain words: "These are COPIES — Claude keeps using the originals in ~/projetos/formato-curso-inema via symlink in ~/.claude/skills/". Two folders, two truths, no mechanism linking them.
On the day of the copy, both sides are identical. Then you fix a prompt bug in the original — Claude's skill improves, the dsh copy doesn't. Then you tweak a path in the copy because inside the container ~/projetos resolves differently — now the copy has a fix the original lacks. Two weeks later nobody knows which one is right, and "right" has become a question with no answer. That's drift: silent divergence between copies that should be the same thing.
Compare the two halves. On top, the blue and dashed red lines start from the same point and pull apart with every edit — nobody programmed that, it's just time passing. Below, everything comes out of a single block: the output is disposable and can be rebuilt at any moment.
💡 New here?
A Skill is a folder with a SKILL.md inside: instructions the agent loads on demand to carry out a type of task. The Runtime is the program that runs the agent (Claude Code, Codex CLI, dsh-sandbox). The Canonical source is the only place you edit — everything else is generated from it. Drift is when a copy no longer matches the source. MCP (Model Context Protocol) is the standard through which an agent gains external tools: generating an image, publishing a post, querying an API.
Key concepts
Works today, diverges tomorrow, without warning.
The only file you edit by hand.
dist/ is disposable: deleting and rebuilding it is free.
Three skills copied by hand into ~/projetos/dsh-skills.
📥 import: SKILL.md becomes a portable definition
The kit's sync-skills.sh is a thin wrapper around polyskill. The import command takes a skill that already exists in ~/.claude/skills/<nome> and translates it into the portable format inside skills/<nome>/, in the kit's repository. Two files come out of it: definition.md, with YAML frontmatter and a Markdown body, and polyskill.yaml, which states which runtimes this skill should serve.
Two guarantees matter here. First: import never bulk-copies ~/.claude or ~/.codex — you name skills one by one, on purpose, because migrating 117 skills at once is like moving an entire wardrobe without opening the drawers. Second: the round-trip is lossless for the core of the spec (name, description, body, scripts/, references/, assets/) and comes with a warning for each runtime's extensions. If something doesn't survive the translation, polyskill tells you.
🎯 Import your first skill
Goal: turn the session-handoff skill — the same one from the pilot recorded in tasks/current.md — into a canonical source inside the kit.
# prerequisite, only once on this machine
npm i -g polyskill
polyskill --version # 0.1.0
cd ~/projetos/agente-claude-codex
# import from Claude into the portable format
scripts/sync-skills.sh import session-handoff
# importada: skills/session-handoff
# what was created
ls skills/session-handoff/
# definition.md polyskill.yaml
# replace with the name of any of your skills
scripts/sync-skills.sh import <your-skill>
How to check: head -12 skills/session-handoff/definition.md shows the frontmatter with name and description matching the original SKILL.md. If the script replies skip <nome>: não existe em ~/.claude/skills, check the spelling — the name is the folder's, not the slash command's.
✓ Good candidates to import
- ✓Skills classified as reusable in the module 2.1 audit.
- ✓The ones you use every week — the payoff shows up fast.
- ✓The ones already hand-copied somewhere (stop the bleeding first).
- ✓Small skills, to learn the cycle before tackling the big ones.
✗ Leave for later
- ✗Adapter skills: without the MCP on the target, they don't run (topic 6).
- ✗Skills classified as native: they depend on a hook Codex doesn't have.
- ✗Folders without
SKILL.md: there's nothing to import; handle them by hand. - ✗Batches of 50: import 5 at a time and run the drift check between batches.
Key concepts
YAML frontmatter + Markdown body. The source.
Which runtimes this skill serves.
Lossless at the core; warnings on extensions.
Never bulk-copies the runtime folders.
🏗️ build: dist/claude and dist/codex
build walks through every folder in skills/ and, for each one, asks polyskill to emit the optimized version for each configured runtime. The result goes to skills/<nome>/dist/claude/<nome>/ and skills/<nome>/dist/codex/<nome>/. Two outputs, one source. The golden rule of the whole module fits in one sentence: you edit definition.md; you never edit anything inside dist/.
Notice there is only one block on the left. The three boxes on the right are outputs: deleting any of them loses nothing, because build rebuilds it. The third one is dashed because the --dsh target is still a planned item in the kit's tasks/current.md, not a ready-made option.
🎯 Generate the copies for both runtimes
Goal: produce dist/claude and dist/codex from the imported definition, without touching any runtime folder yet.
cd ~/projetos/agente-claude-codex
scripts/sync-skills.sh build
# build: skills/session-handoff/
# check what was emitted
find skills/<your-skill>/dist -maxdepth 3 -type d
# skills/<your-skill>/dist/claude/<your-skill>
# skills/<your-skill>/dist/codex/<your-skill>
# if a file in dist/ was edited by hand, the build refuses;
# FORCE=1 regenerates over it (polyskill build --force)
FORCE=1 scripts/sync-skills.sh build
How to verify: both folders exist and each one contains the skill file in its runtime's format. Run build twice in a row without editing anything: the second run shouldn't change a single file — the build is deterministic, and that's what makes drift reliable.
💡 Practical tip
Version skills/ in git and ignore dist/. The source deserves history; the output doesn't. When someone clones the kit, one build rebuilds everything — and the repository diff goes back to showing only what you actually wrote, instead of hundreds of generated lines.
Key concepts
What knows how to translate the definition into each executor's format.
Generated output. Never edit it; always regenerate.
Overwrites output someone edited by hand.
Same source, same output — the foundation of drift.
📦 install --both, with a backup alongside
install is the only command in this module that writes outside the kit's repository. It copies skills/<name>/dist/<runtime>/<name>/ to ~/.claude/skills/<name> or ~/.codex/skills/<name>, depending on the target you ask for: --claude, --codex or --both. Before overwriting any folder that already exists, it copies what was there to .<name>.bak-<timestamp>, in the same folder. You never lose the previous version without a way back.
There's a Codex-specific detail the script handles for you: besides ~/.codex/skills, Codex also discovers skills in ~/.agents/skills. When the target includes Codex and that folder exists, the script mirrors the installation there too. That's why the doctor from module 2.1 reports two different counts on this machine — 27 skills in ~/.codex/skills and 29 in ~/.agents/skills.
🎯 Install on both runtimes
Goal: put the same generated skill on both executors and confirm the backup exists.
cd ~/projetos/agente-claude-codex
# Codex only, if you want to go slowly
scripts/sync-skills.sh install session-handoff --codex
# both at once
scripts/sync-skills.sh install session-handoff --both
# installed: /home/<user>/.claude/skills/session-handoff (backup alongside if it already existed)
# installed: /home/<user>/.codex/skills/session-handoff (backup alongside if it already existed)
# mirrored: ~/.agents/skills/session-handoff
# backups are hidden, in the same folder
ls -d ~/.claude/skills/.session-handoff.bak-*
# roll back, if you need to
rm -rf ~/.claude/skills/session-handoff
cp -a ~/.claude/skills/.session-handoff.bak-<timestamp> \
~/.claude/skills/session-handoff
How to verify: open a new session in each runtime and ask it to list the available skills; the name must show up in both. If the script says rode build antes: skills/<nome>/dist/<runtime>/<nome>, you skipped topic 3.
Checks the output
If dist/<runtime>/<name> doesn't exist, the script stops with a message and exit code 1. It doesn't make up content.
Makes the backup
If the target already existed, it becomes .<name>.bak-<timestamp> alongside it. One per install, with the time in the name.
Copies
cp -a preserves permissions and structure — the skill's scripts stay executable.
Mirrors to Codex
If the target is Codex and ~/.agents/skills exists, the same folder is copied there.
Logs
Prints each installed path. Paste that output into handoffs/latest.md: it's the evidence that the step ran.
Key concepts
Installs to Claude and Codex in the same run.
Hidden backup alongside; rolling back is a cp -a.
Second folder where Codex discovers skills.
Without a build, install stops and tells you what's missing.
📡 drift: [ok] or [DRIFT] per runtime
drift is the shortest command and the most important one. For each skill in skills/ and each runtime, it compares the generated output with what is installed, using diff -rq. Each line has three possible answers: [ok] when they are identical, [DRIFT] when they have diverged, and [não instalada] (not installed) when the folder is missing on one side or the other. It also exits with code 1 if any line shows DRIFT. So you can hook it into a check.sh or a weekly routine and get warned instead of finding out by accident.
🎯 Measure and resolve a drift
Goal: cause a DRIFT on purpose, watch it show up, and resolve it at the source, not in the copy.
cd ~/projetos/agente-claude-codex
# 1. clean state
scripts/sync-skills.sh drift; echo "exit=$?"
# [ok] session-handoff → claude
# [ok] session-handoff → codex
# exit=0
# 2. someone edits the installed copy (this is how it starts)
echo "# anotação solta" >> ~/.codex/skills/session-handoff/SKILL.md
# 3. drift flags it
scripts/sync-skills.sh drift; echo "exit=$?"
# [ok] session-handoff → claude
# [DRIFT] session-handoff → codex
# exit=1
# 4. decide: was the edit good? move it to the source and regenerate
$EDITOR skills/session-handoff/definition.md
scripts/sync-skills.sh build
scripts/sync-skills.sh install session-handoff --both
scripts/sync-skills.sh drift; echo "exit=$?" # back to exit=0
How to check: the exit=1 from step 3 and the exit=0 from step 4. If [não instalada] shows up, the skill exists in the source but was never installed in that runtime, so run install. Replace session-handoff with <your-skill> to repeat this with your own.
✓ How to resolve a DRIFT
- ✓Read the
diffbefore deciding: the edit to the copy may be a good one. - ✓If it is, move it into
definition.mdand regenerate. - ✓If it isn't, reinstall over it. The backup keeps the previous version.
- ✓Run drift after each batch of installs, not just at the end.
✗ What turns drift into debt
- ✗Editing directly in
~/.codex/skills"just this once". - ✗Running
FORCE=1without reading what will be overwritten. - ✗Leaving
[DRIFT]on screen for weeks: it stops being a signal and becomes noise. - ✗Treating
[não instalada]as an error: it is often a deliberate choice.
Key concepts
Generated and installed are identical byte for byte.
They diverged; someone edited the copy. Exit code 1.
The folder is missing on one side (not installed); not a failure.
You fix definition.md, never dist/.
🔌 Adapter skills: only after MCP
The audit in module 2.1 flagged 15 skills as adapter skills: heygen, magnific, the eight printing-press variants, and others. They don't depend on Claude on a whim. They depend on MCP tools that are currently registered only in Claude. The doctor is explicit about this: "no MCP in Codex: skills marked 'adapter' only work after codex mcp add". Porting their Markdown before registering the server produces a skill that loads, tries to call a tool that doesn't exist and fails halfway through. That's worse than not porting it at all.
So the order is: first codex mcp add on the target, then import, build, install, drift. Registering the MCP never copies the secret. The key stays where it has always been, in a .env referenced through an environment variable. The audit report follows the same rule: it lists MCP server names and never values.
🎯 Unlock an adapter skill
Goal: register the MCP in Codex without copying the key, and only then port the skill that depends on it.
# 1. what already exists in Codex today
codex mcp list
# 2. register it by referencing the variable, never the key's value
codex mcp add <server-name> \
--env API_KEY="$<YOUR_ENV_VARIABLE>" \
-- <server-command>
# 3. confirm it shows up
codex mcp list | grep <server-name>
# 4. now run the module's cycle
cd ~/projetos/agente-claude-codex
scripts/sync-skills.sh import <adapter-skill>
scripts/sync-skills.sh build
scripts/sync-skills.sh install <adapter-skill> --codex
scripts/sync-skills.sh drift
How to check: run scripts/doctor.sh again. The [aviso] nenhum MCP no Codex (no MCP in Codex) line should be gone. Then, in a new session, ask Codex to run the skill on a minimal task. If the tool doesn't show up, the server was registered but isn't starting. The problem is the command from step 2, not the skill.
⚠️ Honesty about the state of this step
On 2026-09-14, in the kit's README, the sync-skills.sh line was marked as "not run" — unlike adapt-instructions.sh, which had already passed a dry-run with the 71/7 from module 2.2. The command exists and has been read and reviewed, but nobody had run the full import → build → install → drift cycle on a real skill. That's not a flaw in the material: it's the difference between written and proven, which this course refuses to blur.
When you run it, you become the evidence. Write down the result (passed, failed or partial) and on which runtime. One paragraph in handoffs/latest.md changes the kit's status from "not run" to "run on such-and-such date, with such-and-such output".
# handoffs/latest.md — the record that closes the module
## Session 2026-09-14 · polyskill
- Run: `sync-skills.sh import session-handoff` → `build` → `install --both` → `drift`.
- Result: `[ok] session-handoff → claude`, `[ok] session-handoff → codex`, exit=0.
- Evidence: output pasted below; backups in `~/.claude/skills/.session-handoff.bak-*`.
- Not run yet: adapter skills (they depend on `codex mcp add`; no MCP in Codex).
- Next action: register the first MCP in Codex and repeat the cycle with an adapter skill.
💡 Practical tip
"Not run" is a legitimate state and deserves to be written down. What corrupts a project isn't admitting that a step hasn't been run yet — it's silence, which makes everyone assume it was. If something breaks during the cycle, also log it in FALHAS.md: one line with the date, what broke, the smallest possible fix and whether the cause was the prompt or the infrastructure.
Key concepts
Depends on an MCP tool in the target runtime.
Registers the server before you port the skill.
An environment variable, never the value in the command.
An honest state; it becomes evidence when you run it.
Self-check (optional): drift returned [DRIFT] minha-skill → codex. What is the right first step?
🎯 Module summary
~/projetos/dsh-skills show how two folders turn into two versions of the truth.SKILL.md becomes definition.md + polyskill.yaml; the build emits dist/claude and dist/codex; install copies with a backup alongside and mirrors to ~/.agents/skills.[ok], [DRIFT] or [não instalada] per runtime, with exit code 1 on DRIFT. Always fix it at the source.codex mcp add first; and as of 2026-09-14 this step was still listed as "not run" in the kit. Run it and log it in handoffs/latest.md.Next module:
2.5 — Readback: prove it in a new session, with five questions and two runtimes