workflow-composer
"MANDATORY: Read this skill before planning or calling search, fetch, or agent when the current task needs its own research, browsing, or judgment for each of 5 or more independent, comparable items (
Files1
View on GitHubSKILL.mdskills/workflow-composer/SKILL.md
GitHubWorkflow Composer
workflow/run in this document means the run tool on the workflow MCP server: call it with the mcp tool using server: "workflow", name: "run".
workflow/run executes a deterministic JavaScript orchestration script that coordinates multiple subagents. The whole workflow runs as one background job; its return value is injected into the parent conversation when it finishes.
When to use
Required condition — every item must clear this bar:
- Each item requires non-trivial independent reasoning, research, browsing, or open-ended tool exploration. Item count alone is never sufficient. Before reaching for
workflow/run, ask: "could this be a single script looping over the items?" — if yes, write that script and execute it directly with bounded concurrency instead.
With that condition met, use workflow/run when any of the following applies:
- The task contains 5 or more independent, comparable items processable concurrently. With 10 or more, fan out instead of working through them yourself.
- The user asks to research, compare, summarize, or analyze many entities with the same output structure.
- The outputs need to be collected into a table, report, ranking, dataset, or other combined deliverable.
- The orchestration needs loops, conditions, retries, staged execution, aggregation, or a large/dynamic fan-out.
Do not use it when there are only a few small independent tasks (direct agent calls are fine), when the whole task is a shortlist or recommendation, one question with several facets, or work a handful of searches can finish, when the items are answerable immediately from context, when the task is inherently sequential or tightly coupled, or when the per-item work is the same deterministic command or script with different parameters — that is a batch job for one script with bounded concurrency, not a workflow.
MCP capability and concurrency contract
Before a workflow whose item agents depend on an MCP server, call server.list and obey that server description's Subagents and Concurrency contract.
- Use
sandbox: 'isolated'when stateful tasks must proceed independently in parallel. - Give parallel file-producing agents deterministic, distinct output paths.
- Keep operations marked parent-only in the parent agent; subagents cannot make them available by retrying through another interface.
- If a required tool is unavailable or fails, collect and report the blocker. Never fabricate completion, reuse another item's artifact, or substitute a different artifact type unless the task explicitly allows that fallback.
Decomposition contract
For item-based fan-out:
- Exactly one dedicated
agent()call per independent item — never bundle several items into one subagent. - The parent agent must not perform item-level research before or alongside the workflow; it orchestrates.
- Every subagent gets one clearly bounded subject and a fully self-contained prompt.
- Comparable subagents share the same structured output fields (group
{schema}). - Item agents run inside one named
parallelgroup, preserving input order so outputs map back to inputs. - Handle individual failures without discarding successful results.
Bad: agent('Research Nintendo, PlayStation, Xbox, Atari, Sega, …', {brief: 'Research all consoles'})
Bad: agent('run enrich.sh item-42') × 230 — deterministic command fan-out belongs in a single script with bounded concurrency, not in per-item subagents.
Correct:
const consoles = ['Nintendo NES', 'PlayStation', 'Xbox', 'Atari 2600', 'Sega Dreamcast']
const results = await parallel('Research consoles', () =>
consoles.map(name =>
agent(`Research ${name} and return the requested structured information.`, {brief: `Research ${name}`})
)
)
Map/reduce contract
When the final deliverable requires cross-item synthesis (comparative report, ranking, merged document, executive summary):
- Map: one agent per item inside a
parallelgroup. - Collect: keep successful structured results, record failures.
- Reduce: exactly one reducer agent over the collected results.
- Return: reducer output plus useful map results and explicit report paths. Have the parent confirm the files exist and deliver them from the shared sandbox, without re-researching the items.
Count the reducer in both estimated_agent_calls and max_agent_calls. If deterministic code can combine the results without judgment (joining strings, returning rows), skip the reducer agent.
Discovering the item set
- Items given by the user → use that list directly.
- Item set scopable from stable domain knowledge → define a reasonable list and state the selection criteria.
- Discovery itself requires research → run one discovery agent with a JSON Schema, read the array from its structured result, then fan out one agent per discovered item. Use a conservative high-side
estimated_agent_callsand count the discovery agent (and any reducer) in the budgets. Fan out only for work discovery did not already do; if its result already answers the task, stop there.
Execution model
- The script runs in a sandboxed JavaScript environment with no I/O: no filesystem, network, timers, or
require/import. Subagents use their own tools and filesystem. - The script must be deterministic:
Math.random()and the entireDateobject (includingnew Date(...)with arguments) are unavailable. Workflows survive restarts by replaying the script against a journal of completedagentcalls; nondeterministic control flow breaks replay. Vary prompts by stable inputs or array indexes. - Plain JavaScript control flow (variables, loops, conditionals, array methods, async/await, string/JSON operations) is fully available; the script body may use
awaitdirectly. - The script's completion value (
returnor last expression) becomes the workflow result. - Workflow completion is a barrier over every
agent()call: even if a Promise is not explicitly awaited, the job waits for that subagent to finish. Still useawaitorparallel()whenever its result participates in the returned value; an unawaited result is discarded. scriptmust contain only a syntactically valid JavaScript function body — no Markdown fences or surrounding prose.
Primitives
agent(prompt, {brief, schema, input_files, sandbox, effort_level})returns a Promise of one subagent result.briefis required (≤120 chars): a self-explanatory title of what THIS subtask does, written in the user's language and specific to the item — shown verbatim as the subtask's row title in the user-facing task board. Say what is being done to what (e.g.Scrape ACME pricing page); never a generic label likeagent 3/subtask/Product 3. Prompts must be self-contained (exact item, goal, required fields, source/verification requirements, constraints, expected file paths and output format): the subagent does not see the parent conversation.parallel(brief, factory, {schema}?)concurrently runs the agent promises returned byfactoryas a named group.briefis required (≤80 chars): a descriptive group name summarizing the whole batch, shown as the group header.factorymust be a function returning an array ofagent()promises — a bare promise array is rejected. Optional{schema}is the group default JSON Schema: agents created inside without their ownschemainherit it; a per-agentschemaoverrides it; nested groups use the nearest enclosing schema.log(message)publishes concise progress, visible viajobinfo.
Structured results
Give every comparable-item group a shared JSON Schema: outputs stay structurally consistent, aggregation is reliable, and the UI renders the group as a table (columns from the schema's properties, one row per subagent result).
A successful value is already JSON-decoded when the subagent returned valid JSON — never call JSON.parse() on a schema-constrained result. Results are rejected unless they are valid JSON conforming to the effective schema (own schema, else nearest group {schema}).
Files
- Return structured data for aggregation. The current Sandbox Host does not populate durable artifact refs in a child result's
files; do not rely ontype: "file"orprevious.filesto deliver or transfer output files. For file-producing work, use shared mode with explicit, unique parent-sandbox paths and have the parent task verify and deliver those files through its normal file tools. - Declare every child input file explicitly in
input_files:{path: '/home/ubuntu/input.csv', name: 'data'}for an existing parent-sandbox file. ArtifactRef inputs are not supported by the current Sandbox Host. A path merely mentioned in prompt text is never transferred. sandbox: 'shared'(default) runs subagents in the parent sandbox — give each item a deterministic, collision-free output path.sandbox: 'isolated'gives a fresh disposable sandbox for untrusted/heavy work; declared inputs are transferred and the child receives its actual local path.
Budget parameters
effort_level: default effort for every subagent in the workflow; defaults tolite. Eachagent()may override it withlite,standard, ormax. Keep independent research, extraction, and transformation onlite; usestandardfor reducers or complex judgment, and reservemaxfor exceptional cases.effort_levelis only supported by the workflow input and eachagent()options object;parallel()group options support onlyschema.max_agent_calls: requestedagent()budget (1–2000), not a credit limit. At creation the addon addsmax(2, floor(requested * 0.1)), capped at 2000. The returned effective limit is persisted as the hard upper bound; restarting does not add another allowance. Report that effective limit when discussing the budget. Put the full intended fan-out in one workflow rather than splitting it into trial batches.estimated_agent_calls: conservative high-side estimate of calls actually executed (≤max_agent_calls). Count everything: discovery + item/map + reducer + validation agents. Exact count for deterministic lists; realistic high-side estimate for dynamic branches — never intentionally underestimate.Agent registrations are paced by the worker, separately from Host execution concurrency.
parallelexpresses independence, not an immediate burst; do not split the workflow to bypass submission pacing.
Failure policy
Default and recommended for item fan-out: collect. Every agent() resolves to {ok: true, value, files} or {ok: false, error} in input order. error is {code, message} — read the fields; do not interpolate the whole object as a string. Preserve successful results even when some items fail:
const successful = results.filter(r => r.ok).map(r => r.value)
const failures = results
.map((r, i) => (r.ok ? null : {item: items[i], code: r.error.code, message: r.error.message}))
.filter(Boolean)
Use fail_fast only when later work would be invalid after any individual failure.
Confirmation gate
The Host checks confirmation before subagents start: absent an existing auto-start authorization, an estimate or effective budget above 20 requires confirmation (for example, requested 20 becomes effective 22). A pending workflow is already registered — never call workflow/run again for the same task. Stop the turn and wait; on explicit acceptance use job to approve the exact job id, on explicit rejection use job to reject it. Never approve without a new user reply. Approval does not remove the saved effective budget.
If the effective budget is exhausted on a compatible Host, the workflow pauses instead of marking the remaining items failed. Do not submit another workflow/run. If the effective limit is already 2000, no increase is possible: keep the job paused, inspect retained results and in-flight subtasks with job list/job info, or use job kill to stop. Otherwise, use job continue to propose a higher absolute max_agent_calls (never above 2000), present that exact total, and repeat it only after a new explicit approving user reply. Never interpret refusal or unrelated input as approval. Completed results are reused. An older Host may instead report that resumable pause is unsupported; do not claim it paused successfully.
Recommended item-research template
const items = [
{id: 'item-a', name: 'Item A'},
{id: 'item-b', name: 'Item B'},
{id: 'item-c', name: 'Item C'}
]
const itemSchema = JSON.stringify({
type: 'object',
properties: {
id: {type: 'string'},
name: {type: 'string'},
summary: {type: 'string'},
key_facts: {type: 'array', items: {type: 'string'}},
report_path: {type: 'string'}
},
required: ['id', 'name', 'summary', 'key_facts', 'report_path']
})
const mapped = await parallel(
'Research all items',
() => items.map((item, index) =>
agent(
'Research exactly one item: ' + item.name + '.\n' +
'Verify important facts using authoritative sources.\n' +
'Return the requested structured fields.\n' +
'Write the detailed report to /home/ubuntu/reports/' +
String(index + 1).padStart(2, '0') + '-' + item.id + '.md. ' +
'Return that exact path in report_path.',
{brief: `Research ${item.name}`, sandbox: 'shared'}
)
),
{schema: itemSchema}
)
const successful = mapped.filter(r => r.ok).map(r => r.value)
const failures = mapped
.map((r, i) => (r.ok ? null : {item: items[i].name, code: r.error.code, message: r.error.message}))
.filter(Boolean)
const reduced = await agent(
'Create the final cross-item report using these structured results:\n' +
JSON.stringify(successful) + '\n' +
'Include a comparison table, key differences, shared patterns, and a clear conclusion. ' +
'Save it to /home/ubuntu/reports/final-report.md and return that path in report_path.',
{
brief: 'Synthesize final report',
sandbox: 'shared',
schema: JSON.stringify({
type: 'object',
properties: {report_path: {type: 'string'}, summary: {type: 'string'}},
required: ['report_path', 'summary']
})
}
)
return {items: successful, failures, final: reduced.ok ? reduced.value : null}
Budgets for this shape: estimated_agent_calls = items.length + 1, max_agent_calls = items.length + 1, failure_policy = collect.
Final checklist
Before calling workflow/run, verify:
- The task genuinely contains multiple independent units of work — each needing independent research or judgment, not the same deterministic command with different parameters — with one agent per item and no bundling.
- Item prompts are self-contained; comparable agents share a JSON Schema; the group has a clear user-facing brief.
- File paths are deterministic and collision-free; shared-mode outputs are verified by the parent task; inputs are declared as parent-sandbox paths in
input_files. - Discovery and reducer agents are counted in the budgets;
collectfailures are handled explicitly. - The script is valid deterministic JavaScript only.
- The parent agent is orchestrating, not doing item-level work itself.