MODULE 2.4

🧩 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.

6
Topics
~30
Minutes
Interm.
Level
Practice
Type
1

🧬 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.

manual copy time pulls the two folders apart day 0 · identical prompt fix path tweak original (Claude) copy (dsh) · diverged which one is right? canonical source + build definition.md dist/claude dist/codex regenerating is cheap divergence has nowhere to take root

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

Manual copy

Works today, diverges tomorrow, without warning.

Canonical source

The only file you edit by hand.

Generated output

dist/ is disposable: deleting and rebuilding it is free.

Real case

Three skills copied by hand into ~/projetos/dsh-skills.

2

📥 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

definition.md

YAML frontmatter + Markdown body. The source.

polyskill.yaml

Which runtimes this skill serves.

Round-trip

Lossless at the core; warnings on extensions.

Skill by skill

Never bulk-copies the runtime folders.

3

🏗️ 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/.

skills/<nome>/ definition.md polyskill.yaml canonical source · you edit here build per-runtime adapters dist/claude → ~/.claude/skillsClaude Code dist/codex → ~/.codex/skillsCodex CLI · mirrored in ~/.agents/skills --dsh → ~/projetos/dsh-skillsthird executor · target still to be added

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

Runtime adapter

What knows how to translate the definition into each executor's format.

dist/

Generated output. Never edit it; always regenerate.

--force

Overwrites output someone edited by hand.

Determinism

Same source, same output — the foundation of drift.

4

📦 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.

1

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.

2

Makes the backup

If the target already existed, it becomes .<name>.bak-<timestamp> alongside it. One per install, with the time in the name.

3

Copies

cp -a preserves permissions and structure — the skill's scripts stay executable.

4

Mirrors to Codex

If the target is Codex and ~/.agents/skills exists, the same folder is copied there.

5

Logs

Prints each installed path. Paste that output into handoffs/latest.md: it's the evidence that the step ran.

Key concepts

--both

Installs to Claude and Codex in the same run.

.bak-<timestamp>

Hidden backup alongside; rolling back is a cp -a.

~/.agents/skills

Second folder where Codex discovers skills.

Loud failure

Without a build, install stops and tells you what's missing.

5

📡 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 diff before deciding: the edit to the copy may be a good one.
  • If it is, move it into definition.md and 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=1 without 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

[ok]

Generated and installed are identical byte for byte.

[DRIFT]

They diverged; someone edited the copy. Exit code 1.

[não instalada]

The folder is missing on one side (not installed); not a failure.

Resolve at the source

You fix definition.md, never dist/.

6

🔌 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

Adapter skill

Depends on an MCP tool in the target runtime.

codex mcp add

Registers the server before you port the skill.

Key by reference

An environment variable, never the value in the command.

Not run

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

Manual copies drift apart — the three skills copied by hand into ~/projetos/dsh-skills show how two folders turn into two versions of the truth.
import → build → installSKILL.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.
drift[ok], [DRIFT] or [não instalada] per runtime, with exit code 1 on DRIFT. Always fix it at the source.
Adapter only after the MCPcodex 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