Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Workflows

A workflow is a JavaScript file the Claude Code harness loads from ~/.claude/workflows/ and offers to its Workflow tool. It declares export const meta = {...} and a body that orchestrates subagents with agent(), parallel(), pipeline(), phase(), and log().

The harness wraps that body when it runs a workflow, so top-level await, a top-level return, and references to harness-injected globals (phase, agent, parallel, and the rest) are normal there, even though they make the raw file invalid as a standalone ES module: a node --check or bundler run against it rejects it for exactly that reason. mind never runs or validates the body, so this only explains why the shipped example files “don’t validate” as plain JS.

mind treats it as a sixth item kind, discovered and installed like any other:

<repo>/
  workflows/review-changes.js
$ mind learn workflow:review-changes

~/.mind/store/workflow/review-changes     the copy
~/.claude/workflows/review-changes.js     the symlink

The scan is flat and the extension is exactly .js, matching the harness’s own loader: a workflows/nested/deep.js, a .mjs, or a .ts is not an item.

Descriptions come from meta

A .js file has no YAML frontmatter, so the item’s description comes from the meta object instead. probe shows whenToUse beside it, the way the harness’s own workflow list renders the pair; recall reads an installed item’s description from the manifest, where whenToUse is never recorded, and shows the description alone.

export const meta = {
  name: 'review-changes',
  description: 'Review changed files across dimensions',
  whenToUse: 'before opening a PR',
}
$ mind probe --no-tui review
  workflow:review-changes   agents  ab12cd3  Review changed files across dimensions - before opening a PR

A mind.toml [[items]].description still overrides the description, and leaves whenToUse standing.

