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 amind.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 recallandmind probeinspect what is installed and what is available;mind syncandmind upgradekeep sources and installed items current.- For authoring,
mind init-sourceandmind reviewscaffold 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-clion 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 --unmanagedalso 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-itemsdrops the source without touching installed items.rm -rf ~/.mind(or theMIND_HOMEoverride) removes the store, source clones, manifest, registry, and config – everything mind tracks on disk.forget/unmeldthe items you want cleaned up first: deleting~/.mindleaves lobe symlinks dangling, andmind introspecton a lobe with no~/.mindreports every link as broken.- Remove the
mindbinary itself:- Install script: delete it from
~/.local/bin/mind, or from$MIND_INSTALL_DIR/mindif you overrode the target directory. - Homebrew:
brew uninstall mind. cargo install:cargo uninstall mind-cli.
- Install script: delete it from
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
| Example | Shows | Key spec |
|---|---|---|
| starter | Convention discovery: a repo with no mind.toml, items found by skills/<n>/SKILL.md, agents/<n>.md, rules/<n>.md | discovery.md |
| hello | The repo-root mind.toml’s [source].roots scanning examples/hello; what mind meld jaemk/mind offers | discovery.md |
| tooling | The tool kind plus path tokens {{self}}, {{tools:name}}, {{path:ref}} | tooling.md |
| hooks | Source [[hooks]]: build/install tooling at meld, tear down at unmeld, with the disclosure prompt | install-hooks.md |
| explicit | Authoritative [[items]] inventory: export control, custom path/link, per-item install/uninstall hooks | discovery.md |
| monorepo | [source].roots: convention discovery rooted at per-package subtrees | discovery.md |
| discover | [discover] kind globs with per-kind include/exclude lists, for layouts roots cannot express | discovery.md |
| namespacing | A prefix plus {{ns:name}} reference tokens that survive a rename | namespacing.md |
| super-source | [discover].sources: a curated registry that melds other repos, optionally namespaced or auto-installed | discovery.md |
| marketplace-plugin | A Claude .claude-plugin/plugin.json: skills and agents mapped to items, unsupported components reported | marketplace.md |
| marketplace-catalog | A Claude .claude-plugin/marketplace.json: a catalog of in-repo plugins, each a namespaced sub-source | marketplace.md |
| marketplace-curator | A repo that is both a Claude marketplace and a mind curator: [discover].sources composes with the plugin manifest instead of suppressing it | marketplace.md |
| policy | An enterprise managed policy: trusted-source allowlist, require-pinned, lobe lock | policy.md |
| drift | Item drift end to end: recall/introspect flag an out-of-date item, upgrade re-links it | lifecycle.md |
| multi-lobe | Linking one item into more than one agent home (lobe) via config lobes | storage.md |
| absorb | Claiming an unmanaged lobe item into a version-controlled source with absorb | absorb.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 probeopens 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 --recursiveoffers every nested source for install. See super-source. - Meld a Claude plugin or marketplace. A repo with a
.claude-plugin/plugin.jsonor.claude-plugin/marketplace.jsonmelds 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
lobesin~/.mind/config.toml. See Configuration and multi-lobe. - Stay up to date.
mind syncrefreshes every source;mind upgradeupgrades installed items and reports deltas first. See Commands and drift. - Claim an unmanaged lobe item into a managed source.
mind absorbmoves 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.tomlneeded. See Source layout and starter. - Declare an explicit inventory, or control what is exported. List items with
[[items]]to turn convention off, set custompath/link, and omit files you do not want offered. See explicit. - Lay out a monorepo or subtree. Point
[source].rootsat the package subtrees, or use[discover]kind globs for layouts roots cannot express. See monorepo and discover. - Share helper tooling across items. Ship a
toolitem 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/uninstallhooks (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-sourcescaffolds amind.tomland reports references;mind reviewvalidates 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- askills/<name>/directory containing aSKILL.mdand any associated resources (scripts, templates, etc).agent- anagents/<name>.mdfile.rule- arules/<name>.mdfile.tool- atools/<name>/directory containing aTOOL.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
| command | does |
|---|---|
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 man | shell 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.
| alias | primary verb |
|---|---|
add | meld |
install | learn |
uninstall | forget |
update | sync |
search | probe |
list | recall |
doctor | introspect |
self-update | evolve |
Note (migration):
--link-onlyonmeldis now--register-only;--unlink-onlyonunmeldis now--keep-items. The old spellings continue to work as hidden deprecated aliases. Theconfig targetandunmeld detachaliases are removed.
Note (migration):
upgradenow syncs the involved sources before computing deltas (equivalent to runningsyncthenupgradeon those sources). Pass--no-syncto skip the fetch step and get the old behavior.sync --upgradeis kept as deprecated sugar butupgradealone 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:
| value | effect |
|---|---|
HEAD | freeze 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 stableand--pin branch=stableare different.--pin stabletakes a one-time snapshot ofstable(frozen;syncnever updates it);--pin branch=stabletracks the branch (floating;syncadvances it). Use thebranch=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:
| pattern | selects |
|---|---|
'*' | 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 tolearn/forget/upgrade(AmbiguousItem). Item names are never suffix-matched, so this last case only fires across kinds (the manifest is keyedkind:name), e.g.upgrade greethitting bothskill:greetandagent: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 embeddedowner/repo#source qualifier insideupgrade’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 hardSourceNotFounderror.upgrade <filter>with no match: reports up to date and exits 0.--source <filter>(recall/probe) with no match: an empty listing.
Item links: install one skill by URL
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 (repoorowner/repo; a multi-source match is normal, not an error), or a glob matched against the fullhost/owner/repoidentity (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 bykind: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 probewith no opt-out launches the TUI. It requires a TTY on stdout.- It falls back to the non-interactive catalog listing when
--no-tuiis given, when--jsonis given, or when stdout is not a terminal (piped or redirected). - The
query,--kind, and--sourcearguments 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
recallreports. - 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.
Navigation keys
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.
Search
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
Enterto return. The verbs that suspend aremeldandunmeld. - While the TUI holds the terminal, every
gitchild runs non-interactively (GIT_TERMINAL_PROMPT=0and an sshBatchMode=yeswrapper), 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
qquits 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:
| Harness | Skills dir | Agents dir | mind 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:
| preset | target path | kinds |
|---|---|---|
gemini | ~/.gemini/config | skill |
codex | ~/.agents | skill |
universal | ~/.agents | skill |
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 .cursorfor 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 futuremind learn, and the copied files are committable to the repo. Managed lobes use symlinks into~/.mind/storeand 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
geminipreset path changed from~/.geminito~/.gemini/configin a previous release. If you added this preset earlier, your~/.mind/config.tomlmay still have the old path. Update it by running:mind config lobes remove ~/.gemini && mind config lobes add --preset geminiOr hand-edit
~/.mind/config.tomland replace~/.geminiwith~/.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.
link-project
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:
--to <path>flag on the command line.MIND_ABSORB_TOenvironment variable.absorb-tokey 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).
--yesproceeds after displaying that statement.- A non-TTY run without
--yesrefuses withConfirmationRequiredand 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:
--to <path>– the explicit destination flag.MIND_ABSORB_TOenvironment variable.absorb_tokey 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
| flag | effect |
|---|---|
--to <path> | Destination source directory (highest precedence). |
-f / --force | Overwrite 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
--yesrefuses withConfirmationRequiredand changes nothing. - Declining the prompt leaves the original lobe entry in place and the manifest unchanged.
What absorb does internally
- Resolves
<ref>to a single unmanaged item (ABS-1). - Confirms the destination source is a git repository. The
--to/MIND_ABSORB_TO/absorb_topath must already be a git repo; a non-repo path is an error.~/.mind/personalis the one pathabsorbwill create andgit initon demand (spec ABS-5). - Lists the move and any stray copies to delete; prompts
[y/N](skipped with--yes). - 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. - Stages and commits the moved item in the destination repo with the message
absorb <kind>:<name>(spec ABS-5). - Runs
learnon the item; the lobe path is now a managed symlink. - Records the item in the manifest keyed
kind:effective-namewith 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):
| OS | Path |
|---|---|
| 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,meldrefuses any repo whose identity does not matchallow(SourceNotAllowed) before any clone or egress (POL-11).learn,sync, andupgradeoperate 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
lockabsent or false,allowis advisory: a non-matchingmeldis 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: bothmind evolveandmind evolve --checkfail before any network call with a policy error. The disable applies to--checkas well so the binary does not nag about available updates (POL-52).self-update = "X.Y.Z":evolveresolves to that exact version offline (noapi.github.comcall), behaving as if--to X.Y.Zwere passed. The pin is validated as a dotted-numeric version at policy-parse time (POL-5 fail closed); a leadingvis stripped before validation. If--to Vis also passed and V differs from the pin, the command fails with a policy error naming the conflict.evolve --checkreports against the pinned version and respects the no-downgrade logic (POL-53).self-update = true: identical to the absent key;evolveis 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 fetchagainst 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 itsSHA256SUMS. These redirect tohttps://release-assets.githubusercontent.com/...; allowlist that host.*.githubusercontent.comis the most resilient pattern: GitHub has migrated release assets between CDN subdomains.objects.githubusercontent.commay still be needed for older GitHub Enterprise Server deployments.
install.sh(install script). The same twogithub.com/release-assets.githubusercontent.comdownload URLs, plushttps://raw.githubusercontent.com/jaemk/mind/main/resources/install.shfor the script itself, andhttps://api.github.com/...unless you pinMIND_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:8080For NTLM/Kerberos proxy auth, set
http.proxyAuthMethod:git config --global http.proxyAuthMethod negotiate -
curl (used by
evolveandinstall.sh) intentionally ignores uppercaseHTTP_PROXY. SetHTTPS_PROXYor the lowercase formhttps_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
evolveandinstall.sh):CURL_CA_BUNDLEorSSL_CERT_FILEin 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/repoform, or setssh = truein~/.mind/config.tomlso theowner/reposhorthand clones over SSH (see Configuration). The runningssh-agentsupplies 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 orfile://remote all meld the same way. Source identity ishost/owner/repo, so a GHEgithub.example.com/...source and agithub.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/*(identitylocal/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 = falseto block all local-path andfile://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.
-
In a private repo, commit a
mind.tomllisting the sources and items the team should have (a super-source with a[discover].sourceslist, or a single source’s own inventory).mind dumpcan generate one from a reference machine. -
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_hostsfirst; 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--yesinstalls without prompting. A non-TTYmeldwithout--yesregisters 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-checkand--dangerously-skip-build-hook-check(both execute arbitrary code from the source; only for sources you trust). -
Verify the result as structured JSON:
mind recall --jsonrecall --jsonnests items under each source; usejqto 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. Runmind introspect; it reports missing links and drift, andmind introspect --fixrecreates missing symlinks. -
learnrefused to overwrite a path. mind will not clobber a file or link it did not create (the clobber guard). Move the existing one aside, thenlearnagain. -
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_HOMEandMIND_DEFAULT_LOBE(CLAUDE_HOMEis a legacy alias for the latter and is still read). -
Before publishing a source, run
mind review <path>to check itsmind.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
qin the main view, or Ctrl-C twice from anywhere (search box, dialogs) to force exit. -
A
mind.tomlchange 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-versionlikewise uses a hyphen; and entry keys under[discover].sourcesare exact (DSC-31). Runmind review <path>to check the file before melding. -
A source’s items are not discovered after setting
rootsinmind.toml.roots = [](an explicit empty list) is distinct from omitting the key: it scans zero roots and finds nothing. Omittingroots(or removing the key) keeps the default behavior of scanning the repo root (DSC-50). Set the actual subdirectory paths, or removerootsentirely. -
learnreportsLinkOccupiedand refuses to overwrite. The clobber guard will not replace a path that mind did not create (LIFE-41). Move the existing file aside, thenlearnagain, or passlearn --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 withLinkOccupied). -
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 contentUse
mind recall --sourcesto find the source, and a forge compare view (or a local clone’sgit 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 byupgraderestores the newer version. -
An item shows as out of date in
recall/probewithout 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-syncand thenupgradethe item, or restore the edited file to its original content. See Configuration for where store and source files live. -
meldorsyncfails with “git executable not found”. mind shells out togitfor all clone and fetch operations; putgiton your PATH first. See the Install page. -
meld/syncfails behind a proxy (HTTP 407 or a connection refused). mind inherits the environment; setHTTPS_PROXY(andNO_PROXYfor internal hosts) or git’s ownhttp.proxy(git config --global http.proxy http://proxy.corp:8080). See Restricted networks and enterprise. -
A clone or
evolvefails with a TLS/certificate error behind a company proxy. Point the tool at the corporate CA: git viahttp.sslCAInfo(git config --global http.sslCAInfo /path/to/corp-ca.pem), curl (used byevolveandinstall.sh) viaCURL_CA_BUNDLEorSSL_CERT_FILE. Note that thewgetfallback path honors no CA environment variable, so if onlywgetis present the corporate CA must be in the system trust store (orwgetrc); prefer havingcurlinstalled. See Restricted networks and enterprise. -
evolvefails with a 403 fromhttps://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. SetGITHUB_TOKEN(orGH_TOKEN) to any GitHub token;evolvesends it as a bearer header on the API request, moving you into the authenticated 5000/hour tier. The token is only sent toapi.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
meldfails to authenticate. mind uses your existing git auth: configure a git credential helper for HTTPS, or meld the SSH form (git@host:owner/repo, orssh = truein~/.mind/config.toml) with a key in yourssh-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
~/.claudeonly. -
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. Runmind recall --sourcesto find the source’s name, thenmind 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 geminibefore 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 --fixprunes 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.mdanchor. Bundled files (aresources/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
toolitem shares it through the store. Put it once undertools/<name>/and reference it by token ({{tools:name}}).mindcarries 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.
| token | expands 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:
- Convention (default, no file): the scanner finds
skills/<name>/SKILL.md,agents/<name>.md,rules/<name>.md, andtools/<name>/. - Frontmatter (always read): each item’s
description(and a tool’sbin/build) come from the frontmatter it already carries. - Claude plugin manifest (optional): a
.claude-plugin/plugin.jsonor.claude-plugin/marketplace.jsonin 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 authoritativemind.tomloverrides it. See Claude plugin marketplaces. 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 contains | convention scan | result |
|---|---|---|
| nothing / no file | on | items found by convention (or by a .claude-plugin/ manifest, if present) |
[source] only | on | convention items, plus metadata (namespace, pin, …) |
[discover].sources only | on | convention items, plus a curated chain |
[[items]] | off | exactly the declared items |
[discover] with kind globs | off | exactly 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(previouslyprefix): every item installs as<namespace>:<name>(identity, store path, symlink, and ref), except an agent’s harness link, which stays bare. A consumer’smeld --namespace <ns>overrides it;meld --namespace ''removes it. See namespacing. The old keyprefixstill parses as a deprecated alias; new sources should usenamespace.
Note (migration):
[source].prefixis deprecated in favor of[source].namespace. Both keys parse and behave identically.mind dumpandmind init-sourcenow emitnamespace. Update yourmind.tomlat any time; no re-install is needed.
install(deprecated): a shell command run once onmeld, 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-checkruns 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: howsynctracks upstream. Declare at most one; two is an error. A consumermeld --pinoverrides 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
binandbuildare valid only on atool; on any other kind they are a schema error.install/uninstallare per-item lifecycle hooks (valid on any kind), distinct frombuild(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 specmeldaccepts (owner/repo, a host-qualified spec, or a local path).namespace(previouslyas): impose a namespace on that nested source. The old keyasstill parses as a deprecated alias.install(defaultfalse): whentrue, 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 barekind:namerefs selecting only a subset of the nested source’s items to offer for install, e.g.install-items = ["skill:review", "agent:dev"].install = trueand a non-emptyinstall-itemsare mutually exclusive (declaring both is an error). The empty-list form (install-items = []) is never used in practice; that case isinstall = 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.syncuses whichever is set, the same as if the source had declared it in its own[source]table (DSC-41). A consumer’s explicitmeld --pinstill 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 ofmeld --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 requiredrunfield, and optionalname,optional, andeventfields. 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, amin-mind-versionthe runningminddoes 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].installfield (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:
- 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.) - Repo-side:
[source].prefixinmind.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 |
|---|---|
| unprefixed | review |
--namespace jk | jk: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 component | mind item |
|---|---|
skills/<name>/SKILL.md | a skill |
agents/<name>.md | an agent |
commands/, hooks/, .mcp.json, LSP, monitors, themes, output-styles | not 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].sourcesentry, tracking its own upstream commit. Like other nested sources it is registered and left available;meld --recursiveextends 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.tomlthat declares[[items]]or[discover]kind globs is authoritative and suppresses the plugin-manifest layer, the same way it suppresses convention scanning.meldprints a note that a.claude-plugin/manifest was found and ignored. - A
[source]-onlymind.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].sourcesspec. - 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:
- examples/marketplace-plugin
- a single plugin: one skill (namespaced by the plugin name), one agent (bare),
and unsupported
commands//hooks/that report a skipped count.
- a single plugin: one skill (namespaced by the plugin name), one agent (bare),
and unsupported
- examples/marketplace-catalog
- a catalog listing two in-repo plugins, each with its own name and items.
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
toolkind: 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
---
| key | meaning |
|---|---|
description | shown in recall / probe |
bin | the entrypoint; what {{tools:name}} resolves to (TOOL-5) |
build | per-item build command run in staging at install (HOOK-70..73) |
The entrypoint resolution order is (TOOL-5):
mind.toml[[items]].binTOOL.mdfrontmatterbin:- 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 frombin:(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:
- Discovers items exactly as melding would: convention scanning
(
skills/<n>/SKILL.md,agents/<n>.md,rules/<n>.md,tools/<n>/), then an authoritativemind.tomlif one is present. What it reports is what melding would install. - Scaffolds a
mind.tomlwhen none exists (INIT-3). The scaffold contains a[source]table with adescriptionplaceholder and a commented-outprefix. An existingmind.tomlis never overwritten. - 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 asunguarded-referenceadvisories in the sameadvisory [kind]: messageformat thatreviewuses, and only when a prefix is in effect (see below). - Reports
duplicate-toolingadvisories for any non-markdown helper file that is byte-identical across two or more items (INIT-7), in the same finding format asreview. These are advisory only;--templatedoes not fix them. Extracting a shared tool intotools/<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.