Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 97 additions & 9 deletions src/node/builtinSkills/workflow-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ Prefer a workflow when the task is a repeatable orchestration pattern, especiall
- A reusable slash-invokable process, like deep research or deep review.
- Durable calls into user-defined host actions under `.mux/actions` or `~/.mux/actions`.

Do **not** create a workflow for a small one-off edit or a single simple investigation. The conductor cannot run arbitrary host operations directly; use `action.*` only for explicit, metadata-declared workflow actions, and delegate open-ended shell/filesystem/web investigation to sub-agents.
Do **not** create a workflow for a small one-off edit or a single simple investigation. The conductor cannot run arbitrary host operations directly; use `action.*` / `parallelActions` only for explicit, metadata-declared workflow actions, and delegate open-ended shell/filesystem/web investigation to sub-agents.

## Before authoring

1. Run `workflow_list` to see existing workflows.
2. If an existing workflow is close, run `workflow_read({ name })` and adapt the pattern.
3. Run `workflow_action_list` before writing `action.*` calls; use each action's metadata `inputSchema`, `outputSchema`, `effect`, and `permissions` to choose valid arguments.
3. Run `workflow_action_list` before writing `action.*` or `parallelActions` calls; use each action's metadata `inputSchema`, `outputSchema`, `effect`, and `permissions` to choose valid arguments.
4. For one-off drafts, write a scratch workflow:

