Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

mind is a manager for agent tooling: skills, agents, rules, and tools. It melds arbitrary git repos and links the items they offer into one or more agent homes (default ~/.claude).

  • A source is a melded git repo (mind meld). It offers items: skills, agents, rules, and tools, found by convention or declared in a mind.toml.
  • mind learn <item> copies an item into the store (~/.mind/store) and symlinks it into each lobe (agent home). A tool is the exception: store-only helper tooling reached by reference, not linked into a lobe by default. Lobes can be non-Claude homes (Gemini CLI, Codex CLI, Antigravity, Windsurf) with a per-kind filter so only the compatible item types link in. Windsurf is project-scoped (a project lobe, not a global one); see Configuration.
  • mind recall and mind probe inspect what is installed and what is available; mind sync and mind upgrade keep sources and installed items current.
  • For authoring, mind init-source and mind review scaffold and validate a source for publishing.

This site is the reference for installing, using, and authoring mind. Start with Install and the Quickstart; Commands is the full verb reference. For authoring a source, see Source layout and Authoring a source. The normative behavior is the spec.

Install

Requirements

mind runs git to clone and sync sources, so git must be installed and on your PATH. The methods below fetch the mind binary itself; they do not install git. Without git, meld and sync fail with a clear “git executable not found” error.

Install script (Linux and Apple Silicon macOS)

curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/jaemk/mind/main/resources/install.sh | sh

Downloads the release binary for your platform (x86_64 or aarch64 Linux, or aarch64 macOS) and installs it to ~/.local/bin. On Linux it fetches the musl build, which is statically linked and runs on any glibc version (Ubuntu 22.04, Debian 12, RHEL 9, Amazon Linux 2, and newer). The script verifies the download against the published SHA256SUMS asset before extracting. Override the target dir with MIND_INSTALL_DIR or pin a version with MIND_VERSION:

curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/jaemk/mind/main/resources/install.sh \
  | MIND_INSTALL_DIR=/usr/local/bin MIND_VERSION=0.12.0 sh

If ~/.local/bin is not on your PATH, the script prints the line to add.

Homebrew (Apple Silicon macOS and Linux)

brew tap jaemk/mind https://github.com/jaemk/mind
brew trust jaemk/mind
brew install mind

The repo is not named homebrew-mind, so the tap needs its clone URL. Homebrew bottles are provided for Apple Silicon macOS (arm64) and Linux (x86_64 and aarch64, glibc-linked). Intel macOS is not covered by the tap; use cargo install mind-cli instead (see below).

Note (migration): Earlier versions of this page said Intel macOS should use the tap. That instruction was wrong: no Intel macOS bottle exists. Use cargo install mind-cli on Intel macOS.

cargo install (Linux and macOS)

cargo install mind-cli

Builds from source using the Rust toolchain. This is the recommended path for Intel macOS and any other Linux or macOS host not covered by the install script or Homebrew tap. Requires Rust 1.88 or later (rustup is the standard way to install it).

The supported platforms are Linux and macOS; the binary does not currently build on Windows. On Windows, run mind under WSL (Windows Subsystem for Linux).

Updating

mind evolve updates the binary itself to the latest release. It reports the target version and the resolved target triple (the exact artifact it would fetch) and prompts before downloading, unless --yes is given (--check reports without changing anything, --to <v> pins a target). It uses the same download path as the install script and verifies the SHA256SUMS asset before swapping in the new binary.

If a gh binary is on PATH, evolve also verifies the downloaded archive’s build-provenance attestation (gh attestation verify) before swapping. Without gh, this step is skipped and evolve proceeds as it always has. Unlike install.sh (which never blocks on this check), evolve aborts the swap, leaving the existing binary in place, when gh runs the check and reports the artifact does not verify; it still proceeds, with a note, when gh itself could not complete the check (no network, or a gh build without the attestation subcommand).

Uninstall

Remove installed items and sources first, then the binary.

  • mind forget <item> removes one installed item: its store copy (~/.mind/store/<kind>/<name>) and its symlink in every lobe. mind forget --unmanaged also removes lobe items mind did not install (with no <item>, every unmanaged item across all lobes).
  • mind unmeld <name> drops a melded source and uninstalls the items it installed; --keep-items drops the source without touching installed items.
  • rm -rf ~/.mind (or the MIND_HOME override) removes the store, source clones, manifest, registry, and config – everything mind tracks on disk. forget/unmeld the items you want cleaned up first: deleting ~/.mind leaves lobe symlinks dangling, and mind introspect on a lobe with no ~/.mind reports every link as broken.
  • Remove the mind binary itself:
    • Install script: delete it from ~/.local/bin/mind, or from $MIND_INSTALL_DIR/mind if you overrode the target directory.
    • Homebrew: brew uninstall mind.
    • cargo install: cargo uninstall mind-cli.

Quickstart

Meld a source and install its items:

mind meld jaemk/mind   # clone this repo, prompt to install its items
mind recall            # list what's installed

mind meld jaemk/mind registers the hello-mind example skill plus two curated sources it points at, anthropics/skills and ComposioHQ/awesome-claude-skills, without installing their items. Melding any other repo works the same way:

mind meld owner/repo   # clone and prompt to install items
mind recall            # list what's installed

meld presents available items and prompts to install. Confirm to install all, or select individually. To register without installing and choose items later:

mind meld owner/repo --register-only   # register only, skip install prompt
mind probe                              # browse available items (interactive TUI)
mind learn <item>                       # install a specific item
mind recall                             # list what's installed

Agent homes can be Claude Code, Gemini CLI, Codex CLI, Antigravity, or Windsurf – not just ~/.claude. Run mind config lobes detect to find which of these are installed and add matching lobes (Windsurf is project-scoped, so it gets a mind link-project hint instead of an auto-added lobe). See Configuration for the per-harness path table and preset commands.

To try mind’s install flow against a source you can inspect and edit freely, clone the repo and meld the bundled starter example (a plain convention layout, see examples/starter/):

git clone --depth 1 https://github.com/jaemk/mind /tmp/mind-repo
cp -r /tmp/mind-repo/examples/starter /tmp/starter
cd /tmp/starter && git init -q && git add -A && git commit -qm init
mind meld /tmp/starter   # prompts to install; confirm to install all three
mind recall

Commands is the full verb reference. Source layout covers how a source repo exposes items.

Examples

The examples/ directory in the repo holds runnable sources, each a small but valid mind source with its own README.md and a test that melds it so it cannot rot. This page maps each use case, consumer and maintainer, to the example that demonstrates it.

Every example is a directory inside the mind repo, not its own git repo, so clone the repo, copy the example out, and init a repo before melding (each README shows the exact commands):

git clone --depth 1 https://github.com/jaemk/mind /tmp/mind-repo
cp -r /tmp/mind-repo/examples/<name> /tmp/<name>-demo
cd /tmp/<name>-demo && git init -q && git add -A && git commit -qm init
mind meld /tmp/<name>-demo
mind probe

Example catalog

ExampleShowsKey spec
starterConvention discovery: a repo with no mind.toml, items found by skills/<n>/SKILL.md, agents/<n>.md, rules/<n>.mddiscovery.md
helloThe repo-root mind.toml’s [source].roots scanning examples/hello; what mind meld jaemk/mind offersdiscovery.md
toolingThe tool kind plus path tokens {{self}}, {{tools:name}}, {{path:ref}}tooling.md
hooksSource [[hooks]]: build/install tooling at meld, tear down at unmeld, with the disclosure promptinstall-hooks.md
explicitAuthoritative [[items]] inventory: export control, custom path/link, per-item install/uninstall hooksdiscovery.md
monorepo[source].roots: convention discovery rooted at per-package subtreesdiscovery.md
discover[discover] kind globs with per-kind include/exclude lists, for layouts roots cannot expressdiscovery.md
namespacingA prefix plus {{ns:name}} reference tokens that survive a renamenamespacing.md
super-source[discover].sources: a curated registry that melds other repos, optionally namespaced or auto-installeddiscovery.md
marketplace-pluginA Claude .claude-plugin/plugin.json: skills and agents mapped to items, unsupported components reportedmarketplace.md
marketplace-catalogA Claude .claude-plugin/marketplace.json: a catalog of in-repo plugins, each a namespaced sub-sourcemarketplace.md
marketplace-curatorA repo that is both a Claude marketplace and a mind curator: [discover].sources composes with the plugin manifest instead of suppressing itmarketplace.md
policyAn enterprise managed policy: trusted-source allowlist, require-pinned, lobe lockpolicy.md
driftItem drift end to end: recall/introspect flag an out-of-date item, upgrade re-links itlifecycle.md
multi-lobeLinking one item into more than one agent home (lobe) via config lobesstorage.md
absorbClaiming an unmanaged lobe item into a version-controlled source with absorbabsorb.md

Consumer use cases

You are installing and managing tooling that other people authored.

  • Install an item from a source. Meld a repo, then learn an item. See the Quickstart and the starter example (mind meld, mind learn).
  • Browse and search what is available. mind probe opens an interactive browser, or prints a listing when piped. See Commands.
  • Resolve a name collision between two sources. Namespace one on install with mind meld <repo> --namespace <prefix>, so its items install as <prefix>:<name>. See namespacing and Troubleshooting.
  • Pull from a curated registry. Meld a super-source to register a whole chain of repos at once; meld --recursive offers every nested source for install. See super-source.
  • Meld a Claude plugin or marketplace. A repo with a .claude-plugin/plugin.json or .claude-plugin/marketplace.json melds with no re-packaging; its skills and agents show up as items. See Claude plugin marketplaces and the marketplace-plugin / marketplace-catalog examples.
  • Install into more than one agent home. Configure lobes in ~/.mind/config.toml. See Configuration and multi-lobe.
  • Stay up to date. mind sync refreshes every source; mind upgrade upgrades installed items and reports deltas first. See Commands and drift.
  • Claim an unmanaged lobe item into a managed source. mind absorb moves a file you placed in a lobe by hand into a source you own and installs it as a managed item. See absorb and the absorb example.
  • Run under an enterprise policy. A fixed-path managed policy restricts a client to trusted sources and locks related settings. See policy.

Maintainer use cases

You are authoring a source repo for others to meld.

  • Ship items with zero config. Use the convention layout; no mind.toml needed. See Source layout and starter.
  • Declare an explicit inventory, or control what is exported. List items with [[items]] to turn convention off, set custom path/link, and omit files you do not want offered. See explicit.
  • Lay out a monorepo or subtree. Point [source].roots at the package subtrees, or use [discover] kind globs for layouts roots cannot express. See monorepo and discover.
  • Share helper tooling across items. Ship a tool item and reference it with {{tools:name}} / {{path:ref}}, or bundle a script with one skill and address it with {{self}}. See tooling and Source layout.
  • Build or install tooling at meld. Declare a source [[hooks]] install entry (and an uninstall entry for teardown). See Install hooks and hooks.
  • Run a host side effect per item. Declare per-item install/uninstall hooks (or [[items.hooks]]). See explicit.
  • Make intra-source references survive a prefix. Write sibling references as {{ns:name}} tokens. See namespacing and Authoring a source.
  • Curate other repos into a registry. List them in [discover].sources; a bare list keeps your own convention items too. See super-source; for a repo that is both a Claude marketplace and a curator, see marketplace-curator.
  • Validate and scaffold before publishing. mind init-source scaffolds a mind.toml and reports references; mind review validates a source. See Authoring a source.

Commands

Mental model

mind connects sources (git repos full of agent tooling) and lobes (your agent homes, like ~/.claude/~/.agents).

Source. A melded git repo. mind meld <repo> clones the repo into ~/.mind/sources/<host>/<owner>/<repo> and records it in ~/.mind/sources.json. This initializes the source and makes its items available to mind learn. mind sync (or re-melding, re-running mind meld <repo>) refreshes the clone.

Item. A unit offered by a source, one of four kinds:

  • skill - a skills/<name>/ directory containing a SKILL.md and any associated resources (scripts, templates, etc).
  • agent - an agents/<name>.md file.
  • rule - a rules/<name>.md file.
  • tool - a tools/<name>/ directory containing a TOOL.md, a script/executable, any associated resources. Tools are an optional feature to assist with managing and referencing shared scripts/executables utilized by multiple skills.

Items are discovered by convention (the paths above) or declared in a mind.toml.

Lobe. A directory mind links items into: the directory holding skills/, agents/, and rules/. A lobe may be a global agent home (~/.claude, ~/.gemini/config) or a project subdirectory (.windsurf inside a project root). The default lobe is ~/.claude; you can add Gemini, Codex, Windsurf, Antigravity, or any directory, each with an optional per-kind filter (see Configuration). The gemini preset (path ~/.gemini/config) covers both Gemini CLI and the Antigravity IDE. Users of the Antigravity CLI who previously used an antigravity-cli preset should configure a custom lobe path manually.

Learn. mind learn <item> copies the item out of the source clone into the store (~/.mind/store/<kind>/<name>) and symlinks that store copy into every lobe. The store copy is the stable thing your agent homes point at, so a later sync cannot change an installed item under you until you choose to upgrade. forget reverses it.

What each step puts on disk

mind meld jaemk/mind clones the source. Nothing is linked yet:

$ mind meld jaemk/mind

~/.mind/
  sources.json                              # registry: `mind` is now melded
  sources/
    github.com/jaemk/mind/                  # the clone (a staging area)
      examples/hello/skills/hello-mind/     # the offered skill
        SKILL.md
      ...                                   # the rest of the repo
  store/                                    # empty - nothing learned yet

mind probe browses what the melded sources offer before you learn anything (kind:name, source, content hash, description):

$ mind probe

  skill:hello-mind  github.com/jaemk/mind  c62e88cc  A hello-world skill; confirms mind melded this repo

mind learn jaemk/mind#skill:hello-mind copies that one item into the store and symlinks it into the lobe:

$ mind learn jaemk/mind#skill:hello-mind

~/.mind/
  manifest.json                                  # registry: `hello-mind` installed
  store/
    skill/hello-mind/SKILL.md                    # the copy, taken from the clone
  sources/
    github.com/jaemk/mind/ ...                   # clone untouched

~/.claude/                                       # a lobe (agent home)
  skills/
    hello-mind -> ~/.mind/store/skill/hello-mind # symlink the harness discovers

The harness now resolves /hello-mind through that symlink. A tool the skill referenced would instead land in ~/.mind/store/tool/<name> with no symlink under ~/.claude, present for the skill to call but invisible to the harness.

Stay current. sync refreshes every source clone; upgrade moves installed items to the refreshed version, reporting hash and commit deltas before changing anything; evolve updates the mind binary itself.

Inspect. recall and probe show what is installed and what is available; introspect reports drift and broken links.

Verbs