The reader is minimal, not a JavaScript parser, and it recognizes exactly one opening form: a literal export const meta = { (whitespace or a // / /* */ comment between the tokens is fine), immediately followed by an object literal. A wrapping call (export const meta = Object.freeze({...})), a missing export, a spread expression, or an interpolated/templated value are not recognized: each of these reads as no meta found, the same as a file that has none at all. Where it cannot read a value the item simply lists without a description; it never fails a scan or an install on the shape of a workflow’s code. The harness’s own reader is the authority on what actually loads.

What mind reports but will not enforce

The harness skips a workflow with no readable meta, a missing or empty name or description, or a file over 524288 bytes. review reports each as a workflow-unloadable advisory, and learn and upgrade both warn and install it anyway: an upstream source that introduces the defect between two syncs is the case most likely to surface it at upgrade.

$ mind review ./agents
advisory [workflow-unloadable]: workflow:deploy: the harness will not load this
  workflow: `meta.description` is missing

review also emits a workflow-content advisory for every workflow item, unconditionally: it is a disclosure that mind neither reads nor validates a workflow’s body, not a defect signal, and it fires whether or not the file has any problem at all. It will inflate any CI gate that just counts review advisories.

Nothing here is a gate. mind does not judge item content for any other kind, and a disagreement between the two readers is not a reason to refuse an install. The phrasing above (“the harness will not load this workflow”) describes what mind itself could or could not read, not a guarantee about the harness’s own behavior on that file: mind has no way to run the harness’s loader to check. The size cap is the harness’s, so mind reports an overage rather than enforcing a limit of its own.

The name the harness answers to

The harness keys a workflow by the name in its meta object, not by its file name. An item’s name in mind is its file stem, as for every other file-shaped kind, so the two can diverge.

Write the namespace token in meta.name to keep them in step:

export const meta = {
  name: '{{ns:review-changes}}',
  description: 'Review changed files across dimensions',
}

It expands at install to the effective name (review-changes unprefixed, jk:review-changes under a namespace), which is also the spelling the harness uses for a plugin’s workflow. So the installed file carries a plain string literal that matches the name mind recall reports.

When the two do not match, learn and upgrade both say so and install anyway, review reports it as workflow-name, and mind recall workflow:<name> keeps showing it:

$ mind recall workflow:review-changes
workflow:review-changes
  ...
  harness the harness resolves it as 'deploy', not 'review-changes' -- ...

Two workflows whose meta.name agree are one workflow to the harness. Both install; both are reported, the same four places – review names the finding workflow-name-collision. One shared name is one report, naming the other claimants; past a few it names the first three and counts the rest, so a source with many same-named workflows produces one bounded message rather than one report per file.

A workflow has no frontmatter, so the requires: key (Dependencies between items) is not available to it. Its only dependency channel is a {{ns:}} token in its body: that is the one token family that pulls a referenced sibling into the install closure, the way requires: does for a markdown item. All four token families still expand in a workflow file, not just in meta, since a workflow’s strings are agent prompts:

agent(`follow the checklist in {{path:skill:review}}/SKILL.md`)

A path token renders in the ~ home form there, the reading an agent gets, not the absolute form an expand:-listed script gets. Note the footgun in the example above: {{path:}}, unlike {{ns:}}, forms no dependency edge, so this workflow can install with skill:review absent. Write {{ns:review}} instead of, or alongside, the path token if the workflow should actually depend on that skill.

One consequence: only four openers are live tokens in a workflow’s code – {{ns:, {{self}}, {{tools:, and {{path: – the same families a markdown item expands. Anything else starting with {{ (a template literal’s ${{, or ordinary JS object-literal syntax that happens to contain {{) passes through untouched: it is not scanned for and never read as a token. That is the same rule markdown items have lived under, and it has the same consequence for these four forms: one that resolves to no sibling is a hard install failure (BadReference), not inert text.

review reports a bad reference in a workflow the same as in any markdown item, but review --fix will not rewrite a .js file: the rewrite passes match sibling names as words, and a workflow is code, so an automated rewrite risks corrupting it. Fix a flagged workflow reference by hand.

Plugins

A Claude plugin’s workflows/ directory maps to the kind the same way its commands/ does, on a directly melded .claude-plugin/plugin.json and on each in-repo entry of a marketplace catalog:

$ mind probe --no-tui deploy
  workflow:acme-tools:deploy   acme-tools  ab12cd3  Stage, verify, and cut a release - when a release is ready to go out

The harness names a plugin’s workflow <plugin>:<meta.name>, and a plugin’s items take the plugin name as their default namespace. So the {{ns:}} token expands to exactly that spelling. See examples/marketplace-plugin/workflows/deploy.js.

A manifest’s workflows key points elsewhere on purpose, and mind ignores it as it ignores every other component-path override, so the harness loads from the declared path while mind scans only workflows/, and the two disagree. A plugin that sets the key contributes no workflow items from the declared path, and the skipped-components note says nothing about it. Setting the key is not harmless.

A workflow cannot be installed by item link either: a deep link’s blob form names a single .md file and its tree form names a skill directory, and neither matches a .js workflow file, so mind refuses the link by name. Meld the repo, or learn workflow:<name>, instead.

Lobes

A workflow links into the default Claude lobe and into any lobe whose kinds filter names workflow. A lobe with no filter admits it. The non-Claude harness presets (gemini, codex, universal, windsurf) admit skills only, so they are unaffected. That includes link-project, which resolves to one of those presets (windsurf by default) or, with --subdir, to a skill-only filter: neither links a workflow.

A project lobe still needs nothing special, but it has to be one that admits the kind. The harness reads project workflows from <project>/.claude/workflows/, and the lobe that links one there is the bare form:

$ mind config lobes add ./myproject/.claude

Be aware this registers an unfiltered lobe: with no kinds restriction it also backfills every already-installed skill, agent, rule, and command into ./myproject/.claude/, not just workflows – a bigger effect than “add a project workflow lobe” asks for. No CLI flag narrows this: every preset and every --subdir form is skill-only. To get a workflow-only project lobe, add the entry by hand in ~/.mind/config.toml with a kinds restriction instead:

lobes = ["~/.claude", { path = "./myproject/.claude", kinds = ["workflow"] }]