```text
Expand All @@ -39,7 +39,17 @@ Scratch workflows must include a description header and a default exported funct

```js
// description: Short workflow description
export default function workflow({ args, phase, log, agent, action, parallelAgents, applyPatch }) {
export default function workflow({
args,
phase,
log,
agent,
action,
parallelAgents,
parallelActions,
parallelWorkflows,
applyPatch,
}) {
phase("scope", { input: args.input });
return { reportMarkdown: "Done" };
}
Expand Down Expand Up @@ -70,7 +80,17 @@ Runs are durable, so stopping one is non-destructive:
A workflow default export receives one object:

```js
export default function workflow({ args, phase, log, agent, action, parallelAgents, applyPatch }) {}
export default function workflow({
args,
phase,
log,
agent,
action,
parallelAgents,
parallelActions,
parallelWorkflows,
applyPatch,
}) {}
```

### `args`
Expand Down Expand Up @@ -229,6 +249,74 @@ Replay semantics:
- Child failure fails the parent step; parent interruption cascades to active child workflow runs.
- V1 is same-workspace and wait-to-terminal only; fire-and-forget child workflows are intentionally rejected.

### `parallelActions(specs, options?)`

Runs multiple workflow actions concurrently and returns results in input order. Use this for dynamic fan-out over durable host actions such as `workspace.ensure`, `workspace.sendMessage`, `workspace.awaitIdle`, Git snapshots, or `workflows.start` children when you need same-workspace orchestration instead of workflow-owned child agent workspaces.

Each item is an action invocation object:

- `name`: action name, e.g. `"workspace.ensure"`, `"git.status"`, or `"workflows.start"`.
- `id`: stable replay ID, unique within the `parallelActions` call.
- Any normal action fields: `input`, `timeoutMs`, `builtInOnly`, `cwd` / `worktreePath`, `cache`.

```js
const ensured = parallelActions(
items.map((item) => ({
name: "workspace.ensure",
id: "ensure-" + item.key,
input: {
projectPath: item.projectPath,
key: item.key,
title: item.title,
trunkBranch: item.trunkBranch,
},
})),
{ maxParallel: 8 }
);

const messages = parallelActions(
ensured.map((result, index) => ({
name: "workspace.sendMessage",
id: "send-" + items[index].key,
input: {
workspaceId: result.output.workspaceId,
message: buildPrompt(items[index]),
},
}))
);
```

Pass `options.maxParallel` (positive integer) to cap concurrent action executions with a sliding window. `parallelActions([])` returns `[]`, which is useful for dynamic schedules with no current work.

Replay and failure semantics:

- Each item uses the same durable replay/cache/reconcile behavior as a sequential `action.*` call.
- Duplicate item IDs in one `parallelActions` call are rejected before side effects start.
- On failure, queued items stop launching; already-started actions are allowed to settle so mutating/external actions are not abandoned mid-side-effect. The original failure is then thrown.
- Running mutating actions against the same checkout concurrently is only safe when those actions are idempotent/concurrency-safe; otherwise cap `maxParallel` or split the work.

### `parallelWorkflows(specs, options?)`

Convenience wrapper for running same-workspace child workflows concurrently. It maps each item to the built-in `workflows.start` action with `builtInOnly: true`, so it is equivalent to `parallelActions` over `workflows.start` but harder to accidentally shadow with a project action.

```js
const children = parallelWorkflows(
topics.map((topic) => ({
id: "research-" + topic.slug,
name: "deep-research",
args: { input: topic.question },
})),
{ maxParallel: 4 }
);

return {
reportMarkdown: children.map((child) => child.reportMarkdown).join("\n\n---\n\n"),
structuredOutput: { childRunIds: children.map((child) => child.runId) },
};
```

Child workflow replay/background/interruption semantics are the same as `action.workflows.start`.

### `applyPatch(spec)`

Applies a workflow-owned child task's git patch artifact to the current parent workspace. The host always dry-runs first in a temporary worktree and only performs the real apply when the dry-run succeeds. The conductor never receives raw patch text and cannot apply arbitrary patches.
Expand Down Expand Up @@ -347,13 +435,13 @@ function issueListSchema() {

## Replay rules and gotchas

- Every `agent` / `parallelAgents` item and every `applyPatch` call must have a stable `id`.
- The replay key includes the step ID and normalized spec, so changing prompts, schemas, patch source IDs, or apply options creates new work.
- `action.*` calls replay completed results by stable `id`, action source path/hash, input, timeout, and cwd.
- Every `agent` / `parallelAgents` item, every `parallelActions` / `parallelWorkflows` item, and every `applyPatch` call must have a stable `id`.
- The replay key includes the step ID and normalized spec, so changing prompts, schemas, action inputs, child workflow args, patch source IDs, or apply options creates new work.
- `action.*` and `parallelActions` items replay completed results by stable `id`, action source path/hash, input, timeout, and cwd.
- `applyPatch` is a durable mutation effect: completed apply/conflict/failed results are replayed from the journal and are not re-applied on resume.
- The workflow conductor cannot call general tools, import modules, access Node, run shell, read files, use timers, or rely on `Date`/`Math.random`; only declared `action.*` calls can cross that boundary.
- The workflow conductor cannot call general tools, import modules, access Node, run shell, read files, use timers, or rely on `Date`/`Math.random`; only declared `action.*` / `parallelActions` calls can cross that boundary.
- Put open-ended shell/filesystem/web investigation inside delegated sub-agent prompts, or package repeatable host operations as actions with metadata and schemas.
- Cap model-produced fan-out before calling `parallelAgents`.
- Cap model-produced fan-out before calling `parallelAgents`, `parallelActions`, or `parallelWorkflows`.
- Return `{ reportMarkdown, structuredOutput }` so the parent agent and UI both get useful output.

## Minimal pattern
Expand Down
106 changes: 97 additions & 9 deletions src/node/services/agentSkills/builtInSkillContent.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6850,13 +6850,13 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"- A reusable slash-invokable process, like deep research or deep review.",
"- Durable calls into user-defined host actions under `.mux/actions` or `~/.mux/actions`.",
"",
"Do **not** create a workflow for a small one-off edit or a single simple investigation. The conductor cannot run arbitrary host operations directly; use `action.*` only for explicit, metadata-declared workflow actions, and delegate open-ended shell/filesystem/web investigation to sub-agents.",
"Do **not** create a workflow for a small one-off edit or a single simple investigation. The conductor cannot run arbitrary host operations directly; use `action.*` / `parallelActions` only for explicit, metadata-declared workflow actions, and delegate open-ended shell/filesystem/web investigation to sub-agents.",
"",
"## Before authoring",
"",
"1. Run `workflow_list` to see existing workflows.",
"2. If an existing workflow is close, run `workflow_read({ name })` and adapt the pattern.",
"3. Run `workflow_action_list` before writing `action.*` calls; use each action's metadata `inputSchema`, `outputSchema`, `effect`, and `permissions` to choose valid arguments.",
"3. Run `workflow_action_list` before writing `action.*` or `parallelActions` calls; use each action's metadata `inputSchema`, `outputSchema`, `effect`, and `permissions` to choose valid arguments.",
"4. For one-off drafts, write a scratch workflow:",
"",
" ```text",
Expand All @@ -6870,7 +6870,17 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"",
"```js",
"// description: Short workflow description",
"export default function workflow({ args, phase, log, agent, action, parallelAgents, applyPatch }) {",
"export default function workflow({",
" args,",
" phase,",
" log,",
" agent,",
" action,",
" parallelAgents,",
" parallelActions,",
" parallelWorkflows,",
" applyPatch,",
"}) {",
' phase("scope", { input: args.input });',
' return { reportMarkdown: "Done" };',
"}",
Expand Down Expand Up @@ -6901,7 +6911,17 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"A workflow default export receives one object:",
"",
"```js",
"export default function workflow({ args, phase, log, agent, action, parallelAgents, applyPatch }) {}",
"export default function workflow({",
" args,",
" phase,",
" log,",
" agent,",
" action,",
" parallelAgents,",
" parallelActions,",
" parallelWorkflows,",
" applyPatch,",
"}) {}",
"```",
"",
"### `args`",
Expand Down Expand Up @@ -7060,6 +7080,74 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"- Child failure fails the parent step; parent interruption cascades to active child workflow runs.",
"- V1 is same-workspace and wait-to-terminal only; fire-and-forget child workflows are intentionally rejected.",
"",
"### `parallelActions(specs, options?)`",
"",
"Runs multiple workflow actions concurrently and returns results in input order. Use this for dynamic fan-out over durable host actions such as `workspace.ensure`, `workspace.sendMessage`, `workspace.awaitIdle`, Git snapshots, or `workflows.start` children when you need same-workspace orchestration instead of workflow-owned child agent workspaces.",
"",
"Each item is an action invocation object:",
"",
'- `name`: action name, e.g. `"workspace.ensure"`, `"git.status"`, or `"workflows.start"`.',
"- `id`: stable replay ID, unique within the `parallelActions` call.",
"- Any normal action fields: `input`, `timeoutMs`, `builtInOnly`, `cwd` / `worktreePath`, `cache`.",
"",
"```js",
"const ensured = parallelActions(",
" items.map((item) => ({",
' name: "workspace.ensure",',
' id: "ensure-" + item.key,',
" input: {",
" projectPath: item.projectPath,",
" key: item.key,",
" title: item.title,",
" trunkBranch: item.trunkBranch,",
" },",
" })),",
" { maxParallel: 8 }",
");",
"",
"const messages = parallelActions(",
" ensured.map((result, index) => ({",
' name: "workspace.sendMessage",',
' id: "send-" + items[index].key,',
" input: {",
" workspaceId: result.output.workspaceId,",
" message: buildPrompt(items[index]),",
" },",
" }))",
");",
"```",
"",
"Pass `options.maxParallel` (positive integer) to cap concurrent action executions with a sliding window. `parallelActions([])` returns `[]`, which is useful for dynamic schedules with no current work.",
"",
"Replay and failure semantics:",
"",
"- Each item uses the same durable replay/cache/reconcile behavior as a sequential `action.*` call.",
"- Duplicate item IDs in one `parallelActions` call are rejected before side effects start.",
"- On failure, queued items stop launching; already-started actions are allowed to settle so mutating/external actions are not abandoned mid-side-effect. The original failure is then thrown.",
"- Running mutating actions against the same checkout concurrently is only safe when those actions are idempotent/concurrency-safe; otherwise cap `maxParallel` or split the work.",
"",
"### `parallelWorkflows(specs, options?)`",
"",
"Convenience wrapper for running same-workspace child workflows concurrently. It maps each item to the built-in `workflows.start` action with `builtInOnly: true`, so it is equivalent to `parallelActions` over `workflows.start` but harder to accidentally shadow with a project action.",
"",
"```js",
"const children = parallelWorkflows(",
" topics.map((topic) => ({",
' id: "research-" + topic.slug,',
' name: "deep-research",',
" args: { input: topic.question },",
" })),",
" { maxParallel: 4 }",
");",
"",
"return {",
' reportMarkdown: children.map((child) => child.reportMarkdown).join("\\n\\n---\\n\\n"),',
" structuredOutput: { childRunIds: children.map((child) => child.runId) },",
"};",
"```",
"",
"Child workflow replay/background/interruption semantics are the same as `action.workflows.start`.",
"",
"### `applyPatch(spec)`",
"",
"Applies a workflow-owned child task's git patch artifact to the current parent workspace. The host always dry-runs first in a temporary worktree and only performs the real apply when the dry-run succeeds. The conductor never receives raw patch text and cannot apply arbitrary patches.",
Expand Down Expand Up @@ -7178,13 +7266,13 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"",
"## Replay rules and gotchas",
"",
"- Every `agent` / `parallelAgents` item and every `applyPatch` call must have a stable `id`.",
"- The replay key includes the step ID and normalized spec, so changing prompts, schemas, patch source IDs, or apply options creates new work.",
"- `action.*` calls replay completed results by stable `id`, action source path/hash, input, timeout, and cwd.",
"- Every `agent` / `parallelAgents` item, every `parallelActions` / `parallelWorkflows` item, and every `applyPatch` call must have a stable `id`.",
"- The replay key includes the step ID and normalized spec, so changing prompts, schemas, action inputs, child workflow args, patch source IDs, or apply options creates new work.",
"- `action.*` and `parallelActions` items replay completed results by stable `id`, action source path/hash, input, timeout, and cwd.",
"- `applyPatch` is a durable mutation effect: completed apply/conflict/failed results are replayed from the journal and are not re-applied on resume.",
"- The workflow conductor cannot call general tools, import modules, access Node, run shell, read files, use timers, or rely on `Date`/`Math.random`; only declared `action.*` calls can cross that boundary.",
"- The workflow conductor cannot call general tools, import modules, access Node, run shell, read files, use timers, or rely on `Date`/`Math.random`; only declared `action.*` / `parallelActions` calls can cross that boundary.",
"- Put open-ended shell/filesystem/web investigation inside delegated sub-agent prompts, or package repeatable host operations as actions with metadata and schemas.",
"- Cap model-produced fan-out before calling `parallelAgents`.",
"- Cap model-produced fan-out before calling `parallelAgents`, `parallelActions`, or `parallelWorkflows`.",
"- Return `{ reportMarkdown, structuredOutput }` so the parent agent and UI both get useful output.",
"",
"## Minimal pattern",
Expand Down
Loading
Loading