commanddoes
mind meld [<repo>] [--register-only] [--yes] [-f|--force] [-r|--recursive] [-N|--namespace <ns>] [--flat-skills] [--root <dir>] [--add-root <dir>] [--pin <HEAD|ref|branch=NAME|tag=NAME>] [--install-hook <cmd>] [--dangerously-skip-install-hook-check] [--dangerously-skip-build-hook-check]clone and register a source (default .), then prompt to install its items (--register-only registers without installing; --yes installs without prompting; -f/--force overwrites conflicting non-mind link targets; -r/--recursive offers to install items from every nested source a super-source curates). --pin sets the version tracked (see Pinning a source version). --root replaces the scan roots; --add-root adds roots that compose with the source’s own discovery (a marketplace.json/plugin.json or an authoritative mind.toml keeps its items and the added roots are scanned in addition, see Claude plugin marketplaces). <repo> may also be a deep tree/blob URL to one skill (an item link). A meld that discovers no items reports the convention paths it scanned and the flags that reach items laid out elsewhere in the repo. Re-melding an already-melded source installs any missing items, else shows each item’s install state and commit. --pin on a re-meld re-pins the source: it resolves the new pin, re-checks-out the clone if the commit differs, and records both, leaving the source untouched if anything fails. --root, --add-root, --flat-skills, and --install-hook apply only at the meld that first registers a source, since they change what is discovered, so a re-meld notes which of them it ignored (unmeld, then meld again, to change them). Re-melding with a different -N/--namespace registers a new coexisting host/owner/repo@<prefix> instance (its own clone, pin, and items) rather than changing the existing source’s prefix in place; to change a source’s prefix without forking a new instance, use mind probe’s Set namespace action in the source’s details dialog (only offered while the source has no installed items; see Details dialog)
mind init-source [<path>] [--template] [-N|--namespace <ns>] [--marketplace] [--flat-skills]scaffold mind.toml + report references; --template rewrites bare refs as {{ns:}} (maintainer); -N/--namespace sets [source].namespace in the scaffold; --marketplace emits a .claude-plugin/ marketplace scaffold; --flat-skills uses a flat skill layout
mind unmeld <name> [--keep-items] [--yes] [--uninstall-hook <cmd>] [--dangerously-skip-hook-check]uninstall every item the source installed and drop the source (--keep-items skips the uninstall step). <name> is a ref: it must resolve to exactly one source (a non-glob name matching several errors instead of guessing; use a glob to act on several). It may be a bare source name or, to address one specific instance, its full identity: host/owner/repo@<prefix> for an aliased instance, host/owner/repo#<path> for an item-link instance. The same identity forms select an instance for upgrade and recall, e.g. mind unmeld github.com/acme/skills@jk. Contrast sync [source] below, a filter that may match, and act on, several sources at once
mind learn [--yes] [-f|--force] [-n|--dry-run] [--all] [--pin] [--dangerously-skip-install-hook-check] [--dangerously-skip-build-hook-check] <item>install a skill/agent/rule/tool (glob installs many); a partial selection also pulls in the source siblings it references. <item> may also be a deep tree/blob URL to one skill in a repo: the repo registers as a single-item item link and the skill installs in the same step. --force overwrites a conflicting non-mind link target (without it, a conflict prompts on a TTY); --all installs every item of the named source (shorthand for <source>#*); --pin freezes a deep-link URL to the ref’s current commit (ignored for a plain ref); -n/--dry-run previews the dependency closure without installing anything; under --json, installing an item whose closure pulls in dependencies beyond the explicit selection requires --yes, since there is no prompt to answer (refuses with confirmation-required otherwise)
mind forget [--yes] [-f|--force] [--unmanaged] [--dangerously-skip-hook-check] [<item>] (alias unlearn)remove an installed item (glob removes many; a multi-match glob confirms first, --yes skips). --unmanaged scopes removal to unmanaged lobe items only; with no <item>, removes every unmanaged item across all lobes. -f/--force skips the dependents confirmation when the item being removed has dependents. --dangerously-skip-hook-check runs uninstall hooks without the safety prompt
mind sync [source] [--upgrade] [--dangerously-skip-install-hook-check] [--dangerously-skip-build-hook-check]refresh source clones (all, or every source whose name matches the optional [source] filter: exactly, by trailing suffix, or by glob; a suffix shared by several sources matches, and syncs, all of them, unlike unmeld’s ref-style source selection, since sync has no ambiguity check and is a non-destructive refresh). --upgrade is deprecated sugar for sync followed by upgrade, scoped to the matched sources when [source] is given; when the scope spans more than one source, a disclosure names every matched source before the install-hook re-run pass – even with nothing pending – and this is not suppressed by --yes (the two hook-check flags are valid only with --upgrade)
mind upgrade [--yes] [--no-sync] [--dangerously-skip-install-hook-check] [--dangerously-skip-build-hook-check] [item]fetch each involved source, then upgrade installed items to their latest version (re-runs install hooks on sources that advance); item is a filter over installed items (a glob matches several, and a bare non-glob name multi-matches only across kinds, e.g. upgrade greet hitting both skill:greet and agent:greet); an embedded owner/repo# source qualifier is also a filter, not a ref – a trailing-suffix match with no ambiguity check, so it can span several sources and re-run each one’s install hook; when the scope spans more than one source, a disclosure names every matched source before the hook re-run pass, even with nothing pending, and --yes does not suppress it; --no-sync skips the fetch step
mind hooks run <target> [--event install|uninstall|build] [--force|--rerun] [--dangerously-skip-install-hook-check] [--dangerously-skip-build-hook-check] [--json] / mind hooks list <target> [--json]run a source’s or an item’s hooks on demand (outside meld/learn/forget/upgrade), or list the hooks in effect without running any. --rerun is a visible alias for --force (re-run a hook already recorded as run, mirroring meld --force). <target> is a source filter or an owner/repo#item ref; there is no ambiguity check, so a filter matching several sources, or a ref matching several items, runs the hook for each in turn. See Install hooks
mind evolve [--check] [--yes] [--to <v>]upgrade the mind binary itself to the latest release, or to a pinned --to <v> (accepts a prerelease like 1.2.3-rc1 – the only way to reach one, since the latest-release lookup never surfaces a prerelease – and strips a single leading v; --version is a deprecated alias)
mind recall [item] [--sources] [--kind K] [--source S] [--tree] [--json] (alias status)status: each source with its items, marked installed or available; --sources narrows to sources; <item> shows one item’s details; --tree renders installed items as a dependency forest (with an item ref, scopes to that item’s subtree)
mind probe [query] [--kind K] [--source S] [--json] [--no-tui]browse and search items (interactive TUI on a terminal)
mind review <target> [-N|--namespace <ns>] [--json] / mind review --policy <path> [--json]validate a source for publishing, or validate a managed policy file (read-only); a <target> naming an existing directory is read as that local path unless it first matches a melded source’s identity, even when it also looks like a valid owner/repo spec
mind introspect [--fix] [--json]report drift and broken links (optionally repair)
mind config show / mind config lobes add [<dir>] [--preset <name>] [--subdir <rel>] [--snapshot] [--force]|list|remove <path> [--snapshot]|detect [--yes] [--json]view config and manage lobes. add --preset <name> adds a preset lobe; --preset and a base <dir> are composable (e.g. add . --preset windsurf registers a lobe at ./.windsurf). --subdir <rel> targets an arbitrary subdirectory under <dir>. --snapshot on add materializes a one-time frozen copy instead of registering a managed lobe; on remove, detaches a managed lobe by replacing its symlinks with real-file copies before unregistering. --force overwrites a colliding foreign file. detect reports which known harness homes exist; Windsurf is detected via ~/.codeium/windsurf but prints guidance to run link-project instead of auto-adding a lobe. config lobes list and config show include the kinds filter for each lobe, e.g. ~/.gemini/config [skill]. See Configuration for the preset table and per-harness path details.
mind link-project [<dir>] [--preset <name>] [--subdir <rel>] [--snapshot] [--force]convenience alias for config lobes add, with <dir> defaulting to . and --preset defaulting to windsurf; links installed skills into ./.windsurf/skills/ and registers a managed lobe so future mind learn fans new skills into it automatically. --snapshot writes real-file copies instead of a managed lobe: unmanaged, committable, and not updated by a later mind learn (see Presets)
mind dump [--output <path>] [--whole-sources]write a super-source mind.toml reproducing the current melded and installed state (to stdout or --output <path>); each source is pinned to its recorded commit; item-filtered by default (--whole-sources emits install = true for every source regardless of install count)
mind absorb <ref> [--to <path>] [-f|--force]claim a single unmanaged lobe item into a version-controlled source and install it as a managed item; --to sets the destination (see absorb for full destination precedence); --force overwrites a kind:name collision at the destination
mind completions <shell> / mind manshell completions / man page

A source repo exposes items by convention (skills/<n>/SKILL.md, agents/<n>.md, rules/<n>.md, tools/<n>/), via a mind.toml, or via a Claude .claude-plugin/ manifest (see Claude plugin marketplaces). See Source layout and the examples/: starter for the plain convention layout, namespacing for {{ns:}} reference tokens under a namespace, and policy for an enterprise managed policy. The full behavioral spec is at spec/.

Verb aliases

Each primary verb has a visible alias for users familiar with conventional package-manager vocabulary. The primary verb is always preferred in docs and scripts.

aliasprimary verb
addmeld
installlearn
uninstallforget
updatesync
searchprobe
listrecall
doctorintrospect
self-updateevolve

Note (migration): --link-only on meld is now --register-only; --unlink-only on unmeld is now --keep-items. The old spellings continue to work as hidden deprecated aliases. The config target and unmeld detach aliases are removed.

Note (migration): upgrade now syncs the involved sources before computing deltas (equivalent to running sync then upgrade on those sources). Pass --no-sync to skip the fetch step and get the old behavior. sync --upgrade is kept as deprecated sugar but upgrade alone is the preferred form.

Pinning a source version

meld --pin <value> records how a source tracks upstream. The value is required and decides freeze vs follow:

valueeffect
HEADfreeze the current resolved tip to its commit (immutable)
<tag|sha|branch>resolve that ref to its current commit and freeze it
branch=<name>follow that branch (floating; sync advances it)
tag=<name>follow that tag (re-points on sync if it moves)

A freeze records an immutable ref that sync never moves. Freezing a branch or tag snapshots its current tip; it does not keep tracking it. With no --pin, a source follows the remote default branch.

To change a pin later, run meld --pin again against the already-melded source. It resolves the new value against the pin currently in effect, re-checks-out the clone when the commit differs, and records both. Nothing is mutated until every git step has succeeded, so a bad ref leaves the source on its old pin.

Common mistake: --pin stable and --pin branch=stable are different. --pin stable takes a one-time snapshot of stable (frozen; sync never updates it); --pin branch=stable tracks the branch (floating; sync advances it). Use the branch= form to keep following a branch.

# Freeze the current tip.
mind meld owner/repo --pin HEAD

# Freeze a specific point: a tag, a commit sha, or a branch's current tip.
mind meld owner/repo --pin v2.0
mind meld owner/repo --pin a1b2c3d4e5f6...
mind meld owner/repo --pin release-branch

# Follow a moving point. sync advances these to the tip on each run.
mind meld owner/repo --pin branch=stable
mind meld owner/repo --pin tag=latest

A --pin value overrides the repo’s [source] pin directive (see The mind.toml file). --pin requires a value, so a bare --pin is a usage error rather than silently consuming the next argument.

For a deep-link URL, learn --pin (a bare flag on learn) freezes the link’s ref to its current commit in the same step:

# Without --pin the link follows the branch in the URL; with it the link is
# frozen at that branch's current commit.
mind learn https://github.com/owner/repo/tree/main/skills/foo --pin

Note (migration): the old --follow-branch <b> / --pin-tag <t> / --pin-ref <c> flags map to --pin branch=<b> / --pin tag=<t> / --pin <c>. They still work as hidden deprecated aliases.

probe

mind probe with no flags opens an interactive browser of melded sources and items (search, install, remove, meld, unmeld, sync, upgrade) when stdout is a terminal. --no-tui or --json, or a piped or redirected stdout, prints the listing instead.

Selecting items (globs)

learn, forget, upgrade, unmeld, probe, recall, and sync all accept a glob in place of an exact ref or filter argument. The kind prefix, source qualifier, and glob compose:

patternselects
'*'every item across all sources
'skill:*'all skills
'owner/repo#*'all items of one source
'review*'items whose name starts with review

The glob is matched against the effective (installed) name. A glob matching nothing is ItemNotFound (for items) or SourceNotFound (for sources). The exception is upgrade: a glob (or exact ref) that matches no installed item reports up-to-date rather than erroring, since upgrading nothing is a no-op.

Shell-quoting caveat: quote the glob so the shell does not expand it before mind sees it:

mind learn 'skill:*'
mind forget 'owner/repo#*'

Spec: CLI-31, CLI-41, CLI-65.

Refs vs filters

  • Ref must resolve to exactly one target, erroring rather than guessing: unmeld <name> (a non-glob source name; AmbiguousSource), and a bare non-glob item name given to learn/forget/upgrade (AmbiguousItem). Item names are never suffix-matched, so this last case only fires across kinds (the manifest is keyed kind:name), e.g. upgrade greet hitting both skill:greet and agent:greet.
  • Filter may match several targets with no ambiguity check, and every match is acted on: sync’s [source], recall/probe’s --source <filter>, hooks run <target>’s source form, and any glob ('*', 'skill:*', 'owner/repo#*') given to any of the verbs above. The embedded owner/repo# source qualifier inside upgrade’s item argument is a filter too, not a ref: it is a trailing-suffix match with no ambiguity check, so it can span several sources, upgrading each one’s items and re-running each one’s install hook.

No-match behavior differs by verb:

  • sync <filter> with no match: a hard SourceNotFound error.
  • upgrade <filter> with no match: reports up to date and exits 0.
  • --source <filter> (recall/probe) with no match: an empty listing.

Paste the URL of a skill directory (or its SKILL.md) straight from GitHub or GitLab and learn installs just that skill:

mind learn https://github.com/owner/repo/tree/main/skills/foo
mind learn https://github.com/owner/repo/blob/main/skills/foo/SKILL.md

The repo registers as its own single-item source instance with the identity host/owner/repo#<path>: it clones, syncs, and upgrades like any source, but offers exactly the linked skill. Several links into the same repo (and a plain meld of it) coexist as separate sources. The URL’s ref supplies the pin: a branch name follows that branch, a 40-hex commit pins it. Add --pin (a bare flag on learn) to freeze a branch-ref link at the branch’s current commit (see Pinning a source version).

Because the consumer names the exact path, the link bypasses the repo’s declared inventory: it reaches a skill an authoritative mind.toml or a .claude-plugin/marketplace.json does not list (see Claude plugin marketplaces).

mind meld <url> accepts the same form and follows the standard meld flow (--register-only, --namespace, pin flags). forget of the skill leaves the instance registered and hints at mind unmeld <identity> to drop it. A local repo is addressable through file:///path/to/repo/tree/<branch>/<path>.

Spec: spec/item-link.md.

Filtering with –kind and –source

recall and probe accept two composable filters:

  • --kind <skill|agent|rule|tool> narrows to one item kind.
  • --source <filter> narrows to items from a matching source. The filter is an exact name, a trailing suffix (repo or owner/repo; a multi-source match is normal, not an error), or a glob matched against the full host/owner/repo identity (so * spans /):
mind recall --kind skill
mind probe --source '*agents'
mind recall --source my-repo --kind rule

For recall, these filters apply to the installed-items listing only, not to --sources or a single-item lookup. Spec: CLI-83, CLI-86.

Global flags and output

--json, --yes (-y), and --ascii are global flags accepted before or after any verb. Position does not matter: mind --json recall and mind recall --json are equivalent (CLI-150).

Color and Unicode. Output uses ANSI color and Unicode glyphs when all of the following hold: stdout is a TTY, the locale is UTF-8, NO_COLOR is unset, and neither --json nor --ascii is in effect. Any one of those conditions being false forces plain ASCII output with no color escapes. The ASCII fallback substitutes visually equivalent characters (+ installed, ! warning, x error, - available) so no information is lost (CLI-151, CLI-152, CLI-154).

NO_COLOR set to any value (including empty), a non-UTF-8 or unset locale, or --ascii each independently force plain ASCII regardless of the others.

--json output. --json is universal: every verb answers it with exactly one JSON document on stdout, except a closed exclusion list – dump (always emits TOML, CLI-153 does not apply), completions/man (print their script or roff page as the entire output), evolve (writes its own result document from selfupdate.rs rather than through the shared JSON machinery – the document IS JSON, just emitted on a separate path), and init-source (a maintainer scaffolder with no JSON result to offer). Every other verb answers with a document.

recall and probe emit {"schema": 1, "items": [...]}. introspect emits {"schema": 1, "issues": [...], "sources": N, "items": N} where issues is an array of findings and sources/items are integer counts. Every mutating verb (meld, learn, forget, sync, upgrade, unmeld, config lobes add/remove) emits a structured result object with "schema": 1 and at minimum action, target, and outcome fields (CLI-153).

review --json answers with {"schema": 1, "action": "review", "outcome": "clean|advisory|failed", "hard": [...], "advisory": [...], "fixed": [...]}, where hard/advisory are arrays of {"kind": "<slug>", "message": "<text>"} findings and fixed lists the files --fix rewrote. A hard finding still fails review (CLI-132 is unconditional): the document is not printed as a success envelope in that case, but folded into the error envelope’s details member instead (see below).

hooks list --json answers with a document giving each matched source’s or item’s hooks (event, required/optional flag, command, and, for a recorded source install hook, its pending/last-ran status). hooks run --json on a successful run answers with a tally rather than nothing: {"schema": 1, "action": "hooks-run", "target": "...", "event": "...", "existed": N, "ran": N, "skipped": N}.

When an error occurs under --json, the process emits a JSON error envelope on stdout instead of plain text on stderr, then exits 1 (unchanged):

{"schema": 1, "error": {"kind": "item-not-found", "message": "..."}}

The kind field is a stable kebab-case slug per MindError variant (e.g. "item-not-found", "source-not-found", "git", "digest-mismatch"). Scripts may branch on kind to handle specific failures. The message field is the full display text. Exit code is always 1 for runtime errors; clap usage errors (exit 2) remain plain text and are not enveloped (CLI-181, CLI-182, CLI-183).

The envelope carries an optional details member when a verb has more to say than the kind/message pair: a failed review --json records its findings document there instead of printing it as a success envelope (CLI-221).

Exit status

Exit 0 on success. Any MindError exits 1; under --json it is written to stdout as the error envelope above instead of stderr (CLI-100, CLI-181).

sync exits non-zero (SyncFailed) when any per-source fetch fails, even if other sources succeeded; successfully fetched commits are persisted and reported (CLI-54).

review distinguishes hard errors (malformed mind.toml, unknown item kind, unresolved {{ns:}} token) from advisory findings (unguarded references, missing descriptions). Hard errors exit non-zero; advisory-only exits zero. Neither mode writes to disk, except review --fix on a local-path target (CLI-132).

Running unattended / in CI

Pass --yes (-y) to skip confirmation prompts. Without it, any command that would prompt on a TTY instead exits non-zero with ConfirmationRequired when stdin is not a TTY (CLI-23, CLI-42).

meld is the exception: a non-TTY meld without --yes registers the source only, prints a note that nothing was installed, and exits 0. Pass --yes to install its items non-interactively.

Install and uninstall hooks are skipped in non-TTY contexts and a note is printed. To run them unattended, pass --dangerously-skip-install-hook-check (meld/learn/sync/upgrade/hooks run) or --dangerously-skip-hook-check (unmeld/forget, which gate an uninstall hook). This executes arbitrary code from the source; only use it for sources you trust (HOOK-22).

Item build hooks (the per-item build command, distinct from a source’s install hook) are likewise skipped in non-TTY contexts, so an item’s tooling is not built. To run them unattended, pass --dangerously-skip-build-hook-check (available on meld, learn, upgrade, and sync --upgrade). It too executes arbitrary code from the source; only use it for sources you trust.

For an end-to-end CI provisioning recipe, see Team / CI provisioning recipe.

dump

mind dump writes a super-source mind.toml to stdout (or --output <path>) that reproduces the current melded and installed state. Melding the output recreates the same source set at the same revisions. It is the inverse of melding a curated super-source.

mind dump                        # write to stdout
mind dump --output snapshot.toml # write to a file
mind dump --whole-sources        # include all items, not just installed ones

Each entry in the emitted [discover].sources references a melded source and pins it to its currently recorded commit as a pin-ref, overriding any pin the source itself declares (DUMP-1). The meld-time settings are carried through: namespace, scan roots, added roots, and the resolved commit pin (DUMP-4, DUMP-11).

An item link instance is emitted as a deep tree URL rebuilt from its recorded parts, pinned by pin-ref like any other entry, so a skill installed from a pasted URL is reproduced by melding the dump (LNK-13).

Item filtering. By default each source entry is stamped with the install directive that reproduces exactly which items are installed (DUMP-2):

  • Every offered item installed: install = true
  • No items installed: install = false
  • A subset installed: install-items = [...] listing those items by kind:name

--whole-sources disables this filtering and emits install = true for every source, offering the full catalog instead of the recorded subset (DUMP-3).

With no melded sources, dump emits a valid super-source with an empty [discover].sources and exits 0 (DUMP-8).

Interactive TUI

mind probe opens an interactive terminal UI: a browsable, searchable view of every source and item, and the interactive front end for the rest of the CLI (meld, learn, sync, upgrade, config). For the probe verb and its flags, see Commands.

Opening it

  • mind probe with no opt-out launches the TUI. It requires a TTY on stdout.
  • It falls back to the non-interactive catalog listing when --no-tui is given, when --json is given, or when stdout is not a terminal (piped or redirected).
  • The query, --kind, and --source arguments apply in both modes. In the listing they filter it; in the TUI they seed the initial search and filter state.

The browse tree

Two top-level groups, each an independently collapsible tree:

  • Installed: the manifest. Installed items grouped source -> kind -> item, each showing effective name, source, and short commit, matching what recall reports.
  • Available: catalog items of melded sources that are not installed, not-yet-melded sources suggested by the registry, and ad-hoc sources you enter, de-duplicated.

A third Unmanaged group appears below them when the agent home holds items mind did not install. See Unmanaged items.

Under each group the hierarchy is source -> kind (skills, agents, rules) -> item -> detail. Expanding an item shows its description and frontmatter, and for a skill its file tree.

  • Up / Down: move the highlight.
  • Left / Right: collapse / expand a structural node (source and kind buckets).
  • Space: also collapses / expands a structural node.
  • Enter: open the details dialog on a source or item; on a group header or kind bucket it toggles instead.

As the selection moves, the view scrolls to keep the highlighted row away from the top and bottom edges while there are more rows to scroll. Near the start or end of the list the highlight may sit at the edge.

A search box filters the visible tree by case-insensitive substring over item name and description, across both groups, and composes with the active kind and source filters. Clearing the search restores the full tree.

Live refresh

The TUI polls the on-disk registry (sources.json) and manifest (manifest.json) about once a second, so changes made by another mind process or a direct edit appear without a manual reload. A refresh preserves the current selection, expansion, and search state, and is skipped while a mutating action is running.

Actions

Each action invokes the same verb the CLI exposes, against the same registry, manifest, and store, with one exception: upgrade does not fetch first (see below). Every mutating action confirms before applying; destructive actions (forget, unmeld --forget) require an explicit confirmation. Results and errors are shown inline.

  • Install / learn: install the selected Available item. On a higher node it installs in bulk: a source installs every available item from it, a kind bucket every item of that kind, and the Available group everything. Already-installed items are skipped.
  • Forget: uninstall the selected Installed item.
  • Meld / unmeld: meld the selected or entered source; unmeld a melded source. The TUI’s unmeld uninstalls the source’s installed items by default and confirms first.
  • Sync: sync all or the selected source.
  • Upgrade: upgrade pending items (or the selected item), showing the same deltas the CLI would and confirming before applying. Unlike the CLI’s plain upgrade, this does NOT sync first: it acts on whatever the last sync or poll already found to be out of date. Run Sync first if you want to check upstream before upgrading. When nothing is currently known to be out of date, the confirm says so relative to the last sync, not as a claim that you are fully current.
  • Config lobes: view and manage agent homes (list / add / remove).

An install preview arms the same dependency closure as the direct install action. See Dependencies.

Details dialog

Pressing Enter on a source or item opens a centered details dialog describing the node and listing the actions valid for it as a selectable list, each run through the normal confirm-and-execute path.

  • For an item: its kind, source, the commit when installed, and the description. It offers Install when the item is not installed, else Forget.
  • For a source: its name and installed/available item counts, and Install all available items, Uninstall all installed items, and Unmeld. While the source has no installed items, a Set namespace action is also offered: it opens an input for a namespace prefix, so items later install as <prefix>:<name>. It disappears once any item from the source is installed (a source’s prefix locks once items are installed). An action is omitted when it would do nothing.

In the dialog, j / k move the highlight, Enter or y runs the highlighted action, and Esc, q, or n dismisses without acting. On a group header, kind bucket, or suggested source there is no dialog: Enter keeps its toggle/preview.

Registry preview

The Available group lists suggested, not-yet-melded sources: the union of the [discover].sources entries declared by all melded sources, de-duplicated by URL and excluding sources already melded. Expanding a registry entry shallow-clones it to a temporary preview area and shows its catalog tree under Available without registering it. Confirming promotes the preview to a real meld; declining discards the temp clone.

Terminal handling

  • Actions whose verbs may prompt interactively run with the TUI suspended: mind leaves raw mode and the alternate screen, runs the verb on the normal terminal so its prompts read stdin and write stdout exactly as the CLI does, then restores the alternate screen and redraws. After the verb you press Enter to return. The verbs that suspend are meld and unmeld.
  • While the TUI holds the terminal, every git child runs non-interactively (GIT_TERMINAL_PROMPT=0 and an ssh BatchMode=yes wrapper), so an auth-required remote fails fast with an error surfaced inline instead of hanging the UI on a hidden prompt. The suspended interactive meld restores interactive git for its duration, so a passphrase or host-key prompt works on the normal terminal.

Exit

  • q quits from the main view.
  • Ctrl-C is a force-exit from every mode. One Ctrl-C arms and shows a hint; a second consecutive Ctrl-C exits, so a single accidental Ctrl-C while typing does not quit. Any other key disarms.

Configuration

Agent homes (lobes)

learn links items into every configured install-target directory (a lobe). A lobe is any directory mind links items into – a global agent home such as ~/.claude or ~/.gemini/config, or a project subdirectory such as .windsurf inside a project root. Each item is linked under its kind subdirectory: skills/, agents/, rules/. The default lobe is ~/.claude. Configure more in ~/.mind/config.toml:

lobes = ["~/.claude", "~/.config/some-other-agent"]

The file is created with the default lobe (~/.claude) on first use. For a single invocation, set MIND_AGENT_HOMES to a :-separated path list instead.

Lobe precedence (STO-14): MIND_AGENT_HOMES wins over lobes in config.toml, which wins over the default ~/.claude. An unknown key in config.toml is a hard error.

Use mind config lobes add <path> and mind config lobes remove <path> to manage lobes without hand-editing the file; see Commands for the full verb list.

Kinds filter

A lobe may carry a kinds list so only items of the listed kinds link into it. A lobe without a kinds field receives all kinds (existing behavior; a bare string is equivalent):

lobes = ["~/.claude", { path = "~/.gemini/config", kinds = ["skill"] }]

Linking an item into a lobe whose kinds excludes its kind is a no-op for that lobe, not an error. The manifest records only the lobes that actually received a link, so forget, upgrade, and introspect never expect a link a filtered lobe was never given (HARN-1, HARN-2).

Cross-harness lobes

Skills (skills/<n>/SKILL.md) and agents (agents/<n>.md) use layouts that are now cross-harness conventions. mind links them verbatim – no content transform is needed. Rules (rules/<name>.md) have no cross-harness directory equivalent (the analog in other harnesses is a single concatenated context file like AGENTS.md or GEMINI.md, not a directory of per-rule files), so rules are Claude-only and are never linked into a lobe added via a non-Claude preset (HARN-3).

Per-harness path table:

HarnessSkills dirAgents dirmind lobe (parent)
Claude Code~/.claude/skills/<n>/SKILL.md~/.claude/agents/<n>.md~/.claude
Gemini CLI / Antigravity~/.gemini/config/skills/-~/.gemini/config
Codex CLI~/.agents/skills/(subagents)~/.agents
Windsurf<project>/.windsurf/skills/-<project>/.windsurf

~/.agents is a vendor-neutral alias: Codex reads it as its user skills path, so one ~/.agents lobe serves Codex and any harness that follows the same convention.

Windsurf has no global skills directory; it reads skills only from a project’s .windsurf/skills/ folder. A Windsurf lobe is therefore a project lobe, not a global one. Use mind link-project (or mind config lobes add . --preset windsurf) in a project root to register one. Windsurf is detected via ~/.codeium/windsurf; see the Presets section below for how config lobes detect handles it.

Presets

mind config lobes add [<dir>] --preset <name> adds a lobe at the preset’s target path with its kinds filter in one step. --preset and a base <dir> are composable:

presettarget pathkinds
gemini~/.gemini/configskill
codex~/.agentsskill
universal~/.agentsskill
windsurf<dir>/.windsurf (default: <cwd>/.windsurf)skill

gemini, codex, and universal target a fixed global path. windsurf is project-scoped: mind config lobes add --preset windsurf (no <dir>) targets the current directory.

Adding a managed lobe (anything but --snapshot) creates its target directory immediately, so a preset for a harness that is not installed yet still registers and is reachable right away (HARN-15). Already-installed items of the kinds the lobe admits link into the new lobe as part of the same command; no separate introspect --fix step is needed (HARN-17).

Additional flags for config lobes add:

  • --subdir <rel> - target an arbitrary subdirectory under <dir> (e.g. --subdir .cursor for a Cursor project directory) instead of the preset’s default.
  • --snapshot - materialize a one-time frozen copy (real files, not symlinks) into the target and do not register a lobe. A snapshot is not managed: no auto-propagation on future mind learn, and the copied files are committable to the repo. Managed lobes use symlinks into ~/.mind/store and should be gitignored.
  • --force - overwrite a colliding foreign file at the target path.

Examples:

mind config lobes add --preset gemini
# + added gemini lobe ~/.gemini/config [skill]

mind config lobes add . --preset windsurf
# + added windsurf lobe ./.windsurf [skill]

mind config lobes remove <path> [--snapshot] unregisters a lobe. --snapshot detaches a managed lobe: it replaces the managed symlinks with frozen real-file copies and then unregisters the lobe.

Note (migration): The gemini preset path changed from ~/.gemini to ~/.gemini/config in a previous release. If you added this preset earlier, your ~/.mind/config.toml may still have the old path. Update it by running:

mind config lobes remove ~/.gemini && mind config lobes add --preset gemini

Or hand-edit ~/.mind/config.toml and replace ~/.gemini with ~/.gemini/config.

mind config lobes detect detects which known harness homes exist on the machine and reports the matching presets it could add. It never mutates config on its own: it only adds a lobe with --yes or an interactive TTY confirm. --json emits the detection result as structured JSON (HARN-5). Windsurf is detected via ~/.codeium/windsurf; because Windsurf is project-scoped, detect prints guidance to run mind link-project in the project root rather than auto-adding a lobe.

mind config lobes list shows the kinds filter for each lobe (e.g. ~/.gemini/config [skill]); a lobe with no filter shows just the path. mind config show uses the same format.

mind link-project [<dir>] [--preset <name>] [--subdir <rel>] [--snapshot] [--force] is a convenience alias for config lobes add, with <dir> defaulting to the current directory and --preset defaulting to windsurf. Running it in a project root links installed skills into ./.windsurf/skills/ and registers a managed lobe so future mind learn fans new skills into it automatically:

cd ~/projects/myapp
mind link-project
# + added windsurf lobe ./.windsurf [skill]

Managed lobes use symlinks into ~/.mind/store; add .windsurf/ to the project’s .gitignore. Use --snapshot to materialize real-file copies instead (committable to the repo without gitignoring).

Frontmatter portability

mind links skill and agent files verbatim. Frontmatter portability across harnesses – for example, Gemini’s mcp_* tool-permission wildcards vs Claude’s tools: schema – is the author’s responsibility; mind does not rewrite frontmatter to fit a target harness (HARN-6). An item whose frontmatter uses Claude-specific keys will link into a Gemini or Codex lobe correctly, but those keys may be ignored or produce a warning in the target harness.

Absorb destination

mind absorb moves an unmanaged item into a version-controlled source you own. The destination source is resolved from three places, in order – the first one set wins:

  1. --to <path> flag on the command line.
  2. MIND_ABSORB_TO environment variable.
  3. absorb-to key in ~/.mind/config.toml.

When none of the three is set and the run is interactive, absorb prompts and offers ~/.mind/personal as the default. That directory is created and git init-ed on demand if it does not exist. After an interactive resolution, absorb offers to save the chosen path as absorb-to in config.toml so future runs skip the prompt. A --to flag, MIND_ABSORB_TO, or an existing absorb-to value is used as-is and never triggers a save.

A non-TTY run with no destination configured (none of the three sources set) is an error; there is no silent default to assume.

Set the persistent default in ~/.mind/config.toml:

absorb-to = "~/dev/my-agent-library"

~ is expanded at use. The destination must be a git repository; a path that is not a git repo is an error.

Note: the legacy key spelling absorb_to (underscore) is still accepted when reading the file. New writes and the interactive save always use absorb-to.

SSH cloning

To authenticate with an SSH key instead of an https username/password, meld the git@host:owner/repo form, or set ssh = true in ~/.mind/config.toml so the owner/repo shorthand clones over SSH. An https remote still prompts (or uses a credential helper) as git normally does.

Config example

A single ~/.mind/config.toml may contain any combination of the keys:

lobes = ["~/.claude", { path = "~/.gemini/config", kinds = ["skill"] }]
ssh = true
absorb-to = "~/dev/my-agent-library"

Paths

~/.mind/
  config.toml                   persistent settings (lobes, ssh, absorb-to)
  sources.json                  source registry (melded repos)
  manifest.json                 installed-item manifest and file registry
  sources/<host>/<owner>/<repo> clone of each melded repo
  store/<kind>/<name>/          installed copy of each item (name is effective)
  personal/                     built-in absorb destination, created on demand
  .tmp/staging/                 scratch for new copies during transactional installs
  .tmp/backup/                  previous copy held during a swap, for rollback
  .lock                         global advisory lock

Override the roots with MIND_HOME (the ~/.mind tree) and MIND_DEFAULT_LOBE (the default lobe). CLAUDE_HOME is a legacy alias for MIND_DEFAULT_LOBE; MIND_DEFAULT_LOBE takes precedence when both are set.

Concurrency

A global advisory lock (~/.mind/.lock) is held by every mutating command (meld, unmeld, learn, forget, sync, upgrade, introspect --fix, config lobes add|remove). A second concurrent mind invocation blocks until the first finishes. The lock is released when the holding process exits, even on crash, so an aborted run never wedges the next one. Read-only commands (recall, probe, introspect, config show) take a shared lock and proceed concurrently with each other, but never observe a writer mid-update (STO-40..43).

Install and upgrade are transactional

A failed learn or upgrade never leaves you worse off. The new copy is built in a staging directory first; the previous version is moved to a backup and only dropped after the swap succeeds. A failure at any point restores the previous version from backup (LIFE-1..4).

When an installed item’s effective name changes (for example the one-time -->: namespace-separator migration), upgrade reports rename old -> new and handles it the same way: the new name is installed before the old one is removed (LIFE-14). This is normal, not an error. Note that adding --namespace <prefix> (or --as) to an already-melded repo does not rename it: a --as prefix is part of the source’s identity, so a different --as melds a distinct host/owner/repo@<prefix> instance that coexists with the original. That host/owner/repo@<prefix> identity is also the handle for addressing this instance later, e.g. mind unmeld host/owner/repo@<prefix> or mind upgrade host/owner/repo@<prefix>.

For diagnosing a failed install or broken links, see Troubleshooting.

Unmanaged items

An agent home (lobe) often holds skills, agents, and rules that mind did not install: files written by hand or placed by another tool. mind surfaces these as unmanaged items so the listing reflects everything the agent actually sees, and lets forget remove one after a distinct confirmation.

Tools are store-only (not linked into a lobe by default), so unmanaged detection covers skills, agents, and rules only (spec UNM-1).

What counts as unmanaged

A lobe entry is unmanaged when its path is not recorded in any manifest item’s links (spec UNM-1). A managed symlink and an unmanaged file cannot occupy the same path simultaneously; a given path is always one or the other.

mind scans every configured lobe. An item present in more than one lobe is one logical item that records each occupied path.

Listing unmanaged items

recall

mind recall (no arguments) lists unmanaged items after the melded sources, in a group labeled “unmanaged: not installed by mind”, one row per item showing kind:name and the lobe path(s) it occupies (spec UNM-2). No flag is needed to see them; --kind filters them the same way it filters managed items; --source excludes them because they have no source.

recall --json covers managed sources only (its schema is unchanged); unmanaged items are exposed machine-readably through probe --json (spec UNM-2, UNM-3).

probe

mind probe includes unmanaged items in its listing and substring search (spec UNM-3). In the non-interactive listing each unmanaged row is labeled. In --json output each carries "unmanaged": true with no source or hash field. --kind filters; --source excludes them.

In the interactive TUI, unmanaged items appear under an Unmanaged group node, browsable and searchable like a source’s items (spec UNM-6). See Interactive TUI.

Forgetting an unmanaged item

mind forget <kind:name>

When the ref resolves to an unmanaged item, forget removes the lobe entry itself (the file or directory the user owns) and leaves the manifest unchanged (spec UNM-4).

Exact ref only. An unmanaged item is matched only by its exact kind:name. A glob such as forget '*' removes managed items only and never deletes a user’s own files (spec UNM-4).

Confirmation required. Every unmanaged removal prompts first, regardless of count. The prompt states explicitly that the item is not managed by mind and that removal deletes the user’s own file or directory, not a symlink (spec UNM-5).

  • --yes proceeds after displaying that statement.
  • A non-TTY run without --yes refuses with ConfirmationRequired and removes nothing (spec UNM-5).

The --force / clobber flags do not apply here; nothing is being overwritten.

When a bare name matches both a managed and an unmanaged item, add a kind prefix to disambiguate (for example skill:review vs agent:review). See Commands for the full forget verb reference.

Bulk removal of unmanaged items

mind forget --unmanaged [<ref>]

--unmanaged scopes removal to unmanaged lobe items only – the deliberate opt-in inverse of the default, where a glob matches managed items only. Managed (mind-installed) items are never matched in this mode; it cannot delete a mind-installed link or store copy.

Ref forms.

  • With a glob <ref> (e.g. 'skill:*'), every matching unmanaged item is removed. A kind prefix composes with the glob.
  • With an exact kind:name, that single item is removed.
  • With no <ref>, every unmanaged item across all configured lobes is removed.

A <ref> that matches no unmanaged item is an error.

Confirmation. Before removing anything, mind lists all matched items and asks once to confirm, making clear these are not managed by mind and that removal deletes the user’s own files or directories (not symlinks). --yes (-y) skips the prompt. A non-TTY run without --yes refuses with ConfirmationRequired and removes nothing.

The manifest is not touched; these items were never in it. The --force / clobber flags do not apply.

absorb

mind absorb <ref> claims a single unmanaged lobe item (skill, agent, or rule) into a version-controlled source and installs it as a managed item. It is the constructive inverse of forget --unmanaged: instead of deleting an unmanaged file, absorb moves it into a source you own, commits it there, and replaces the lobe entry with a managed symlink. After absorb the item participates in sync, upgrade, and forget like any installed item.

See Unmanaged items for how unmanaged items are detected and listed.

Basic usage

mind absorb skill:my-skill
mind absorb agent:my-agent
mind absorb rule:my-rule
mind absorb my-item --to /path/to/my-source

The ref must resolve to exactly one unmanaged item. A glob ref is an error; bulk absorb is not supported. A kind prefix disambiguates when the same name appears in more than one kind.

Tools are never absorbed: they are store-only and never appear as unmanaged lobe items (spec ABS-1).

Choosing the destination

The destination source is resolved in this precedence order:

  1. --to <path> – the explicit destination flag.
  2. MIND_ABSORB_TO environment variable.
  3. absorb_to key in ~/.mind/config.toml.

The first one set wins; later sources are not consulted (spec ABS-2).

When none of the three is set and the run is interactive, absorb prompts for a destination and offers ~/.mind/personal as the built-in default. That directory is created and git init-ed on demand if it does not already exist. After a prompted choice, absorb offers to save it as absorb_to in config.toml so future invocations skip the prompt (spec ABS-3, ABS-4).

A non-interactive (non-TTY) run with no destination configured is an error (ConfirmationRequired) and changes nothing (spec ABS-3).

When --yes is given on a TTY and no destination is configured, absorb automatically uses (and persists) ~/.mind/personal. On a non-TTY this does not apply: with no destination configured the run errors regardless of --yes (see Running unattended below).

Flags

flageffect
--to <path>Destination source directory (highest precedence).
-f / --forceOverwrite the destination convention path if a kind:name collision already exists there. Without --force, a collision is an error and nothing is changed (spec ABS-6).

The global -y / --yes flag skips the [y/N] confirmation described below.

Confirmation and multi-lobe cleanup

An unmanaged item may occupy more than one lobe. absorb takes the content from one occupied path; because learn relinks the item into every configured lobe (spec STO-14), the other unmanaged copies must be removed so the managed link can take their place.

Before acting, absorb lists what it will move and which stray copies it will delete and prompts [y/N] once. This covers deleting files the user owns, so the prompt runs even when only one lobe is affected (spec ABS-7).

  • --yes (-y) skips the prompt and proceeds.
  • A non-TTY run without --yes refuses with ConfirmationRequired and changes nothing.
  • Declining the prompt leaves the original lobe entry in place and the manifest unchanged.

What absorb does internally

  1. Resolves <ref> to a single unmanaged item (ABS-1).
  2. Confirms the destination source is a git repository. The --to / MIND_ABSORB_TO / absorb_to path must already be a git repo; a non-repo path is an error. ~/.mind/personal is the one path absorb will create and git init on demand (spec ABS-5).
  3. Lists the move and any stray copies to delete; prompts [y/N] (skipped with --yes).
  4. Moves the item into the destination source at its convention path (skills/<name>/, agents/<name>.md, rules/<name>.md). Melds the source first if it is not yet registered.
  5. Stages and commits the moved item in the destination repo with the message absorb <kind>:<name> (spec ABS-5).
  6. Runs learn on the item; the lobe path is now a managed symlink.
  7. Records the item in the manifest keyed kind:effective-name with the destination source as its source (spec ABS-8).

A failure at any step before learn completes leaves the original lobe entry in place and the manifest unchanged. A failed absorb never loses the user’s file (spec ABS-10).

After absorb

The item’s effective name follows the destination source’s prefix when one is in effect (--namespace or [source].prefix in mind.toml). It then appears in mind recall, participates in mind upgrade and mind sync, and can be removed with mind forget <kind:name> like any managed item (spec ABS-8).

Running unattended

Pass --yes (-y) to skip all prompts. In a non-TTY context without --yes, absorb refuses at any point that would normally prompt (destination resolution or the multi-lobe confirmation). You must also supply a destination via --to or MIND_ABSORB_TO or absorb_to in config, because a missing destination in a non-TTY context is itself an error (spec ABS-3).

Install hooks

A source can declare install hooks in mind.toml: shell commands that build or install the tooling its items rely on. A user can supply or override them with meld --install-hook <cmd>.

The full form is a [[hooks]] array-of-tables. Each entry has a required run field (the shell command), an optional name label shown in the disclosure, an optional bool (default false), and an event field ("install" for meld, "uninstall" for unmeld; default "install"). The legacy [source].install string is shorthand for one required install hook:

# mind.toml in a source repo

[[hooks]]
run = "make build"
name = "build tooling"
event = "install"

[[hooks]]
run = "pip install -r requirements.txt"
name = "python deps"
optional = true
event = "install"

[[hooks]]
run = "make clean"
name = "cleanup"
event = "uninstall"

The safety prompt

Because a hook is arbitrary code, mind discloses the source identity, pin, commit, clone path, and exact command before running anything, and prompts [Y/n/a] with three choices: run the hook (the default, a bare Enter), skip it but still install the source and its items, or abort and install nothing. In a non-TTY context (CI, scripts) the hook is skipped and a note is printed; --dangerously-skip-install-hook-check runs it unattended. Overriding a source’s declared install hook with --install-hook is announced in the prompt, which shows both the declared and the overriding command.

The disclosure also shows a version-control browse URL pinned to the disclosed commit alongside the on-disk clone path, so you can read the exact code that will run either locally or in the forge before approving. The URL is produced only for GitHub-shaped https remotes; a GitLab or Bitbucket host, an SSH remote, or a local/file:// source shows the clone path alone (no correct web link exists for those).

Re-runs

A skipped hook is recorded and re-offered by mind upgrade, so you can run it later without the source needing to advance first. On an upgrade re-run the source is already installed, so abort is treated as skip. upgrade also re-runs the hook when a source advances to a new commit. sync --upgrade accepts --dangerously-skip-install-hook-check so a CI pipeline can run hook re-runs unattended. Without the flag, a non-TTY sync --upgrade skips hook re-runs (the same as upgrade).

Uninstall hooks

Uninstall hooks (event = "uninstall") run at unmeld, in the source’s clone, before the clone is removed. They use the same safety-prompt model as install hooks: required hooks prompt run / skip / abort-the-unmeld; optional hooks prompt run / skip; a non-TTY unmeld skips them and notes it. unmeld --uninstall-hook <cmd> supplies or overrides the source’s declared uninstall hooks. unmeld --dangerously-skip-hook-check runs them unattended (--dangerously-skip-install-hook-check, the original spelling, is kept as a hidden alias). A required uninstall hook that fails or is aborted leaves the source melded.

Running hooks on demand

Hooks normally run as a step of another verb: install hooks at meld/upgrade, uninstall hooks at unmeld, and item hooks at learn/forget/upgrade. mind hooks run runs them outside those flows, so you can run a hook you earlier skipped, re-run one whose effect was later lost (a deleted build output or side effect), or retry one that failed transiently, without a full re-meld or reinstall. Every hook it runs goes through the same disclosure and consent prompt as an automatic run.

mind hooks run <source>                     # the source's pending install hooks
mind hooks run <source> --force             # every install hook, even already-run ones
mind hooks run <source> --event uninstall   # the source's uninstall hooks
mind hooks run <source>#<item>              # an installed item's install hooks
mind hooks run <source>#<item> --event build  # rebuild the item (transactional)
mind hooks list <source>                    # list hooks in effect, run nothing

<target> is a source filter (the source’s own [[hooks]]) or an owner/repo#item ref (that item’s hooks); there is no ambiguity check, so a filter matching several sources, or a ref matching several items, runs the hook for each in turn. --event selects the lifecycle event (install, uninstall, or build); build is valid only for an item target.

An item-link instance’s own identity is owner/repo#<path> (see Item links), which is spelled the same way as “item <path> in source owner/repo”. When a target matches both a registered source and an installed item, mind refuses it rather than picking one, and names both disambiguated forms. Force either reading:

mind hooks run source:<target>              # the source's own hooks
mind hooks run <source>#<kind>:<name>       # that item's hooks

For a source install run, only pending install hooks run by default (a hook that never ran, was skipped, or whose recorded commit is behind the source’s current commit); --force re-runs every install hook regardless. An item target runs the item’s hooks in place against its installed store copy and requires the item to be installed. --event build rebuilds the item through the normal transactional install path, so a failed rebuild leaves the existing copy untouched.

The --dangerously-skip-install-hook-check and --dangerously-skip-build-hook-check flags apply exactly as they do to the automatic flows: without them a non-TTY run skips the hooks, and a required hook’s failure or abort is a non-zero exit.

mind hooks list <target> reports the hooks in effect for a source and its installed items – each hook’s event, required/optional flag, and command, and for a recorded source install hook whether it is pending and the commit it last ran at – without running any. It is the read-only companion to hooks run.

Visibility

recall --sources marks a source that carries hooks with a count-aware token in its status bracket (e.g. 1 hook or 3 hooks). mind review <repo> lists every declared hook (install and uninstall), showing each hook’s command, event, and whether it is required or optional. mind hooks list <target> shows the same detail plus the pending/last-ran state of recorded install hooks.

[source].install is deprecated in favor of the [[hooks]] form. See The mind.toml file for the schema and spec/install-hooks.md for the full behavior.

Managed policy (enterprise)

A managed policy file lets an administrator constrain mind machine-wide. It restricts the client to a trusted set of sources and locks related settings, modeled on Claude Code’s managed settings: a file at a fixed system path takes precedence over user configuration and the user cannot override it.

This is a compliance guardrail, not a security sandbox. mind runs with the user’s own privileges, so a user can still place agent files under an agent home by hand without invoking mind; the policy cannot prevent that. What it provides is a policy the user cannot edit (enforced by file permissions), refusal of disallowed operations through mind, and auditability.

The policy file

mind reads a managed policy file on every invocation from a fixed per-OS system path (POL-1):

OSPath
Linux/etc/mind/policy.toml
macOS/Library/Application Support/mind/policy.toml
Windows%PROGRAMDATA%\mind\policy.toml

Windows-native use is not a supported configuration. For WSL environments, deploy the policy to /etc/mind/policy.toml within the WSL distribution (the Linux path above applies inside WSL).

The path is owned by an administrator and world-readable but not user-writable. It is not relocatable by MIND_HOME or other user environment (POL-2).

MIND_POLICY_FILE overrides the path only when no file exists at the system path (for tests and non-managed use). When the system file exists it is authoritative and MIND_POLICY_FILE is ignored. The env path is honored only when the file it names exists; a set-but-missing MIND_POLICY_FILE is treated as no policy, not a hard error (POL-2).

When a policy is in effect it is authoritative over user configuration (~/.mind/config.toml) and the user’s registry: a user cannot widen what the policy restricts (POL-3).

With no policy file present, mind is unmanaged and behaves exactly as it does without this feature. Every control below is opt-in and absent unless the policy sets it, so the feature is inert by default (POL-4).

A policy file that does not parse, has unknown keys, or violates an internal rule is a hard error on every command that loads it (fail closed), naming the problem; mind does not silently fall back to unmanaged (POL-5). The one exception is plain mind recall (read-only, no agent-home access): it does not load the policy and remains usable as an escape hatch during a botched rollout. mind review --policy <file> bypasses the system policy path and can diagnose a broken deployed policy without needing to fix it first. Validate a new policy with mind review --policy before deploying it.

Schema

[sources]
# Allowlist matched against a source's host/owner/repo identity. `*` matches
# within one path segment.
allow = ["github.com/acme/*", "github.example.com/platform/*"]

# Refuse to meld any source whose identity is not in `allow`. Without lock, the
# allowlist is advisory (a non-matching meld warns but proceeds).
lock = true

# Forbid local-path and file:// melds under lock regardless of allow patterns.
# Default is true (local-path melds are evaluated against allow like any source).
allow-local = false

# Every meld must resolve to a tag or ref; floating branches are refused. When
# pinned, every auto_meld entry below must declare a tag or ref.
pinned = true

# Sources mind provisions automatically (melds if not already present), during
# `sync`. `repo` is a repo spec as `meld` accepts (owner/repo, a URL, git@, or a
# path); its derived host/owner/repo identity must satisfy `allow` under lock.
#
# `install = true` (default false): after provisioning, install every item the
# source offers headlessly (equivalent to `learn '<source>#*' --yes`).
# `run-build-hooks = true` (default false): also run item build hooks during the
# headless install; equivalent to --dangerously-skip-build-hook-check. Only
# meaningful when `install = true`.
[[sources.auto_meld]]
repo = "acme/agent-baseline"
tag = "v1.4.0"
install = true

[[sources.auto_meld]]
repo = "https://github.example.com/platform/security-rules"
ref = "9f3a1c2e7b1d0a4c5e6f8a9b0c1d2e3f40516273"
install = true

[lobes]
# Lock the agent homes: `config lobes` edits and $MIND_AGENT_HOMES are refused,
# and the effective homes are exactly `targets`. With lock off, `targets` is a
# base set the user's configured lobes are unioned onto.
lock = true
targets = ["~/.claude"]

[binary]
# Control `mind evolve` (binary self-update). Absent or true: unrestricted.
# false: evolve and evolve --check fail before any network call.
# A version string: evolve resolves to that version offline; --to with a
# different value is refused; an invalid string fails policy parse.
self-update = false     # or true, or "0.14.0"

The scalar [sources] keys precede the [[sources.auto_meld]] tables, per TOML ordering. Source identity is host/owner/repo.

Trusted-source allowlist

[sources].allow is a list of patterns matched against a source’s host/owner/repo identity, where * matches within a path segment, so github.com/acme/* matches every repo under acme (POL-10). When GHES runs on a non-standard port, the identity includes the port (e.g. github.example.com:8443/owner/repo); patterns must include the port to match, for example github.example.com:8443/platform/*.

Local-path identity. A meld of an absolute path, a .//../ relative path, or a file:// URL gets identity local/<parent-dir>/<repo> where <parent-dir> is the last path component above the repo directory. For example, /srv/mirrors/agent-baseline gets identity local/mirrors/agent-baseline. To allow a mirror directory under lock, add a pattern like local/mirrors/* to allow.

Item-link and alias instances. An item-link instance’s identity carries a #<path> suffix (host/owner/repo#skills/foo), and an @<prefix> alias instance’s identity carries an @<prefix> suffix (host/owner/repo@jk). Allow/lock matching strips both suffixes before comparing, so it always runs against the bare host/owner/repo identity. A link or alias instance therefore inherits its repo’s allow decision, and an allow pattern never needs to name a #path or @prefix suffix.

[sources].lock is the enforcement switch:

  • With lock = true, meld refuses any repo whose identity does not match allow (SourceNotAllowed) before any clone or egress (POL-11). learn, sync, and upgrade operate only on allow-matching sources. A registered source that is no longer allowed is reported and skipped, not updated or installed from (POL-12).
  • With lock absent or false, allow is advisory: a non-matching meld is warned about but not refused, and the verbs above are not restricted (POL-13).

Local-path control (allow-local)

[sources]
allow-local = false   # default: true

allow-local = false forbids all local-path and file:// melds when lock = true, regardless of what allow patterns match (POL-56). The error names allow-local = false as the reason, distinct from the generic pattern-miss message. With lock absent or false, allow-local has no effect.

allow-local = true (the default) preserves existing behavior: local-path and file:// melds are evaluated against allow like any other source under lock (POL-57).

When to use allow-local = false. Admitting local paths under a lock delegates control to whoever can write that directory: a user who can clone any repo locally and meld it from a path matching local/*/* bypasses the network restriction. allow-local = false closes that gap for machines where the lock is meant to enforce source origin, not just pattern matching. Combine it with filesystem permissions on any mirror directory that employees can read but not write to.

Require pinned

With [sources].pinned = true, every meld must resolve to a tag or ref pin (--pin <ref>, --pin tag=<name>, or a [source] pin directive that resolves to a tag or ref); a floating branch (--pin branch=<name> or the default branch) is refused (UnpinnedSourceForbidden) (POL-20).

Note that a tag=<name> pin is accepted but is still a moving ref: sync re-points it if the upstream tag is moved. Only --pin <ref> (which freezes a resolved commit) is truly immutable, so pinned = true alone does not guarantee a source is frozen to one commit.

With pinned = true, every [sources].auto_meld entry must declare a tag or ref. A policy whose auto_meld contains an unpinned entry or a follow_branch entry is invalid and is reported by mind review --policy (POL-21).

Auto-meld (org provisioning)

[sources].auto_meld is a list of tables, each with repo (a repo spec as meld accepts: owner/repo, a URL, git@..., or a path) and an optional pin: tag, ref, or follow_branch. mind provisions these by melding any that are not already melded (POL-30). It is a base set, not an exclusive one: with lock off the user may meld additional sources beyond it; when locked, only allow-matching sources are permitted.

Every auto_meld entry must satisfy allow when lock is true, matched on the host/owner/repo identity derived from its repo spec. An entry outside the allowlist, or whose repo does not parse, is an invalid policy (POL-31).

Auto-meld provisioning runs during sync, using each entry’s declared pin. It is idempotent: an entry already melded at the declared pin is left unchanged (POL-32). Provisioning runs only for a full mind sync with no selector; a scoped mind sync <source> skips it entirely, with no message, so a runbook that only ever runs a scoped sync never converges on the policy’s base set.

Item install. By default, provisioning registers the source only; no items are installed. Set install = true on an entry to also install every item the source offers, headlessly, after successful provisioning (POL-58). This is equivalent to running mind learn '<source>#*' --yes for that source.

Build hooks for items installed via the install = true pass are skipped by default (HOOK-72 non-TTY path). Set run-build-hooks = true on the same entry to run them, equivalent to --dangerously-skip-build-hook-check (POL-59). Only set this for sources you control or have audited, since build hooks are arbitrary code.

Install hooks follow the same non-TTY path: they are skipped in a non-interactive context. A failed item install is soft-failed per POL-34 (warn, record, continue); other items and sources still process, and sync exits non-zero if any item failed (POL-60).

Lobe lock

With [lobes].lock = true, the effective agent homes are exactly [lobes].targets; config lobes add / config lobes remove are refused and the user’s lobes (from ~/.mind/config.toml) and MIND_AGENT_HOMES are ignored. With targets absent under a lock, the lock pins the default ~/.claude (POL-40).

With [lobes].lock absent or false, [lobes].targets is a base set the user extends: the effective agent homes are targets unioned with the user’s configured lobes (POL-41).

Binary self-update

[binary].self-update controls mind evolve (and its self-update alias). A missing [binary] table or missing key leaves evolve unrestricted (POL-51).

  • self-update = false: both mind evolve and mind evolve --check fail before any network call with a policy error. The disable applies to --check as well so the binary does not nag about available updates (POL-52).
  • self-update = "X.Y.Z": evolve resolves to that exact version offline (no api.github.com call), behaving as if --to X.Y.Z were passed. The pin is validated as a dotted-numeric version at policy-parse time (POL-5 fail closed); a leading v is stripped before validation. If --to V is also passed and V differs from the pin, the command fails with a policy error naming the conflict. evolve --check reports against the pinned version and respects the no-downgrade logic (POL-53).
  • self-update = true: identical to the absent key; evolve is unrestricted. Exists so a policy can explicitly re-enable updates in a layered deployment (POL-54).

The pin is an upper bound for evolve, not a fleet version enforcement (POL-66). When IT distributes a binary newer than the pin (e.g. pin 0.14.0, running binary 0.15.0), evolve and evolve --check print a human-readable warning and exit 0, rather than downgrading:

warning: running 0.15.0 differs from the managed policy pin 0.14.0; the policy
pin is an upper bound and does not downgrade

The --json path emits no warning text on stdout; the structured outcome field (not-downgrading) is the machine-readable hook for fleet skew monitoring. See IT-managed binaries in the enterprise guide for deployment guidance and the trust model for binary updates.

Schema evolution and deployment ordering

A policy file may only use keys the oldest deployed binary understands. Adding a new policy key (such as [binary] in 0.14.0) to a policy file while some fleet machines still run an older binary causes every mind command on those machines to fail with an “unknown field” error (fail-closed per POL-5).

Upgrade binaries before deploying a policy that uses new keys.

Starting with 0.15.0, a policy may declare min-mind-version at the top level:

min-mind-version = "0.15.0"

[sources]
lock = true

mind checks this key before the strict field-validation parse (POL-61). When a binary older than min-mind-version loads the policy, it reports:

error: invalid managed policy at /etc/mind/policy.toml:
managed policy requires mind >= 0.15.0, running 0.14.0; run `mind evolve`

instead of an opaque “unknown field” error (POL-62). The gate only helps binaries built after 0.15.0; the ordering constraint still applies to older binaries. An invalid min-mind-version value (not a dotted-numeric string) is a hard parse error consistent with POL-5 (POL-63). Validate any policy with mind review --policy <path> before deploying it.

Deploying the policy file

The policy file should be owned by root and not writable by non-root users. Correct deployment on Linux:

sudo mkdir -p /etc/mind
sudo cp policy.toml /etc/mind/policy.toml
sudo chown root:root /etc/mind/policy.toml
sudo chmod 644 /etc/mind/policy.toml
sudo chown root:root /etc/mind
sudo chmod 755 /etc/mind

For macOS (/Library/Application Support/mind/):

sudo mkdir -p "/Library/Application Support/mind"
sudo cp policy.toml "/Library/Application Support/mind/policy.toml"
sudo chown root:wheel "/Library/Application Support/mind/policy.toml"
sudo chmod 644 "/Library/Application Support/mind/policy.toml"
sudo chown root:wheel "/Library/Application Support/mind"
sudo chmod 755 "/Library/Application Support/mind"

An Ansible task:

- name: deploy mind policy
  copy:
    src: policy.toml
    dest: /etc/mind/policy.toml
    owner: root
    group: root
    mode: "0644"
- name: secure policy directory
  file:
    path: /etc/mind
    owner: root
    group: root
    mode: "0755"

When mind loads the system policy file, it warns to stderr if the file or its parent directory is group/world-writable or not root-owned (POL-64). The warning is not a refusal: the policy still loads and enforces, so a misprovisioned fleet stays functional while the misconfiguration is visible. Example:

warning: managed policy /etc/mind/policy.toml is group/world-writable; a local
user could alter enforced policy. chown root and chmod 644.

The check is skipped when the policy was located via $MIND_POLICY_FILE (user-trust path, POL-65).

Validation

mind review --policy <path> statically validates a managed policy file without cloning: it parses the TOML, rejects unknown keys, checks that allow patterns are well formed and auto_meld repo specs parse, enforces that every auto_meld entry is pinned when pinned = true (POL-21), and that every auto_meld entry satisfies allow when locked (POL-31). It reports hard errors and advisories and exits non-zero on a hard error (POL-50).

Caveat: install hooks are not gated

The policy does not currently gate install-hook execution. A source’s build command is arbitrary code, and it still runs under the normal hook safety prompt rather than under any policy control. How a policy should govern install hooks is an open research item, not yet specified. See Install hooks for the disclosure and prompt model that applies.

Example

A worked example is at examples/policy.

Restricted networks and enterprise

mind has no built-in HTTP client. Every network touch is a git, curl, or wget subprocess spawned with the parent environment inherited, so the same proxy, CA, and credential configuration you already use for those tools applies unchanged. mind sets or strips nothing from the environment (in TUI mode it sets GIT_TERMINAL_PROMPT=0 and wraps GIT_SSH_COMMAND to suppress interactive git prompts; immaterial for non-interactive CLI use), and there is no telemetry or phone-home. This page lists the egress endpoints a firewall allowlist needs and the knobs for proxies, custom CAs, private repos, IT-managed binaries, and air-gapped installs.

For machine-wide policy enforcement (trusted-source allowlist, pinning, lobe lock), see Managed policy; this page shows how those controls fit an enterprise deployment.

Egress endpoints

A corporate allowlist needs the hosts each operation contacts:

  • Melding and syncing sources. git clone / git fetch against whatever hosts you meld (GitHub, GitHub Enterprise, an internal GitLab, an SSH remote, or a local path). No fixed host: it is exactly the source URLs you register.
  • mind evolve (binary self-update).
    • https://api.github.com/repos/jaemk/mind/releases/latest (skipped with --version, see below).
    • https://github.com/jaemk/mind/releases/download/... for the release asset and its SHA256SUMS. These redirect to https://release-assets.githubusercontent.com/...; allowlist that host. *.githubusercontent.com is the most resilient pattern: GitHub has migrated release assets between CDN subdomains. objects.githubusercontent.com may still be needed for older GitHub Enterprise Server deployments.
  • install.sh (install script). The same two github.com / release-assets.githubusercontent.com download URLs, plus https://raw.githubusercontent.com/jaemk/mind/main/resources/install.sh for the script itself, and https://api.github.com/... unless you pin MIND_VERSION.

Proxies

The git, curl, and wget subprocesses read proxy environment variables from the parent process, but behavior differs by tool:

  • git honors HTTPS_PROXY, HTTP_PROXY, NO_PROXY, and their lowercase forms, plus its own config:

    git config --global http.proxy http://proxy.corp.example:8080
    

    For NTLM/Kerberos proxy auth, set http.proxyAuthMethod:

    git config --global http.proxyAuthMethod negotiate
    
  • curl (used by evolve and install.sh) intentionally ignores uppercase HTTP_PROXY. Set HTTPS_PROXY or the lowercase form https_proxy.

  • wget (fallback when curl is absent) reads only lowercase forms (https_proxy, http_proxy, no_proxy); uppercase variables are ignored.

The safest approach is to export both cases:

export HTTPS_PROXY=http://proxy.corp.example:8080
export https_proxy=http://proxy.corp.example:8080
export NO_PROXY=localhost,127.0.0.1
export no_proxy=localhost,127.0.0.1

For NTLM or Kerberos proxy authentication, curl reads ~/.curlrc (mind exposes no curl argument knob). Add proxy-negotiate there:

proxy = http://proxy.corp.example:8080
proxy-negotiate

mind neither sets nor unsets any of these, so whatever works for a bare git clone or curl works for mind.

Custom CA / TLS-intercepting proxy

Behind a proxy that re-signs TLS with a corporate root CA, point each tool at the CA bundle the way you already do:

  • git: git config --global http.sslCAInfo /path/to/corp-ca.pem, or install the CA into the system trust store.
  • curl (used by evolve and install.sh): CURL_CA_BUNDLE or SSL_CERT_FILE in the environment.

The wget fallback path (used by install.sh and evolve only when curl is absent) honors no CA environment variable. Behind a custom root CA, the CA must be in the system trust store (or configured in wgetrc); an env var alone will not help. Prefer curl, which reads CURL_CA_BUNDLE / SSL_CERT_FILE. The same wget fallback also reads only lowercase proxy environment variables (https_proxy, not HTTPS_PROXY); see Proxies above.

Private repos

Private sources work with no mind-specific configuration: it shells out to git, so your credential helpers and SSH agents apply untouched.

  • SSH. Meld the git@host:owner/repo form, or set ssh = true in ~/.mind/config.toml so the owner/repo shorthand clones over SSH (see Configuration). The running ssh-agent supplies keys.
  • HTTPS. A configured git credential helper supplies the token or password git would normally prompt for.
  • Any host. GitHub Enterprise, a full clone URL, the git@ SSH form, and a local path or file:// remote all meld the same way. Source identity is host/owner/repo, so a GHE github.example.com/... source and a github.com/... source never collide. When GHES runs on a non-standard port, the identity includes the port (e.g. github.example.com:8443/owner/repo); allow patterns in a managed policy must include the port to match.

IT-managed binaries (no self-update)

To stop end users from updating the binary, either install mind to a root-owned path (so an in-place evolve cannot write it) or set the managed policy [binary].self-update knob:

[binary]
self-update = false

With self-update = false, both mind evolve and mind evolve --check fail fast with a policy error (self-update is disabled by the managed policy) before any network call. To pin a rollout to a specific version instead of blocking outright, set a version string:

[binary]
self-update = "0.14.0"

mind evolve then resolves to that exact version offline (no api.github.com call), and evolve --to with any other value is refused. Absent or self-update = true leaves evolve unrestricted. See Managed policy for where the policy file lives and how it is enforced.

The pin is an upper bound, not a fleet version enforcement. When IT distributes a binary newer than the pin (e.g. the policy says 0.14.0 but the installed binary is 0.15.0), evolve does not downgrade. Instead it prints a human-readable warning and exits 0:

warning: running 0.15.0 differs from the managed policy pin 0.14.0; the policy
pin is an upper bound and does not downgrade

To detect this skew in a script or monitoring job, run evolve --check --json and inspect the outcome field: a value of not-downgrading means the running binary is above the pin. The exit code is 0 in all cases so cron jobs are not broken.

Trust model for binary updates

SHA256SUMS is fetched over the same HTTPS channel as the release artifact itself. The checksums verify integrity (no corruption or truncation in transit) but not origin or authenticity: a host-trusted TLS-terminating proxy can substitute both the artifact and its matching checksums without triggering a checksum mismatch. Releases are not code-signed. For environments where egress goes through an intercepting proxy you do not fully trust, the correct posture is self-update = false combined with IT-distributed binaries delivered through a separately audited channel.

GITHUB_TOKEN/GH_TOKEN visibility on shared hosts. evolve sends GITHUB_TOKEN (or GH_TOKEN) as a bearer header on api.github.com requests (see Troubleshooting for why you’d set one). With curl, the header is passed via a private (mode 0600) config file, not the command line. The wget fallback (used only when curl is absent) has no equivalent, so it passes the header on the command line, where it is briefly visible to other local users via the process table (e.g. ps, /proc/<pid>/cmdline) for the duration of the API call. On shared hosts, prefer curl over wget when a token is set.

Air-gapped and api-blocked installs

When only github.com is allowlisted and api.github.com is blocked, pin the version so install.sh skips the API call:

curl --proto '=https' --tlsv1.2 -fsSL \
  https://raw.githubusercontent.com/jaemk/mind/main/resources/install.sh \
  | MIND_VERSION=0.14.0 sh

install.sh resolves the latest tag from api.github.com only when MIND_VERSION is unset; pinning it downloads the release asset from github.com directly. mind evolve --to <v> is the same: the version bypasses the API.

For fully air-gapped mirrors, meld from a local path or a file:// remote pointing at an internal clone. Source melding accepts any git remote git can reach, including internal hosts.

Air-gapped installs under a managed policy lock. A local-path or file:// meld gets identity local/<parent-dir>/<repo>. Under lock = true with a GHES-scoped allowlist (e.g. github.example.com/platform/*), every local meld is refused because the identity does not match. There are two ways to reconcile this:

  • Add an allow pattern that covers the mirror directory:

    [sources]
    lock = true
    allow = ["github.example.com/platform/*", "local/mirrors/*"]
    

    This allows melds from /srv/mirrors/* (identity local/mirrors/<repo>) while still blocking other local paths. Note that admitting any local-path pattern delegates control to whoever can write that directory: a user who can clone any repo into a matching path bypasses the network restriction. Combine this with filesystem permissions that allow reads but not writes to the mirror directory.

  • Set allow-local = false to block all local-path and file:// melds under lock, regardless of allow patterns:

    [sources]
    lock = true
    allow-local = false
    allow = ["github.example.com/platform/*"]
    

    Use this when the lock is meant to enforce source origin and local-path melds should never be permitted. See Managed policy for details.

Network timeouts

mind evolve reads the connect timeout from MIND_HTTP_TIMEOUT_SECS (seconds, default 15). Raise it for a slow proxy:

MIND_HTTP_TIMEOUT_SECS=60 mind evolve

install.sh uses a fixed 15-second connect timeout and does not read MIND_HTTP_TIMEOUT_SECS (it runs before mind is on PATH).

For slow or blackholed git clones and fetches, use git’s own knobs, for example an abort when throughput stays under a floor:

git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 30

Managed-policy provisioning

A managed policy file constrains mind machine-wide: it restricts the client to a trusted source allowlist, can require every source to be pinned, and can auto-provision a base set of sources on sync. The file lives at a fixed system path an administrator controls (see Managed policy for the per-OS paths and full schema). A worked example:

[sources]
# Only sources under these identities may be melded.
allow = ["github.example.com/platform/*"]
# Refuse any meld outside the allowlist (without lock, allow is advisory).
lock = true
# Every source must resolve to a tag or ref; no floating branches.
pinned = true

# Provision a baseline source automatically during `sync` and install its items.
# `install = true` runs the item install pass after provisioning (headless, --yes).
# `run-build-hooks = true` also runs item build hooks (arbitrary code; only for
# sources you control).
[[sources.auto_meld]]
repo = "https://github.example.com/platform/agent-baseline"
tag = "v1.4.0"
install = true

[binary]
# Block user-initiated binary updates.
self-update = false

[lobes]
# The effective agent home is exactly ~/.claude; config lobes edits are refused.
lock = true
targets = ["~/.claude"]

Auto-meld runs during sync, melding any listed source not already present at its declared pin (it is idempotent). Provisioning failures are soft: an auto_meld entry that fails to provision does not block the rest, already-melded sources still sync normally, and sync exits non-zero after the run if any provisioning entry failed. Validate a policy before deploying it with mind review --policy <path>.

Policy schema and deployment ordering. A policy file must only use keys the oldest deployed binary understands. Upgrading binaries before deploying a policy that uses new keys prevents “unknown field” errors on old binaries (which fail closed per POL-5). Starting with 0.15.0, a policy can declare min-mind-version = "0.15.0" at the top level; an old binary that reads it reports a clear version error instead of an opaque field error. See Schema evolution in the policy reference for the full constraint and the min-mind-version key.

Policy file permissions. mind warns to stderr when the system policy file or its parent directory is group/world-writable or not root-owned. See Deploying the policy file for the recommended chown/chmod steps and an Ansible snippet.

Team / CI provisioning recipe

To provision a fleet of machines from a curated source, commit a mind.toml super-source to a private repo and meld it in CI. See The mind.toml file for the super-source format.

  1. In a private repo, commit a mind.toml listing the sources and items the team should have (a super-source with a [discover].sources list, or a single source’s own inventory). mind dump can generate one from a reference machine.

  2. In CI, meld it non-interactively and install its items. If this is the first SSH connection to the host in this environment, pre-seed known_hosts first; a headless clone fails on host-key verification otherwise:

    ssh-keyscan -H github.example.com >> ~/.ssh/known_hosts
    mind meld git@github.example.com:platform/agent-config --yes
    

    --yes installs without prompting. A non-TTY meld without --yes registers the source only and exits 0 without installing anything, so CI that wants items installed must pass --yes. If the sources are trusted and you want their install and build hooks to run unattended, add --dangerously-skip-install-hook-check and --dangerously-skip-build-hook-check (both execute arbitrary code from the source; only for sources you trust).

  3. Verify the result as structured JSON:

    mind recall --json
    

    recall --json nests items under each source; use jq to find uninstalled items across all sources:

    mind recall --json | jq '.items[].items[] | select(.installed | not)'
    

    Branch on that output (or a non-zero exit) to fail the job if an expected item is missing.

Under a managed policy with auto_meld and install = true, a CI step is as small as mind sync followed by mind recall --json. The sync provisions each listed source and installs its items headlessly; the recall confirms the result. See Managed-policy provisioning for the schema and install/run-build-hooks field reference.

Troubleshooting

  • An item didn’t show up in ~/.claude. Run mind introspect; it reports missing links and drift, and mind introspect --fix recreates missing symlinks.

  • learn refused to overwrite a path. mind will not clobber a file or link it did not create (the clobber guard). Move the existing one aside, then learn again.

  • Two sources ship an item with the same name. Namespace one with mind meld <repo> --namespace <prefix>, so its items install as <prefix>:<name>. See examples/namespacing/.

  • Where things live: see Configuration. Override the roots with MIND_HOME and MIND_DEFAULT_LOBE (CLAUDE_HOME is a legacy alias for the latter and is still read).

  • Before publishing a source, run mind review <path> to check its mind.toml, item kinds, {{ns:}} references, and pin directive. See Authoring a source.

  • To authenticate with an SSH key, see Configuration.

  • Stuck in the TUI. Press q in the main view, or Ctrl-C twice from anywhere (search box, dialogs) to force exit.

  • A mind.toml change is rejected as a parse error. The file is strict (deny_unknown_fields), so a misspelled key is a hard error, not silently ignored (DSC-30). Common near-misses: pin keys are hyphenated (follow-branch, pin-tag, pin-ref), not underscored; min-mind-version likewise uses a hyphen; and entry keys under [discover].sources are exact (DSC-31). Run mind review <path> to check the file before melding.

  • A source’s items are not discovered after setting roots in mind.toml. roots = [] (an explicit empty list) is distinct from omitting the key: it scans zero roots and finds nothing. Omitting roots (or removing the key) keeps the default behavior of scanning the repo root (DSC-50). Set the actual subdirectory paths, or remove roots entirely.

  • learn reports LinkOccupied and refuses to overwrite. The clobber guard will not replace a path that mind did not create (LIFE-41). Move the existing file aside, then learn again, or pass learn --force (CLI-35) to replace it unconditionally. Note: mind supports only unix-like platforms, where items are linked with real symlinks. On a non-unix platform an install is refused up front with an “unsupported platform” error (LIFE-50), rather than falling back to a copy that mind cannot later recognize as its own (which would break reinstall/upgrade with LinkOccupied).

  • Roll back an item to an older source version (downgrade). The upgrade mechanism applies whatever the source’s pin resolves to, and an item is pending on any content change, not only a newer commit (LIFE-11). So re-pin the source to the older commit and apply it:

    mind meld <repo> --pin <old-sha>   # re-pins the already-melded source (CLI-209)
    mind upgrade <item> --yes          # applies the older content
    

    Use mind recall --sources to find the source, and a forge compare view (or a local clone’s git log) to pick the target <old-sha>. To pin an exact ref by name instead of a commit, --pin tag=<name> or --pin branch=<name> also work. Re-pinning forward again (--pin <newer-sha> or --pin HEAD) followed by upgrade restores the newer version.

  • An item shows as out of date in recall/probe without an upstream change. Editing a store or source file by hand changes its content hash; mind compares source-content hashes and reports the delta as drift (LIFE-33, CLI-75). Either re-sync and then upgrade the item, or restore the edited file to its original content. See Configuration for where store and source files live.

  • meld or sync fails with “git executable not found”. mind shells out to git for all clone and fetch operations; put git on your PATH first. See the Install page.

  • meld/sync fails behind a proxy (HTTP 407 or a connection refused). mind inherits the environment; set HTTPS_PROXY (and NO_PROXY for internal hosts) or git’s own http.proxy (git config --global http.proxy http://proxy.corp:8080). See Restricted networks and enterprise.

  • A clone or evolve fails with a TLS/certificate error behind a company proxy. Point the tool at the corporate CA: git via http.sslCAInfo (git config --global http.sslCAInfo /path/to/corp-ca.pem), curl (used by evolve and install.sh) via CURL_CA_BUNDLE or SSL_CERT_FILE. Note that the wget fallback path honors no CA environment variable, so if only wget is present the corporate CA must be in the system trust store (or wgetrc); prefer having curl installed. See Restricted networks and enterprise.

  • evolve fails with a 403 from https://api.github.com/repos/.../releases/latest. This is GitHub’s unauthenticated REST API rate limit (60 requests/hour per source IP), which a shared workplace egress IP exhausts quickly. Set GITHUB_TOKEN (or GH_TOKEN) to any GitHub token; evolve sends it as a bearer header on the API request, moving you into the authenticated 5000/hour tier. The token is only sent to api.github.com, never to the artifact download. As a stopgap without a token, mind evolve --to <v> resolves the target from the flag and skips the API call entirely.

  • A private-repo meld fails to authenticate. mind uses your existing git auth: configure a git credential helper for HTTPS, or meld the SSH form (git@host:owner/repo, or ssh = true in ~/.mind/config.toml) with a key in your ssh-agent. See Restricted networks and enterprise.

  • A skill links to a Gemini or Codex lobe but a rule does not. Rules have no cross-harness directory equivalent and are Claude-only (HARN-3). Only skills and agents are linked into non-Claude lobes. If the lobe was added via a preset, this is expected; rules remain in ~/.claude only.

  • An agent’s tool permissions don’t work in Gemini or Codex after linking. mind links files verbatim and does not rewrite frontmatter. A skill or agent whose frontmatter uses Claude-specific keys (e.g. the tools: allow-list schema) will link correctly but those keys may be ignored or produce a warning in the target harness. Adapt the frontmatter for the target harness by hand (HARN-6). See Configuration.

  • A melded local source’s directory was moved or deleted. Commands that list the catalog (recall, probe, introspect, upgrade) now warn and continue instead of failing outright. Run mind recall --sources to find the source’s name, then mind unmeld <name> to drop it, or restore the directory at its original path (CLI-213).

  • A lobe was added for a harness that is not installed yet (e.g. config lobes add --preset gemini before Gemini CLI exists). This now works: the target directory is created and already-installed items are linked into it immediately (HARN-15, HARN-17). If an older config still has a lobe whose directory or parent is unreachable, mind introspect --fix prunes it.

Source layout

A source is a git repo mind melds. It offers items, discovered by convention or declared in a mind.toml. The convention layout, which works on any repo with no config:

<repo>/
  skills/<name>/SKILL.md     a skill (the whole directory is the item)
  agents/<name>.md           an agent
  rules/<name>.md            a rule
  tools/<name>/              a tool (the whole directory; no anchor file)
  mind.toml                  optional: metadata, export control, odd layouts

The kinds:

  • skill: a directory with a SKILL.md anchor. Bundled files (a resources/ dir, scripts) ship with it.
  • agent / rule: a single markdown file.
  • tool: a directory of helper scripts or a compiled binary. A tool is store-only: other items reference it, and by default it is not linked into an agent home (a tool can opt in with an explicit link, see Tooling).

A mind.toml is optional enrichment, never a gate. It carries source metadata, a namespace prefix, and (when you need it) explicit [[items]] or [discover] globs for non-standard or monorepo layouts. See examples/.

A repo published for Claude Code’s plugin system needs no changes either: a .claude-plugin/plugin.json or .claude-plugin/marketplace.json is read as a discovery input, mapping the plugin’s skills and agents to mind items. See Claude plugin marketplaces.

Where shared helpers belong

A helper used by a single skill lives in that skill’s own directory and is addressed with {{self}}:

skills/review/
  SKILL.md           # ... run {{self}}/resources/pr.py ...
  resources/pr.py

A helper used by more than one item has two good homes. Either:

  • An install hook puts it in a known location. Declare a [[hooks]] install entry to run your install script, which installs the shared tooling wherever you want, and have your items call it there. This suits anything with a build step or a dependency to fetch, and a source onboards its build once.
  • A tool item shares it through the store. Put it once under tools/<name>/ and reference it by token ({{tools:name}}). mind carries it in the store and expands the token at install.
tools/detect/detect          # the shared script, shipped once
skills/a/SKILL.md            # ... {{tools:detect}} ...
skills/b/SKILL.md            # ... {{tools:detect}} ...

Copying a byte-identical helper into several items works too; mind review and mind init-source note it as a duplicate-tooling advisory (informational, not a defect) in case you would rather share it once.

Referencing items and resources

To reference one item from another, mind provides tokens it expands at install. They are useful mainly under a namespace prefix (which renames items) or across multiple agent homes; an unprefixed single-home source can often just use the name or a bundled path.

tokenexpands to
{{ns:name}}a sibling item’s effective name (use in prose, e.g. “hand off to {{ns:dev}}”)
{{self}}the item’s own store directory (its bundled resources)
{{tools:name}}a sibling tool’s entrypoint
{{path:ref}}a sibling item’s store directory, for a non-entrypoint file ({{path:tool:detect}}/lib.sh)

References resolve within the same source only: ship a tool in the same source as the items that use it.

Tokens expand only in markdown files. A token in a bundled script (a resources/pr.py) is left literal by default. To expand it there, list the file in the item’s expand: frontmatter, so a script can locate its tooling without a language-specific self-locate; see Tooling and shared scripts.

Hardcoded paths

mind learn copies an item into the store (~/.mind/store/<kind>/<name>) and symlinks it into each agent home (~/.claude/skills/<name>, agents/<name>.md, rules/<name>.md). A tool is the exception: it is store-only and, by default, not linked into an agent home.

A path you control is fine: pointing at a location your install hook populates works as long as your hook and your items agree on it. What is fragile is hardcoding mind’s OWN install layout, since that layout shifts under you. mind review classifies those as the advisory hardcoded-path finding:

  • A skill referencing its own resources by an agent-home path (~/.claude/skills/<self>/resources/x) resolves through the skill’s symlink today, but breaks the moment a prefix renames the item (<prefix>:<self>) or a second agent home is configured. {{self}} generalizes it. Fragile, not broken.
  • A reference to a tool item by an agent-home path never resolves: a tool is not linked there. Use {{tools:name}} (or install it elsewhere via a hook).
  • Any reference under a prefix points at the wrong effective name, since a literal path does not track the rename.

A token keeps a leading ~ when the store is under your home, so a Claude settings.json permission glob such as Bash(~/.mind/store/**) matches the expansion.

mind review recognizes these install paths written with ~, $HOME, ${HOME}, or an absolute /home/<user> / /Users/<user> root, and mind review --fix rewrites the ones that map confidently to a token. The finding is advisory, so a deliberate fixed-location-via-install-hook layout is your call.

The mind.toml file

A source repo may place a mind.toml at its root to declare what it offers and how mind should treat it. It is always optional: a repo with no mind.toml is discovered by convention. mind.toml is enrichment, never a gate.

There are four discovery layers, in precedence order:

  1. Convention (default, no file): the scanner finds skills/<name>/SKILL.md, agents/<name>.md, rules/<name>.md, and tools/<name>/.
  2. Frontmatter (always read): each item’s description (and a tool’s bin / build) come from the frontmatter it already carries.
  3. Claude plugin manifest (optional): a .claude-plugin/plugin.json or .claude-plugin/marketplace.json in the repo is read as a discovery input. It is authoritative for the items it declares (convention scanning is skipped for the paths it covers) but an authoritative mind.toml overrides it. See Claude plugin marketplaces.
  4. mind.toml (optional): [source] metadata is read regardless. Declaring [[items]] or [discover] item globs makes the file authoritative: convention scanning and the plugin-manifest layer are both turned off and only what the file lists is offered.

When does mind.toml take over discovery?

mind.toml containsconvention scanresult
nothing / no fileonitems found by convention (or by a .claude-plugin/ manifest, if present)
[source] onlyonconvention items, plus metadata (namespace, pin, …)
[discover].sources onlyonconvention items, plus a curated chain
[[items]]offexactly the declared items
[discover] with kind globsoffexactly the glob matches

The last two are authoritative. A bare [discover].sources list (curating other repos) does not turn off convention scanning, so a repo can ship its own items by convention and curate others at the same time (see Regular plus super-source).

When a repo has no authoritative mind.toml but does carry a Claude .claude-plugin/plugin.json or .claude-plugin/marketplace.json, that manifest supplies the items instead of convention scanning. An authoritative mind.toml overrides it (and meld notes the manifest was ignored). See Claude plugin marketplaces.

[source] - repo metadata

All keys are optional.

[source]
description = "James's agent library"   # shown in recall/probe
namespace = "jk"                         # items install as jk:<name>
min-mind-version = "0.5"                 # refuse to scan under an older mind
follow-branch = "main"                   # pin: track a branch ...
# pin-tag = "v2"                          # ... or fix to a tag ...
# pin-ref = "a1b2c3d"                     # ... or to an exact commit (pick one)
roots = ["packages"]                     # scan under these dirs, not the repo root
  • namespace (previously prefix): every item installs as <namespace>:<name> (identity, store path, symlink, and ref), except an agent’s harness link, which stays bare. A consumer’s meld --namespace <ns> overrides it; meld --namespace '' removes it. See namespacing. The old key prefix still parses as a deprecated alias; new sources should use namespace.

Note (migration): [source].prefix is deprecated in favor of [source].namespace. Both keys parse and behave identically. mind dump and mind init-source now emit namespace. Update your mind.toml at any time; no re-install is needed.

  • install (deprecated): a shell command run once on meld, after checkout, to build or install the tooling the source’s items rely on. It is disclosed and prompted before it runs (--dangerously-skip-install-hook-check runs it unattended). Deprecated in favor of a [[hooks]] install entry below; still parsed, but new sources should use [[hooks]].
  • follow-branch / pin-tag / pin-ref: how sync tracks upstream. Declare at most one; two is an error. A consumer meld --pin overrides it (see Pinning a source version).
  • roots: convention discovery scans under each listed directory instead of the repo root, for a monorepo or subtree layout. Ignored when the file is authoritative ([[items]]/[discover] paths are always repo-root-relative).

[[hooks]] - lifecycle hooks

Zero or more named hooks the maintainer declares. Each runs on the host (gated by a disclosure prompt) at the bound lifecycle event.

[[hooks]]
run = "make build"          # the shell command (required)
name = "build tooling"      # label shown in the disclosure (else the command)
optional = false            # required (run/skip/abort) vs optional (run/skip)
event = "install"           # "install" (on meld, default) or "uninstall" (on unmeld)

[[hooks]]
run = "make clean"
event = "uninstall"
optional = true

[[hooks]] is the canonical form. The legacy [source].install is a deprecated shorthand for one required install hook (still parsed); use [[hooks]] instead.

[[items]] - explicit inventory (authoritative)

List items explicitly when convention does not fit (a non-standard layout, export control, custom link targets). Declaring any [[items]] turns off convention scanning for the source.

[[items]]
kind = "rule"                    # skill | agent | rule | tool (required)
name = "style"                   # the bare name (required)
path = "guidelines/style.md"     # path relative to the repo root; a dir for skills/tools (required)
link = "rules/house-style.md"    # optional: link target relative to the agent home
description = "House style"       # optional: overrides frontmatter

[[items]]
kind = "tool"
name = "detect"
path = "tools/detect"
bin = "detect.sh"                # tool only: what {{tools:detect}} resolves to
build = "make"                   # tool only: per-item build, run in staging at install
install = "./setup.sh"           # any kind: host side effect run after install
uninstall = "./teardown.sh"      # any kind: host cleanup run before removal
  • bin and build are valid only on a tool; on any other kind they are a schema error.
  • install / uninstall are per-item lifecycle hooks (valid on any kind), distinct from build (which produces the item’s content) and from the source-level [[hooks]].

[discover] - glob-based discovery

For odd or monorepo layouts where the items exist but not at the convention paths. Declaring any kind globs makes the file authoritative.

[discover]
skills = { include = ["packages/*/skill"], exclude = ["packages/internal/*"] }
agents = { include = ["agents/**/*.md"] }
rules  = { include = ["rules/*.md"] }
tools  = { include = ["packages/*/tool"] }   # globs match the tool DIRECTORY

Within a kind, include globs are matched first, then anything also matched by an exclude glob is dropped. Tool globs match the tool directory (its TOOL.md, if present, supplies metadata), not an anchor file.

[discover].sources - curated super-source

A repo can list other repos to meld, acting as a curated registry. Melding it recursively melds each listed source (skipping any already registered; cycles terminate). Each nested source is registered independently and tracks its own upstream commit. mind dump generates exactly this shape of mind.toml from your installed state, so you can author a super-source automatically rather than by hand (see dump).

[discover]
sources = [
  { source = "owner/repo" },                        # melded, items left available
  { source = "github:foo/bar", namespace = "fb" },   # imposed namespace (like meld --namespace)
  { source = "owner/recommended", install = true }, # offered for install on meld
]

The equivalent table-array form is also valid:

[[discover.sources]]
source = "owner/recommended"
install = true
  • source: any repo spec meld accepts (owner/repo, a host-qualified spec, or a local path).
  • namespace (previously as): impose a namespace on that nested source. The old key as still parses as a deprecated alias.
  • install (default false): when true, melding the super-source offers that nested source’s items for install (the same preview-and-prompt as the top-level source), instead of leaving them registered but not installed.
  • install-items: a list of bare kind:name refs selecting only a subset of the nested source’s items to offer for install, e.g. install-items = ["skill:review", "agent:dev"]. install = true and a non-empty install-items are mutually exclusive (declaring both is an error). The empty-list form (install-items = []) is never used in practice; that case is install = false.

By default a melded super-source registers the whole chain but installs only its own items plus the install = true (or install-items) entries. meld --recursive (-r) offers every nested source for install.

Adopting un-onboarded sources (DSC-59/60/61)

A [[discover.sources]] entry may supply configuration for a nested source that has no mind.toml of its own:

  • follow-branch / pin-tag / pin-ref: curator-supplied pin directive for the nested source. Declare at most one; two is an error. sync uses whichever is set, the same as if the source had declared it in its own [source] table (DSC-41). A consumer’s explicit meld --pin still overrides this.
  • roots: convention scan roots for the nested source, for a monorepo or subtree layout (DSC-50).
  • add-roots: extra convention scan roots that compose with the nested source’s own discovery, the curator-side form of meld --add-root (DSC-84).
  • [[discover.sources.hooks]]: one or more hooks to run for the nested source. Each entry has the same shape as a source’s own [[hooks]] entry: a required run field, and optional name, optional, and event fields. They run under the same disclosure and safety prompt as the source’s own hooks (including the non-TTY skip and --dangerously-skip-install-hook-check).

roots, add-roots, flat-skills, and the hooks apply ONLY when the nested source ships no mind.toml. If the nested source has one, that file is authoritative for its own discovery and hooks and the curator-supplied values are ignored (a warning is emitted). The gate is whole-file: a nested mind.toml, even one declaring none of them, suppresses all four. add-roots is gated even though the consumer-side meld --add-root is not, so a curator cannot reach past a nested source’s authoritative export control on the consumer’s behalf (DSC-88).

The pin directive is NOT gated: it is authoritative whether or not the nested source ships a mind.toml, overriding that source’s own [source] pin (DSC-65). namespace and install are likewise unaffected; they always apply.

# Adopt a source that has no mind.toml: supply config it lacks.
# follow-branch, roots, and [[discover.sources.hooks]] apply only because
# this source ships no mind.toml of its own (DSC-60).
[[discover.sources]]
source = "owner/unonboarded"
follow-branch = "main"           # track this branch (DSC-41)
roots = ["packages/agents"]      # scan under this subdir, not the repo root (DSC-50)

[[discover.sources.hooks]]       # build hook, same shape as [[hooks]] (HOOK-50)
run = "make build"
name = "build tooling"
event = "install"

Scenarios

A regular source

A repo that ships its own skills, agents, rules, and tools. The simplest form is no mind.toml at all (pure convention). Add a mind.toml to attach metadata or a namespace:

[source]
description = "James's agent library"
namespace = "jk"

Convention scanning still finds skills/<name>/SKILL.md, agents/<name>.md, etc., and each installs as jk:<name>. Use [[items]] or [discover] instead only when the layout is non-standard (those turn convention off).

A super-source

A curated registry that ships no items of its own, only a list of other repos:

[source]
description = "Curated agent registry"

[discover]
sources = [
  { source = "acme/agents" },
  { source = "acme/skills", namespace = "acme", install = true },
]

Melding it registers acme/agents and acme/skills (the latter namespaced acme: and offered for install). It has no items of its own, so without install = true (or meld --recursive) nothing installs; mind probe browses what the chain offers.

Regular plus super-source

A repo that both ships its own items and curates others. Because a bare [discover].sources is not authoritative, convention scanning still runs for this repo’s own items:

[source]
description = "James's library and registry"
namespace = "jk"

# This repo's own items are still found by convention (skills/, agents/, ...)
# because only [discover].sources is declared, not [[items]] or kind globs.

[discover]
sources = [
  { source = "acme/skills", namespace = "acme", install = true },
]

Melding installs this repo’s own jk:<name> items (per the default install prompt) and registers acme/skills (offered for install via its install = true flag). If you instead need to declare this repo’s items explicitly while also curating, add [[items]] or [discover] kind globs alongside sources; that turns convention off, so list every item the repo offers.

Authoring a source

A source is a git repo. Publishing one is mostly “lay the items out and push”; mind melds arbitrary repos with no config, so there is no manifest to write and nothing to register.

Publish in two minutes

A repo with a skills/ directory is already a source. Nothing else is required.

myrepo/
  skills/greet/SKILL.md      # a skill (the directory is the item)
  agents/reviewer.md         # optional: an agent
  rules/style.md             # optional: a rule

Push it, and anyone can meld it:

mind meld owner/myrepo       # discovers the items by convention
mind probe                   # they show up, ready to learn

See examples/starter for a minimal repo of this shape and Source layout for the full convention.

Flat skill layout

If your repo keeps skill directories at the root with no skills/ container (<repo>/greet/SKILL.md), set [source].flat-skills so convention discovery finds them:

[source]
flat-skills = true

A consumer can also force it at meld time with mind meld owner/repo --flat-skills (useful for adopting a flat-layout repo that has not set the flag itself). It applies to skills only; agents, rules, and tools keep their agents/ / rules/ / tools/ directories. See DSC-74..77.

A Claude plugin or marketplace

A repo published for Claude Code’s plugin system is meldable as-is: a .claude-plugin/plugin.json or .claude-plugin/marketplace.json is read as a discovery source, no mind.toml needed. See Authoring a plugin or marketplace.

Preparing a source

Two commands help a maintainer prepare a repo for melding: init-source scaffolds and reports, review validates.

init-source

Run mind init-source in the repo. It discovers the items the repo offers (exactly as melding would), reports the references among them, scaffolds a mind.toml if none exists, and surfaces advisories:

mind init-source                 # report + scaffold
mind init-source --template      # also rewrite bare sibling refs to {{ns:}}

It is read-only except for creating an absent mind.toml and, with --template, editing item files. It registers nothing and touches no agent home.

review

mind review <target> validates a source for publishing without changing anything (--fix is the one exception). <target> is a melded source name, a local path, or a repo spec; with no target it reviews the current directory. A target naming an existing directory is read as that local path unless it first matches a melded source’s identity, even when it also looks like a valid owner/repo spec, so the same relative path init-source scaffolded works unchanged with review (CLI-214, CLI-215).

Findings are hard (non-zero exit) or advisory:

  • hard: a malformed mind.toml, an unknown item kind, a min-mind-version the running mind does not meet, or an unresolved {{ns:}} / {{self}} / {{tools:}} / {{path:}} token.
  • advisory: a missing description, a hardcoded install path (hardcoded-path, classified by what it resolves to), a sibling tool named in prose without a token, a misplaced {{ns:}} token, a helper script duplicated across items (duplicate-tooling), a deprecated [source].install field (deprecated-field, pointing to the [[hooks]] form), and each declared install/uninstall hook (so a consumer sees the source will run code).
mind review                      # review the current dir
mind review owner/repo           # review a melded source or repo spec
mind review . --fix              # rewrite confidently-mapped findings in place

--fix rewrites a local working tree only: recognized hardcoded paths become tokens, misplaced {{ns:}} tokens are un-wrapped, and bare sibling names are templatized. Structural advisories like duplicate-tooling are left for you to resolve by hand.

Resources and tooling

Where an item’s resources and shared tooling belong (bundled with {{self}}, put in a known location by an install hook, or shared through the store as a tool item) is covered in Source layout. mind review’s duplicate-tooling, bare-tool-reference, and hardcoded-path findings are advisory: each of those layouts is valid.

Namespacing

Most sources give their items unique, descriptive names and are never prefixed, so this does not come up. A prefix exists only for the collision case: two sources that both ship a review would land at the same path, so a prefix namespaces one (<prefix>:<name>). The effective prefix is, in order: the consumer’s meld --namespace <prefix>, the repo’s [source].prefix, else none.

A prefix renames items, so if a prefixed source’s items reference each other by name, those references must be tokens: {{ns:name}} in prose, and the path tokens ({{self}} / {{tools:name}} / {{path:ref}}) for code and paths. mind expands each at install. review and init-source warn (advisory) when a source that is being prefixed references a sibling in bare prose. An unprefixed source, or one with no intra-source references, needs none of this. Agents are the exception: they link under their bare name even under a prefix, so a reference to a sibling agent is not renamed and does not warn.

See the spec for the normative rules and examples/namespacing for a worked source.

Namespacing

Two melded sources can each ship an item of the same name (both a review). Without a prefix they would land at the same install path. A prefix namespaces a source so every item from it installs as <prefix>:<name>: the effective name, store path, symlink, and the name mind uses in the ref all carry the prefix. Agents are the one exception, because the harness keys them by frontmatter name rather than by their link path; see Agents are not namespaced. Most sources give their items unique, descriptive names and never need a prefix; this page covers the case where a prefix is needed.

Setting a prefix (NS-1, NS-2)

Two ways, in precedence order:

  1. Consumer-side: meld --namespace <prefix> (short -n). Stored as the source alias and takes priority over anything the repo declares. meld --namespace '' removes a prefix. (--as <prefix> is a deprecated alias for --namespace.)
  2. Repo-side: [source].prefix in mind.toml.
[source]
prefix = "jk"

With prefix jk, every item in the source installs as jk:<bare-name>. The catalog and the item’s stable identity keep the bare name; the prefix is applied at install time (NS-3). To change a prefix after items are installed, forget the installed items first (mind forget) and then re-meld with the new prefix.

Agents are not namespaced

A prefix reaches skills, rules, and tools but not an agent’s harness identity (NS-40, NS-41, NS-42). A skill is keyed by its directory name, so prefixing its directory and link changes the name the harness resolves. An agent is keyed by the name field in its frontmatter, not its filename, so renaming the link to <prefix>:<name> would not change the resolved name. mind therefore links an agent under its bare frontmatter name in each lobe even when the source has a prefix in effect (NS-40). The prefix still applies to the agent’s store path and manifest key, so its stable identity stays collision-free and a prefix change is still a rename; only the harness-visible link is bare.

Because agents link under their bare name, two melded sources that each ship an agent with the same frontmatter name resolve to the same lobe link. mind detects this instead of silently repointing: learning an agent whose bare name already maps to an installed agent from a different source fails with an AgentCollision error telling you to mind forget the existing one first, and meld surfaces it as an advisory warning (NS-41). A prefix does not avert the collision, since it does not reach the agent link.

Because a prefix cannot disambiguate agents for you, give a custom agent profile a distinctive frontmatter name so it does not clash with agents from other sources. If a generic name (dev, review, lead) is unavoidable, bake the namespace into the name itself (acme-dev, acme-review), since that is the identity the harness resolves and the only part you control.

A sibling agent’s name is the same bare name with or without a prefix, so a bare prose reference to it resolves either way and the unguarded-reference warning does not fire for it (NS-42). A {{ns:}} token naming a sibling agent still expands (to the bare name) and is not an error, so tokenizing an agent reference is harmless.

Why {{ns:name}} tokens exist (NS-10, NS-11)

The Claude harness resolves a skill at runtime by the name in the text, and a prefix changes that name. A plain-prose reference like “run the review skill” breaks once review installs as jk:review. Authors write intra-source references as {{ns:name}} instead, where name is the sibling’s bare name.

At install time, mind expands each token to the referent’s effective name:

source installed as{{ns:review}} expands to
unprefixedreview
--namespace jkjk:review

Expansion runs whether or not a prefix is in effect (NS-14), so a token-using source installs correctly in both cases. A token whose referent is an agent expands to the bare name in both cases, since agents are not namespaced (Agents are not namespaced).

Minimal example

A source with an agent lead that references a skill review:

<!-- agents/lead.md -->
When implementing, run the {{ns:review}} skill.

Installed unprefixed:

When implementing, run the review skill.

Installed with meld --namespace jk:

When implementing, run the jk:review skill.

A worked multi-item source is at examples/namespacing.

Validation at install time (NS-12, NS-13)

A token whose name is not a sibling in the same source is a BadReference error at install time, naming the referencing item and the bad referent. Expansion runs in a staging copy during the transactional install, so a bad reference fails before the live install is touched.

Content with no {{ns: tokens is copied unchanged. Non-UTF-8 files are not scanned (NS-13).

Whitespace inside a token ({{ns: name }}) is trimmed before the sibling lookup. An unterminated token ({{ns: with no closing }}) is left verbatim rather than treated as a reference or an error (NS-15).

Scope: prose only (NS-24)

{{ns:name}} is a prose name reference. It is misplaced in a fenced code block, an inline code span, adjacent to a path separator (/ or ~), or in a frontmatter structured field like name:, where name-substitution would yield broken code, a broken path, or (under a prefix) a wrong identity. For code and paths, use the path tokens ({{self}}, {{tools:name}}, {{path:ref}} described in Tooling) instead of {{ns:}}.

mind review flags misplaced {{ns:}} tokens (CLI-139); init-source --template does not create them (INIT-5).

Unguarded-reference warning (NS-20 to NS-23)

A source whose items reference siblings in bare prose (no token) breaks at runtime under a prefix. mind does not guess and rewrite prose, because sibling names are often common words. Instead, when melding a source with a prefix in effect, meld scans each item’s text files for sibling names that appear outside any {{ns:}} token and warns for each item where it finds one (NS-20).

Matching is whole-word (alphanumeric, _, and - are word characters); an item’s own name is not reported against itself (NS-21). A reference whose referent is a sibling agent is not reported either, since agents keep their bare name under a prefix and so the reference does not break (NS-42). The warning is advisory and does not fail meld; it does not rewrite anything (NS-22). No warning is emitted when no prefix is in effect (NS-23).

Authoring tools

mind init-source --template rewrites bare sibling references to {{ns:}} tokens. mind review . --fix does the same for a working tree. Both are covered in Authoring a source, which also describes how init-source and review surface advisories when a prefixed source’s items reference each other in bare prose.

Claude plugin marketplaces

Claude Code ships its own plugin system: a repo can carry a .claude-plugin/plugin.json (a single plugin) or a .claude-plugin/marketplace.json (a catalog of plugins). mind reads these manifests as a discovery input, so a repo published for Claude’s plugin system melds like any other source, with no re-packaging by its author.

mind meld owner/claude-plugin-repo   # a repo with .claude-plugin/plugin.json
mind probe                           # its skills and agents show up as items
mind learn <plugin-name>:<skill>     # install one, same as any source

A marketplace is a source, not a sink

mind treats a plugin manifest strictly as a way to discover items. Discovered items go to ~/.mind/store and are symlinked into each lobe exactly like a convention- or mind.toml-discovered item. mind never writes into Claude’s plugin cache (~/.claude/plugins/cache/...), its settings.json enabledPlugins, or known_marketplaces.json. Consuming a marketplace does not turn mind into a plugin publisher: it does not emit .claude-plugin/ manifests or register anything with Claude.

This keeps everything mind gives a source: namespacing, {{ns:}} reference expansion, the broader rule/tool taxonomy, and the source-hash drift model that sync, upgrade, and introspect use.

A single plugin (plugin.json)

A plugin is a directory with .claude-plugin/plugin.json whose component directories sit at the plugin root. Claude’s layout for skills and agents is byte-for-byte mind’s convention layout, so the mapping is direct:

Plugin componentmind item
skills/<name>/SKILL.mda skill
agents/<name>.mdan agent
commands/, hooks/, .mcp.json, LSP, monitors, themes, output-stylesnot installed (no mind equivalent)

A plugin has no rules or tools component, so nothing maps to those kinds.

The projection is lossy and says so: when a plugin declares components mind cannot represent, meld prints a count of what it skipped, for example 2 hooks, 1 mcp server not installed (no mind equivalent). You are never left believing the plugin installed in full when part of it was dropped.

Naming: the plugin name is the default prefix

A plugin’s name becomes the default namespace prefix for its items, mirroring Claude’s mandatory plugin:skill naming. So a plugin named acme-tools shipping a greet skill installs as acme-tools:greet. Unlike the native system the prefix stays optional and consumer-overridable: meld --namespace <p> overrides it and meld --namespace '' clears it.

The prefix does not reach agents. An agent links under its bare frontmatter name regardless of the prefix (the harness keys agents by that name, not by the plugin scope), so two sources shipping a same-named agent collide and mind surfaces the collision rather than silently resolving it. See namespacing.

A plugin’s version and description are read as metadata (the description overrides frontmatter; the version is recorded for display). Drift and upgrade still compare source content, not the declared version, so bumping version alone is not what triggers an upgrade.

A marketplace catalog (marketplace.json)

A marketplace.json lists several plugins, each either in-repo (a path inside the catalog repo) or an external git source. This is the native analog of a mind curated super-source, so it reuses that machinery: each listed plugin becomes a sub-source.

  • An in-repo plugin is a scan root inside the catalog repo, read per the single-plugin rules above. Like a normal source’s own items, in-repo plugins are offered for install on meld.
  • An external plugin is a nested git source, melded and registered like any [discover].sources entry, tracking its own upstream commit. Like other nested sources it is registered and left available; meld --recursive extends install to the whole chain.

Each entry’s name namespaces that plugin’s items, and an entry may carry a version read as metadata. Where a marketplace entry and an in-repo plugin.json supply the same field, the entry wins (mirroring Claude’s non-strict mode, where the marketplace overrides the plugin manifest).

The post-meld probe hint and the sync re-walk apply to a marketplace exactly as they do to any super-source.

Installing items the manifest does not list

A marketplace.json often lists a curated subset of what the repo actually contains. Two ways to reach the rest:

Compose extra scan roots: meld --add-root <dir> (repeatable). The manifest keeps defining its items and each added root is convention-scanned in addition, so unlisted skills under those directories become installable:

mind meld owner/marketplace-repo --add-root community --add-root experimental

Each added root is scanned in both skill layouts at once (the skills/ container and flat bare-name directories), plus agents/, rules/, and tools/. An item both the manifest and an added root contribute is offered once, under the manifest’s namespace. Unlike --root (which replaces the scan layout and suppresses the manifest), --add-root never suppresses anything; it also composes with an authoritative mind.toml. The roots persist on the source and apply to later scans and sync.

Link one skill directly. Paste the skill’s tree/blob URL; the manifest does not gate it because you named the exact path:

mind learn https://github.com/owner/marketplace-repo/tree/main/community/foo

See item links.

Precedence: when a plugin manifest is used

The plugin-manifest layer slots alongside the other discovery layers. A source’s own mind.toml still wins:

  • A mind.toml that declares [[items]] or [discover] kind globs is authoritative and suppresses the plugin-manifest layer, the same way it suppresses convention scanning. meld prints a note that a .claude-plugin/ manifest was found and ignored.
  • A [source]-only mind.toml (metadata, no item globs) composes: its metadata is read and the plugin manifest supplies the items.
  • With no authoritative mind.toml, a plugin manifest (if present) is authoritative for the items it declares, and convention scanning is skipped for the paths it covers.

Safety

Manifests are attacker-controlled content shipped by a melded repo and are held to the same guards as mind.toml:

  • They are parsed strictly; a malformed manifest is rejected rather than partially trusted.
  • Every path a manifest contributes (a plugin root, an in-repo plugin path, a component path, a link target) is validated by the same safe-relative-path rule as [[items]] path/link: an absolute path, a leading ~, a .. component, or a NUL byte is rejected. A melded marketplace cannot read files outside its clone or place a symlink outside a lobe.
  • An external plugin source is pinned and validated through the same path as a [discover].sources spec.
  • Names and descriptions taken from a manifest have ANSI escapes and control characters stripped before display, so catalog-controlled text cannot inject terminal sequences.

Provenance

recall --sources and the probe source view label a source whose items came from a plugin manifest with its origin (claude-plugin or claude-marketplace), so you can tell a native-plugin source from a convention or mind.toml source at a glance.

Authoring a plugin or marketplace

You don’t need a mind.toml to make a Claude plugin repo meldable: the manifest layer is enough on its own, and everything below is Claude’s own format, not something mind adds.

A single plugin. Put .claude-plugin/plugin.json at the repo root (or at a scan root) with name, and optionally version and description:

{
  "name": "acme-tools",
  "version": "1.0.0",
  "description": "Acme developer tools plugin"
}

Lay out skills/<name>/SKILL.md and agents/<name>.md next to it, same as any mind source (Source layout). commands/, hooks/, .mcp.json, and the other Claude-only component kinds are fine to keep in the repo for Claude users; mind just skips them (with a printed count) rather than erroring, so one repo layout serves both consumers. There is no plugin-level place for a rule or a tool - if you want mind users to get those, add a mind.toml with [[items]] for them; it composes with the plugin manifest as long as it stays metadata-only or covers different items (Precedence).

Pick name deliberately: it becomes every consumer’s default namespace prefix (acme-tools:greet), so treat it like a package name - short, unique enough to avoid colliding with other plugins someone might meld, and stable across releases (renaming it renames every item for existing consumers).

A marketplace catalog. Put .claude-plugin/marketplace.json at the repo root listing each plugin by name and source (a repo-relative path for an in-repo plugin, or a git URL for an external one), plus optional version / description:

{
  "name": "Acme Marketplace",
  "plugins": [
    { "name": "alpha", "source": "./plugins/alpha", "description": "Alpha plugin" },
    { "name": "beta", "source": "./plugins/beta", "description": "Beta plugin" }
  ]
}

Each in-repo entry’s source must resolve inside the repo (no .., no absolute path, no ~) or mind rejects it (Safety). An entry’s version/description override the same fields in that plugin’s own plugin.json if it has one, so a curator can relabel a plugin without editing its manifest.

Validate either shape the same way you’d validate any mind source: run mind meld <path-or-repo> against a local clone and mind probe to confirm the items and namespacing came out as expected.

Try it

Two runnable fixtures live in the repo:

The normative behavior is spec/marketplace.md (MKT-1..11).

Tools and path tokens

Some agent libraries ship helper scripts or compiled binaries that their skills and agents call at runtime. mind handles this with two mechanisms:

  • The tool kind: a store-only installable for helpers that are shared across items or do not belong inside a single skill directory.
  • Path-reference tokens: {{self}}, {{tools:name}}, and {{path:ref}}, expanded at install to stable store paths.

See the worked example for a complete source you can browse.

The tool kind (TOOL-1..7)

A tool is a fourth item kind alongside skill, agent, and rule. Its purpose is to be referenced by other items; unlike a skill, by default it is not linked into an agent home and the Claude harness does not discover it directly (TOOL-3). A tool can opt in to a link with an explicit link field (TOOL-4, below).

Convention discovery (TOOL-1): every immediate subdirectory of a tools/ directory under a scan root is a tool. The whole directory is the item; no anchor file is required. A bare tools/detect/ is a tool named detect.

Store path: ~/.mind/store/tool/<effective-name>/. The manifest records the store path with an empty links set. forget removes the store copy; recall reports it as installed (TOOL-3).

Optional link: a tool can declare an explicit link field in mind.toml [[items]] to also surface it under an agent home, but the default is store-only (TOOL-4).

Namespacing (TOOL-6): a prefix gives a tool the effective name <prefix>:<name>, the same as a skill or rule (agents are the exception and keep their bare link name). Stable identity is (source, kind, bare_name), so a prefix change is a rename matched by upgrade/introspect.

When to use a tool

A helper that is bundled with a single skill (a resources/ script) does not need to be a tool. Put it in the skill directory and address it with {{self}} (see source layout). Use the tool kind when:

  • The helper is shared across two or more items.
  • The helper stands alone and is not logically part of any one skill.
  • The helper must be compiled and the build belongs to the item rather than to a source-level install hook.

mind review treats a byte-identical helper copied into several items as a duplicate-tooling advisory (informational), suggesting it as a tool candidate. Keeping per-item copies is equally valid.

TOOL.md frontmatter (TOOL-2, TOOL-5)

Place a TOOL.md in the tool directory to declare metadata:

---
description: Detect the project type from files in the current directory.
bin: detect.sh
build: make
---
keymeaning
descriptionshown in recall / probe
binthe entrypoint; what {{tools:name}} resolves to (TOOL-5)
buildper-item build command run in staging at install (HOOK-70..73)

The entrypoint resolution order is (TOOL-5):

  1. mind.toml [[items]].bin
  2. TOOL.md frontmatter bin:
  3. Convention: a file named after the tool at the tool dir root (e.g. tools/detect/detect) when it is present in the source

A tool that nothing invokes as an executable need not resolve a bin.

A mind.toml [[items]] entry overrides TOOL.md fields. With neither, a tool has no description and no explicit bin or build. For the [[items]] form and [discover].tools globs (which match the tool directory, not an anchor file) see The mind.toml file.

The bin and build fields are valid only on a tool; on any other kind they are a mind.toml schema error (TOOL-7).

Build hooks for compiled tooling

The common case is a source-level install hook ([[hooks]] in mind.toml) that builds all tooling once at meld, producing artifacts in the working tree; the tool directory is then copied as its final state at learn (TOOL-1). Use a per-item build when you want an isolated, rollback-safe build that runs in staging before the store swap. See Install hooks for the full hook model (HOOK-70..73).


Path-reference tokens (TOOL-10..16)

Tokens expand at install to stable store paths. Expansion runs in the same staging pass that expands {{ns:}} (see spec/namespacing.md), so a bad reference fails before the live install is touched. The recorded content hash is of the token (source) form, so drift detection compares source with source (TOOL-13).

Tokens expand only in markdown files (an extension of md, markdown, mdown, or mkd), whatever the item kind (TOOL-14, TOOL-19). A token in a non-markdown bundled file – a script, data – is left exactly as written; the designed use of a path token is prose, e.g. a skill telling Claude to run {{tools:detect}}. Inner whitespace is trimmed ({{ path:x }} works); an unterminated token (no closing }}) is left verbatim; non-UTF-8 files are not scanned. To expand tokens in a specific script, list it in expand: (below).

References resolve within the same source only: ship a tool in the same source as the items that use it (TOOL-15).

Expanding tokens in a script: expand: (NS-57, TOOL-20)

A bundled script that must call a sibling tool or reach an adjacent file can opt that file into expansion, so its tokens resolve at install instead of being left literal. Add an expand: key to the item’s frontmatter, listing item-relative paths (whitespace-separated, the same form as requires:):

---
name: review
description: Review code changes.
expand: resources/pr.py
---

Now resources/pr.py is expanded exactly like markdown. A token in it renders as an absolute store path (/home/you/.mind/store/tool/detect/detect.sh), not the ~ form markdown uses, because the file is read by a program that does not expand a leading ~ (a Python pathlib.Path, say). So a script locates its tooling without a language-specific self-locate:

# resources/pr.py -- {{tools:detect}} is rewritten to an absolute path at install
det = Path("{{tools:detect}}")

The key lives on the item, so declaring it keeps convention discovery on and says nothing about other items (it does not make a source authoritative). An entry naming a file the item does not ship, an absolute path, or a .. segment fails the install (BadReference) and is reported by review as a hard bad-expand finding, so a typo surfaces before a meld. An unresolved token in a listed file is a hard failure, exactly as in markdown.

Cross-source tooling is still out of scope: expand: bakes an install-time path that works because the tool ships in the same source. A script needing a tool from a different source has no token mechanism; ship the tool alongside its callers.

{{self}} (TOOL-10)

Expands to the item’s own store directory. Available in every kind.

{{self}}/resources/helper.sh

A skill addresses its bundled resources with {{self}} without hardcoding its installed name. Without {{self}}, a prefix rename (e.g. voice to jk:voice) would silently point at the wrong path.

{{tools:name}} (TOOL-12)

Expands to a sibling tool’s entrypoint: the tool’s store directory joined with its resolved bin (TOOL-5). The plural tools: is distinct from the tool: kind-qualifier used in {{path:}}.

{{tools:detect}}

A name that is not a sibling tool, or a tool with no resolvable bin, is a BadReference error.

{{path:ref}} (TOOL-11)

Expands to a sibling item’s store directory, for reaching non-entrypoint files. ref is a bare sibling name, optionally kind-qualified:

{{path:tool:detect}}/lib.sh
{{path:skill:review}}/resources/pr.py

An unqualified ref that matches items of more than one kind is a BadReference error. A ref matching no sibling is also a BadReference error.

{{tools:name}} is shorthand for {{path:tool:name}}/<bin>: use {{tools:name}} when you want the entrypoint, {{path:tool:name}} when you want the directory.

Tilde rendering (TOOL-16)

A token renders the store root with a leading ~ when the store is under the user’s home directory (the default ~/.mind/store). This keeps the expansion matchable by a Claude settings.json permission glob:

"Bash(~/.mind/store/**)"

An absolute path would not match that glob. When MIND_HOME points outside home, or when the home directory cannot be determined, the token expands to the absolute path.


Worked example

Source tree (see examples/tooling):

tools/detect/
  TOOL.md          # bin: detect.sh
  detect.sh        # the entrypoint
  lib.sh           # a library sourced by callers

skills/scan/
  SKILL.md
  resources/notes.md

tools/detect/TOOL.md:

---
description: Detect the project type from files in the current directory.
bin: detect.sh
---

skills/scan/SKILL.md (excerpt):

1. Run the shared tool: `{{tools:detect}} .`
2. Source its library: `. {{path:tool:detect}}/lib.sh`
3. Record the result: `{{self}}/resources/notes.md`

After mind learn scan:

  • {{tools:detect}} expands to ~/.mind/store/tool/detect/detect.sh – the entrypoint from bin: (TOOL-5, TOOL-12).
  • {{path:tool:detect}} expands to ~/.mind/store/tool/detect – the store directory, for non-entrypoint files (TOOL-11).
  • {{self}} expands to ~/.mind/store/skill/scan – the skill’s own store directory (TOOL-10).

All three use tilde syntax so a Bash(~/.mind/store/**) permission rule covers them (TOOL-16).

Under a prefix (meld --namespace jk), {{tools:detect}} expands to ~/.mind/store/tool/jk:detect/detect.sh – no edits to the skill needed.

Within-source dependency resolution

Items in a source can reference siblings via {{ns:name}} tokens (see Namespacing). Those tokens are dependency edges. When you install a partial selection, mind follows those edges to the full transitive closure so you get a working set, not a dangling one.

What counts as a dependency

A dependency is an intra-source reference. There are two ways to declare one, and the closure that learn installs is their union.

{{ns:name}} tokens – a token appearing in one of the item’s markdown files (the whole skill directory’s .md/.markdown/.mdown/.mkd files, or the agent/rule file) names a sibling as a dependency. A token in a non-markdown bundled file (a shell script, a data file) forms no dependency edge. This is the inline form: the reference lives in the prose and is also rewritten to the effective name on install. See Namespacing for token expansion rules.

requires: frontmatter key – a top-level scalar in the item’s frontmatter (SKILL.md for a skill, the .md for an agent or rule), listing whitespace-separated intra-source refs:

---
description: My skill
requires: skill:plan agent:test
---

This is the pure-metadata form: it adds a dependency edge without any prose reference. Use it when an item needs a sibling at runtime but does not mention it in its text.

Each entry is either a qualified ref (kind:name, e.g. skill:plan) or a bare name. A bare name matches across kinds; if more than one sibling shares that bare name across different kinds, the ref is ambiguous and must be qualified. A source-qualified ref (owner/repo#name) is rejected – requires is intra-source only. An entry that resolves to no sibling (typo, unknown item, ambiguous bare name, or source-qualified ref) is a hard error at install.

Dependencies never cross sources; both {{ns:}} tokens and requires: entries are always resolved within the one source.

Partial learn pulls in the closure

When learn selects every item in a source (e.g. learn owner/repo#*), resolution is a no-op: every referent installs anyway (DEP-10). The work is for a partial selection.

Example: a source ships skill:review, agent:dev, and agent:test. review references dev via {{ns:dev}}, and dev references test via {{ns:test}}.

mind learn skill:review

mind resolves the closure – review -> dev -> test – and installs all three, dependencies first (DEP-11, DEP-21, DEP-30).

What you see before install

When the closure adds items beyond your explicit selection, mind prints the dependency tree and prompts before changing anything (DEP-31):

skill:review  [selected]
  agent:dev   [dependency]
    agent:test  [dependency]

Install these 3 items? [y/N]

Nodes already in the manifest are marked [installed] and are not re-installed (DEP-23). Cycles are shown as marked back-edges rather than expanded again (DEP-22).

--yes (or answering y) confirms without prompting. Under --json there is no prompt at all: --yes is mandatory whenever the closure adds items beyond the explicit selection, and learn --json without it refuses with confirmation-required instead of installing unprompted. See Commands - learn for the full flag reference.

--dry-run preview

--dry-run renders the same dependency tree and lists the full closure that would be installed, then exits without installing anything (DEP-32):

mind learn --dry-run skill:review

Interactive TUI (probe)

Choosing to install an available item in mind probe shows the same dependency tree in the confirm step before applying, with the same selected / dependency / already-installed distinction. Confirming installs the whole closure in dependency-first order; declining installs nothing (DEP-40, DEP-41).

forget does not cascade

forget removes exactly the one named item. It does not automatically remove its dependencies, because a dependency may be shared by another installed item. Uninstall is always a per-item operation (DEP-50).

init-source

mind init-source [path] helps a source author prepare a repo for melding. It scaffolds a mind.toml, reports the intra-source reference graph it detects, and optionally rewrites bare sibling references into {{ns:}} tokens. It is the setup counterpart to review (which validates without changing anything).

What it does

Run mind init-source in the repo (or pass a path):

mind init-source                 # report + scaffold
mind init-source --template      # also rewrite bare sibling refs to {{ns:}}
mind init-source path/to/repo

On each run it:

  1. Discovers items exactly as melding would: convention scanning (skills/<n>/SKILL.md, agents/<n>.md, rules/<n>.md, tools/<n>/), then an authoritative mind.toml if one is present. What it reports is what melding would install.
  2. Scaffolds a mind.toml when none exists (INIT-3). The scaffold contains a [source] table with a description placeholder and a commented-out prefix. An existing mind.toml is never overwritten.
  3. Reports the intra-source dependency graph (INIT-4): for each item, the siblings it references via existing {{ns:name}} tokens, and the siblings it mentions in bare prose. Bare-prose mentions are emitted as unguarded-reference advisories in the same advisory [kind]: message format that review uses, and only when a prefix is in effect (see below).
  4. Reports duplicate-tooling advisories for any non-markdown helper file that is byte-identical across two or more items (INIT-7), in the same finding format as review. These are advisory only; --template does not fix them. Extracting a shared tool into tools/<name>/ is a structural change made by hand.

--template: rewriting bare references

With --template, init-source rewrites each bare whole-word sibling mention in item text to its {{ns:name}} token (INIT-5), writes the changed files, and reports each rewrite.

What is rewritten:

  • Bare whole-word occurrences of a sibling item’s name in prose.

What is left alone:

  • Text already inside a {{ns:}} token.
  • Names inside a fenced code block, an inline code span, a path, or a frontmatter structured field. A keyword or path component is never wrapped.

The rewrite is heuristic: a sibling’s name can be an ordinary English word, so the result should be reviewed (e.g. with git diff) before committing.

See Namespacing for how {{ns:name}} tokens work and when they are needed.

Bare-reference advisories

init-source emits unguarded-reference advisories only when an effective prefix is in force for the repo ([source].prefix in mind.toml) (INIT-9). Without a prefix, bare sibling references resolve as written at runtime, so flagging them would be noise. This matches meld and review, which also suppress this advisory on unprefixed sources.

{{ns:}}-token edges in the dependency graph and the --template rewrite are unaffected by this gate; only the advisory is gated.

Safety contract

init-source makes no network calls and does not read or write the store or any agent home (INIT-6). Without --template it is read-only except for creating an absent mind.toml. With --template it edits item files in the target repo only.

Broader workflow

init-source sets up the repo; review validates it before publishing. The full authoring workflow is in Authoring a source.