Context Mixer — Curated Agentic Context
Context Mixer is three command-line tools that manage the lifecycle of curated agentic context — agents, skills, and the engineering intents behind them — across AI coding assistants.
| Tool | Role |
|---|---|
| cmx | The package manager: installs, versions, updates, and reconciles agents and skills across every platform you use |
| cmf (Context Mixer Forge) | The compiler: assembles a profile-specific slice of an intent atlas into an installed agent or skill, and records exactly what it compiled |
| cmv (Context Mixer Verify) | The verifier: checks, deterministically and from source alone, that a repository holds the intents cmf compiled for it |
The intent atlas
cmf and cmv work from an intent atlas: an externally maintained repository of falsifiable engineering intents, organized by the ecosystems they apply to, with the validators that check each intent beside it and the sensors that recognize each ecosystem in a project. The reference atlas is svetzal/guidelines, one person's body of knowledge about how software should be built. Fork it or replace it with your own; the tooling carries no opinions of its own and verifies any atlas the same way. The loop is walked once in Compiling and Verifying Intents, and its reasoning is in Deterministic Verification of Intents.
cmx: the package manager
cmx manages the lifecycle of agents and skills for AI coding assistants — versioning, installation, updates, and distribution.
What are agents and skills?
| Artifact | Shape | Purpose |
|---|---|---|
| Agent | Single .md file with YAML frontmatter | Curated guidance for a tech stack — applies across many repositories |
| Skill | Directory with SKILL.md + supporting files | Composable tool capability — task-specific functionality |
What cmx does
- Source management — register git repositories or local directories as artifact sources (plugin marketplaces)
- Install & update — install agents and skills globally or per-project, across the platforms you use, tracking versions and checksums
- Status tracking — see what's installed, what's outdated, what's deprecated;
cmx doctorsurveys the whole system - Reconcile — promote in-place edits back to a canonical home, and sync a skill that has diverged across tools
- Sets — group installed artifacts into named, activatable sets so you can switch off the standing context cost of unrelated work without losing track of it
- LLM-powered diff — use AI to analyze differences between installed and source versions, directionally
- Cross-platform — works with Claude Code, GitHub Copilot, Cursor, Windsurf, Gemini CLI, opencode, Codex CLI, Pi, Crush, Amp, Zed, OpenHands, Hermes, and Devin
Quick example
# Add a source marketplace
cmx source add guidelines https://github.com/svetzal/guidelines
# Search across all sources
cmx search python
# Browse a specific source
cmx source browse guidelines
# Install an agent globally
cmx agent install python-craftsperson
# Install all available agents
cmx agent install --all
# Group installed artifacts into a set you can switch off later
cmx set create rust-work
cmx set add rust-work rust-craftsperson
# Check what needs updating
cmx outdated
# See an LLM-powered analysis of changes
cmx agent diff rust-craftsperson
# Update everything
cmx agent update --all
Where next
- Installation and the Quick Start for cmx.
- Compiling and Verifying Intents for the cmf and cmv loop.
- Writing Intents to author an atlas, including validators and sensors.
- Command references for cmx, cmf, and cmv.
Installation
Homebrew (macOS and Linux)
brew tap svetzal/tap
brew install cmx
From GitHub Releases
Download the latest binary for your platform from GitHub Releases.
| Platform | Archive |
|---|---|
| macOS (Apple Silicon) | cmx-darwin-arm64.tar.gz |
| macOS (Intel) | cmx-darwin-x64.tar.gz |
| Linux (x64) | cmx-linux-x64.tar.gz |
The archive holds all three binaries. Extract and place them somewhere on
your PATH:
tar xzf cmx-darwin-arm64.tar.gz
sudo mv cmx cmf cmv /usr/local/bin/
From source
git clone https://github.com/svetzal/context-mixer2.git
cd context-mixer2
./install.sh
install.sh installs cmx with the llm feature, installs cmf and cmv
lean (cmv has no features at all; it is deterministic by construction and never
talks to a model), and refreshes zsh completions at ~/.zfunc/_cmx when ~/.zfunc/ already exists.
Shell completions
cmx completions <shell> writes the generated completion script to stdout.
Supported shells are bash, zsh, fish, elvish, and powershell.
Zsh:
mkdir -p ~/.zfunc
cmx completions zsh > ~/.zfunc/_cmx
Then add ~/.zfunc to fpath and run autoload -Uz compinit && compinit.
Bash:
cmx completions bash | sudo tee /etc/bash_completion.d/cmx >/dev/null
Verify installation
cmx --version
cmf --version
cmv --version
Quick Start
1. Add a source
Sources are git repositories (or local directories) that contain agents and skills. Register one:
# From a git URL
cmx source add guidelines https://github.com/svetzal/guidelines
# Or from a local directory
cmx source add my-agents ~/Work/my-agents
You can also add Anthropic's official skills:
cmx source add anthropic-skills https://github.com/anthropics/skills
2. Browse available artifacts
cmx source browse guidelines
This shows all agents and skills in the source, with versions and deprecation status.
3. Install artifacts
# Install a single agent
cmx agent install python-craftsperson
# Install from a specific source
cmx agent install guidelines:python-craftsperson
# Install all available agents
cmx agent install --all
# Install into the current project (instead of globally)
cmx agent install python-craftsperson --local
4. Check status
# See everything installed
cmx list
# Check what's outdated
cmx outdated
5. Update
# Update a specific agent
cmx agent update python-craftsperson
# Update all tracked agents
cmx agent update --all
Where artifacts get installed
On a fresh setup with nothing tracked yet, cmx installs to Claude Code's global directories:
| Type | Global | Local |
|---|---|---|
| Agents | ~/.claude/agents/ | .claude/agents/ |
| Skills | ~/.claude/skills/ | .claude/skills/ |
Once you use more tools, a bare install lands on every platform already in
use so your set stays in sync — or pass --platform <tool> to target one. See
Installing Artifacts and
Platform Paths.
Sources
Sources are plugin marketplace repositories — git repos or local directories containing agents and skills.
Adding sources
# Git repository (cloned automatically)
cmx source add team https://github.com/acme/agent-standards
# Local directory
cmx source add guidelines ~/Work/guidelines
Listing sources
cmx source list
Browsing a source
cmx source browse guidelines
Shows all agents and skills with their versions. Deprecated artifacts are marked with ⛔.
Updating sources
Git-backed sources are automatically updated when stale (>60 minutes since last fetch) during browse, install, and outdated operations.
To explicitly update:
# Update a specific source
cmx source update guidelines
# Update all git-backed sources
cmx source update
Removing sources
cmx source remove guidelines
For git-backed sources, this also deletes the local clone.
Marketplace format
cmx supports two source formats:
-
Marketplace repos — contain
.claude-plugin/marketplace.jsonlisting plugins with their agents and skills. Compatible with Claude Code's native plugin system. -
Simple repos — any directory with
.mdagent files and skill directories containingSKILL.md. cmx auto-discovers these by walking the tree.
See Marketplace Structure for details on creating your own.
Searching
Search across all registered sources for agents and skills by keyword.
Usage
cmx search <keyword>
The search matches against both artifact names and descriptions (case-insensitive).
Example
$ cmx search python
Name Type Version Source Description
---------------------- ----- ------- ---------------- -----------
python-craftsperson agent 1.3.1 guidelines Use this agent when writing, reviewing...
uv-python-craftsperson agent 1.3.1 guidelines Use this agent when writing, reviewing...
2 result(s) found.
Searching across multiple sources
Search scans every registered source. If you have both your team's marketplace and Anthropic's skills registered, results from all sources appear together:
$ cmx search pdf
Name Type Version Source Description
------------- ----- ------- ---------------- -----------
canvas-design skill - anthropic-skills Create beautiful visual art in .png and .pdf...
pdf skill - anthropic-skills Use this skill whenever the user wants to do...
2 result(s) found.
Auto-update
Search triggers auto-update for stale git-backed sources (>60 minutes since last fetch) to ensure results are current.
After finding what you need
Install directly from the search results:
cmx skill install anthropic-skills:pdf
Installing Artifacts
Install a single artifact
cmx agent install python-craftsperson
cmx skill install skill-creator
If the artifact exists in multiple sources, cmx will ask you to disambiguate:
cmx agent install guidelines:python-craftsperson
Install all available
cmx agent install --all
cmx skill install --all
This installs every artifact from registered sources that isn't already tracked in the lock file with a matching version.
Local vs global install
By default, artifacts install globally (e.g. ~/.claude/agents/ or ~/.claude/skills/ for Claude Code).
Use --local to install into the current project:
cmx agent install python-craftsperson --local
This installs to the platform's project-scoped directory (e.g. .claude/agents/ for Claude Code).
Choosing a platform
By default — with no --platform — cmx installs to every platform already in
use, i.e. those with tracked artifacts at the target scope. A new install
joins the tools you actually run (e.g. Claude + Codex + Hermes) and stays in sync
across them; when nothing is tracked yet it falls back to Claude. Each landing is
reported on its own line, naming the platform.
Use --platform to constrain the install to one tool (which also onboards a new
one):
cmx agent install python-craftsperson --platform cursor
cmx skill install skill-creator --platform windsurf --local
You can also set the platform via environment variable:
export CMX_PLATFORM=copilot
cmx agent install python-craftsperson
Supported platform values: claude, copilot, cursor, windsurf, gemini,
opencode, codex, pi, crush, amp, zed, openhands, hermes, devin.
To make the in-use set explicit rather than inferred, declare a managed set with
cmx config platforms. See
Platform Paths for the full directory table.
What happens on install
- The artifact is copied to the appropriate platform directory
- A SHA-256 checksum is computed
- The version (if present in frontmatter) is recorded
- An entry is written to the platform's lock file with source, checksum, version, and timestamp
Listing & Status
List installed artifacts
# Everything
cmx list
# Agents only
cmx agent list
# Skills only
cmx skill list
The list view shows a table with status indicators:
Global agents:
Name Installed Source Available
-------------------------- --------- ---------- ---------
python-craftsperson 1.3.1 guidelines 1.3.1 ✅
rust-craftsperson 1.0.0 guidelines 2.0.0 ⚠️
clojure-craftsperson - guidelines 1.0.1 ⚠️
skill-writing 1.0.0 guidelines 1.0.0 ⛔
my-custom-agent - - -
Status indicators
| Icon | Meaning |
|---|---|
| ✅ | Up to date — installed version matches source |
| ⚠️ | Attention needed — untracked, behind, or version unknown |
| ⛔ | Deprecated — source has marked this artifact as deprecated |
| (blank) | No source available — manually installed, not from any registered source |
Check what's outdated
cmx outdated
Shows only artifacts that need attention — outdated, untracked, or changed in the source. Includes artifacts on disk that match a source but were never installed through cmx.
Managing Context Cost with Sets
An installed skill or agent is never free. Every assistant that injects skill/agent descriptions loads each installed artifact's trigger description on every turn, whether or not it fires. A machine curated to fifty skills carries fifty descriptions of fixed cost, most of them irrelevant to whatever you're doing right now.
cmx's usual lever for that cost is install/uninstall — presence on disk. That lever is too coarse for day-to-day use: uninstalling to reclaim context means losing the record that you ever wanted the artifact, and reinstalling means finding it in a source again. Sets add a second, cheaper lever — a named group of installed artifacts you can activate and deactivate together, so the context cost of one body of work can be switched on and off as a unit without losing track of what belongs together.
Creating a set and adding members
cmx set create rust-work --desc "Rust craftsmanship + foundry"
cmx set add rust-work rust-craftsperson
add resolves each name's kind and source from the lock file, so the
artifact must already be installed, and it accepts more than one name per
call. If a name is ambiguous across kinds, disambiguate with skill: /
agent: — e.g. cmx set add rust-work skill:foundry.
cmx set show rust-work
Set 'rust-work' (inactive)
Rust craftsmanship + foundry
Footprint: ~1.8k chars (not loaded)
agent rust-craftsperson (source: guidelines) [installed] ~1.8k chars
Activating and deactivating
activate and deactivate are plan-then-apply, like the safety model
elsewhere in cmx: the first run shows exactly what would change, and nothing
happens until you pass --apply.
cmx set deactivate rust-work # preview: what would be uninstalled
cmx set deactivate rust-work --apply # execute it
$ cmx set deactivate rust-work
Plan to deactivate set 'rust-work':
agent rust-craftsperson: uninstall
~/.claude/agents/rust-craftsperson.md
Re-run with --apply to make these changes.
Deactivating uninstalls every member not held by another active set and
marks the set inactive — but it does not forget the set's definition.
Reactivating later reinstalls fresh from each member's pinned source, so
there's never a stale, drifted copy sitting around while the set is off:
cmx set activate rust-work --apply
activate is idempotent — safe to re-run any time, including as a repair
after cmx doctor flags a set with a missing member.
Sharing a member across sets
If two sets both claim the same artifact, deactivating one leaves it installed as long as the other is still active — reference counting, not a plain uninstall. You never have to worry about one set's cleanup pulling an artifact out from under a different set that still needs it.
Local edits block deactivation
A member with local edits (the installed copy has drifted from what cmx
tracked) blocks its own uninstall during deactivate, so cmx never silently
discards edited content. Pass --force to discard the drift and uninstall it
anyway:
cmx set deactivate rust-work --apply --force
Reading the context-footprint column
cmx set list and cmx set show report a footprint_chars figure — the
total character count of the set's members' trigger descriptions, i.e. the
context weight the set carries while active:
cmx set list
Name State Members Footprint
--------- -------- ------- ---------
rust-work inactive 1 ~1.8k chars (not loaded)
Use this to compare sets before deciding what to switch off — a set with a heavy footprint of low-relevance skills is exactly the kind of thing worth deactivating while you're working on something unrelated.
Seeding a set from a plugin
A marketplace plugin already groups related agents/skills for distribution.
--from-plugin hands you that same grouping as a starting point for a set,
without installing anything:
cmx set create rust-work --from-plugin guidelines:rust-toolkit
From there the set is yours to curate — add or remove members regardless of which source or plugin they originally came from.
Worked example: turning off client work while blogging
Say you have a client-ort set of skills specific to one client's codebase,
and you're about to spend the afternoon writing a blog post — those skills'
trigger descriptions are pure overhead for that work:
cmx set deactivate client-ort --apply # reclaim the context while blogging
# ... write the post ...
cmx set activate client-ort --apply # back to client work tomorrow
Nothing about the client set's membership was lost in between — only its on-disk presence, and the context cost that came with it.
Checking consistency
cmx doctor cross-references every set's declared membership against what's
actually installed, and flags the same two situations described above under
its own reference entry: an active set
missing a member, or an inactive set with a member still lingering. Run
cmx doctor after any manual install/uninstall work to make sure your sets
still agree with reality.
Updating Artifacts
Update a single artifact
cmx agent update python-craftsperson
cmx skill update skill-creator
This reinstalls the artifact from its original source, updating the lock file with the new version and checksum.
Without --platform, update targets only the default platform (Claude).
--all means all tracked artifacts, not all platforms. If you want every
installed copy of a skill to match, use sync, not update.
Update all tracked artifacts
cmx agent update --all
cmx skill update --all
Updates every artifact in the lock file that has a different checksum than the current source version.
Decision rule: update pulls from source to one platform; sync reconciles
between install locations; promote pushes in-place edits back to the
canonical home.
After any mutation, re-run cmx doctor to confirm the drift is actually gone.
An exit 0 from update means the command ran, not that every sibling copy is
now reconciled.
Adopting untracked artifacts
If you have agents or skills installed on disk that weren't installed through cmx (e.g., manually copied), use install --all to adopt them:
cmx agent install --all
This will install (and track) every source artifact that isn't already in the lock file with a matching version — including ones already on disk.
Promoting Local Edits
cmx {skill,agent} promote <name> pushes in-place edits of an installed artifact
back into your canonical home — it is the mirror of
update.
updatepulls the source copy over your installed one, discarding any local edits.promotecopies your installed copy into the home and refreshes itshome-provenance lock baselines, so the artifact reads astrackedagain with your edits as the new canonical content.
The authoring loop
This supports the common way skills actually evolve: an assistant (or you) edits a skill where it's installed, then you promote those edits to the home so they become the source of truth — and project out from there.
# … an assistant improves ~/.agents/skills/personal-finance in place …
cmx skill diff personal-finance # see what changed, and in which direction
cmx skill promote personal-finance # make those edits canonical in the home
promote operates on the copy cmx diff shows — global scope preferred, then
project. After promoting, the home holds your edits and any other platform still
carrying the old content is reported as drifted and pointed at
sync.
Example
$ cmx skill promote personal-finance
Promoted 'personal-finance' (1.3.0) into the home: ~/.config/context-mixer/home/skills/personal-finance
re-tracked for: claude, codex
When promote is refused
The target is the home only, so promote declines cases where the installed
copy isn't a canonical home candidate, and steers you to the right command
instead:
| Situation | Why | Do this instead |
|---|---|---|
| Artifact is tracked from a git source | the source, not the home, is authoritative | edit the source clone and cmx <kind> update, or update --force to discard the edits |
| Artifact is untracked / orphaned | nothing tracks it yet | cmx <kind> adopt <name> (hand-authored) or cmx <kind> install <name> (source-backed) |
| A Codex agent | its installed copy is TOML, not canonical markdown | edit the markdown in the home or source directly |
promote vs sync vs update
promote— keep the installed edits, making them canonical in the home.sync— reconcile a skill's copies between install locations (no source involved); use this when you want every installed copy to match.update— pull the source over one installed copy (default platform unless--platformis given), discarding local edits;--forceoverwrites a modified one.
cmx skill diff recommends a direction and prints the exact promote /
update commands in its reconcile footer.
After promote, re-run cmx doctor to confirm the remaining platform copies
are actually reconciled.
Reconciling Across Platforms
cmx skill sync <name> reconciles a skill that has diverged across platforms
— different content in different install locations — by copying one copy over the
others, so every platform carries the same thing.
Unlike update (which pulls from a registered source) and promote (which
pushes into the home), sync works between install locations. That means
it also reconciles external skills and any skill with no source at all — the
cases update/promote can't touch.
cmx skill sync personal-finance
cmx doctor flags divergence as (diverged) and its hint routes here when the
skill is external or source-less.
Which copy wins
By default the newest version wins. Force the direction with --from,
preview with --dry-run, or reconcile project scope with --local:
cmx skill sync personal-finance --from codex # codex's copy wins
cmx skill sync personal-finance --dry-run # preview, write nothing
cmx skill sync personal-finance --local # project scope
$ cmx skill sync personal-finance
Reconciled 'personal-finance' from codex (v1.3.0):
updated ~/.claude/skills/personal-finance (v1.2.0 → v1.3.0) [claude]
When it can't auto-pick
If the differing copies are unversioned or share a version, sync won't
guess. It lists each diverging copy (platforms, location, size) and prints the
exact per-copy --from command so you can choose — scoped to a managed platform,
so a copy shared by the .agents/skills cohort reads as --from codex rather
than --from opencode. When the skill is also tracked from the home, it points
at promote as the make-one-copy-canonical-then-re-project
alternative.
Skills only
sync is skills-only for now. Agents are reformatted per platform (e.g. Codex
TOML), so a byte-level cross-platform comparison — and a straight copy between
locations — isn't meaningful. Reconcile agents through their source (update) or
the home (promote) instead.
sync vs promote vs update
sync— reconcile a skill's copies between install locations; no source or home involved. Use this when you want every installed copy to match. Newest version wins by default.promote— push the installed edits into the home, making them canonical.update— pull the source over one installed copy (default platform unless--platformis given).
After sync, re-run cmx doctor to confirm the divergence is gone. An exit 0
means the command ran, not that some other untouched copy could not still need
attention.
Bringing an Existing System Under Control
If you've used AI coding assistants for a while, your agents and skills are
probably scattered and unmanaged: hand-authored skills in ~/.claude/skills, a
tool's bundled defaults in ~/.hermes/skills, things you installed once and
edited, lock entries pointing at sources that moved. This guide walks the
real-world process of bringing that pile under cmx control, using the situations
you'll actually hit.
The whole process is driven by one read-only command:
cmx doctor
doctor surveys every supported platform's install directories and lock files
and classifies each artifact. It changes nothing — it just tells you the truth.
A typical first run on a lived-in system:
Summary: 18 tracked, 3 drifted, 0 untracked, 43 orphaned, 0 external, 1 missing · 0 diverged.
Work through it one state at a time. Each section below is a state doctor
reports, what it means, and how to resolve it.
missing — a lock entry with no file
A lock file records the artifact, but it's gone from disk. Usually it was
deleted out-of-band (not via cmx uninstall, which would have cleared the lock
entry too).
First decide whether it's recoverable. If a registered source still provides it, reinstall:
cmx skill install <name>
If the source no longer has it either — it was genuinely retired — the lock entry is just cruft. Clear it:
cmx skill uninstall <name>
uninstall reconciles a missing entry even though the file is already gone: it
removes the stale lock entry and tells you the file was already absent. (Check
the source before concluding it's gone — see the drift section below, where a
"vanished" skill turned out to have just moved.)
drifted — tracked, but edited since install
The installed copy's checksum no longer matches what was recorded at install. Three things masquerade as drift; diagnose before you act.
1. Transient build artifacts (not real drift)
A skill that ships runnable scripts will look drifted the moment you run them —
npm install creates node_modules/, Python creates __pycache__/. cmx
ignores these (and *.pyc, .git/, .DS_Store) when checksumming, so they
shouldn't cause drift. If a script-bearing skill is still drifted after an
upgrade, the change is in real content, not the artifacts.
2. The source moved or advanced, your copy didn't change
Compare your installed copy against the current source. If they're identical
(ignoring node_modules), your copy has no real edits — the drift is just a
stale install-time snapshot. Re-sync:
cmx skill update <name>
A copy that already matches the source isn't treated as a local edit, so no
--force is needed; the update just refreshes the lock entry.
Watch for relocations. A source repo restructure can move a skill (e.g. from
skills/xtoplugins/foo/skills/x) without removing it.cmx search <name>finds it by name regardless of path — so confirm a skill is truly gone before treating it as unrecoverable. cmx tracks by name, soupdatestill works across a relocation.
3. Your copy is genuinely ahead of the source
Sometimes you improved the installed copy and the work never went back upstream
— extra files, a sharper prompt. diff your copy against the current source to
see exactly what you added. You have two good options:
- Push it upstream (preferred when the source is yours or accepts
contributions): copy your version into the source repo, commit and push, then
cmx skill update <name>re-syncs cleanly (no--forceneeded once your copy matches the source) and the drift disappears — with your work now shared. - Keep it local: leave it as-is for now, or canonicalize it (see orphaned below — though adopt currently targets orphans, not drifted artifacts).
Either way, never update --force a genuinely-ahead copy without capturing
your changes first — force overwrites them with the source version.
untracked vs orphaned — the distinction that keeps adoption safe
Both mean "on disk, no lock entry." The difference is whether a registered source provides it, and it determines the right fix:
-
untracked— a source does provide it; you installed it out-of-band. The fix is to track it, recording provenance:cmx skill install <name> -
orphaned— no source provides it; it's hand-authored (or from a source you never registered). This is the adopt candidate — see below.
This split is why adoption is safe: adopt and --adopt-all act only on
orphaned artifacts. Naming an untracked artifact to adopt steers you to
install instead, so you never adopt source-backed content as if it were your
private work.
orphaned — your hand-authored artifacts → adopt into the home
Orphaned artifacts have no source, so they belong in your canonical home
(~/.config/context-mixer/home by default) — the tool-neutral source of truth
that survives switching assistants. Adopting copies the artifact verbatim into
the home, registers the home as a source, and records provenance so it
reclassifies to tracked. The original is never moved.
Not everything orphaned is yours
The trap: a tool's bundled/stock skills also show up as orphaned (they
shipped with the tool, untracked). Adopting those into your home pollutes it
with a vendor's defaults. So a blanket cmx doctor --adopt-all is usually too
blunt — it adopts every orphan, including the stock bundle.
Curate instead. List the orphans and their locations:
cmx doctor # the Location column tells you where each lives
Then adopt selectively. By location (everything you authored lives in one place):
cmx skill adopt --all --from ~/.claude/skills
…which adopts your skills under that directory while leaving, say,
~/.hermes/skills (a tool's stock bundle) untouched. Or by name, for a hand-
picked set:
cmx skill adopt clipboard gilt foundry whatsapp
Named adoption is all-or-nothing — if any name isn't an adoptable orphan, the batch aborts with the reason, and nothing is copied.
external — artifacts another tool manages
Some orphans aren't yours and never will be: a tool's bundled/stock skills,
sitting in its own directory. You don't want to adopt them, but you also don't
want doctor nagging about them forever. Declare them external — cmx then
reports them as external (a steady state, not flagged) and adoption skips them
entirely. (The one thing doctor will still surface is a divergence — an
external artifact whose copies disagree across locations — since that's a real
anomaly, even though its owning tool, not cmx, is the one to re-sync it.)
cmx config external add ~/.hermes/skills # the whole stock directory
cmx config external add some-vendored-skill # or a single artifact by name
cmx config external list # review the rules
A directory rule (with ~ expanding to your home) covers everything under it,
including skills the tool adds later; a bare name matches that artifact
anywhere. External artifacts still appear in doctor for visibility — they just
no longer count toward the non-zero exit, and cmx <kind> adopt refuses them
(pointing you to remove the rule first if you ever do want to manage one).
This is the difference between orphaned (yours, hand-authored → adopt) and external (another tool's → leave it): both have no cmx lock entry, but only the first is your responsibility.
Projecting outward (the payoff)
Once your artifacts are in the home, it's a normal registered source. Migrating your curated set onto another tool is one command per target:
cmx skill install --all --platform opencode # or codex, pi, hermes, …
This is the point of the whole exercise: you curate once, in a tool-neutral home, and project into each assistant's native location and format — so your library outlives any single tool.
Converging
Re-run cmx doctor after each step. doctor exits non-zero while any drift,
untracked, orphaned, or missing artifact remains (external and tracked don't
count), so you can drive it to a clean resting point — and even wire it into a
hook or CI check once you're there. A fully-curated system looks like:
Summary: 40 tracked, 0 drifted, 0 untracked, 0 orphaned, 24 external, 0 missing · 0 diverged.
…where the external count is a tool's stock bundle you've deliberately marked
(see above), so doctor exits zero — everything that's yours is tracked, and
everything that isn't is acknowledged but unflagged.
One skill, many tools. A skill you've projected to several assistants is reported as a single tracked artifact whose
Platformscolumn lists every surveyed copy asplatform@version— not as "duplicates."divergedis reserved for the rare case where copies' content actually disagrees across locations, even when the displayed versions happen to match. An agent's Codex copy is generated TOML, so it is compared through the Codex projection of the Markdown copy rather than byte-for-byte: reformatting alone is never divergence, but a hand-edited or stale TOML (including one written by a cmx before 3.2.0, which lacks the preserved frontmatter block) is —cmx agent install <name> --forcerewrites it.doctor's hint names the fix that fits the artifact's provenance:cmx skill sync <name>reconciles copies between install locations (the right move for anexternalor source-less skill), whilecmx <kind> update <name> --force(re-sync from the source) orcmx <kind> promote <name>(keep the edited copy) apply when the artifact is tracked from a source or the home.cmx skill diff <name>shows which copy differs first.
Diffing with LLM Analysis
cmx diff shows how an installed artifact differs from its source — directionally,
so you can see which copy holds which change and pick the right way to reconcile.
cmx agent diff python-craftsperson
cmx skill diff personal-finance
If every installed copy is byte-identical to the source, cmx reports "matches"
without calling the LLM. (The diff command requires a build with the llm
feature; a lean build doesn't include it.)
What you get
The output names both sides with their real names — the source (home, or
the repo) and the platform whose copy is shown (e.g. codex) — and uses them
consistently, so you never have to map "installed"/"source" onto an actual copy.
It stays compact by default:
- a header naming each side, its path and version, and flagging the installed copy as edited locally when its bytes no longer match the lock baseline;
- a per-file change summary —
Mmodified (with+added −removedcounts),Aadded (only in the installed copy),Ddeleted (only in the source) — under a stated convention:−lines are the source,+lines are the installed copy; - an LLM summary of what changed and which direction it recommends;
- a reconcile footer offering both directions and picking neither.
The full line-by-line unified diff prints only with --full (a one-line hint
points there), so a large change reads in ~20 lines instead of hundreds:
cmx skill diff personal-finance --full
Example output
Comparing personal-finance (skill) vs home
codex ~/.agents/skills/personal-finance (1.3.0, edited locally)
home ~/.config/context-mixer/home/skills/personal-finance (1.3.0)
− lines are home, + lines are codex:
Changed files:
M SKILL.md +12 −3
A helpers/categorize.py only in codex
Summary:
The installed copy adds a categorisation helper and tightens the SKILL.md
trigger wording. Behavioural addition, not a fix — keep it if the edits are
yours.
Reconcile — pick a direction:
keep codex's edits — copy codex into home cmx skill promote personal-finance
discard codex's edits — pull home over codex cmx skill update personal-finance --force
(overwrites your local edits)
(run with --full to see the line-by-line diff)
The two reconcile directions map to two commands: promote keeps the
installed edits by copying them into the home (offered when the source is the
home — see Promoting Local Edits), and update --force
discards them by pulling the source over the installed copy. diff recommends a
direction but commits to neither.
Multi-platform aware
diff agrees with cmx doctor: it surveys every
installed copy of a skill across your managed platforms, not just the active
one. When more than one copy exists it shows a per-platform matrix — which copies
match the source, which differ — and focuses the detailed diff on a copy that
actually differs, so it never reports "matches home" while another platform's
copy has drifted:
claude ~/.claude/skills/personal-finance matches home
codex ~/.agents/skills/personal-finance differs from home (+12 −3) ← detailed below
The reconcile commands are qualified with --platform for the focused copy
(preferring a managed platform, e.g. --platform codex over opencode), and a
fully-consistent skill reports "matches home on all N installed copies". Agents
stay single-copy — they're reformatted per platform (e.g. Codex TOML), so a
byte-level cross-platform comparison isn't meaningful.
Works with untracked artifacts
diff works on any installed artifact that has a matching source — even ones not
tracked in the lock file. It finds the file on disk and compares directly.
LLM configuration
See Configuration to set up the LLM gateway and model.
Configuration
cmx stores configuration in ~/.config/context-mixer/config.json.
View current settings
cmx config show
LLM Gateway
The diff command uses an LLM for analysis. By default, cmx uses OpenAI with the gpt-5.4 model.
Set the gateway
# OpenAI (default) — uses $OPENAI_API_KEY
cmx config gateway openai
# Ollama (local)
cmx config gateway ollama
Set the model
# OpenAI models
cmx config model gpt-5.4
# Ollama models
cmx config gateway ollama
cmx config model qwen3.5:27b
Managed platforms
By default cmx infers which platforms to act on: a bare install targets the
platforms already in use, while uninstall and doctor consider every supported
platform. Declare a managed set to make that explicit and authoritative — when it
is non-empty, a default (no --platform) install/uninstall acts on exactly
those platforms and doctor surveys only those:
cmx config platforms add claude
cmx config platforms add codex # cmx now manages exactly claude + codex
cmx config platforms list
cmx config platforms remove codex
The set is stored as lowercase names in config.json ("platforms": ["claude", "codex"]) and shown by cmx config show (as (inferred) when unset). This is
the clean way to onboard a tool before its first install, and to stop cmx from
scanning tools you don't use. An explicit --platform still overrides the set
for a single command. See the
command reference for details.
External rules
Artifacts another tool manages — e.g. a tool's bundled/stock skills in its own
directory — can be declared external so cmx doctor reports them as external
(a steady state, not flagged) instead of as orphaned, and so adopt never sweeps
them into your home:
cmx config external add ~/.hermes/skills # a whole tool's skill directory
cmx config external add some-skill # a single artifact by name
cmx config external list
cmx config external remove some-skill
Each rule is either a directory (an install location — ~ expands to your
home, and it covers everything under it) or a bare artifact name (matched
wherever it lives). See
Bringing a System Under Control for how external rules fit
the adoption workflow.
Environment variables
| Variable | Purpose |
|---|---|
OPENAI_API_KEY | API key for OpenAI gateway |
OLLAMA_HOST | Ollama server URL (default: http://localhost:11434) |
Compiling and Verifying Intents
cmx installs agents and skills that someone already wrote. cmf and cmv close a different loop: cmf compiles engineering intents from an intent atlas into the guidance a project's agents read, and cmv verifies, from the project's source alone, that the code holds those intents. This page walks the loop once, end to end. The flags are in the cmf and cmv references.
What you need
-
An intent atlas — a git repository of intent records with validators beside them and an
ecosystems.tomldeclaring what it can recognize. The reference atlas is svetzal/guidelines. Register it as a cmx source so both tools can find it by name:cmx source add guidelines https://github.com/svetzal/guidelines -
A profile — the TOML file that names the slice of the atlas your project wants: which intents, for which ecosystems, on which delivery surface. A profile lives in the atlas under
profiles/or anywhere you keep it; see Profile schema. -
cmf and cmv on your PATH — both ship with cmx (see Installation).
1. Compile guidance into the project
From the project root, with the atlas checked out at ~/atlas:
cmf --root ~/atlas install profiles/rust-service --local
The preview shows what will be written. It also senses the project's ecosystems with the atlas's sensors and warns when the profile targets one the project does not show — compiling a Rust profile into a Python service is almost always the wrong profile. When the plan looks right:
cmf --root ~/atlas install profiles/rust-service --local --apply
To install for Codex alone, add --platform codex to both the preview and
apply commands. An agent profile then writes a generated TOML agent under
.codex/agents/; a skill profile writes SKILL.md under .agents/skills/.
Your project's existing AGENTS.md stays as it is. Without --platform,
cmf continues to use the platforms resolved from cmx configuration and locks.
Two things land:
- The assembled agent or skill, installed into the project-local locations of
your configured platforms — the same places
cmx agent install --localuses. .context-mixer/cmf-manifest.json, the compile manifest: exactly which intents went into the guidance, each record's checksum, the profile and its ecosystems, and the atlas revision cmf read. This is the contract cmv verifies against. Commit it. A project without a manifest has nothing to be held to.
cmf assemble <profile> --explain shows the selection without installing
anything, including which records the ecosystem filter excluded and which
specializations were pulled in for your ecosystems.
2. Tell the validators what only your project knows
Most validators read the code and need nothing else. A few need a fact the
atlas cannot know — which literals mark your business rules, which symbols
count as blocking in your domain. Those go in cmv.toml at the project root,
one table per intent, handed to that intent's validators verbatim:
[intent."craftsperson/rust/isolate-functional-core-from-effects"]
business_rule_pattern = "\\b10_?000\\b|\\b500\\b|\\b20\\b"
business_rule_minimum_matches = 2
Skip the file entirely if no validator asks for anything; every intent then
receives an empty configuration. cmv explain <intent> shows the exact
document a validator would receive.
3. Verify
cmv check
cmv reads the manifest, finds the atlas (through the cmx source registry, or
the path the manifest recorded, or --atlas <path>), detects the project's
ecosystems with the atlas's sensors, runs each compiled intent's validators
for those ecosystems, and reports one line per intent:
PASS craftsperson/rust/put-gateways-at-effect-boundaries
src/source.rs:10
FAIL craftsperson/rust/use-structured-tracing (required)
production code uses println! at src/lib.rs:42
src/lib.rs:42
N/A craftsperson/rust/prefer-fakes-at-boundaries
no tests, so nothing was substituted either way
1 pass, 1 fail, 1 not applicable, 0 unchecked, 0 unguided; adherence 50.0%
Every verdict comes from a validator parsing the code. Nothing asks a model, reads a transcript, or runs your project. The same inputs produce the same output, byte for byte.
The states, and what they do to the exit code:
| State | Meaning | Exit |
|---|---|---|
PASS | The code holds the intent | — |
FAIL | It does not | 1 when the validator is required |
N/A | The intent's condition never arose (a "mock only owned boundaries" rule against a suite with no mocks) | — |
UNCHECKED | No verdict: no validator for your ecosystems, or the validator crashed, timed out, or could not start — the reason is printed | 1 only with --strict |
UNGUIDED | The manifest dropped this intent, so the guidance never reached the project | — |
Exit 0 means every required, applicable intent held. Exit 2 means cmv could
not reach a verdict at all: no manifest, an unreadable atlas, bad usage.
--json emits the full report for CI and for tooling.
4. Put it in CI
cmv is a linter as far as CI is concerned. It needs the atlas at the revision the manifest pinned, so clone the atlas with history rather than shallowly:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: svetzal/guidelines
path: atlas
fetch-depth: 0
- run: cmv check --atlas ./atlas --strict
--strict turns an unchecked intent into a failure, which is what you want in
CI: a validator that could not run should not read as a pass. Leave it off
locally while a language's validators are still being written.
5. When the atlas moves
The manifest pins the atlas revision cmf compiled from. When the atlas's HEAD
has moved past that pin, cmv still verifies against the pinned tree — it
materializes that revision into a scratch directory and runs the validators
from there — so a corrected validator upstream never silently changes a
verdict you already have. The report then adds one informational line:
atlas has moved: HEAD 9f3c1e2a7b04 vs pinned a1b2c3d4e5f6; 2 compiled records or validators changed; re-run cmf install to recompile
Intents whose record or validator changed at HEAD are marked stale. None
of this changes the exit code. The remedy is always to recompile with
cmf install, because a newer atlas may change which intents apply, and that
decision belongs to cmf. cmv never re-pins on its own.
cmv status shows each compiled artifact's pin, the atlas's HEAD, which tree
was verified, the detected ecosystems, and how many compiled intents have a
validator, without running anything. Use --artifact <name> to inspect one
artifact in a multi-profile project.
6. Understanding one intent
cmv explain craftsperson/rust/use-structured-tracing --artifact AGENTS
shows how the manifest entry resolved to a record, every validator the record declares, which of them would run for the detected ecosystems and with exactly which command line, the configuration document they would receive, and whether the record is stale. A project with multiple compiled artifacts must select one; cmv lists the available identities if the selection is ambiguous. Use it when a verdict surprises you, before changing either the code or the atlas.
For validator authors
While correcting a validator in a working copy of the atlas, verify against that working tree instead of the pin:
cmv check --atlas ~/atlas --at-head
A validator change is not done until the atlas's own calibration gate is green: every validator ships with a fixture it passes and one it does not, and the gate runs them all twice and requires identical output. See Writing Intents.
Writing Agents
An agent is a single .md file with YAML frontmatter that provides curated guidance for a tech stack.
Minimal agent
---
name: python-craftsperson
description: |
Use this agent when writing, reviewing, or maintaining Python code.
model: sonnet
---
You are an expert Python developer...
Required fields
| Field | Description |
|---|---|
name | Kebab-case identifier |
description | When to use this agent — include examples for better matching |
model | sonnet, opus, haiku, or inherit |
Optional cmx fields
| Field | Description |
|---|---|
version | Semver version (e.g., 1.3.1) |
deprecated | true to mark as deprecated |
deprecated_reason | Why it's deprecated |
deprecated_replacement | Name of the replacement artifact |
Agent body
The markdown body after the frontmatter closing --- becomes the agent's system prompt. Write it in second person ("You are...").
File naming
The file name (without .md) becomes the artifact name used by cmx. For example, python-craftsperson.md → artifact name python-craftsperson.
Writing Skills
A skill is a directory containing a SKILL.md file with YAML frontmatter, plus optional supporting files.
Directory structure
my-skill/
├── SKILL.md # Required: frontmatter + instructions
├── scripts/ # Optional: automation scripts
├── references/ # Optional: detailed documentation
└── examples/ # Optional: templates, samples
Minimal skill
---
name: my-skill
description: What this skill does and when to use it.
---
# My Skill
Instructions for the AI agent...
Required fields
| Field | Description |
|---|---|
name | Kebab-case identifier — must match the parent directory name |
description | What + when — used for matching against user requests |
Optional cmx fields
| Field | Description |
|---|---|
version | Semver version |
deprecated | true to mark as deprecated |
deprecated_reason | Why it's deprecated |
deprecated_replacement | Name of the replacement artifact |
Follows the Agent Skills specification
cmx skills follow the Agent Skills specification, making them compatible with Claude Code, GitHub Copilot, Cursor, and other AI coding assistants.
Directory naming
The directory name becomes the artifact name. For example, skills/blog-image-generator/SKILL.md → artifact name blog-image-generator.
Marketplace Structure
A marketplace is a git repository that distributes agents and skills. cmx supports the Claude Code plugin marketplace format and a simple fallback.
Plugin marketplace (recommended)
Add a .claude-plugin/marketplace.json to your repo. This is compatible with Claude Code's native plugin system.
cmx supports three plugin declaration styles within marketplace.json, matching the formats used across Anthropic's own repositories.
Format 1: Explicit agents/skills arrays
Best when you want precise control over which artifacts are exposed:
my-marketplace/
├── .claude-plugin/
│ └── marketplace.json
├── agents/
│ ├── python-craftsperson.md
│ └── typescript-craftsperson.md
├── skills/
│ ├── blog-image-generator/
│ │ └── SKILL.md
│ └── code-review/
│ └── SKILL.md
└── README.md
{
"name": "my-marketplace",
"owner": { "name": "Your Name" },
"plugins": [
{
"name": "my-plugin",
"description": "Description of this collection",
"source": "./",
"agents": [
"./agents/python-craftsperson.md",
"./agents/typescript-craftsperson.md"
],
"skills": [
"./skills/blog-image-generator",
"./skills/code-review"
]
}
]
}
Paths in agents and skills arrays are resolved relative to the repository root.
Format 2: Source path without explicit arrays
Best when each plugin lives in its own subdirectory. cmx walks the directory to discover artifacts automatically:
my-marketplace/
├── .claude-plugin/
│ └── marketplace.json
├── plugins/
│ ├── code-review/
│ │ ├── reviewer.md
│ │ └── review-skill/
│ │ └── SKILL.md
│ └── commit-tools/
│ └── committer.md
└── README.md
{
"name": "my-marketplace",
"owner": { "name": "Your Name" },
"plugins": [
{
"name": "code-review",
"description": "Automated code review",
"source": "./plugins/code-review"
},
{
"name": "commit-tools",
"description": "Git commit workflows",
"source": "./plugins/commit-tools"
}
]
}
When no agents/skills arrays are present, cmx resolves the source path and walks that directory to find .md agents and SKILL.md skills.
Format 3: Remote source objects (not yet supported)
The official Claude Code plugin format also supports remote sources (url, github, git-subdir, npm). cmx recognizes these entries and emits a warning. Future versions may add support for fetching remote plugin sources.
{
"plugins": [
{
"name": "some-plugin",
"source": {
"source": "url",
"url": "https://github.com/example/plugin.git",
"sha": "a1b2c3d4..."
}
}
]
}
Scanning priority
- If
agents/skillsarrays exist on a plugin entry, cmx uses them directly. - If only a
sourcepath string is present, cmx walks that directory. - If
sourceis an object (remote), cmx warns and skips.
Simple directory (fallback)
Without marketplace.json, cmx walks the entire tree looking for:
.mdfiles with frontmatter containingnameanddescription→ agents- Directories containing
SKILL.mdwith frontmatter → skills
This works for quick setups but the marketplace format is preferred for explicit control and Claude Code compatibility.
Registering with cmx
# As a git source
cmx source add my-marketplace https://github.com/you/my-marketplace
# As a local directory
cmx source add my-marketplace ~/Work/my-marketplace
Compatibility
A marketplace registered with cmx can also be used directly with Claude Code:
/plugin marketplace add https://github.com/you/my-marketplace
Creating Plugins
A plugin is a directory that bundles agents and skills into a distributable unit within a marketplace. Each plugin has its own metadata, version, and category.
Plugin structure
my-plugin/
├── .claude-plugin/
│ └── plugin.json
├── agents/
│ ├── reviewer.md
│ └── planner.md
└── skills/
└── code-review/
└── SKILL.md
The .claude-plugin/plugin.json file identifies the directory as a plugin:
{
"name": "my-plugin",
"version": "0.1.0",
"description": "A collection of code review tools",
"author": {
"name": "Your Name",
"email": "you@example.com"
},
"license": "MIT",
"keywords": ["review", "quality"]
}
Plugin scaffolding, validation, marketplace generation, and manifest projection are outside cmf. cmf materializes structured intents and installs the resulting artifact directly through cmx-core; use the repository's chosen publishing workflow when a distributable plugin is actually required.
Plugin categories
Plugins can be organized into categories via the category field in marketplace.json:
| Category | Purpose |
|---|---|
ecosystem | Language and framework tooling (e.g., rust-craft, python-craft) |
functional | Cross-cutting capabilities (e.g., code-review, documentation) |
personal | Individual workflow preferences and style guides |
Categories are optional. Plugins without a category appear as "uncategorized" in status output.
Writing Intents
Intent records are the canonical source for guidance. Each TOML file captures one falsifiable engineering intention: the capability it enables, the threat it addresses, the expectation behind it, the strategy delivered to an agent, and the tradeoff incurred by following it.
Generated agents and skills are delivery artifacts. Do not treat them as a second authoring source.
Repository structure
Intent records live below intents/. Nested directories express collections
and specialization contexts:
intents/
└── craftsperson/
├── verify-before-declaring-completion.toml
└── rust/
└── require-green-cargo-tests.toml
The first record's key is
craftsperson/verify-before-declaring-completion; the nested record's key is
craftsperson/rust/require-green-cargo-tests.
Record shape
id = "guidelines.intent.verify-before-declaring-completion"
title = "Verify before declaring completion"
category = "quality"
tags = ["verification", "completion", "evidence"]
status = "hypothesized"
confidence = 0.99
capability = "Completion claims describe checked behavior."
threat = "A contributor reports success from partial evidence."
expectation = "Local edits can fail tests or integration checks."
strategy = "Review the diff, run proportionate checks, and disclose gaps."
tradeoff = "Verification adds latency."
[[relations]]
type = "specializes"
target = "craftsperson/verify-work"
category is the primary stable area of concern. Tags provide lateral
classification. Relationships form the semantic graph: specializes makes
general guidance concrete for a narrower context, while related-to records a
meaningful non-hierarchical association.
Ecosystems
The directory hierarchy is the realization hierarchy: craftsperson/python/
specializes the general craftsperson catalogue for Python, and
craftsperson/python/uv/ narrows that to uv-based projects. A record's
ecosystem qualifiers are the key segments between the collection root and
the slug — ["python", "uv"] for craftsperson/python/uv/pin-interpreter,
none for a general record. No record field restates this; the tooling derives
it from the key.
A profile declares the ecosystems it targets, and cmf admits a record only when
every qualifier it has is declared, so a Python profile never compiles Java
advice. With the ecosystems declared, cmf also walks specializes edges
downward: general advice selected by category and tag compiles to the
ecosystem's own version of it. See
Ecosystems in the cmf reference.
The atlas declares which ecosystems it supports, and how to recognize each at
a project root, in ecosystems.toml beside intents/:
[python]
signatures = [
{ file = "pyproject.toml" },
{ file = "setup.py" },
{ glob = "requirements*.txt" },
]
[uv]
implies = ["python"]
signatures = [
{ file = "uv.lock" },
{ file = "pyproject.toml", contains = "[tool.uv]" },
]
Every name must be a directory the hierarchy uses, and a nested ecosystem must
implies its parent; cmf and cmv check this against the records when they
read the atlas. cmf uses the detected set to warn when a profile targets an
ecosystem the project does not show; cmv uses it to choose which validators
run. An atlas with no sensor file detects nothing, and both tools say so rather
than guessing. The predicate kinds and their rules are in
Sensors.
Validators
A record may declare, as an evidence entry, an executable that decides from a project's source alone whether the code holds the intent:
evidence = [
{ type = "architecture_review", description = "Core modules depend on project-owned gateway contracts rather than concrete vendor clients.", required = true },
{ type = "static-check", language = "rust", run = "checks/rust/put-gateways-at-effect-boundaries.py", description = "A project-owned gateway trait is declared, implemented by a concrete adapter, and depended upon by at least one module that performs no I/O.", required = true },
]
descriptionis what an agent sees — cmf renders it into the guidance under Require — so write it as the observable expectation the check enforces, not as "runs a script".languageis the source language the validator reads, in the atlas's ecosystem vocabulary; cmv runs the entries matching the ecosystems it detects.runis a path relative to the atlas root. Any executable works; the validator owns its own fact extraction (Python'sast, asynbinary for Rust).requireddecides whether a failing verdict fails the verification run.
A static-check entry with neither language nor run is ordinary descriptive
evidence — an expectation a validator may later make executable. An entry with
only one of the two is rejected when the atlas is read.
cmv runs a validator as <atlas-root>/<run> --workspace <project> --config <json> and expects one JSON document on stdout: applicable, followed,
signals, evidence, and locations. Anything else — a crash, a timeout,
unparseable output — leaves the intent unchecked with the reason; cmv never
guesses. The contract is specified in
How validators run.
A validator that cannot fail measures nothing. Every validator ships with a
fixture it is expected to pass and one it is expected to not pass, in the
language it validates, and the atlas's own calibration gate runs all of them
twice, requiring byte-identical output. A validator change is not done until
that gate is green. The reference atlas keeps its validators, fixtures, and
gate under checks/ and documents the layout in its own README.
Materialization and verification
Intent authoring — records, validators, and sensors — belongs to the atlas and its own tooling. cmf and cmv read the atlas and never write to it. Once the collection is valid, a materialization profile consumes it:
cmf assemble universal-craftsperson --explain
cmf scans the relevant TOML fields, filters by the profile's ecosystems,
expands only the graph relationships the profile permits, and emits an agent or
skill without changing the atlas. cmf install --local --apply installs that
artifact into a project and writes the compile manifest beside the
project's lock file: exactly which records went in, at which checksums, from
which atlas revision. cmv reads the manifest to hold the project to those
intents; see Compiling and Verifying Intents.
Versioning
cmx tracks artifact identity with two complementary mechanisms.
Checksums (automatic)
SHA-256 checksums are always computed — no author effort required. They detect that something changed, but not what or how significant.
| Comparison | Detects |
|---|---|
| Installed vs source | Source has been updated since install |
| Installed vs lock file | Local copy was hand-edited |
Versions (opt-in)
Add a version field to your frontmatter:
---
name: python-craftsperson
description: ...
version: 1.3.1
model: sonnet
---
Versions communicate significance of changes. Use semantic versioning:
- Major (2.0.0) — significant rewrites, changed philosophy or approach
- Minor (1.1.0) — new capabilities, added guidance
- Patch (1.0.1) — small fixes, formatting, metadata updates
How they work together
| Installed | Source | cmx shows |
|---|---|---|
| Checksums match | — | ✅ Up to date |
| Versions differ | 1.0 → 1.1 | ⚠️ Update available |
| No version, checksum differs | — | ⚠️ Source has changed |
| No lock entry, source has version | — | ⚠️ Untracked |
Design principle: checksums are free and always present; versions are opt-in and add human-meaningful context when authors choose to provide them.
Deprecation
Mark agents or skills as deprecated when they should no longer be used.
Frontmatter fields
All three fields are progressive — use as many as apply:
---
name: skill-writing
version: 1.0.0
deprecated: true
deprecated_reason: Superseded by Anthropic's skill-creator
deprecated_replacement: skill-creator
description: ...
---
| Field | Required | Description |
|---|---|---|
deprecated | Yes | Set to true |
deprecated_reason | No | Why it's deprecated — include actionable info |
deprecated_replacement | No | Name of the replacement artifact |
How it surfaces
In source browse
Skills:
blog-image-generator v1.0.0
skill-writing v1.0.0 ⛔ DEPRECATED: Superseded by skill-creator (use skill-creator instead)
In list
The status column shows ⛔ instead of ✅, even if the installed version matches the source.
In outdated
Deprecated artifacts appear in the outdated list so users know to take action.
Best practices
- Include actionable instructions in
deprecated_reason— tell users how to get the replacement - If the replacement is in a different source, include the
cmx source addcommand in the reason - Set
deprecated_replacementonly if the replacement is installable via cmx
Command Reference
Global options
| Option | Description |
|---|---|
--platform <platform> | Target a single platform: claude, copilot, cursor, windsurf, gemini, opencode, codex, pi, crush, amp, zed, openhands, hermes |
The --platform flag is global and can be placed anywhere on the command line.
It can also be set via the CMX_PLATFORM environment variable.
When omitted, install and uninstall act across every platform already in
use (those with tracked artifacts) — so a curated set stays in sync across the
tools you actually run — while single-target commands (info, update,
adopt, …) default to Claude. Pass --platform <tool> to constrain an
operation to one platform (which also onboards a new tool on install). See
Platform Paths for the full directory table, and
cmx config platforms (below) to make the managed set explicit.
Source management
| Command | Description |
|---|---|
cmx source add <name> <path-or-url> | Register a marketplace source |
cmx source list | List registered sources |
cmx source browse <name> | Show available artifacts in a source |
cmx source update [name] | Fetch latest for git sources (all if no name) |
cmx source remove <name> | Unregister and clean up clone |
Agent management
| Command | Description |
|---|---|
cmx agent install <name>... | Install one or more agents from sources |
cmx agent install <source>:<name> | Install from a specific source |
cmx agent install --all | Install all available agents |
cmx agent install <name> --local | Install into current project |
cmx agent install <name> --platform cursor | Install to Cursor |
cmx agent update <name> | Update an agent from its source (replaces the installed copy with the source's) |
cmx agent update --all | Update all tracked agents |
cmx agent promote <name> | Push in-place edits of the installed copy back into the canonical home — the mirror of update |
cmx agent uninstall <name>... | Uninstall one or more agents; sweeps every platform unless --platform is given |
cmx agent adopt <name>... | Adopt one or more orphaned, hand-authored agents into the canonical home |
cmx agent unadopt <name>... | Remove agents from the home (originals stay); --external also marks them external |
cmx agent list | List installed agents (cmx-managed); --all includes external |
cmx agent info <name> | Show source, version, activation trigger, and (in an llm build) a summary |
cmx agent diff <name> | Directional diff vs the source; --full for the line-by-line view (requires llm feature) |
install and uninstall accept multiple names in one command (e.g.
cmx agent install a b c). Both are best-effort: each name is processed
independently and per-name failures are collected and reported rather than
aborting the batch. install exits non-zero if any name failed; uninstall
exits non-zero only when nothing at all was removed.
Both are multi-platform by default. With no --platform, a bare install
lands the artifact on every platform already in use (falling back to Claude when
nothing is tracked yet), and uninstall sweeps every platform — removing each
physical copy and clearing every platform's lock entry. Pass --platform <tool>
to scope either to one platform: install onboards just that tool, uninstall
removes from just that one and leaves the others intact.
update is intentionally different: without --platform, it targets only the
default platform (Claude). cmx <kind> update --all means all tracked artifacts
on that one platform, not all platforms.
An update replaces a tracked installed copy with the source's, so files the
source has dropped are removed rather than left behind. Platforms that share one
install directory (for example Codex and Hermes, which both use .agents/skills
at project scope) are kept in step: updating on one refreshes the others' lock
entries, so the next platform's update doesn't mistake the shared files for
local edits.
Skill management
Same commands as agent, using cmx skill instead of cmx agent, plus one
skill-only command:
| Command | Description |
|---|---|
cmx skill sync <name> | Reconcile a skill that has diverged across platforms by copying one copy over the others |
sync works between install locations rather than from a registered source,
so it also reconciles external skills and any skill with no source. By default
the newest version wins; pass --from <platform> to force the direction,
--dry-run to preview, or --local to reconcile project scope. When the
differing copies are unversioned (or share a version) it asks for --from
rather than guessing, and lists each copy so you can choose. Agents are excluded
because they're reformatted per platform (e.g. Codex TOML), so a byte-level
cross-platform comparison isn't meaningful.
Use sync when you want every installed copy of a skill to match. update
pulls from source to one platform; promote pushes an installed copy back into
the canonical home.
cmx {agent,skill} promote
promote is the mirror of update. Where update pulls the source copy over
your installed one (discarding local edits), promote copies the installed
copy into the canonical home and refreshes its home-provenance lock baselines,
so the artifact reads as tracked again. Use it for the common authoring loop: an
assistant edits its own skill where it's installed, then you promote those edits
to the home. It promotes the copy cmx diff shows (global scope preferred, then
project). The target is the home only — a git-sourced artifact is rejected with
guidance (edit the source clone, or update --force to discard), as is an
untracked one (steered to adopt/install); a Codex agent is rejected too,
since its installed copy is TOML rather than canonical markdown. Any other
home-tracked platform whose copy still differs afterward is reported as drifted
and pointed at sync.
Aggregate commands
| Command | Description |
|---|---|
cmx list | List installed agents and skills (cmx-managed inventory) |
cmx list --all | Include external (tool-managed) artifacts in the listing |
cmx outdated | Show artifacts needing attention |
cmx search <keyword> | Search all sources by name and description |
cmx info <name> | Show detailed metadata for an installed artifact (searches both kinds) |
cmx completions <shell> | Generate a shell completion script to stdout |
cmx doctor | Survey every platform; show only what needs attention (read-only) |
cmx doctor --all | Show the full inventory, not just problems |
cmx doctor --local | Also include project (local) scope in the survey |
cmx doctor --json | Emit the survey as machine-readable JSON to stdout |
cmx doctor --adopt-all | Adopt every orphaned artifact into the canonical home (deprecated; use cmx <kind> adopt --all) |
cmx init | Install cmx's own companion agent skill (global scope by default) |
cmx completions
cmx completions <shell> generates a completion script to stdout only, so
you can pipe or redirect it wherever your shell expects it. Supported values
are bash, zsh, fish, elvish, and powershell. This command is
read-only and does not support --json.
Examples:
cmx completions zsh > ~/.zfunc/_cmx
cmx completions bash | sudo tee /etc/bash_completion.d/cmx >/dev/null
cmx info
cmx info <name> shows the key details of an installed artifact: its scope and
path, version (and any available update), source provenance and checksums, and —
for a skill — its file tree. Two fields are worth calling out:
- Activates when (skills) / Description (agents) — the artifact's
descriptionfrontmatter. For a skill this is precisely its activation trigger: the "use this when…" text the assistant reads to decide whether to load the skill. Multi-line YAML (description: >folded or|literal) is rendered in full. - What it does — a short LLM-generated paragraph summarizing the artifact,
produced via the configured LLM gateway (
cmx config gateway/model). This requires a build with thellmfeature; a lean build prints a one-line hint in its place. Summary generation is best-effort — if the provider is unreachable,infostill prints everything else.
The top-level cmx info <name> searches both kinds; the kind-scoped
cmx skill info <name> and cmx agent info <name> look only at that kind.
Sets
A set is a locally-defined, named group of installed artifacts (agents
and/or skills) with a desired activation state — active or inactive. It is
the consumer-side lever for standing context cost: an installed skill's
trigger description is loaded on every turn whether or not it fires, so a
machine curated to fifty skills carries fifty descriptions of fixed cost.
Uninstalling to reclaim that cost loses the record that you wanted the
artifact at all; a set lets you switch a whole group off and back on as a
unit, fully reversible, with the grouping remembered. See
Managing Context Cost with Sets for the task-oriented
walkthrough.
cmx set create <name> [--desc <text>] [--from-plugin <source>:<plugin>] [--local]
cmx set list [--local] [--json]
cmx set show <name> [--local] [--json]
cmx set add <name> <artifact>... [--local]
cmx set remove <name> <artifact>... [--local]
cmx set activate <name> [--apply] [--local]
cmx set deactivate <name> [--apply] [--force] [--local]
cmx set delete <name> [--purge [--apply] [--force]] [--local]
cmx set rename <old> <new> [--local]
Every subcommand defaults to global scope; pass --local to operate on a
project-scoped set instead (.context-mixer/sets.json, symmetric with the
local lock file). create, add, remove, activate, deactivate,
delete, and rename are the mutating verbs (marked [Mutates] in
--help); list and show are read-only and support --json.
| State | Meaning |
|---|---|
active | Every member is installed; cmx set activate made it so |
inactive | Members are not installed on the set's behalf; either never activated, or deactivate uninstalled them |
activate installs every member from its pinned source into the normally
resolved install targets and marks the set active — it is idempotent, so
re-running it safely repairs a set that's only partially installed.
deactivate uninstalls every member not held by another active set and
marks the set inactive. Deactivating does not delete the set's definition —
only delete does that — so a deactivated set can always be reactivated
later without rediscovering its members.
Plan-by-default, --apply to execute
activate and deactivate (and delete --purge) show the concrete plan —
which members would be installed or uninstalled, and where — without touching
disk. Pass --apply to execute exactly that plan:
$ cmx set activate rust-work
Plan to activate set 'rust-work':
agent rust-craftsperson: install
~/guidelines/plugins/rust-ecosystem/agents/rust-craftsperson.md -> ~/.claude/agents/rust-craftsperson.md
Re-run with --apply to make these changes.
$ cmx set activate rust-work --apply
Set 'rust-work' activated.
agent rust-craftsperson: installed
~/guidelines/plugins/rust-ecosystem/agents/rust-craftsperson.md -> ~/.claude/agents/rust-craftsperson.md
Reference counting across sets
An artifact can belong to more than one set. deactivate only uninstalls a
member when no other active set still claims it — the same rule
cmx doctor's set-consistency check applies when deciding whether a lingering
install is a problem. Deactivating one set therefore never yanks an artifact
out from under a different set that's still active and needs it.
The drift guard
A member with local edits (installed copy differs from what cmx tracked)
blocks its own uninstall during deactivate — cmx won't silently discard
edited content. Pass --force to discard the drifted copy and uninstall it
anyway. The same guard and --force override apply to delete --purge,
since a purge is a deactivate followed by deleting the definition.
Seeding from a marketplace plugin
cmx set create <name> --from-plugin <source>:<plugin> seeds the new set's
membership from a marketplace plugin's declared agents/skills (its
marketplace.json entry) — without installing anything. This is the
publisher-side plugin grouping handed to you as a starting point for a
consumer-side set; from there the set is yours to add/remove members from
regardless of which source or plugin they came from.
delete --purge
cmx set delete <name> removes only the set's definition; its members stay
installed (they may belong to something else). --purge also deactivates the
set first — uninstalling any member not held by another active set — before
deleting it. Like activate/deactivate, the purge is previewed by default
and only executed with --apply; --force applies the same drift override.
cmx set list --json / cmx set show --json
{
"scope": "global",
"sets": [
{ "name": "rust-work", "state": "active", "member_count": 1, "footprint_chars": 1800 }
]
}
{
"description": "Rust craftsmanship + foundry",
"footprint_chars": 1800,
"members": [
{
"footprint_chars": 1800,
"installed": true,
"kind": "agent",
"name": "rust-craftsperson",
"source": "guidelines"
}
],
"name": "rust-work",
"scope": "global",
"state": "active"
}
footprint_chars is the total character count of the set's members' trigger
descriptions (the description frontmatter an assistant loads on every
turn) — the context-footprint cost the set carries while active. A member
whose description can't be resolved (source missing, artifact not found)
contributes 0 to the set's total and its own footprint_chars is null
rather than 0, so a script can tell "counted as zero" apart from
"genuinely couldn't resolve."
cmx doctor
doctor is a read-only survey of your platforms' install directories and
lock files. It mutates nothing — its job is to make a disorganized installation
visible before any command changes it. By default it surveys every supported
platform; once you declare a managed set with cmx config platforms, it surveys
only those, and the header reports the count it actually looked at (e.g. "2
managed platform(s) surveyed") rather than always claiming all fourteen.
By default doctor shows only what needs attention — drifted, untracked,
orphaned, missing, or diverged artifacts — because it's a doctor, for fixing
broken things. Healthy tracked and external artifacts are counted in the
summary line but not listed. Pass --all for the full inventory. When
nothing's wrong it reports "everything cmx manages is healthy." For each
artifact it reports one of:
| State | Meaning | Remedy |
|---|---|---|
tracked | recorded in a lock file with a matching checksum | — (unless diverged) |
drifted | tracked, but the on-disk copy was edited after install | cmx info <name> to inspect |
untracked | on disk, no lock entry, but a registered source provides it (installed out-of-band) | cmx <kind> install <name> to track it |
orphaned | on disk, no lock entry, and no source provides it (hand-authored) | cmx <kind> adopt <name> to canonicalize into the home |
external | on disk, but declared external in config (managed by another tool) | — (unless diverged) |
missing | in a lock file, but the file is gone from disk | cmx <kind> uninstall <name> to clear the stale entry |
The untracked vs orphaned split matters for bringing a system under control:
untracked artifacts have a real upstream source, so the right move is to track
them (install); orphaned artifacts are yours alone, so they belong in the
canonical home (adopt). cmx doctor --adopt-all and cmx <kind> adopt <name>
therefore act only on orphaned artifacts — an untracked artifact is steered
to install instead of being adopted as if it were private.
A skill installed for several tools is reported as one logical artifact
whose Platforms column lists every surveyed copy as platform@version — not
as N duplicates. That's the intended "curate once, project to many" outcome.
The only multi-location situation doctor flags is (diverged): copies whose
content differs across locations. The comparison is representation-aware:
an agent installed for Codex is generated TOML rather than the portable
Markdown, so a Codex copy is compared against the Codex projection of the
Markdown copy (what cmx agent install would write today), never against its
raw bytes. Reformatting alone is therefore never divergence — but a Codex TOML
that has been hand-edited, was written by an older cmx (no preserved
frontmatter block), or was generated from a different version of the Markdown
is. A divergence is an anomaly worth surfacing whoever owns the artifact,
so it's flagged even for external artifacts; cmx just can't be the one to
re-sync an external one (its owning tool must). Because the table attributes
the version to each platform directly,
it can show equal versions for content-diverged copies (for example
claude@1.1.2, codex@1.1.2) or version skew (codex@3.2.0, claude@3.3.0)
without hiding which copy is where. A detail line under the summary still names
the paths:
• hopper-coordinator diverges: ~/.agents/skills @ 3.2.0, ~/.claude/skills @ 3.3.0
Re-sync a divergence with the tool that fits its provenance. Doctor's hint is case-directed so you can pick the right command by situation:
- source- or home-backed, edited in place →
cmx skill promote <name> - source-backed, restore from source →
cmx skill update <name> --force - external / source-less →
cmx skill sync <name>(or--from <platform>) - not sure? inspect first →
cmx skill diff <name>
Set-consistency checks
doctor also cross-references every set's declared membership
against what the survey found installed, at every scope it surveys. Two
mismatches are flagged:
| Problem | Meaning | Remedy |
|---|---|---|
active_missing | The set is active, but this member isn't installed | cmx set activate <name> repairs it (idempotent) |
inactive_lingering | The set is inactive, but this member is still installed and not held by any other active set | cmx set deactivate <name> — previews first, clears it with --apply |
The same reference-counting rule deactivate itself uses applies here: a
member shared by two sets is never flagged inactive_lingering while either
set is still active. In the human view each mismatch appears as its own
detail line under the summary:
• set 'rust-work' (global): agent rust-craftsperson is active but not installed
Exit codes
doctor exits non-zero (2) when it finds drift, untracked, orphaned,
missing, or diverged artifacts, or a set-consistency mismatch, so it is
usable in a pre-commit hook or CI check. A consistent tracked or
external artifact never fails it — only a genuine anomaly does. This holds
for both the human and --json output:
| Exit code | Meaning |
|---|---|
0 | no issues found |
2 | actionable issues found (drifted, untracked, orphaned, missing, or diverged artifacts, or a set-consistency mismatch) |
cmx doctor --json
--json prints the survey as a single JSON document to stdout only — no
human table, no prose. Any warnings (like the --adopt-all deprecation notice
below) still go to stderr, so cmx doctor --json | jq . always sees clean JSON.
The shape mirrors the human view:
{
"scope": "global",
"platforms_surveyed": 13,
"showing": "needs_attention",
"summary": {
"tracked": 40, "drifted": 1, "untracked": 0,
"orphaned": 1, "external": 8, "missing": 0, "diverged": 4,
"set_inconsistent": 1
},
"artifacts": [
{
"kind": "skill",
"name": "hopper-coordinator",
"scope": "global",
"state": "external",
"versions": ["3.2.0", "3.3.0"],
"source": null,
"tools": [],
"diverged": true,
"locations": [
{ "path": "~/.agents/skills", "platform": "codex", "version": "3.2.0", "state": "external" },
{ "path": "~/.claude/skills", "platform": "claude", "version": "3.3.0", "state": "external" }
]
}
],
"set_inconsistencies": [
{
"set": "rust-work",
"scope": "global",
"kind": "agent",
"member": "rust-craftsperson",
"problem": "active_missing"
}
]
}
showing reflects the same selection the human table would use — --all
switches it from "needs_attention" to "all" and includes healthy artifacts
too. An artifact carries version when every copy agrees, or versions (an
array) when they diverge. Every artifact's locations array replaces the human
view's free-text "diverges: ..." line with structured {path, platform, version, state} entries, so a script never has to parse prose to find where
copies disagree. set_inconsistencies mirrors the set-consistency check above
— problem is "active_missing" or "inactive_lingering".
cmx init
cmx init installs cmx's own companion agent skill — the skill that teaches an
agent to drive cmx itself — through the shared cmx-core library, the same
embeddable installer other fleet tools (parite, foundry) use for their own
companion skills.
| Flag | Effect |
|---|---|
| (none) | Install/update at global scope (~/.claude/skills/cmx/) — the default, since a companion skill describes the tool, not one project |
--local | Install into the current project (.claude/skills/cmx/) instead |
--force | Overwrite even if the installed copy is a newer version than the bundled one (otherwise refused) |
--remove | Uninstall — removes the skill directory and clears its lock entry (leaves the shared cmx-lock.json in place) |
--json | Emit a machine-readable report instead of human text — the only cmx command that does, today |
--global | Accepted as a no-op alias; global is already the default |
Re-running cmx init when the installed copy is already current reports a
skip. cmx init --global --force always exits 0, matching the fleet-wide
registry contract other tools' automation depends on.
Canonical home & adoption
The canonical home is a tool-neutral directory that holds your hand-authored
private agents and skills — the source of truth that survives switching coding
assistants. It defaults to ~/.config/context-mixer/home (override with the
home field in config.json) and is auto-registered as a visible local source
named home.
| Command | Description |
|---|---|
cmx home init | Create the home directory and register it as the home source |
cmx home path | Print the resolved home directory |
cmx skill adopt <name>... | Adopt one or more named orphaned skills into the home |
cmx agent adopt <name>... | Adopt one or more named orphaned agents into the home |
cmx skill adopt --all [--from <dir>] | Adopt all orphaned skills, optionally only those under <dir> |
cmx agent adopt --all [--from <dir>] | Adopt all orphaned agents, optionally only those under <dir> |
cmx doctor --adopt-all [--from <dir>] | Deprecated — adopt every orphan the survey finds (both kinds), optionally scoped to <dir>; prints a stderr warning and will be removed in the next major version. Use cmx skill adopt --all / cmx agent adopt --all instead. |
Adoption acts only on orphaned artifacts. Naming an untracked
(source-available) artifact steers you to install; an already-tracked or
drifted one is rejected. Named adoption is all-or-nothing — if any name is
invalid, nothing is adopted. The --from <dir> filter restricts a bulk adopt to
a single install location (e.g. --from ~/.claude/skills to adopt your own
skills while leaving another tool's bundled-skill directory untouched).
Adoption copies, never moves. It places a verbatim copy of the orphan in the
home, registers the home as a source, and records provenance (source: home,
with the artifact's checksum) in the lock file of every platform that reads the
orphan's location — so the original reclassifies from orphaned to tracked.
The original file is left exactly where it was.
Migrating a private skill set between tools
cmx doctor # see what's orphaned
cmx skill adopt --all # canonicalize the orphaned private skills
cmx agent adopt --all # canonicalize the orphaned private agents
cmx skill install --all --platform opencode # project the home to a new tool
After adoption the home is a normal source, so projecting it to any platform is
just install --all --platform <tool>.
Configuration
| Command | Description |
|---|---|
cmx config show | Show current LLM settings, external rules, and the managed-platform set |
cmx config gateway <openai|ollama> | Set LLM provider |
cmx config model <name> | Set LLM model |
cmx config external list | List the configured external rules |
cmx config external add <dir-or-name> | Mark a directory or artifact name as external |
cmx config external remove <dir-or-name> | Remove an external rule |
cmx config platforms list | List the platforms cmx manages |
cmx config platforms add <platform> | Add a platform to the managed set |
cmx config platforms remove <platform> | Remove a platform from the managed set |
Managed platforms
By default cmx infers which platforms to act on: a bare install targets the
platforms already in use, while every other cross-platform command — uninstall,
doctor, info (and its "did you mean" hints), sync, diff, promote, set activate/deactivate, and adopt — considers every supported platform.
Declaring a managed set makes that explicit and authoritative — when it is
non-empty, a default (no --platform) install/uninstall acts on exactly those
platforms, doctor surveys only those, and info/the did-you-mean hints only
look for artifacts on those platforms too, so cmx ignores tools you don't use
instead of scanning all fourteen:
cmx config platforms add claude
cmx config platforms add codex # now cmx manages exactly claude + codex
cmx config platforms list
The set is stored as lowercase names in config.json ("platforms": ["claude", "codex"]) and shown by cmx config show (as (inferred) when unset). Onboard a
tool before any install with cmx config platforms add <tool>; an explicit
--platform still overrides the set for a single command.
External artifacts
Artifacts that another tool manages — e.g. a tool's bundled/stock skills in
its own directory — can be declared external so cmx doctor reports them as
external (a steady state, not flagged) instead of as orphaned, and so
adopt/--adopt-all never sweep them into your home. The one exception is a
divergence: if an external artifact's copies disagree across locations,
doctor still surfaces it — that's a real anomaly even though its owning tool,
not cmx, must re-sync it.
Each rule is either a directory (an install location — ~ expands to your
home) or a bare artifact name:
cmx config external add ~/.hermes/skills # a whole tool's skill directory
cmx config external add some-skill # a single artifact by name
A directory rule covers everything under it (including artifacts added later); a name rule matches that artifact wherever it lives.
cmf Command Reference
cmf is a read-only consumer of a structured intent atlas. Production atlas
records are maintained outside this project; the small
reference-atlas belongs to this project
and exercises the cmf-to-cmv contract. cmf scans an atlas to assemble and
install agent-facing guidance.
Run commands from the atlas root, or pass --root <path>. Intent
records live below intents/. Named profiles resolve below profiles/, while
an explicit profile path can live elsewhere.
Commands
| Command | Description |
|---|---|
cmf assemble <profile> | Write an assembled agent or SKILL.md document to stdout |
cmf install <profile> | Preview platform-aware installation through cmx-core |
cmf install <profile> --apply | Apply the displayed installation plan |
cmf install <profile> --platform codex | Preview installation for Codex only |
cmf status | Count structured intents, materialization profiles, and declared sensors |
cmf validate [--json] | Check every atlas record, sensor declaration, profile, selection, budget, and declared validator path without writing files |
validate checks the whole atlas before installation. It parses intent records
and ecosystem sensors, loads every TOML profile under profiles/, and assembles
each profile using the same selection and token-budget rules as install.
Every executable static-check must point to an executable file within the
atlas; descriptive entries with neither language nor run remain valid.
Validation never invokes a validator and does not write to the atlas, project,
or home directory.
cmf --root ./reference-atlas validate
cmf --root ./reference-atlas validate --json
Exit status 0 means valid, 2 means invalid atlas input (and also clap
command-line usage errors). Human output names the source file and field for
each failure. JSON output has stable valid, intents, profiles, and
diagnostics fields; each diagnostic has file, field, and message.
For malformed intent TOML, file is the scanned record path, even when the
parser includes a location in its error message. A missing required field uses
that field's name (for example, title); syntax errors without a field use
record.
Validation continues after an invalid record or profile and reports each
failure it can inspect in one run. A profile that selects an invalid record
may also report a missing selection. Repair the record first, then rerun.
Records and profiles are visited in sorted path order, so repeated runs over
the same atlas produce the same JSON bytes. The counts include successfully
scanned intents and successfully assembled profiles.
assemble --explain writes selected intent keys, graph traversals, and the
estimated token count to stderr, leaving stdout safe for redirection.
--surface agent|skill can override a profile's delivery surface.
--manifest <path> additionally writes the compile manifest (below) to that
path.
install is global by default. Use --local for project scope and --force
to replace drifted or newer installed guidance. Target platforms come from
cmx configuration and existing lock state; cmf does not duplicate their path
or format rules. Pass --platform codex to target only Codex, including when
Codex is not yet in the managed set. Codex agents are transformed from the
assembled Markdown into .codex/agents/<name>.toml; skills are installed as
.agents/skills/<name>/SKILL.md. Neither target replaces a project's
hand-authored AGENTS.md. Omitting --platform preserves the existing
cmx target resolution. A local install also records the compile manifest at
.context-mixer/cmf-manifest.json, beside the local lock file: the preview
says so, and --apply writes it. Each successful local artifact write
updates only that artifact’s compilation record; a preview or refused install
leaves the manifest unchanged. Global installs write no manifest.
A local install (preview and apply alike) also compares the profile's declared
[select] ecosystems with what the atlas's sensors detect in the
current directory, and warns on stderr when a declared ecosystem is missing —
a profile compiled for rust against a Python project is almost certainly the
wrong profile:
warning: profile rust-shipping targets rust but this project shows python
The line ends … but the atlas declares no sensors when the atlas has no
sensor file, and … but nothing was detected when its sensors found nothing.
A profile that declares no ecosystems filters nothing and never warns.
assemble has no project and does no detection.
cmf status adds a Sensors: N ecosystems line (Sensors: none declared
when the atlas has no sensor file).
Compile manifest
The manifest is cmf's second output: a JSON record of what was compiled, so a
verifier can later hold the project to exactly those intents. It carries a
schema version (1 for a standalone assemble --manifest record; local
installs use a schema-2 artifacts array of these records), the compiled_at instant, the atlas (its
path, plus its cmx source name and git revision when the root is a
registered source or a git checkout — both omitted otherwise), the profile
id, version, and declared ecosystems (always present, empty when the profile
declared none), the delivered artifact (name, surface, and sha256:
checksum of its content), one intents entry per retained record (id,
catalog key, and the sha256: checksum of the record file) in the order
they were retained, and a dropped list that is currently always empty
because assembly fails rather than drops on budget overrun.
Profile schema
id = "rust-dependency-change"
version = "0.1.0"
description = "Use when adding, removing, or upgrading Rust dependencies."
surface = "skill"
budget_tokens = 2800
[select]
keys = ["craftsperson/audit-dependency-risk"]
categories = ["dependencies", "quality"]
tags = ["security", "cargo"]
ecosystems = ["rust"]
[graph]
follow = ["specializes", "related-to"]
max_related_depth = 1
prefer_specializations = true
[content]
include = ["guidance", "rationale", "evidence"]
A profile must name exact keys or combine category and tag filters. This guard
prevents accidental whole-catalogue exports. Graph expansion is bounded, and
generation fails instead of truncating when the shaped artifact exceeds its
declared context budget. prefer_specializations defaults to true whether or
not a [graph] table is present: a selected specialization drops the general
record it specializes, and, when ecosystems is declared, the declared
ecosystems' specializations of every selected record are pulled in first (see
Downward expansion).
Ecosystems
The atlas carries a record's language and tooling as the directory it
sits in — the directory hierarchy is the realization hierarchy, so
craftsperson/python/uv/ specializes Python guidance for uv-based projects. A
record's ecosystem qualifiers are the segments of its catalog key between
the collection root and the slug: craftsperson/python/uv/pin-interpreter has
qualifiers python and uv, craftsperson/rust/use-structured-tracing has
rust, and craftsperson/isolate-functional-core-from-effects has none.
[select] ecosystems names the ecosystems the artifact targets. A record is
eligible when it has no qualifiers or every qualifier it has is declared;
so ecosystems = ["python"] admits the general records and the python/
specializations but not python/uv/, and ["python", "uv"] admits all three.
A profile that declares no ecosystems applies no filter. Eligibility applies in
three places:
- Category/tag selection never picks an ineligible record. Without a
filter,
categories = ["quality"]withtags = ["testing"]pulls every ecosystem's testing records into one artifact; with one, only the declared ecosystems' records (and the general ones) remain. - Graph expansion skips an edge to an ineligible target instead of
following it, and records the skip in
--explain's traversal as<key> --<relation>--> <target> (skipped: ecosystem). - Explicit keys must be eligible: naming a key outside the declared ecosystems is an error that names the key and the qualifier that excluded it, because a profile should not contradict itself.
--explain prints the declared ecosystems and how many category/tag matches
the filter excluded. The compile manifest records the declared list as
profile.ecosystems (empty when none) so a verifier can compare it with the
ecosystems it detects (cmv reports the difference as profile_mismatch).
Downward expansion
Graph expansion along follow edges walks upward: from a specialization
along its specializes edge to the general record. A profile that selects
general advice by category and tag would therefore never find the ecosystem's
own version of it. Once the ecosystems are declared it is safe to walk
downward too, because only the project's own specializations qualify.
After selection and follow expansion, cmf pulls in every eligible record that
specializes a selected record, transitively to a fixpoint: a general record
pulls python/…, which pulls python/uv/… when uv is declared. The existing
shadow removal then drops the general parent, so the ecosystem's version
replaces the general advice rather than duplicating it. Downward expansion
runs only when both gates hold; otherwise it is a no-op:
[graph] prefer_specializationsistrue(its default). Its meaning — prefer an ecosystem's specialization over the general advice — now covers both finding specializations and dropping the parents they shadow.[select] ecosystemsis non-empty. Without a declared list, downward expansion would pull every language's version of every general record, which is exactly the failure the filter exists to prevent.
Assembly order is: initial selection → follow expansion → downward expansion
→ shadowed-parent removal → budget check. Records added by downward expansion
do not have their own follow edges expanded; that keeps the pass bounded
and deterministic, and their specializes edge already points at a selected
parent. Each pull appears in --explain's traversal as
<general-key> <--specializes-- <specialization-key>, and --explain prints
the total as specialized downward: N beside the ecosystem lines.
Selected intents render as compact, ordered blocks rather than separate
guidance, rationale, and evidence sections. Within each block, rationale
contributes the capability, threat, expectation, and accepted trade-off;
guidance contributes the preferred strategy; and evidence contributes
required or optional verification. Intent titles and evidence-type metadata
stay in --explain provenance instead of consuming delivered context.
Sensors
The atlas declares the ecosystems it supports, and how to recognize each in a
project, in ecosystems.toml at its root. cmf and cmv read it — cmf for the
install --local warning above, cmv to decide which validators run — and
neither carries a built-in table of build files.
[python]
signatures = [
{ file = "pyproject.toml" },
{ file = "setup.py" },
{ glob = "requirements*.txt" },
]
[uv]
implies = ["python"]
signatures = [
{ file = "uv.lock" },
{ file = "pyproject.toml", contains = "[tool.uv]" },
]
One table per ecosystem. signatures is a non-empty list of predicates; the
ecosystem is detected when any one holds. implies (optional) names the
ecosystems detected whenever this one is, transitively — uv implies
python. The predicate kinds are:
| Predicate | Holds when |
|---|---|
{ file = "<path>" } | the root-relative path exists, as a file or a directory |
{ file = "<path>", contains = "<literal>" } | the file exists and holds the literal |
{ glob = "<pattern>" } | at least one root-level entry, file or directory, has a name matching the pattern |
Every predicate reads only the project root, so detection is deterministic
and, at a pinned revision, pinned with everything else. A predicate with an
unknown key, no recognized kind, a contains without file, a glob
combined with file, or an empty value is a parse error naming the ecosystem
and the signature; a file path may not be absolute or contain ...
The names are the atlas's own vocabulary: an ecosystem's name must be a
directory the realization hierarchy below intents/ uses (python for
intents/craftsperson/python/, uv for the nested
intents/craftsperson/python/uv/). cmf and cmv validate the file against
the scanned records and reject the atlas, naming the ecosystem and the record
that exposed the problem, when a declared name is not such a directory, when
an implies target is not declared, or when a declared nested ecosystem does
not imply its parent. An ecosystem the records use but the file does not
declare is simply undetectable.
An atlas with no sensor file detects nothing. cmf then cannot compare a profile's ecosystems (the warning says so) and cmv reports every validator-bearing intent unchecked with a reason (see the cmv reference).
Validator evidence
An evidence entry of type static-check declares an executable validator for
the intent. It carries two extra fields: language, the source language the
validator reads (rust, python, …), and run, the path of the validator
executable relative to the atlas root. A record may declare one
static-check entry per language it can be checked in.
evidence = [
{ type = "architecture_review", description = "Pure modules import no gateway.", required = true },
{ type = "static-check", language = "rust", run = "checks/rust/isolate_functional_core.py", description = "No gateway trait is referenced from a pure module.", required = true },
]
Only the description renders — under Require or Observe when useful,
exactly like any other evidence — so the agent learns what will be checked
while language and run never reach the delivered artifact. A static-check
entry with neither language nor run is ordinary descriptive evidence — an
expectation a validator may later make executable — and declares no validator.
cmf rejects an atlas at scan time, naming the record and entry, when a
static-check entry carries only one of language and run (both are needed
to declare a validator), when either field appears on another evidence type,
or when run is absolute or contains ... Executing validators is the
job of the verifier, cmv (see CMV.md); cmf only records and renders
them.
cmv Command Reference
cmv verifies, deterministically, that a project holds the intents cmf compiled
for it. It reads the compile manifest cmf install --local wrote, resolves
each compiled intent's record in the atlas, runs the validators that
match the workspace's ecosystems (detected by the atlas's own sensors), and
exits nonzero when a required intent is not held. It never asks a model, never reads the clock, and never executes
project code: given the same workspace, manifest, atlas, and
cmv.toml, its output is byte-identical.
Run it from the project root, or pass --root <project>.
check, status, and explain accept standalone schema-1 manifests and
schema-2 local collections. Schema 1 also accepts the older knowledge_base
block name. An unsupported schema exits 2 with the manifest path before cmv
resolves the atlas or runs any validator. Default check verifies every
artifact in a collection, each against its own pinned atlas revision, and
returns nonzero if any artifact fails. JSON output contains an artifacts
array with each artifact identity and its own check report; schema-1 output
keeps its original shape. Default status reports every compilation at its
own pin. explain requires --artifact <name> when a collection contains
multiple compilations; add --surface agent|skill when the name is shared.
The repository's reference atlas contains representative records, sensors, validators, and source cases for testing this workflow. Production atlases are maintained separately.
Commands
cmv check [--json] [--strict] [--root <project>] [--manifest <path>] [--atlas <path>] [--at-head]
cmv status [--json] [--artifact <name> [--surface agent|skill]] [--root <project>] [--manifest <path>] [--atlas <path>] [--at-head]
cmv explain <intent> [--json] [--artifact <name> [--surface agent|skill]] [--root <project>] [--manifest <path>] [--atlas <path>] [--at-head]
| Command | Description |
|---|---|
cmv check | Run every artifact’s compiled validators and report attributed verdicts |
cmv check --strict | Also fail the run when any intent could not be checked |
cmv check --json | Emit the full per-intent report as JSON for CI |
cmv status | Summarize the manifest, pin, ecosystems, and validator coverage without running any validator |
cmv explain <intent> | Show what check would do for one intent — record resolution, validators, argv, config, stale — without running anything |
--at-head | Verify the atlas's working tree even when the manifest pins a revision its HEAD has moved past |
Defaults:
| Input | Default |
|---|---|
--root | the current directory |
--manifest | <root>/.context-mixer/cmf-manifest.json, where cmf install --local --apply writes it |
--atlas | resolved through the cmx source registry, then the manifest (see below); --knowledge-base still parses as a hidden alias |
A relative path — on the command line, in the registry, or in the manifest — is resolved against cmv's working directory.
Where the atlas comes from
The atlas is the registry (CMV.md, design decision 5): it is a git
repository, so it is a cmx source, and cmv finds it the way cmx would. The
resolution order is fixed, and every report says which step answered in its
atlas.resolved_by field:
resolved_by | Step |
|---|---|
override | --atlas <path> (or its hidden alias --knowledge-base) was given; nothing else is consulted |
source | the manifest's atlas.source names a registered cmx source (cmx source list); its local directory or clone is used |
path | the manifest's recorded atlas.path |
A recorded source name the registry does not know falls through to path with
a warning on stderr naming the source and suggesting cmx source add: the
manifest still says where the records were when cmf read them, so the run can
proceed.
The pinned revision
The manifest records the git revision cmf read. When the resolved atlas is
a git checkout whose HEAD differs from that pin, cmv verifies against
the pinned tree, not the working tree: it runs
git -C <atlas> archive --format=tar -o <scratch>/kb.tar <revision>
tar -xf <scratch>/kb.tar -C <scratch>/kb
and reads records from, and runs validators in, <scratch>/kb (executable bits
survive git archive). Validators' run paths resolve inside it. The scratch
directory is removed when the run ends and never appears in output.
When HEAD equals the pin, the manifest has no revision, or the root is not
a git checkout, cmv uses the root directly. When the pinned revision cannot be
archived — typically because the checkout has not fetched it — cmv exits 2
naming the revision and suggesting cmx source update <name> (or git fetch
when the atlas is not a registered source).
--at-head bypasses the pin and verifies the working tree; the report's
verified_against says head. This is for validator authors iterating on an
atlas: edit the script, run cmv check --at-head against a project,
repeat. It never re-pins anything.
Every check, status, and explain report carries an atlas block:
| Field | Meaning |
|---|---|
path | the atlas root as resolved |
resolved_by | override, source, or path |
source | the cmx source name the manifest recorded, if any |
pinned_revision | the manifest's revision, if any |
head_revision | the working tree's HEAD, when it is a git checkout |
verified_against | pinned when the verdicts came from the pinned revision (materialized, or checked out as HEAD); head otherwise |
moved | true when HEAD differs from the pin |
moved is informational and never changes the exit code. In human output it
adds one line after the summary:
atlas has moved: HEAD ffffffffffff vs pinned a1b2c3d4e5f6; 2 compiled records or validators changed; re-run cmf install to recompile
Recompiling is cmf's decision — a newer atlas may change selection — so cmv only reports it.
Ecosystem detection
cmv verifies as the ecosystems the atlas's sensors detect at the project
root. The atlas declares them in ecosystems.toml at its root — one table
per ecosystem, each with the signatures that recognize it and the ecosystems
it implies (see the cmf reference for the
schema). cmv has no built-in table of build files: the atlas knows which
ecosystems it supports, and the names are its own vocabulary (the directories
below intents/), so the sensors are read from the tree being verified — the
pinned tree when one is materialized — and validated against its records
before anything runs.
A validator runs only when its language is in the detected set. Setting
ecosystems in cmv.toml replaces detection entirely.
An atlas with no sensor file (or one declaring nothing) detects nothing,
and cmv says so rather than guessing: when cmv.toml gives no override,
every validator-bearing intent is unchecked with the reason
atlas declares no sensors; set ecosystems in cmv.toml to override
cmv status reports Ecosystems: none (atlas declares no sensors; …), and
cmv explain marks each validator skipped: with the same reason. The
atlas.sensors field of every JSON report says whether the verified tree
declared any.
When the manifest's profile.ecosystems names an ecosystem detection did not
find, cmv check reports it as profile_mismatch and ends the human listing
with one informational line:
manifest profile targets rust but the workspace shows python; the guidance may be for a different ecosystem
or … but no sensors detected anything when the detected set is empty. It
never changes the exit code, and it is not computed when the atlas declares
no sensors. A cmv.toml override counts as the workspace's word.
Project config: cmv.toml
Hand-authored, at the project root, entirely optional. It carries what neither the atlas nor the code can supply.
# Replace the atlas's sensor detection; an empty list runs no validators.
ecosystems = ["rust"]
# Seconds before a validator is killed and its intent reported unchecked.
# Default 60.
validator_timeout_seconds = 30
# Per-intent settings, keyed by catalog key. Each table is handed to that
# intent's validators verbatim as the --config JSON document; an intent
# without a table receives {}.
[intent."craftsperson/rust/isolate-functional-core"]
business_rule_pattern = "\\b10_?000\\b|\\b500\\b"
business_rule_minimum_matches = 2
[intent."craftsperson/python/nonblocking-async-io"]
blocking_symbols = ["time.sleep", "requests.get"]
Unknown top-level keys are rejected, so a misspelled ecosystems cannot
silently fall back to detection.
How validators run
For each intent in the manifest, cmv resolves the record by its catalog key
first: the key is the compile-time locator, and a record's id may
intentionally recur across collections (one specialization per language), so
an id on its own cannot locate a record. When no record sits at the key, cmv
falls back to the id only if exactly one record carries it — a record that
moved. When several do, the intent is reported unchecked with a reason naming
those records; cmf install recompiles against the current keys. cmv then runs
each static-check validator whose language matches:
<atlas-root>/<run> --workspace <project-root> --config <tempfile.json>
with the atlas root as the working directory. The validator writes one JSON document to stdout and exits 0:
{
"applicable": true,
"followed": false,
"signals": { "effectful_modules": ["src/http.rs"] },
"evidence": ["no gateway trait is declared"],
"locations": [{ "path": "src/http.rs", "line": 14 }]
}
applicable and followed are required; signals (default {}), evidence
(default []), and locations (default []; line optional) may be omitted.
When several validators match one intent (a record with one validator per language, in a workspace with several ecosystems) they combine all-must-pass: any failure fails the intent; otherwise any unchecked run leaves it unchecked; otherwise it passes when at least one run was applicable.
Verdict states
| State | Meaning | Effect on exit code |
|---|---|---|
pass | Validator ran; applicable: true, followed: true | — |
fail | Validator ran; applicable: true, followed: false | 1 if the validator is required |
not_applicable | Validator ran; applicable: false — the condition never arose | — |
unchecked | No validator for the workspace's ecosystems (or the atlas declares no sensors), record missing from the atlas, validator could not start, crashed, timed out, or wrote no parseable verdict — the reason is reported | 1 only with --strict |
unguided | The manifest lists the intent as dropped; the guidance never reached the artifact | — |
An intent is additionally marked stale when, at the working tree's HEAD,
its record's bytes no longer match the checksum the manifest recorded at
compile time, or — when validators ran from a materialized pinned tree — any
of its validators' run files differ between HEAD and that tree. Stale is
always computed against the working tree, never the pinned tree, so a moved
atlas shows exactly which compiled intents it touched. Stale is
informational and never changes the exit code; the remedy is to re-run
cmf install, because a newer atlas may change selection, which is
cmf's decision.
Exit codes
| Code | Meaning |
|---|---|
0 | Every required, applicable validator passed |
1 | A required validator failed, or, with --strict, an intent was unchecked |
2 | Missing or malformed manifest, unreadable atlas, or bad usage |
An optional validator's failure is reported but never changes the exit code.
Human output
A linter-style listing, one line per intent, grouped by state with settled outcomes first and problems last, so they sit beside the summary:
PASS craftsperson/rust/put-gateways-at-effect-boundaries
every effectful call crosses a gateway trait
UNGUIDED craftsperson/rust/name-for-intent
budget
UNCHECKED craftsperson/rust/compile-public-documentation
no validator for ecosystems [rust]
FAIL craftsperson/rust/isolate-functional-core (required) (stale: record changed since compile)
business rules live beside I/O in src/main.rs
src/main.rs:12
1 pass, 1 fail, 0 not applicable, 1 unchecked, 1 unguided; adherence 50.0%
1 record changed in the atlas since compile; re-run `cmf install` to recompile.
Under each intent come its evidence strings, then its locations as
path:line, then the reason when unchecked or unguided. The adherence rate is
pass / (pass + fail): the denominator is what the code had occasion to
exhibit.
JSON report
cmv check --json writes one pretty-printed document. It carries no timestamp
and no temporary path, so it is byte-stable across runs.
{
"schema": 1,
"manifest": {
"profile": { "id": "rust-shipping", "version": "0.3.0" },
"atlas": { "source": "guidelines", "path": "kb", "revision": "a1b2c3d4…" }
},
"atlas": {
"path": "/home/me/guidelines",
"resolved_by": "source",
"source": "guidelines",
"pinned_revision": "a1b2c3d4…",
"head_revision": "ffffffff…",
"verified_against": "pinned",
"moved": true,
"sensors": true
},
"ecosystems": ["rust"],
"profile_mismatch": [],
"intents": [
{
"id": "guidelines.intent.isolate-functional-core",
"key": "craftsperson/rust/isolate-functional-core",
"language": "rust",
"required": true,
"state": "fail",
"description": "No module holding business rules references a gateway.",
"signals": { "business_rule_minimum_matches": 2 },
"evidence": ["business rules live beside I/O in src/main.rs"],
"locations": [{ "path": "src/main.rs", "line": 12 }],
"stale": true
}
],
"summary": {
"pass": 1,
"fail": 1,
"not_applicable": 0,
"unchecked": 1,
"unguided": 1,
"adherence_rate": 0.5,
"exit_code": 1
}
}
manifestechoes the manifest'sprofileandatlasas recorded at compile time (sourceandrevisionare omitted when the manifest has none).atlasis where cmv actually read from and which tree it verified; see "The pinned revision" above for every field.sensorssays whether that tree declares any ecosystem sensors.ecosystemsis the set the workspace was verified as (detected, or thecmv.tomloverride;[]when nothing was detected or nothing could be), andprofile_mismatchlists the manifest profile's declared ecosystems that were not detected (see "Ecosystem detection").intentslists every compiled intent in manifest order, then every dropped intent.stateis one ofpass,fail,not_applicable,unchecked,unguided;reasonis present for the last two.languageanddescriptionarenullwhen no validator ran;idisnullfor a dropped intent whose record is no longer in the atlas.languageanddescriptionjoin several validators' values with,and/respectively, andsignalsis then an object keyed by language.summary.adherence_rateispass / (pass + fail)rounded to four decimals, ornullwhen nothing was applicable.summary.exit_codeis the code cmv exits with.
cmv status --json emits schema, manifest_path, profile, artifact,
atlas (the block above plus exists), ecosystems, intents,
dropped, and coverage (with_validator, missing, stale), with
coverage null when the atlas could not be scanned. The human form
adds HEAD revision and Verified against lines after Pinned revision, and
the atlas has moved line at the end when moved. For a schema-2 collection,
default JSON wraps these per-artifact reports in { "schema": 2, "artifacts": [...] }.
Use --artifact to get one report. Each artifact is scanned against its own
pinned atlas revision.
Explaining one intent
cmv explain <intent> answers "what would check do with this one?" without
running anything. The argument is a catalog key, or a record id when it
contains a .. cmv exits 2 when the manifest neither compiled nor dropped
the intent. For a collection with multiple artifacts, cmv exits 2 with the
available identities until you select one with --artifact. If two surfaces
share the name, also pass --surface.
Artifact: AGENTS (agent)
Intent: craftsperson/rust/isolate-functional-core
Id: guidelines.intent.isolate-functional-core
Record: resolved by key
Title: Isolate the functional core
Status: confirmed (informational; never gates)
Compiled: yes
Dropped: no
Stale: yes (record or validator changed at HEAD since compile)
Atlas: /home/me/guidelines (resolved by cmx source, verified against pinned revision)
Ecosystems: rust
Config: {"business_rule_minimum_matches":2,"business_rule_pattern":"\\b500\\b"}
Validators:
rust checks/rust/isolate_functional_core.py (required) would run
No module holding business rules references a gateway.
<pinned-tree>/checks/rust/isolate_functional_core.py --workspace /home/me/project --config <scratch>/1.json
python checks/python/isolate_functional_core.py (required) skipped: language python is not among the workspace's ecosystems [rust]
No module holding business rules imports an I/O client.
<pinned-tree>/checks/python/isolate_functional_core.py --workspace /home/me/project --config <scratch>/1.json
Recordisresolved by key,resolved by id(no record at the manifest's key, but exactly one carries its id — a moved record), ornot found in atlas, followed in parentheses by the records sharing the id when that is what stopped the fallback.Configis the exact JSON document the validators would receive through--config: the intent's[intent."<key>"]table fromcmv.toml, or{}.- Each validator line shows its language,
run, whether it is required, and whether it would run here; below it, its description and the exact argv.<scratch>stands for the per-run temporary directory and<pinned-tree>for a materialized pinned revision — both are created at check time and never appear in output, soexplainis byte-stable like everything else. When cmv verifies the checkout directly, the program path is the real one. - A dropped intent shows
Dropped: yes (<reason>); its validators are listed but marked skipped, because the guidance never reached the artifact.
cmv explain <intent> --json emits schema, artifact, atlas, ecosystems,
and intent (key, id, resolution, title, status, compiled,
dropped, drop_reason, stale, config, and validators, each with
language, run, required, description, would_run, skipped, and
argv).
Frontmatter Reference
Agent frontmatter
---
name: python-craftsperson
description: |
Use this agent when writing, reviewing, or maintaining Python code.
version: 1.3.1
model: sonnet
deprecated: true
deprecated_reason: Replaced by python-craftsperson-v2
deprecated_replacement: python-craftsperson-v2
---
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string | Kebab-case identifier |
description | Yes | string | When to use — include examples for matching |
model | Yes | string | sonnet, opus, haiku, or inherit |
version | No | string | Semver version |
deprecated | No | boolean | true to mark deprecated |
deprecated_reason | No | string | Why deprecated — include actionable info |
deprecated_replacement | No | string | Replacement artifact name |
Skill frontmatter (SKILL.md)
---
name: blog-image-generator
description: Generate images for blog posts.
version: 1.0.0
---
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string | Kebab-case, must match parent directory name |
description | Yes | string | What + when to use |
version | No | string | Semver version |
deprecated | No | boolean | true to mark deprecated |
deprecated_reason | No | string | Why deprecated |
deprecated_replacement | No | string | Replacement artifact name |
marketplace.json
{
"name": "marketplace-id",
"owner": { "name": "Author", "email": "author@example.com" },
"metadata": { "description": "...", "version": "1.0.0" },
"plugins": [{
"name": "plugin-id",
"description": "...",
"source": "./",
"agents": ["./agents/my-agent.md"],
"skills": ["./skills/my-skill"]
}]
}
The metadata block — and both of its fields, description and version — is
optional, matching the Claude Code marketplace spec; a partial or absent
metadata block parses fine. (cmx once required both fields, so a source that
omitted either failed to load during the survey that backs cmx list and
cmx doctor.)
Lock File
cmx records installed state in JSON lock files.
Locations
Each platform has its own lock file so installations for different AI tools do not interfere.
| Scope | Claude Code | Other platforms |
|---|---|---|
| Global | ~/.config/context-mixer/cmx-lock.json | ~/.config/context-mixer/cmx-lock-<platform>.json |
| Local | .context-mixer/cmx-lock.json | .context-mixer/cmx-lock-<platform>.json |
Where <platform> is one of copilot, cursor, windsurf, or gemini.
Claude Code uses cmx-lock.json (no suffix) for backward compatibility.
Local lock files can be committed to version control for team sharing.
Format
{
"version": 1,
"packages": {
"python-craftsperson": {
"type": "agent",
"version": "1.3.1",
"installed_at": "2026-03-20T10:30:00Z",
"source": {
"repo": "guidelines",
"path": "agents/python-craftsperson.md"
},
"source_checksum": "sha256:abc123...",
"installed_checksum": "sha256:abc123..."
}
}
}
Fields
| Field | Description |
|---|---|
type | agent or skill |
version | From frontmatter at install time (null if absent) |
installed_at | ISO 8601 timestamp |
source.repo | Name of the registered source |
source.path | Relative path within the source |
source_checksum | SHA-256 of the artifact in the source at install time |
installed_checksum | SHA-256 of what was written to disk |
How checksums are used
- source_checksum vs current source — detects upstream changes
- installed_checksum vs current file — detects local edits
- Both match at install time, then drift independently
Platform Paths
cmx can install agents and skills to any supported AI coding assistant platform.
With no --platform, install/uninstall act across every platform already in
use (falling back to Claude Code when nothing is tracked yet), and single-target
commands default to Claude Code. Use the --platform flag or the CMX_PLATFORM
environment variable to target one specific platform — Claude Code's unsuffixed
lock file (below) preserves backward compatibility with installs made before
platform selection existed.
Selecting a platform
# Install to Cursor globally
cmx agent install python-craftsperson --platform cursor
# Install to Windsurf locally
cmx skill install skill-creator --platform windsurf --local
# Set the platform via environment variable
export CMX_PLATFORM=copilot
cmx agent install python-craftsperson
The --platform flag is global — it applies to all subcommands.
Supported platforms
| Platform | Value | Project agents | User agents | Project skills | User skills |
|---|---|---|---|---|---|
| Claude Code | claude (default) | .claude/agents/ | ~/.claude/agents/ | .claude/skills/ | ~/.claude/skills/ |
| GitHub Copilot | copilot | .github/agents/ | ~/.copilot/agents/ | .github/skills/ | ~/.copilot/skills/ |
| Cursor | cursor | .cursor/agents/ | ~/.cursor/agents/ | .cursor/skills/ | ~/.cursor/skills/ |
| Windsurf | windsurf | .windsurf/agents/ | ~/.codeium/windsurf/agents/ | .windsurf/skills/ | ~/.codeium/windsurf/skills/ |
| Gemini CLI | gemini | .gemini/agents/ | ~/.gemini/agents/ | .gemini/skills/ | ~/.gemini/skills/ |
| opencode | opencode | .opencode/agent/ | ~/.config/opencode/agent/ | .agents/skills/ | ~/.agents/skills/ |
| Codex CLI | codex | .codex/agents/ ¹ | ~/.codex/agents/ ¹ | .agents/skills/ | ~/.agents/skills/ |
| Pi | pi | (not supported) ² | (not supported) ² | .agents/skills/ | ~/.agents/skills/ |
| Crush | crush | (not supported) ² | (not supported) ² | .agents/skills/ | ~/.agents/skills/ |
| Amp | amp | (not supported) ² | (not supported) ² | .agents/skills/ | ~/.config/agents/skills/ ³ |
| Zed | zed | (not supported) ² | (not supported) ² | .agents/skills/ | ~/.agents/skills/ |
| OpenHands | openhands | (not supported) ² | (not supported) ² | .agents/skills/ | ~/.agents/skills/ |
| Hermes | hermes | (not supported) ² | (not supported) ² | .agents/skills/ ⁴ | ~/.hermes/skills/ ⁴ |
| Devin | devin | (not supported) ² | (not supported) ² | .agents/skills/ ⁵ | ~/.agents/skills/ ⁵ |
¹ Codex agents are TOML, not markdown. cmx agents are markdown files with
YAML frontmatter; the Codex CLI defines subagents as standalone TOML files. When
you install an agent with --platform codex, cmx transforms the source markdown
into a Codex subagent document (<name>.toml): name, description,
developer_instructions (the markdown body), and model,
model_reasoning_effort, and sandbox_mode when the frontmatter sets them.
Codex rejects any key it does not define, so every other frontmatter entry —
including metadata.version — is kept at the top of the file as a
# ----fenced comment block; Codex ignores it, and cmx agent list and
cmx doctor read the version back from it.
² Skills-only platforms. Pi, Crush, Amp, Zed, OpenHands, Hermes, and Devin
have no file-droppable agent concept (their "agents" are tool-gating profiles,
runtime delegations, executable plugins, or trigger-activated skills), so
cmx agent install --platform <tool> fails with a clear error. They support
skills only.
³ Amp resolves user-scoped skills under XDG (~/.config/agents/skills/)
rather than ~/.agents/skills/. Project skills still use .agents/skills/.
⁴ Hermes is global-centric. Its auto-read source of truth is
~/.hermes/skills/, so cmx installs user-scoped skills there. Project skills use
the shared .agents/skills/, which Hermes reads only if you add it to
skills.external_dirs in ~/.hermes/config.yaml.
⁵ Devin's skill discovery is repository-scoped. Devin (Cognition's cloud
software engineer) discovers SKILL.md files in the repositories connected to
it, with .agents/skills/ as its recommended location — so project-scoped
installs are the ones Devin actually reads. It has no user-global discovery on
your machine; a global install lands in the shared ~/.agents/skills/ for
consistency with the cohort, but only reaches Devin once committed to a repo it
can see.
The shared .agents skills convention
The .agents/skills/ directory (project) and ~/.agents/skills/ (user) is an
emerging cross-tool standard — the agentskills.io
SKILL.md format. opencode, Codex, Pi, Crush, Zed, OpenHands, and Devin all read
it natively, so cmx installs skills there for the whole cohort. (Amp and Hermes read
the project .agents/skills/ too but resolve user-scoped skills elsewhere — see
notes ³ and ⁴.) This is a shared directory: a skill installed under one of
these platforms is visible to the others.
A practical consequence: because each platform keeps its own lock file (below),
uninstalling a skill with one of these platforms removes the underlying
.agents/skills/<name> directory for all tools that read it. Reinstall it
under the other platform if you only meant to stop tracking it for one.
Per-platform lock files
Each platform maintains its own lock file so installations for different tools do not interfere with each other.
| Platform | Global lock file | Local lock file |
|---|---|---|
| Claude Code | ~/.config/context-mixer/cmx-lock.json | .context-mixer/cmx-lock.json |
| Copilot | ~/.config/context-mixer/cmx-lock-copilot.json | .context-mixer/cmx-lock-copilot.json |
| Cursor | ~/.config/context-mixer/cmx-lock-cursor.json | .context-mixer/cmx-lock-cursor.json |
| Windsurf | ~/.config/context-mixer/cmx-lock-windsurf.json | .context-mixer/cmx-lock-windsurf.json |
| Gemini CLI | ~/.config/context-mixer/cmx-lock-gemini.json | .context-mixer/cmx-lock-gemini.json |
| opencode | ~/.config/context-mixer/cmx-lock-opencode.json | .context-mixer/cmx-lock-opencode.json |
| Codex CLI | ~/.config/context-mixer/cmx-lock-codex.json | .context-mixer/cmx-lock-codex.json |
| Pi | ~/.config/context-mixer/cmx-lock-pi.json | .context-mixer/cmx-lock-pi.json |
| Crush | ~/.config/context-mixer/cmx-lock-crush.json | .context-mixer/cmx-lock-crush.json |
| Amp | ~/.config/context-mixer/cmx-lock-amp.json | .context-mixer/cmx-lock-amp.json |
| Zed | ~/.config/context-mixer/cmx-lock-zed.json | .context-mixer/cmx-lock-zed.json |
| OpenHands | ~/.config/context-mixer/cmx-lock-openhands.json | .context-mixer/cmx-lock-openhands.json |
| Hermes | ~/.config/context-mixer/cmx-lock-hermes.json | .context-mixer/cmx-lock-hermes.json |
| Devin | ~/.config/context-mixer/cmx-lock-devin.json | .context-mixer/cmx-lock-devin.json |
Claude Code keeps cmx-lock.json (no suffix) for backward compatibility with
installations made before platform selection was introduced.
Multi-Tool Platform Support
Status: Living design document. This is published in the open on purpose — it records why cmx supports the platforms it does, the way it does, so users and contributors can see our design goals and trade-offs rather than having to reverse-engineer them. Last substantive update: 2026-05.
Why this document exists
cmx installs agents (markdown system-prompt files) and skills
(directories containing SKILL.md + supporting files) into the per-tool
locations that AI coding assistants read. As we expanded beyond Claude Code, we
needed to know — concretely, per tool — where artifacts go, what format each
tool expects, and which of cmx's artifact kinds each tool can even consume.
This note records that research (so we don't re-derive it), the structural patterns it revealed, the abstraction options we weighed, and the scope decisions that produced the shipped design.
Design principles
These are the durable commitments behind the decisions on this page. We publish them so you can judge whether cmx's direction fits your values — not just whether it works today.
- Honesty over the appearance of coverage. If a tool can't consume an
artifact kind, cmx fails with a clear error rather than writing files the tool
will never read. (→
supports()/ skip-unsupported.) - Favor open, cross-tool standards. We invest first in the shared
.agents/skills/standard because it serves many tools at once; per-tool quirks are the long tail. (→ the skills cohort.) - Layer on top; don't replace. cmx adds version-tracking and cross-tool management over each tool's native system. It won't try to become a tool's plugin system, manage its secrets, or own its config. (→ file-drop-only; config-merge/MCP deferred; charter non-goals.)
- Lean by default. The default build carries no needless dependencies or advisory surface; we'll hand-roll a small, well-tested thing before pulling in a crate for it. (→ the hand-rolled Codex TOML emitter.)
- Provenance and integrity are first-class. Every install records source, version, and checksum in a per-platform lockfile, so you can always see what's installed, from where, and whether it has drifted.
- One curated set, projected outward. You manage a single curated set of artifacts; cmx projects it into many tools' native locations and formats. You curate once, not once per tool.
Direction
The phasing below is the roadmap, and the
scope decisions mark the current boundaries —
deliberately, not permanently. The deferred items (a Command artifact kind, a
config-merge/MCP engine, more tools) are open questions where those boundaries
may be revisited. If your needs press on one of them, that signal is welcome —
it's part of how the direction gets set.
TL;DR — the three findings that matter
-
.agents/skills/is a real cross-tool standard. The agentskills.ioSKILL.md-in-a-directory format, read from project.agents/skills/and user~/.agents/skills/, is consumed natively by opencode, Codex, Pi, Crush, Amp, Zed, and OpenHands. It is byte compatible with cmx's existing skill model. One skill install serves the whole cohort at once — this is cmx's highest-leverage surface. -
"Agent" is the un-portable dimension, and often isn't a file at all. A portable markdown agent only works for Claude and opencode. Codex needs TOML (we transform it). Cline/Crush/Amp/Goose/Zed/Hermes have no file-droppable agent — they use rules, recipes, TS plugins, settings-embedded tool-gating profiles, or runtime delegation. So a cmx "agent" maps to different things per tool, or to nothing.
-
A second install mechanism exists that cmx deliberately does not model: structured config-merge. Skills/commands are file-drop (copy → checksum → lockfile — cmx's model). MCP servers, Zed profiles, Goose extensions, and Crush/Continue config are merge a key into a shared JSON/YAML/TOML file — no per-artifact file to checksum, requires deep-merge without clobbering hand-edits. Decision: cmx stays file-drop only (see Scope decisions).
Capability matrix
Legend: ✓✓ native/first-class · ✓ supported · ✗ none/unsupported.
"Reads .agents/skills?" = consumes the shared cross-tool skill location.
| Tool | Skill = SKILL.md dir? | Reads .agents/skills? | Agent-as-file? | Commands | MCP config | Plugin/registry | cmx status |
|---|---|---|---|---|---|---|---|
| Claude Code | ✓✓ | (uses .claude/skills) | ✓ md | ✓ .claude/commands | ✓ | .claude-plugin/ marketplace | implemented |
| GitHub Copilot | ✓ | ✗ | ✓ md | — | — | external | implemented |
| Cursor | ✓ | ✗ (.cursor/skills) | ✓ md | — | ✓ | external | implemented |
| Windsurf | ✓ | ✗ | ✓ md | — | ✓ | external | implemented |
| Gemini CLI | ✓ | ✗ | ✓ md | — | ✓ | external | implemented |
| opencode | ✓✓ | ✓ | ✓ md (.opencode/agent) | ✓ | ✓ | ✗ (npm/JS) | implemented |
| Codex CLI | ✓✓ | ✓ | ✓ TOML (transform) | ✓ prompts (deprecated) | ✓ | ✗ | implemented |
| Pi | ✓✓ | ✓ | ✗ | ✗ | ✓ | tap (GitHub repos) | implemented |
| Crush | ✓✓ | ✓ | ✗ (internal only) | skills w/ flag | ✓ crush.json | ✗ | implemented |
| Amp | ✓✓ | ✓ (user: ~/.config/agents/skills) | ✗ (TS plugins) | ✓ .agents/commands | ✓ settings.json | ✗ (TS plugins) | implemented |
| Zed | ✓✓ | ✓ (flat only) | ✗ (settings.json profiles) | skills via /name | ✓ context_servers | extension.toml (not agents/skills) | implemented |
| OpenHands | ✓✓ | ✓ | agents are triggered skills | ✗ distinct | ✓ | ✓ extensions registry (npm) | implemented |
| Hermes | ✓✓ | ✓ (opt-in external_dirs; user: ~/.hermes/skills) | ✗ (SOUL.md + runtime delegate) | built-in slash only | ✓ config.yaml | tap + consumes claude-marketplace | implemented |
| Aider | ✗ | ✗ | ✗ (fixed modes; --read a md file) | ✗ (built-in only) | ✗ native | ✗ | researched, not impl |
| Cline | ✓ (.claude/skills, ~/.cline/skills) | ✗ | ✗ (Plan/Act + rules) | ✓ workflows .md | ✓✓ + marketplace | ✗ (MCP marketplace only) | researched, not impl |
| Continue | ✗ (typed "blocks") | ✗ | YAML assistants / CLI md+frontmatter | ✓ prompts | ✓ | ✓✓ Continue Hub (uses: owner/name@ver) | researched, not impl |
| Goose | ✗ (use MCP ext) | ✗ | recipes (YAML) | recipe-backed | ✓✓ (extensions are MCP) | goose:// deeplink registry | researched, not impl |
Install path reference (implemented tools)
Project paths are relative to the repo root; user paths to $HOME.
| Tool | Project agents | User agents | Project skills | User skills | Lock slug |
|---|---|---|---|---|---|
| claude | .claude/agents/ | ~/.claude/agents/ | .claude/skills/ | ~/.claude/skills/ | (none) |
| copilot | .github/agents/ | ~/.copilot/agents/ | .github/skills/ | ~/.copilot/skills/ | copilot |
| cursor | .cursor/agents/ | ~/.cursor/agents/ | .cursor/skills/ | ~/.cursor/skills/ | cursor |
| windsurf | .windsurf/agents/ | ~/.codeium/windsurf/agents/ | .windsurf/skills/ | ~/.codeium/windsurf/skills/ | windsurf |
| gemini | .gemini/agents/ | ~/.gemini/agents/ | .gemini/skills/ | ~/.gemini/skills/ | gemini |
| opencode | .opencode/agent/ | ~/.config/opencode/agent/ | .agents/skills/ | ~/.agents/skills/ | opencode |
| codex | .codex/agents/ (TOML) | ~/.codex/agents/ (TOML) | .agents/skills/ | ~/.agents/skills/ | codex |
| pi | — | — | .agents/skills/ | ~/.agents/skills/ | pi |
| crush | — | — | .agents/skills/ | ~/.agents/skills/ | crush |
| amp | — | — | .agents/skills/ | ~/.config/agents/skills/ | amp |
| zed | — | — | .agents/skills/ | ~/.agents/skills/ | zed |
| openhands | — | — | .agents/skills/ | ~/.agents/skills/ | openhands |
| hermes | — | — | .agents/skills/ | ~/.hermes/skills/ | hermes |
| devin | — | — | .agents/skills/ | ~/.agents/skills/ | devin |
Notes:
- Codex agents are transformed, not copied: cmx parses the source markdown
frontmatter + body and emits a Codex subagent TOML (
name,description,developer_instructions, plusmodel,model_reasoning_effort, andsandbox_modewhen set). Codex parses its agent files withdeny_unknown_fields, so nothing else may be a key; the rest of the frontmatter (metadata.versionincluded) rides along as a# ----fenced comment block thatcmx_core::agent::preserved_frontmatterreads back forlistanddoctor. Seecmx-core/src/agent.rs. - Amp resolves user-scoped skills under XDG (
~/.config/agents/skills/), not~/.agents/skills/. Project skills use the shared path. - Hermes is global-centric: its auto-read source of truth is
~/.hermes/skills/. It reads.agents/skills/only if you add it toskills.external_dirsin~/.hermes/config.yaml. - Shared-directory caveat: because
.agents/skills/is shared, uninstalling a skill under one of these platforms removes the directory for all tools that read it (each platform still keeps its own lockfile).
The abstraction we considered (and why the lean version won)
The richest model on the table was an Artifact × Target capability
descriptor with pluggable install strategies:
#![allow(unused)] fn main() { enum InstallStrategy { FileDrop { dir, transform: Option<Transform> }, // copy/transform a file or dir ConfigMerge { file, pointer, entry_schema }, // merge a key into shared JSON/YAML/TOML Reference { registry, slug_fmt }, // tool's own package manager (e.g. Continue Hub) Unsupported { reason }, } // Platform::target(kind, scope) -> Target { strategy, .. } }
…plus growing ArtifactKind from {Agent, Skill} toward
{Agent, Skill, Command, McpServer}.
We did not build this. Once the scope decision ruled out
config-merge, the only live strategies were FileDrop and Unsupported — which
the existing design already models cleanly via:
Platform::install_subpath(kind, scope) -> PathBuf— per-(platform, kind, scope) file-drop location,Platform::supports(kind) -> bool— theUnsupportedgate,Platform::agent_extension()+Platform::transforms_agent_to_toml()— the oneTransformwe need (Codex).
Introducing the full enum would have been speculative abstraction (YAGNI) for a single strategy. The capability descriptor is the right shape to revisit if and when config-merge is in scope.
Scope decisions
| Decision | Choice | Rationale |
|---|---|---|
| Unsupported artifact kinds | Skip with a clear error | Each platform declares supported kinds; e.g. cmx agent install --platform pi fails loudly rather than writing where the tool never reads. |
| Manifest generation for new tools | Out of scope | None of opencode/codex/pi/crush/amp/zed/openhands/hermes has a Claude-style plugin/marketplace manifest; generating .X-plugin/ dirs would be dead files. |
| Codex agents | Transform md → TOML now | Codex subagents are TOML; a verbatim copy wouldn't work. Hand-rolled emitter, no new dependency. |
| Codex / cohort skill location | Shared .agents/skills/ | Matches official docs and the cross-tool standard; serves multiple tools at once. |
| Config-merge / MCP servers | Out of scope (file-drop only) | MCP/Zed-profile/Goose/Crush config is a merge into a shared file, not a file install. It's a different engine and brushes the charter non-goal "managing LLM API keys" (MCP entries can carry secrets). Revisit deliberately if pursued. |
Continue Hub (Reference) | Out of scope | Continue has its own package manager; cmx would be redundant/parallel to it. |
Phasing
- Phase 1 (done): opencode, codex, pi (commit
308523f); then crush, amp, zed, openhands, hermes (commit71021dd). All file-drop. - Phase 2 (not started): a
Commandartifact kind — portable markdown slash-commands (Amp.agents/commands/, Cline workflows, codex/Continue prompts). File-droppable and fairly portable. - Phase 3 (deferred, needs charter call): a
ConfigMergestrategy + anMcpServerartifact kind. Highest value for "manage the complexity," but a different engine and a scope expansion.
Known future snags
- Cline breaks the shared-path pattern. It reads skills from
.claude/skills/~/.cline/skills, not.agents/skills. Adding Cline means a tool-specific skill target (and the.agents-cohort assumption no longer being universal). - Editor-embedded config is brittle to locate. Cline's MCP settings live
under VS Code
globalStorage/<publisher>/…, which varies per editor fork (Code, Insiders, VSCodium, Cursor, Windsurf). Any future config-merge work must detect the host editor. - Version-dialect drift. OpenHands has three coexisting skill layouts
(
.agents/skills, deprecated.openhands/skills, deprecated.openhands/microagents) and two frontmatter dialects. We target only the current standard.
Per-tool research summaries & sources
Each tool was researched against an identical capability template (identity, config dirs, agents, skills, commands, instruction files, MCP, plugin manifest, distinctive strengths, abstraction friction).
Implemented
- opencode —
~/.config/opencode/,.opencode/. Markdown agents inagent/(loader globs{agent,agents}); skills.opencode/skills+.agents/skills+.claude/skills. AGENTS.md (falls back to CLAUDE.md). No plugin manifest (npm/JS modules). Sources: https://opencode.ai/docs/agents/, https://opencode.ai/docs/skills/, https://opencode.ai/docs/rules/, https://opencode.ai/docs/plugins/. - Codex CLI —
~/.codex/,.codex/. TOML subagents inagents/(name/description/developer_instructions); skills officially at.agents/skills/. AGENTS.md (+AGENT.md/CLAUDE.mdfallback). No plugin manifest. Sources: https://developers.openai.com/codex/subagents, https://developers.openai.com/codex/skills, https://developers.openai.com/codex/config-basic. - Pi (pi.dev) —
~/.pi/agent/,.pi/. Skills =SKILL.mddirs in~/.pi/agent/skills/,~/.agents/skills/,.pi/skills/,.agents/skills/. No agents. AGENTS.md. npm packages. Sources: https://pi.dev, https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md, https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/settings.md. - Crush (charmbracelet/crush) —
~/.config/crush/crush.json,crush.json/.crush.json. Skills (agentskills.io) at.agents/skills,.crush/skills,.claude/skills,~/.agents/skills. No user-definable agents (internalcoder/taskonly). MCP incrush.json. Reads AGENTS.md/CLAUDE.md/CRUSH.md. Native LSP. Sources: https://github.com/charmbracelet/crush, https://agentskills.io, https://deepwiki.com/charmbracelet/crush/2.2-configuration. - Amp (Sourcegraph) —
~/.config/amp/,.amp/. Skills at.agents/skills,.claude/skills,~/.config/agents/skills,~/.config/amp/skills,~/.claude/skills. No file agents (subagents via TS plugins;amp.experimental.createAgent). Commands.agents/commands/*.md. AGENTS.md (+AGENT.md/CLAUDE.md). MCPamp.mcpServersin settings.json. Toolboxes deprecated → TS plugins. Sources: https://ampcode.com/manual, https://ampcode.com/news/slashing-custom-commands, https://ampcode.com/news/AGENTS.md. - OpenHands —
~/.openhands/+~/.agents/skills/; project.agents/skills/(→ legacy.openhands/microagents/). Skills = AgentSkills standard; "agents" are keyword-triggered knowledge skills (triggers:frontmatter). AGENTS.md (+CLAUDE.md/GEMINI.md). MCP viaconfig.toml/~/.openhands/mcp.json. Official extensions registry (npm@openhands/extensions). Sources: https://docs.openhands.dev/overview/skills, https://github.com/OpenHands/extensions, https://agentskills.io/specification. - Zed —
~/.config/zed/(settings.json, AGENTS.md); project.zed/,.rules,<repo>/.agents/skills/. Skills native at~/.agents/skills/(flat only). "Agent profiles" are tool-gating JSON insettings.json(no system prompt). MCP =context_serversin settings.json. Reads.rules/AGENTS.md/CLAUDE.md(+6, precedence-ordered).extension.tomlregistry covers MCP/themes/langs, not agents/skills. Sources: https://zed.dev/docs/ai/skills, https://zed.dev/docs/ai/rules, https://zed.dev/docs/ai/mcp. - Hermes (NousResearch/hermes-agent) —
~/.hermes/(config.yaml, SOUL.md, skills/). Skills = agentskills.io at~/.hermes/skills/; reads~/.agents/skillsonly via opt-inskills.external_dirs. No file agents (single global SOUL.md + runtimedelegate_task). MCPmcp_serversin config.yaml. Context files.hermes.md/HERMES.md> AGENTS.md > CLAUDE.md >.cursorrules. "Tap" registry (GitHub repos of SKILL.md) and consumes Claude-compatible marketplace manifests. Sources: https://hermes-agent.nousresearch.com/docs/user-guide/features/skills, https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp, https://github.com/NousResearch/hermes-agent. - Devin (Cognition) — cloud software engineer; skill discovery is
repository-scoped (indexed/connected repos), no user-global directory on
the local machine. Skills = agentskills.io
SKILL.md; scans.agents/skills/(recommended) plus.github/,.claude/,.cursor/,.codex/,.cognition/, and.windsurf/skills/dirs. No file-droppable agents (knowledge base + playbooks are platform-side, not files). Reads AGENTS.md. cmx maps global scope to the shared~/.agents/skills/for cohort consistency, but only project-scoped (committed) skills reach Devin. Sources: https://docs.devin.ai/product-guides/skills.
Researched, not implemented
- Aider —
.aider.conf.ymldotfile (home + repo + cwd). No agents (4 fixed modes), no skills, no native MCP, no plugin system. Instruction files only via explicit--read/read:(no auto-load). Best cmx fit: install a markdown convention file + register it in.aider.conf.yml; skills have no home. Sources: https://aider.chat/docs/config/aider_conf.html, https://aider.chat/docs/usage/conventions.html. - Cline — VS Code/JetBrains/CLI. Skills = Anthropic-compatible at
.claude/skills,~/.cline/skills(not.agents/skills). Agents = built-in Plan/Act modes +.clinerules(file or dir). Workflows = slash commands. MCP-first with a marketplace; MCP config under VS CodeglobalStorage. Sources: https://docs.cline.bot/customization/skills, https://docs.cline.bot/customization/cline-rules, https://docs.cline.bot/mcp/adding-and-configuring-servers. - Continue —
~/.continue/,.continue/. NoSKILL.md; compositional "blocks" (rules/prompts/models/context/docs/mcp) + "assistants" (YAML or CLI md+frontmatter). Continue Hub registry; install by referenceuses: owner/name@version— its own package manager. Best cmx fit: parallel local installer at best; Continue manages itself. Sources: https://docs.continue.dev/reference, https://docs.continue.dev/customize/deep-dives/rules, https://docs.continue.dev/guides/understanding-configs. - Goose (Block / AAIF) —
~/.config/goose/,.goose/. No skills; capabilities = extensions (which are MCP servers) configured inconfig.yaml. "Recipes" (YAML) are the shareable persona/workflow unit. AGENTS.md +.goosehints. Install viagoose://deeplinks / config merge. Best cmx fit: map cmx agents → recipes; skills unsupported. Sources: https://goose-docs.ai/docs/guides/recipes/recipe-reference/, https://goose-docs.ai/docs/guides/config-files/, https://goose-docs.ai/docs/getting-started/using-extensions/.
Source code map
cmx/src/platform.rs— thePlatformenum and all per-tool knowledge (install_subpath,supports,agent_extension,transforms_agent_to_toml,slug,manifest_dir,targets).cmx-core/src/agent.rs— markdown → Codex TOML transform (pure functional core, shared by cmx and cmf).cmx/src/paths.rs—ConfigPaths::install_dir,installed_artifact_path(platform-aware filename),ensure_supports.book/src/reference/platforms.md— user-facing platform path & lockfile tables.
Canonical Home & cmx doctor
Status: Living design document. Published in the open on purpose — it records why cmx grows a canonical home for hand-authored artifacts and a system-survey command, so users and contributors can see the design goals and trade-offs rather than reverse-engineering them. Last substantive update: 2026-05.
Why this document exists
cmx already projects a curated set of artifacts outward into many tools'
native locations (see Multi-Tool Platform Support).
But it assumed the curated set already lived somewhere managed — a git-backed
marketplace or a tidy local source. Real systems don't start there. They start
as a pile of hand-authored skills and agents inside whatever tool you adopted
first (for many people, ~/.claude/), with no provenance, no checksums, and no
way to move them to the next tool without copy-paste.
Two forces made this urgent:
- Tool portability is now a survival requirement, not a nicety. A coding assistant's licensing or pricing can change such that you must move your automation to a different tool on a deadline. When that happens, your private skills must not be hostage to the tool you're leaving.
- The "curated set" has to have a home that outlives any one tool. If the
source of truth is
~/.claude/skills, then dropping Claude Code means losing the source of truth. The home must be tool-neutral.
This note records the model we chose, the classification cmx doctor reports,
the adoption mechanics, and the scope boundaries.
Design principles (how this extends the existing ones)
This work is a direct application of two commitments already stated in the platform-support design doc, plus one addition:
- One curated set, projected outward. (existing) The canonical home is that curated set, made explicit and tool-neutral. Today's gap was that the set had no first-class home; you projected outward from an accident of history.
- Provenance and integrity are first-class. (existing) Adoption is the act of
giving an un-provenanced artifact provenance: a checksum, a recorded version,
a home.
doctoris how you see provenance (or its absence) across the whole system. - Diagnose before you mutate. (new)
doctoris read-only by contract. You see the full truth of a disorganized system — what's tracked, drifted, orphaned, missing, duplicated — before any command changes a byte. Adoption is a separate, explicit step.
The model
Canonical home
A first-class local directory that holds your hand-authored private agents and skills. It is the authoritative source of truth for artifacts you wrote yourself (as opposed to artifacts you pulled from a remote marketplace).
- Location:
~/.config/context-mixer/homeby default — inside cmx's existing config root, alongsidesources.jsonand the lockfiles — overridable via thehomefield inconfig.json. (We deliberately reuse the established config directory rather than invent a new~/.cmx/tree; cmx already owns~/.config/context-mixer/.) - Structure: a plain artifact tree —
agents/*.mdandskills/<name>/SKILL.md— with nomarketplace.jsonrequired. cmx's existing fallback tree-walking scanner already reads un-manifested repositories, so the home works withlist,install,search, andbrowseunchanged. cmf does not author or lint canonical-home artifacts. - First-class status: the home is auto-registered as a visible local
source named
home(created, and written intosources.json, on first use — e.g. the firstadopt). It then behaves like any registered source:list,install --all,search, andbrowsesee it, and it shows incmx source list. We deliberately chose a visible, explicit source over an invisible injected one: cmx's whole model is that sources are explicit, and keeping the home insources.jsonavoids special-casing the shared config-load path that every command runs through. (Trade-off: like any source, it can besource removed — re-runningadoptorcmx home initre-registers it.) ~/.claude/skillsis demoted. It stops being special. It becomes one install target among the thirteen platforms, on equal footing with opencode, codex, and hermes. The source of truth now survives dropping any single tool — which is the entire point.
cmx doctor — read-only stocktake
doctor surveys every platform's install directories (global, and --local
for project scope) and cross-references each per-platform lock file plus the
canonical home. For each artifact it reports one classification:
| State | Meaning | Typical cause |
|---|---|---|
| tracked | in a lock file, installed checksum matches | a normal cmx install |
| drifted | tracked, but the installed copy was edited after install | hand-tweaked in place |
| untracked | on disk, no lock entry, but a registered source provides it | installed out-of-band → track via install |
| orphaned | on disk, no lock entry, no source provides it | hand-authored artifacts (the ~/.claude/skills pile) → adopt |
| missing | in a lock file, but gone from disk | deleted out-of-band → uninstall clears it |
| diverged | the same logical artifact's copies have different content across locations | edited in one tool but not another |
A skill installed for several tools is one logical artifact listing those
tools — not N duplicates. The survey keeps raw per-location rows internally (adopt
needs them), but the user-facing view groups them by (kind, name, scope).
diverged is the only multi-location situation worth flagging, and it is
decided on content, representation-aware: copies in the same representation
are compared by checksum, while a copy on a transforming platform (Codex's
generated agent TOML) is compared through the Codex projection of the portable
Markdown copy. Reformatting alone is never divergence; an edited or stale TOML
— one that is not byte-for-byte what cmx would generate from the Markdown
sibling today — is.
The untracked vs orphaned split is what makes adoption safe: adopt and
--adopt-all act only on orphaned artifacts. An untracked artifact has a
real upstream source, so adopting it as private would duplicate source content
and lose provenance — it's steered to install instead. (Discovered the hard
way: a real system had a vendor tool's entire bundled-skill collection sitting
untracked on disk; a blanket adopt-all would have swept those defaults into the
personal home.)
doctor mutates nothing. It exits non-zero when anything is off (so it is
usable in a hook or CI later) and prints a grouped, human-readable report plus a
short "what to do next" footer (e.g. "12 orphaned skills — run
cmx doctor --adopt-all to bring them under management").
Adoption — orphaned → tracked
Adoption is the bridge from "disorganized pile" to "managed set":
cmx skill adopt <path>(andcmx agent adopt <path>) copies one orphaned artifact into the canonical home, normalizes its frontmatter (fillsname, defaultsversion: 0.1.0if absent), records its checksum, and registers it.cmx doctor --adopt-allsweeps every orphandoctorfound into the home in one pass.- Adoption copies; it never moves the original. After adoption the original
on-disk copy reclassifies from orphaned to tracked (its checksum now
matches the home). The destructive choice — deleting originals, or rewriting
them as managed installs — is left to the user via existing
uninstall/installcommands. This keeps adoption safe to run on a messy system.
Projection — already built
Once artifacts are in the home, projecting them to whatever tool you are moving
to needs no new code: the home is an authoritative source, so
cmx skill install --all --platform opencode (or codex, hermes, …) installs
the whole set into that tool's native location with full lockfile tracking.
The cutover ritual becomes:
cmx doctor # see the mess
cmx doctor --adopt-all # canonicalize the orphaned private artifacts
cmx skill install --all --platform <target> # project to the new tool
Architecture notes
The one non-trivial structural point: doctor surveys all platforms, but the
existing path/lock machinery is bound to a single active platform (ConfigPaths
carries one Platform, and lock_path / install_dir resolve against it). To
survey every platform without coupling, doctor:
- iterates
Platform::ALL(a new exhaustive slice of variants), and - for each platform derives a per-platform view of
ConfigPaths(thehome_dirandconfig_dirare platform-independent; onlyplatformchanges), then reuses the already-testedinstalled_names,lock_path, and lock-loading functions.
No platform-specific knowledge is duplicated into doctor; it composes the
existing pure functions per platform. This keeps the survey honest as new
platforms are added — a platform that appears in Platform::ALL is automatically
surveyed.
Scope decisions
| Decision | Choice | Rationale |
|---|---|---|
doctor mutation | Read-only by contract | Diagnose before you mutate. The only writing path is the explicit adopt / --adopt-all flag. |
| Adoption of originals | Copy, never move | Safe to run on a messy system; reclassifies originals to tracked without risking data loss. Deleting/migrating originals stays an explicit, separate user action. |
| Home structure | Plain tree, no manifest | The fallback scanner already reads un-manifested repos; requiring marketplace.json for purely-local private artifacts would be friction with no benefit. |
| Home location | ~/.config/context-mixer/home, config-overridable | Reuses cmx's existing config root (next to sources.json and the lockfiles) rather than inventing a new ~/.cmx/ tree. Still tool-neutral — it lives under cmx's own directory, not any single assistant's — so it outlives tool changes. |
| Home as a source | Visible, auto-registered local source | Consistent with cmx's explicit-source model; the home lives in sources.json and shows in source list, rather than being injected into the shared config-load path. Auto-registered on first adopt (or cmx home init); re-registers if removed. |
~/.claude/skills | Demoted to an install target | Decouples the source of truth from the tool being abandoned — the motivating requirement. |
Phasing
- Phase 1 —
doctor(read-only) — done. The cross-platform survey and classification, shipped first so the system state is visible before any model decision about the home was finalized. - Phase 2 — canonical home +
adopt— done. Theconfig.jsonhomefield, the auto-registered visiblehomesource,cmx home init/cmx home path,cmx {skill,agent} adopt <name>, andcmx doctor --adopt-all. Adoption copies verbatim into the home and recordshomeprovenance so orphans reclassify as tracked. Projection reuses the existinginstall --all --platform <tool>(the home is just a registered source) — no new code. - Phase 3 — reconciliation ergonomics — done (with a different shape than first
sketched). Rather than a projection convenience that fans the home out (still
just
install --all --platform X), the gap that proved worth closing was reconciling copies that have diverged across tools. Two commands cover it:cmx {skill,agent} promote <name>pushes in-place edits back into the home (the mirror ofupdate), andcmx skill sync <name>reconciles a skill between install locations by copying one copy over the others.doctor's divergence hint routes to whichever fits the artifact's provenance. See Promoting Local Edits and Reconciling Across Platforms.
Note on frontmatter normalization. An earlier sketch had
adoptdefault a missingversionto0.1.0. We deliberately don't: normalizing the home copy would make it differ from the verbatim original, so the original would show as drifted rather than tracked. Adoption copies byte-for-byte; version curation is a separate, explicit step.
Source code map
cmx/src/platform.rs—Platform::ALL(exhaustive variant slice) so the survey is automatically complete.cmx/src/paths.rs—ConfigPaths::with_platform(per-platform view) anddefault_artifact_home()(config_dir.join("home")— not the existinghome_dirfield, which is the OS home).cmx/src/types.rs— thehomefield onCmxConfig.cmx/src/config.rs—resolve_artifact_home(config, paths)(override or default).cmx/src/doctor.rs— the read-only cross-platform survey + classification (ArtifactState,DoctorRow,survey).cmx/src/adopt.rs— copy-into-home + per-platform lock registration +ensure_home_source(registers thehomesource so projection viainstall --allworks with no further setup). Backsadopt,doctor --adopt-all, andhome init/home path.book/src/reference/commands.md— documentsdoctor,adopt, and the home.
Deterministic Verification of Intents
Status: Living design document. Published in the open on purpose — it records why Context Mixer grew a verifier, and why it verifies the way it does, so users and contributors can see the design goals and trade-offs rather than reverse-engineering them. The working note this chapter distils, with its phased plan and open decisions, is
CMV.mdin the repository. Last substantive update: 2026-09.
Why this document exists
cmf compiles a set of intent records into the guidance a project's agents read. Whether the project's code then holds those intents true is a separate question, and for a long time only the behavioural benchmark answered it — by parsing finished code with one deterministic check per intent, never by asking a model.
Those checks proved three things worth keeping:
- Parsing beats asking. A verdict derived from the code is reproducible, cheap, and cannot be talked into agreement. What an agent said it would do is not evidence that it did.
- The question forms generalize; the substrate does not. Python and Rust checks share one verdict shape, one three-state result, and one split between what the check knows and what only the project knows — but Rust needed its own fact extractor. Each language is best at reading its own source.
- Checks get corrected, often. One correction reversed a headline that guidance had broken the software. Evidence has to survive its own scoring rules.
But the checks lived inside the benchmark, and nothing outside it could run them. A project that installed cmf guidance had no way to ask whether its code still followed it. cmv makes that question a normal part of quality verification, run from a project root like a linter, and this chapter records the shape that makes it trustworthy.
The intent atlas
The knowledge base cmf compiles from and cmv verifies against has a name, the intent atlas, and four parts, all read-only to the tooling:
- Ecosystems it supports, each with the sensors that recognize it at a
project root and the ecosystems it implies (
uvimpliespython). - Intent records, placed in the directory hierarchy that names their
ecosystem:
craftsperson/python/uv/specializes Python guidance for uv projects, and that nesting is the record's ecosystem. - Validators beside the intents, one per language that can check them.
- Profiles that name a slice of it for a delivery surface.
Someone who forks the atlas forks all four. The tooling carries no opinion about software; every opinion is in the atlas, and the tooling verifies a forked or replaced atlas exactly as well.
Three responsibilities
| Owns | Reads | Writes | |
|---|---|---|---|
| Intent atlas | Ecosystems and sensors; records and validators; profiles | — | Sensors, records, validators, calibration fixtures |
| cmf (composes) | Selection and rendering | Atlas, profile | Guidance artifact, compile manifest |
| cmv (verifies) | Dispatch, aggregation, exit code | Manifest, project config, workspace | Nothing |
One library, the intent-atlas crate, is the reader of the atlas's shape that
cmf and cmv share — catalog, profiles, selection, sensors, manifest. Neither
binary depends on the other. It is deliberately not part of cmx-core: cmx has
no use for it, and cmx-core's TypeScript twin and lockstep release would be
paid for a consumer that does not exist.
Design decisions
- cmv is purely deterministic. Given the same workspace, manifest, and project configuration, its output is byte-identical. It has no LLM feature, never falls back to a model when a validator is missing, reads nothing outside its inputs, and executes no project code. This holds by construction, not by convention.
- The compile manifest is cmf's second output artifact. Not a debugging
aid: a schema with a version, written beside the project's lock file,
committed with the project, and pinned by golden fixtures. It is the
--explainoutput made durable and given a contract. - Validators live in the atlas, beside their records, declared as evidence entries. cmv carries no opinion about any intent, so it stays stable while validators keep being corrected.
- One validator per language per intent. A record may declare several; cmv runs the ones matching the workspace's detected ecosystems. A language with no validator reports unchecked, never pass.
- The atlas is the registry, pinned by revision. Vendoring validator code into projects would strand every correction; an unpinned live registry would make CI irreproducible. A pin gives both: cmv verifies at the revision the manifest recorded, materializing that tree when the checkout has moved on, and reports the move without changing the exit code.
- Static analysis first. Every existing validator is static, including the ones that sound like runtime properties. Runtime evidence, when it comes, will arrive through the project's own test runner, inheriting its determinism standard rather than inventing one.
requiredgoverns gating; a record's maturity is informational.
The pieces
A validator on a record is an evidence entry with an executable:
evidence = [
{ type = "architecture_review", description = "Core modules depend on project-owned gateway contracts rather than concrete vendor clients.", required = true },
{ type = "static-check", language = "rust", run = "checks/rust/put-gateways-at-effect-boundaries.py", description = "A project-owned gateway trait is declared, implemented by a concrete adapter, and depended upon by at least one module that performs no I/O.", required = true },
]
The description is what the agent sees — cmf renders it into the guidance — so
the agent knows what will be checked. run and language never reach the
artifact. A static-check entry with neither field is ordinary descriptive
evidence, the kind a validator later makes executable.
The invocation protocol is a subprocess with two arguments, --workspace
and --config, and one JSON document on stdout: applicable, followed,
signals, evidence, locations. Anything else — a crash, a timeout,
unparseable output — is unchecked with the reason. cmv never guesses.
Verdicts have three states, not two. Some intents are conditional: "mock only owned boundaries" binds code that mocks something. A suite that tested everything against a live server has no doubles to spec, and scoring that as a violation says something false about the work. Not-applicable intents leave the denominator, so an adherence rate always reads "of the intents this work had occasion to exhibit."
Project configuration holds what neither the atlas nor the code can supply — which literals mark the business rules, which symbols count as blocking — as hand-authored TOML at the project root, the way linters keep theirs. Machine-written state is JSON in the state directory.
Sensors make ecosystem detection the atlas's, not the tooling's. A file at the atlas root declares each ecosystem's signatures in a few predicate kinds (a file exists, a glob matches, a file contains a literal) and its implications. The same detected set drives cmf's warning that a profile targets an ecosystem the project lacks, cmv's choice of validators, and cmv's note that a manifest was compiled for an ecosystem the workspace does not contain. An atlas with no sensors detects nothing, and both tools say so.
Ecosystem eligibility in cmf follows the same hierarchy: a profile declares
the ecosystems it targets, a record is eligible when every ecosystem in its
path is declared, and category-and-tag selection never picks another
language's advice. With that filter in place cmf can also walk specializes
edges downward, so general advice compiles to the ecosystem's own version of
it and the general parent is dropped.
Calibration
A validator that cannot fail measures nothing. Every validator in the atlas ships with a fixture it passes and one it does not, in the language it validates, and the atlas's own gate runs all of them twice and requires byte-identical output. That is where the determinism rule is enforced, and it is also why cmv can trust what it dispatches: the atlas has already proved each validator can fail.
What happened to the benchmark
The behavioural benchmark became a consumer of the same tools rather than the
owner of the checks. It compiles each scenario's profile with cmf, stages the
manifest and project configuration into every trial workspace, and scores with
cmv check. Rescoring an archive of hundreds of agent trials through cmv
reproduced the stored verdicts exactly — and, in doing so, found one real
validator bug the benchmark's own scorer had been masking. That is the
feedback loop the design exists for: a check corrected for cmv is corrected for
the benchmark in the same commit, and every project running cmv in CI is a
calibration source.
What this deliberately is not
- Not a model judging adherence. cmv parses; it never asks.
- Not a runtime monitor. cmv never executes the project.
- Not an author of the atlas. cmf and cmv consume records, validators, and sensors read-only; the atlas's own tooling owns them.