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.

ToolRole
cmxThe 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?

ArtifactShapePurpose
AgentSingle .md file with YAML frontmatterCurated guidance for a tech stack — applies across many repositories
SkillDirectory with SKILL.md + supporting filesComposable 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 doctor surveys 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

Homebrew (macOS and Linux)

brew tap svetzal/tap
brew install cmx

From GitHub Releases

Download the latest binary for your platform from GitHub Releases.

PlatformArchive
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:

TypeGlobalLocal
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:

  1. Marketplace repos — contain .claude-plugin/marketplace.json listing plugins with their agents and skills. Compatible with Claude Code's native plugin system.

  2. Simple repos — any directory with .md agent files and skill directories containing SKILL.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

  1. The artifact is copied to the appropriate platform directory
  2. A SHA-256 checksum is computed
  3. The version (if present in frontmatter) is recorded
  4. 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

IconMeaning
✅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.

  • update pulls the source copy over your installed one, discarding any local edits.
  • promote copies your installed copy into the home and refreshes its home-provenance lock baselines, so the artifact reads as tracked again 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:

SituationWhyDo this instead
Artifact is tracked from a git sourcethe source, not the home, is authoritativeedit the source clone and cmx <kind> update, or update --force to discard the edits
Artifact is untracked / orphanednothing tracks it yetcmx <kind> adopt <name> (hand-authored) or cmx <kind> install <name> (source-backed)
A Codex agentits installed copy is TOML, not canonical markdownedit 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 --platform is given), discarding local edits; --force overwrites 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 --platform is 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/x to plugins/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, so update still 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 --force needed 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 Platforms column lists every surveyed copy as platform@version — not as "duplicates." diverged is 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> --force rewrites 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 an external or source-less skill), while cmx <kind> update <name> --force (re-sync from the source) or cmx <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 — M modified (with +added −removed counts), A added (only in the installed copy), D deleted (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

VariablePurpose
OPENAI_API_KEYAPI key for OpenAI gateway
OLLAMA_HOSTOllama 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.toml declaring 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 --local uses.
  • .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:

StateMeaningExit
PASSThe code holds the intent—
FAILIt does not1 when the validator is required
N/AThe intent's condition never arose (a "mock only owned boundaries" rule against a suite with no mocks)—
UNCHECKEDNo verdict: no validator for your ecosystems, or the validator crashed, timed out, or could not start — the reason is printed1 only with --strict
UNGUIDEDThe 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

FieldDescription
nameKebab-case identifier
descriptionWhen to use this agent — include examples for better matching
modelsonnet, opus, haiku, or inherit

Optional cmx fields

FieldDescription
versionSemver version (e.g., 1.3.1)
deprecatedtrue to mark as deprecated
deprecated_reasonWhy it's deprecated
deprecated_replacementName 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

FieldDescription
nameKebab-case identifier — must match the parent directory name
descriptionWhat + when — used for matching against user requests

Optional cmx fields

FieldDescription
versionSemver version
deprecatedtrue to mark as deprecated
deprecated_reasonWhy it's deprecated
deprecated_replacementName 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.

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

  1. If agents/skills arrays exist on a plugin entry, cmx uses them directly.
  2. If only a source path string is present, cmx walks that directory.
  3. If source is an object (remote), cmx warns and skips.

Simple directory (fallback)

Without marketplace.json, cmx walks the entire tree looking for:

  • .md files with frontmatter containing name and description → agents
  • Directories containing SKILL.md with 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:

CategoryPurpose
ecosystemLanguage and framework tooling (e.g., rust-craft, python-craft)
functionalCross-cutting capabilities (e.g., code-review, documentation)
personalIndividual 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 },
]
  • description is 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".
  • language is the source language the validator reads, in the atlas's ecosystem vocabulary; cmv runs the entries matching the ecosystems it detects.
  • run is a path relative to the atlas root. Any executable works; the validator owns its own fact extraction (Python's ast, a syn binary for Rust).
  • required decides 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.

ComparisonDetects
Installed vs sourceSource has been updated since install
Installed vs lock fileLocal 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

InstalledSourcecmx shows
Checksums match—✅ Up to date
Versions differ1.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: ...
---
FieldRequiredDescription
deprecatedYesSet to true
deprecated_reasonNoWhy it's deprecated — include actionable info
deprecated_replacementNoName 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 add command in the reason
  • Set deprecated_replacement only if the replacement is installable via cmx

Command Reference

Global options

OptionDescription
--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

CommandDescription
cmx source add <name> <path-or-url>Register a marketplace source
cmx source listList 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

CommandDescription
cmx agent install <name>...Install one or more agents from sources
cmx agent install <source>:<name>Install from a specific source
cmx agent install --allInstall all available agents
cmx agent install <name> --localInstall into current project
cmx agent install <name> --platform cursorInstall to Cursor
cmx agent update <name>Update an agent from its source (replaces the installed copy with the source's)
cmx agent update --allUpdate 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 listList 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:

CommandDescription
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

CommandDescription
cmx listList installed agents and skills (cmx-managed inventory)
cmx list --allInclude external (tool-managed) artifacts in the listing
cmx outdatedShow 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 doctorSurvey every platform; show only what needs attention (read-only)
cmx doctor --allShow the full inventory, not just problems
cmx doctor --localAlso include project (local) scope in the survey
cmx doctor --jsonEmit the survey as machine-readable JSON to stdout
cmx doctor --adopt-allAdopt every orphaned artifact into the canonical home (deprecated; use cmx <kind> adopt --all)
cmx initInstall 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 description frontmatter. 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 the llm feature; a lean build prints a one-line hint in its place. Summary generation is best-effort — if the provider is unreachable, info still 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.

StateMeaning
activeEvery member is installed; cmx set activate made it so
inactiveMembers 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:

StateMeaningRemedy
trackedrecorded in a lock file with a matching checksum— (unless diverged)
driftedtracked, but the on-disk copy was edited after installcmx info <name> to inspect
untrackedon disk, no lock entry, but a registered source provides it (installed out-of-band)cmx <kind> install <name> to track it
orphanedon disk, no lock entry, and no source provides it (hand-authored)cmx <kind> adopt <name> to canonicalize into the home
externalon disk, but declared external in config (managed by another tool)— (unless diverged)
missingin a lock file, but the file is gone from diskcmx <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:

ProblemMeaningRemedy
active_missingThe set is active, but this member isn't installedcmx set activate <name> repairs it (idempotent)
inactive_lingeringThe set is inactive, but this member is still installed and not held by any other active setcmx 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 codeMeaning
0no issues found
2actionable 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.

FlagEffect
(none)Install/update at global scope (~/.claude/skills/cmx/) — the default, since a companion skill describes the tool, not one project
--localInstall into the current project (.claude/skills/cmx/) instead
--forceOverwrite even if the installed copy is a newer version than the bundled one (otherwise refused)
--removeUninstall — removes the skill directory and clears its lock entry (leaves the shared cmx-lock.json in place)
--jsonEmit a machine-readable report instead of human text — the only cmx command that does, today
--globalAccepted 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.

CommandDescription
cmx home initCreate the home directory and register it as the home source
cmx home pathPrint 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

CommandDescription
cmx config showShow 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 listList 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 listList 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

CommandDescription
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> --applyApply the displayed installation plan
cmf install <profile> --platform codexPreview installation for Codex only
cmf statusCount 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"] with tags = ["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_specializations is true (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] ecosystems is 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:

PredicateHolds 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]
CommandDescription
cmv checkRun every artifact’s compiled validators and report attributed verdicts
cmv check --strictAlso fail the run when any intent could not be checked
cmv check --jsonEmit the full per-intent report as JSON for CI
cmv statusSummarize 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-headVerify the atlas's working tree even when the manifest pins a revision its HEAD has moved past

Defaults:

InputDefault
--rootthe current directory
--manifest<root>/.context-mixer/cmf-manifest.json, where cmf install --local --apply writes it
--atlasresolved 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_byStep
override--atlas <path> (or its hidden alias --knowledge-base) was given; nothing else is consulted
sourcethe manifest's atlas.source names a registered cmx source (cmx source list); its local directory or clone is used
paththe 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:

FieldMeaning
paththe atlas root as resolved
resolved_byoverride, source, or path
sourcethe cmx source name the manifest recorded, if any
pinned_revisionthe manifest's revision, if any
head_revisionthe working tree's HEAD, when it is a git checkout
verified_againstpinned when the verdicts came from the pinned revision (materialized, or checked out as HEAD); head otherwise
movedtrue 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

StateMeaningEffect on exit code
passValidator ran; applicable: true, followed: true—
failValidator ran; applicable: true, followed: false1 if the validator is required
not_applicableValidator ran; applicable: false — the condition never arose—
uncheckedNo 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 reported1 only with --strict
unguidedThe 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

CodeMeaning
0Every required, applicable validator passed
1A required validator failed, or, with --strict, an intent was unchecked
2Missing 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
  }
}
  • manifest echoes the manifest's profile and atlas as recorded at compile time (source and revision are omitted when the manifest has none).
  • atlas is where cmv actually read from and which tree it verified; see "The pinned revision" above for every field. sensors says whether that tree declares any ecosystem sensors.
  • ecosystems is the set the workspace was verified as (detected, or the cmv.toml override; [] when nothing was detected or nothing could be), and profile_mismatch lists the manifest profile's declared ecosystems that were not detected (see "Ecosystem detection").
  • intents lists every compiled intent in manifest order, then every dropped intent. state is one of pass, fail, not_applicable, unchecked, unguided; reason is present for the last two. language and description are null when no validator ran; id is null for a dropped intent whose record is no longer in the atlas. language and description join several validators' values with , and / respectively, and signals is then an object keyed by language.
  • summary.adherence_rate is pass / (pass + fail) rounded to four decimals, or null when nothing was applicable. summary.exit_code is 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
  • Record is resolved by key, resolved by id (no record at the manifest's key, but exactly one carries its id — a moved record), or not found in atlas, followed in parentheses by the records sharing the id when that is what stopped the fallback.
  • Config is the exact JSON document the validators would receive through --config: the intent's [intent."<key>"] table from cmv.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, so explain is 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
---
FieldRequiredTypeDescription
nameYesstringKebab-case identifier
descriptionYesstringWhen to use — include examples for matching
modelYesstringsonnet, opus, haiku, or inherit
versionNostringSemver version
deprecatedNobooleantrue to mark deprecated
deprecated_reasonNostringWhy deprecated — include actionable info
deprecated_replacementNostringReplacement artifact name

Skill frontmatter (SKILL.md)

---
name: blog-image-generator
description: Generate images for blog posts.
version: 1.0.0
---
FieldRequiredTypeDescription
nameYesstringKebab-case, must match parent directory name
descriptionYesstringWhat + when to use
versionNostringSemver version
deprecatedNobooleantrue to mark deprecated
deprecated_reasonNostringWhy deprecated
deprecated_replacementNostringReplacement 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.

ScopeClaude CodeOther 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

FieldDescription
typeagent or skill
versionFrom frontmatter at install time (null if absent)
installed_atISO 8601 timestamp
source.repoName of the registered source
source.pathRelative path within the source
source_checksumSHA-256 of the artifact in the source at install time
installed_checksumSHA-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

PlatformValueProject agentsUser agentsProject skillsUser skills
Claude Codeclaude (default).claude/agents/~/.claude/agents/.claude/skills/~/.claude/skills/
GitHub Copilotcopilot.github/agents/~/.copilot/agents/.github/skills/~/.copilot/skills/
Cursorcursor.cursor/agents/~/.cursor/agents/.cursor/skills/~/.cursor/skills/
Windsurfwindsurf.windsurf/agents/~/.codeium/windsurf/agents/.windsurf/skills/~/.codeium/windsurf/skills/
Gemini CLIgemini.gemini/agents/~/.gemini/agents/.gemini/skills/~/.gemini/skills/
opencodeopencode.opencode/agent/~/.config/opencode/agent/.agents/skills/~/.agents/skills/
Codex CLIcodex.codex/agents/ ¹~/.codex/agents/ ¹.agents/skills/~/.agents/skills/
Pipi(not supported) ²(not supported) ².agents/skills/~/.agents/skills/
Crushcrush(not supported) ²(not supported) ².agents/skills/~/.agents/skills/
Ampamp(not supported) ²(not supported) ².agents/skills/~/.config/agents/skills/ ³
Zedzed(not supported) ²(not supported) ².agents/skills/~/.agents/skills/
OpenHandsopenhands(not supported) ²(not supported) ².agents/skills/~/.agents/skills/
Hermeshermes(not supported) ²(not supported) ².agents/skills/ ⁴~/.hermes/skills/ ⁴
Devindevin(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.

PlatformGlobal lock fileLocal 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

  1. .agents/skills/ is a real cross-tool standard. The agentskills.io SKILL.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.

  2. "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.

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

ToolSkill = SKILL.md dir?Reads .agents/skills?Agent-as-file?CommandsMCP configPlugin/registrycmx status
Claude Code✓✓(uses .claude/skills)✓ md✓ .claude/commands✓.claude-plugin/ marketplaceimplemented
GitHub Copilot✓✗✓ md——externalimplemented
Cursor✓✗ (.cursor/skills)✓ md—✓externalimplemented
Windsurf✓✗✓ md—✓externalimplemented
Gemini CLI✓✗✓ md—✓externalimplemented
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_serversextension.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.yamltap + consumes claude-marketplaceimplemented
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 registryresearched, not impl

Install path reference (implemented tools)

Project paths are relative to the repo root; user paths to $HOME.

ToolProject agentsUser agentsProject skillsUser skillsLock 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, plus model, model_reasoning_effort, and sandbox_mode when set). Codex parses its agent files with deny_unknown_fields, so nothing else may be a key; the rest of the frontmatter (metadata.version included) rides along as a # ----fenced comment block that cmx_core::agent::preserved_frontmatter reads back for list and doctor. See cmx-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 to skills.external_dirs in ~/.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 — the Unsupported gate,
  • Platform::agent_extension() + Platform::transforms_agent_to_toml() — the one Transform we 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

DecisionChoiceRationale
Unsupported artifact kindsSkip with a clear errorEach 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 toolsOut of scopeNone 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 agentsTransform md → TOML nowCodex subagents are TOML; a verbatim copy wouldn't work. Hand-rolled emitter, no new dependency.
Codex / cohort skill locationShared .agents/skills/Matches official docs and the cross-tool standard; serves multiple tools at once.
Config-merge / MCP serversOut 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 scopeContinue 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 (commit 71021dd). All file-drop.
  • Phase 2 (not started): a Command artifact 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 ConfigMerge strategy + an McpServer artifact 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

Researched, not implemented

Source code map

  • cmx/src/platform.rs — the Platform enum 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:

  1. 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.
  2. 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. doctor is how you see provenance (or its absence) across the whole system.
  • Diagnose before you mutate. (new) doctor is 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/home by default — inside cmx's existing config root, alongside sources.json and the lockfiles — overridable via the home field in config.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/*.md and skills/<name>/SKILL.md — with no marketplace.json required. cmx's existing fallback tree-walking scanner already reads un-manifested repositories, so the home works with list, install, search, and browse unchanged. 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 into sources.json, on first use — e.g. the first adopt). It then behaves like any registered source: list, install --all, search, and browse see it, and it shows in cmx 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 in sources.json avoids special-casing the shared config-load path that every command runs through. (Trade-off: like any source, it can be source removed — re-running adopt or cmx home init re-registers it.)
  • ~/.claude/skills is 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:

StateMeaningTypical cause
trackedin a lock file, installed checksum matchesa normal cmx install
driftedtracked, but the installed copy was edited after installhand-tweaked in place
untrackedon disk, no lock entry, but a registered source provides itinstalled out-of-band → track via install
orphanedon disk, no lock entry, no source provides ithand-authored artifacts (the ~/.claude/skills pile) → adopt
missingin a lock file, but gone from diskdeleted out-of-band → uninstall clears it
divergedthe same logical artifact's copies have different content across locationsedited 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> (and cmx agent adopt <path>) copies one orphaned artifact into the canonical home, normalizes its frontmatter (fills name, defaults version: 0.1.0 if absent), records its checksum, and registers it.
  • cmx doctor --adopt-all sweeps every orphan doctor found 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 / install commands. 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 (the home_dir and config_dir are platform-independent; only platform changes), then reuses the already-tested installed_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

DecisionChoiceRationale
doctor mutationRead-only by contractDiagnose before you mutate. The only writing path is the explicit adopt / --adopt-all flag.
Adoption of originalsCopy, never moveSafe to run on a messy system; reclassifies originals to tracked without risking data loss. Deleting/migrating originals stays an explicit, separate user action.
Home structurePlain tree, no manifestThe 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-overridableReuses 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 sourceVisible, auto-registered local sourceConsistent 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/skillsDemoted to an install targetDecouples 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. The config.json home field, the auto-registered visible home source, cmx home init / cmx home path, cmx {skill,agent} adopt <name>, and cmx doctor --adopt-all. Adoption copies verbatim into the home and records home provenance so orphans reclassify as tracked. Projection reuses the existing install --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 of update), and cmx 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 adopt default a missing version to 0.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) and default_artifact_home() (config_dir.join("home") — not the existing home_dir field, which is the OS home).
  • cmx/src/types.rs — the home field on CmxConfig.
  • 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 the home source so projection via install --all works with no further setup). Backs adopt, doctor --adopt-all, and home init / home path.
  • book/src/reference/commands.md — documents doctor, 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.md in 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 (uv implies python).
  • 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

OwnsReadsWrites
Intent atlasEcosystems and sensors; records and validators; profiles—Sensors, records, validators, calibration fixtures
cmf (composes)Selection and renderingAtlas, profileGuidance artifact, compile manifest
cmv (verifies)Dispatch, aggregation, exit codeManifest, project config, workspaceNothing

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

  1. 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.
  2. 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 --explain output made durable and given a contract.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. required governs 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.