[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DC5FF9AEXVK3KPDBHY7J2V":3,"skill-same:01M6DC5FF9AEXVK3KPDBHY7J2V":112},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":14,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":57,"install":58,"resources":71},"01M6DC5FF9AEXVK3KPDBHY7J2V","workflow-authoring",null,"Use when writing or debugging a saved Claude Code Workflow (a .js\u002F.mjs script under ~\u002F.claude\u002Fworkflows using the injected agent\u002Fparallel\u002Fphase globals to fan out subagents). Covers the runtime API, banned Date.now() non-determinism, worktree isolation, per-fan-in schemas, null-tolerant barriers, and the typed Codex output-schema landmine. Triggers: author a workflow, fan out subagents.","Prekzursil\u002Fagent-skills-toolchain","Prekzursil","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F54636077?v=4","01M6DACZ1PCQHD99SCR1YXG28B","Andrei Mihai Visalon","01M6DACZ670WZF535V3WYYSS4Z",0,"MIT","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"workflow authoring","workflow","authoring","parallel","isolation","triggers","runtime","schema","use","subagents","writing","js","api","worktree","code","debugging","workflows","codex","2026-11-10 10:12:30",false,"---\nname: workflow-authoring\ndescription: \"Use when writing or debugging a saved Claude Code Workflow (a .js\u002F.mjs script under ~\u002F.claude\u002Fworkflows using the injected agent\u002Fparallel\u002Fphase globals to fan out subagents). Covers the runtime API, banned Date.now() non-determinism, worktree isolation, per-fan-in schemas, null-tolerant barriers, and the typed Codex output-schema landmine. Triggers: author a workflow, fan out subagents.\"\n---\n# workflow-authoring\n\nHow to write a **saved Claude Code Workflow** — an orchestration script under\n`~\u002F.claude\u002Fworkflows\u002F*.js` (or `*.mjs`) that the runtime executes to fan out\nsubagents, collect their typed results, and return a structured answer. This is\nthe durable, re-invokable form of a multi-agent job (invoked as `\u002Fname`), as\nopposed to a one-off `Task()` subagent.\n\nThe rules below are distilled from the workspace's own recorded footguns\n(`feedback_workflow_orchestration`, `feedback_parallel_agent_worktree`, the\n\"Workflow args-as-string gotcha\") and the G.6 gem-hunt finds. Follow them and\nyour workflow will not deadlock, will reproduce, and will not contaminate the\nshared git index.\n\n---\n\n## 1. The runtime API (the whole surface)\n\nA workflow file is NOT a plain ES module. The runner injects **eight** globals —\n`agent`, `parallel`, **`pipeline`**, `phase`, `log`, `args`, `budget`, `workflow`\n(plus `console`, `setTimeout`, `clearTimeout`) — reads your `export const meta`,\nwraps the body so top-level `await` and a trailing top-level `return` are legal,\nand executes it in a Node `vm` context.\n\n> **CORRECTED 2026-08-12.** This section previously said \"injects three globals —\n> `agent`, `parallel`, `phase`\". That was measured wrong against the installed\n> runtime, and the omission of `pipeline` is not cosmetic: **0 of the 10 saved\n> workflows on this box use `pipeline()`** while 15 barrier calls do, even though\n> the runtime's own tool doc says **\"DEFAULT TO pipeline()\"**. The globals list,\n> the `args` contract (rule 2), and the effort enum below were all re-derived from\n> the installed binary; see `docs\u002Fworkflow-sota\u002FGROUNDING-NOTES.md` §3 and the\n> verbatim dump at `docs\u002Fworkflow-sota\u002FRUNTIME-API-DUMP.txt`.\n\nThe sandbox is real, not advisory: `codeGeneration.strings = false` (no `eval`,\nno `new Function`), no module loader (`require` is undefined, `import()` throws),\nno filesystem or Node API, and `Date.now()` \u002F argless `new Date()` \u002F `Math.random()`\nare **shimmed to throw** — not merely discouraged. `runInContext` caps only the\n*synchronous* body at 30 s; it does **not** bound an awaited agent, which is why\nper-agent timeouts are your problem (rule 5b).\n\n```js\nexport const meta = {\n  name: 'my-workflow',                       \u002F\u002F the \u002Fslash name; keep it stable\n  description: 'one line — shown in the picker',\n  phases: [{ title: 'Ground' }, { title: 'Fan-out' }, { title: 'Synthesize' }],\n}\n\nphase('Ground')                              \u002F\u002F declare\u002Fenter a phase (telemetry)\nconst SCHEMA = { type: 'object', additionalProperties: false,\n  required: ['answer'], properties: { answer: { type: 'string' } } }\n\nconst ground = await agent('prompt string', {\n  label: 'ground',        \u002F\u002F stable id for this agent (used in logs\u002Ftelemetry)\n  phase: 'Ground',        \u002F\u002F which declared phase this belongs to\n  schema: SCHEMA,         \u002F\u002F JSON-schema; the runtime RETURNS a parsed object\n  effort: 'high',         \u002F\u002F 'low' | 'medium' | 'high' | 'xhigh' | 'max'\n  model: 'opus',          \u002F\u002F 'opus' (default, deepest) or 'fable' for wide fan-out\n})\n\nphase('Fan-out')\nconst results = (await parallel(\n  items.map((it) => () => withTimeout(   \u002F\u002F \u003C-- paste-in helper, NOT a global; rule 5b\n    () => agent(promptFor(it), { label: `lens:${it.key}`, phase: 'Fan-out', schema: SCHEMA }),\n    600000, `lens:${it.key}`))\n)).filter(Boolean)          \u002F\u002F \u003C-- the null-tolerant barrier; see rule 5\n\nreturn { ...ground, results }   \u002F\u002F top-level return is the workflow's result\n```\n\n- `agent(prompt, opts)` → a Promise of the **parsed schema object** (not raw\n  text). Always pass a `schema` so you get typed fields, never prose to re-parse.\n- `parallel(thunks)` → a Promise of an array, one entry per thunk, in order.\n  **Pass an array of zero-arg thunks** (`() => agent(...)`), not an array of\n  already-started promises — the thunks let the runtime schedule\u002Flimit them.\n- `phase(title)` is telemetry\u002Fstructure; call it once per phase before its work.\n- `pipeline(items, stage1, stage2, ...)` → runs each item through **all** stages\n  independently, with **no barrier between stages**. Item A can be in stage 3\n  while item B is still in stage 1. Wall-clock = the slowest single-item chain,\n  not the sum of the slowest-per-stage. Every stage callback receives\n  `(prevResult, originalItem, index)`. A stage that throws drops that item to\n  `null` and skips its remaining stages. **This is the default for multi-stage\n  work.** Caveat measured elsewhere: `pipeline()` is not a cacheable resume unit —\n  resuming a stopped pipeline re-runs its first stage\n  (`skills\u002Fworkflow-foundry\u002FSKILL.md` L4).\n- `log(msg)` → a progress line in the run view. The only output channel from the\n  script body itself.\n- `budget` → `{ total: number|null, spent(): number, remaining(): number }`. The\n  target is a **HARD ceiling**: once `spent() >= total`, further `agent()` calls\n  **throw**. `total` is `null` when the user set no target, and `remaining()` is\n  then `Infinity` — so always guard a budget loop on `budget.total` (rule 5c).\n- `workflow(nameOrRef, args?)` → run another saved workflow inline. The child\n  shares this run's concurrency cap, agent counter, abort signal and budget.\n  **Nesting is one level only**; `workflow()` inside a child throws.\n- Concurrency is capped at `min(16, cores - 2)` per workflow (**14** on this\n  box), with a **1000-agent lifetime cap** as a runaway backstop.\n- Native resume: `Workflow({ scriptPath, resumeFromRunId })` replays the longest\n  unchanged prefix of `agent()` calls from cache. Same script + same args = 100%\n  cache hit. Before diagnosing an empty result, read\n  `\u003CtranscriptDir>\u002Fjournal.jsonl` — it records each agent's actual return value.\n\n---\n\n## 2. `args` is delivered VERBATIM (corrected — the old \"one string\" rule was false)\n\n> **This rule previously said the opposite** and the linter enforced it. Measured\n> against the installed runtime's own tool doc, `args` is *\"the value passed as\n> Workflow's `args` input, **verbatim** (undefined if not provided)\"*. Pass a JSON\n> array and the script receives an array. The lint check was renamed\n> `args-parse-if-string` → **`args-unguarded`** and re-messaged.\n\nTwo things are still true and are the actual footguns:\n\n1. **`args` is `undefined` when the workflow is invoked with none**, so a bare\n   `args.topic` throws a TypeError. Guard it: `const a = args ?? {}`.\n2. **A caller who passes a JSON-*encoded string* instead of a JSON value** does\n   reach the script with one string, and `args.filter` \u002F `args.map` then throw.\n   That is a caller-side mistake, not the runtime contract; add\n   `typeof args === 'string' ? JSON.parse(args) : args` when a caller may do it.\n\nIndependently of both: design for there being no reliable arg channel at all —\n**have the agents auto-discover their input from live files** (Read\u002FGlob). The\nsaved gate Workflows do exactly this: they tell each reviewer where to find the\nplan\u002Fdesign doc and let it locate + Read it.\n\n## 3. Never call `Date.now()` (or `Math.random()`, `new Date()`) in workflow JS\n\nNon-determinism in the orchestration layer breaks two things: **prompt-cache\nstability** (a changing timestamp in a shared prefix busts the cache for every\ndownstream agent — see rule 8) and **reproducibility** (two runs of the same\nworkflow diverge for no reason). If you genuinely need a timestamp or a random\nid (e.g. to name a shadow-log line), **generate it inside an agent's tool call**\n(`Get-Date -Format o` in PowerShell, `date -Is` in a POSIX shell) — never in the\nworkflow's own JavaScript. The gate Workflows produce their shadow-sink timestamp\nthis way for precisely this reason.\n\n## 4. `isolation: \"worktree\"` for any agent that MUTATES files\n\nParallel subagents share ONE git index and working tree by default. A broad\n`git add -A` in one agent then sweeps a sibling's in-flight files into the wrong\ncommit (`feedback_parallel_agent_worktree`). Any agent that WRITES to the repo\nmust run isolated:\n\n```js\nagent(prompt, { label: 'impl:WU3', schema: S, isolation: 'worktree' })\n```\n\nRead-only agents (reviewers, researchers, the gate reviewers) need no worktree —\ndeclare them read-only in the prompt and skip the overhead. Pair worktree\nisolation with **disjoint file scopes** (rule 7) so two mutating agents can never\ntarget the same file.\n\n## 5. Null-tolerant barriers: `.filter(Boolean)` after every `parallel`\n\n`parallel()` has **no per-agent timeout** and a throwing thunk **resolves to\n`null`** rather than rejecting the whole batch. Two consequences:\n\n- **Always** `.filter(Boolean)` the result so one dead agent does not poison the\n  aggregation with a `null` you then dereference.\n- One HUNG (not thrown — actually stuck) agent will still **stall the whole\n  phase** until it dies, because the barrier waits for all thunks. That is the\n  recorded barrier-deadlock (`feedback_workflow_orchestration`). The structural\n  fix is rule 6.\n\n```js\nconst reviews = (await parallel(reviewers.map((r) => () =>\n  withTimeout(() => agent(r.prompt, { label: `review:${r.role}`, schema: REVIEW_SCHEMA }),\n              600000, `review:${r.role}`)   \u002F\u002F rule 5b: bound each thunk (H6 paste-in)\n))).filter(Boolean)   \u002F\u002F a throwing reviewer becomes null -> dropped, phase completes\n```\n\n## 5b. Bound every fanned-out agent — design item H6 (SHIPPED)\n\nRule 5 says a *thrown* agent becomes `null`. A **hung** one is worse: the barrier\nwaits forever. That is not hypothetical here — a 13-hour deadlock and 11 lanes\nstalled behind one hung reviewer are both on the record, and\n`platform\u002Fworkflows\u002Fshared-prod-shard-fanout-build.json` records an 11-hour hung\nproduction unit. Measured 2026-08-12: **15 of 15 barrier calls across all 10 saved\nworkflows had zero timeout protection** (`Promise.race` 0, `setTimeout` 0,\n`AbortController` 0).\n\nThe fix ships as a paste-in block, because the sandbox has no module loader and a\nhelper therefore cannot be imported:\n\n**`templates\u002Fh6-timeout-heartbeat.md`** — `withTimeout(thunk, ms, label)` and\n`settle(thunks, ms, labels)`. `settle()` is a drop-in for `parallel()` that\nreturns an index-aligned array with **no nulls and no holes**: every slot is\neither a real result or a typed drop-receipt `{ __h6: true, __endReason:\n'timeout' | 'threw' | 'null', label, ms }`.\n\nType the drop, do not filter it away (design item H3): the runtime yields `null`\nfor at least three different causes — the user skipped the agent, the subagent\ndied on a terminal error, and (with H6) our own timeout — and `.filter(Boolean)`\nerases that distinction, which is what makes \"12 of 15 lenses returned\"\nunauditable today.\n\n**Honest scope, do not widen it.** `Promise.race` unblocks the **barrier**; it\ndoes **not** kill the agent. The dropped agent keeps running, keeps one of the 14\nconcurrency slots, and keeps spending `budget` until it dies on its own. The\ndesign doc's word \"killed\" (`B3-migration-verification.md:262`) is an overclaim\nagainst this mechanism. Settling experiment: read `budget.spent()` at the timeout\nreceipt and again at workflow end; a rise proves the agent lived on.\n\nRed-proof (`tools\u002Fwf-timeout\u002Ftest_h6_timeout.py`, 20 tests): the helper is\nextracted from the template and executed in a Node `vm` context configured like\nthe real sandbox. The **known-broken control** — a bare `parallel()` over the same\nfive thunks, one never resolving — is required to report `hung: true` (measured:\n1507.9 ms, watchdog); `settle()` over the identical thunks returns in 175.0 ms\nwith exactly one `timeout` receipt and four intact survivors.\n\n## 5c. Guard every budget loop on `budget.total`\n\n`budget.total` is `null` when the user set no token target, and `remaining()` is\nthen `Infinity`. An unguarded `while (budget.remaining() > 50_000)` therefore runs\nstraight to the **1000-agent lifetime cap**. Write\n`while (budget.total && budget.remaining() > 50_000)`. Linted as\n`budget-loop-unguarded`.\n\n## 6. Per-work-unit pipelines, not one fat barrier\n\nPrefer `pipeline()`-style per-WU flows (impl → validate → review → commit for\neach WU independently) over a single wide barrier that waits for the slowest WU\nbefore ANY progress. A per-WU pipeline means a slow or stuck WU idles only\nitself, not the whole cohort — and it composes with the cross-session resume\nmanifest (rule 10) so a died WU is re-driven while its siblings keep their\ncompleted state. When you must use a wide `parallel`, keep the batch small and\nhomogeneous (similar expected runtimes) so the barrier's tail is short.\n\n## 7. One-level nesting + a schema on EVERY fan-in\n\n- **One level of nesting.** `parallel([... agent ...])` at the top is fine.\n  Nesting `parallel` inside an `agent` inside another `parallel` makes runtimes,\n  failures, and telemetry impossible to reason about — flatten into sequential\n  phases instead.\n- **Schema on every fan-in.** Every `agent()` that feeds an aggregation MUST\n  carry a `schema` (with `additionalProperties: false` and an explicit\n  `required`). You then aggregate typed objects, never re-parse prose. The\n  aggregator agent also takes a schema. This is what makes the gates\n  \"schema-typed\": `{ verdict, blocking, ... }` on each reviewer.\n- **`required` is the FLOOR, not the wishlist — over-broad `required[]` rejects\n  good work.** List in `required` ONLY the fields an agent can ALWAYS produce\n  (typically `digest`\u002F`answer` + `evidence`). Anything conditional — a `verdict`\n  a null-result unit has no basis for, an `artifact` path a read-only lens never\n  writes, an `owner_actions` array that is legitimately empty — must be OPTIONAL.\n  A required field the agent cannot fill makes StructuredOutput reject an\n  otherwise-correct return and burn all its retries. Ledger-proven 2026-07-19\n  (`wf_00a25df9-2cd`): a `DIGEST` requiring `verdict\u002Fcorrections\u002Fevidence\u002Fartifact`\n  failed 2 agents 5× each — their real analysis (one wrote a 16 KB artifact to\n  disk) was thrown away purely on the digest shape. Belt-and-braces: have each\n  agent WRITE its full deliverable to disk (rule 11 output contract) so a rejected\n  digest never loses the work — the synthesis fan-in reads the artifact, not the\n  digest.\n- **Editing a shared schema const busts the RESUME cache — plan the schema before\n  the first launch.** Resume is prefix-cached: a completed `agent()` replays only\n  if its `(prompt, opts)` is byte-identical, and `opts.schema` is part of that.\n  A schema referenced by the FIRST phase's agents is therefore the worst thing to\n  edit on a resume — it changes every agent's `opts`, so the longest-unchanged\n  prefix is empty and ALL agents re-run live (a full \"from beginning\" run you may\n  have been explicitly told to avoid). Corollary: get the schema right up front;\n  if you must fix an over-strict schema mid-run, the cache-preserving move is a\n  ONE-OFF standalone `Task()` for the single failed lens (writing the same\n  artifact path), NOT an edit-and-resume of the shared const.\n\n## 8. Cache-cliff-aware fan-out\n\nFor a wide fan-out (many lenses\u002FWUs), keep the **shared prompt prefix\nbyte-stable** across all agents so the prompt cache is hit N-1 times instead of\npaid N times. Concretely: build one immutable `CONTEXT` \u002F `PROBLEM` string and\nprepend it identically to every agent prompt (only the per-agent tail varies);\ndo not interpolate anything volatile (timestamps, random ids, re-serialised JSON\nwith unstable key order) into that prefix — that is the cache cliff, and it is\nthe other reason rule 3 bans `Date.now()`. Pack a repo once (repomix) and let N\nagents grep the pack rather than each re-reading the tree.\n\n## 9. Typed Codex `--output-schema` landmine (#15451)\n\nWhen a workflow fans out to Codex (via the mounted `codex mcp-server` \u002F\n`codex exec`), a typed `--output-schema` is **silently ignored if any MCP tools\nare active** in that Codex invocation (OpenAI codex issue #15451). You will get\nfree-form text back and your `schema` parse will fail or mis-fill. Mitigations:\nrun the schema-typed Codex lane with MCP tools disabled for that call, or\nvalidate\u002Frepair Codex's output on the Claude side before aggregating. Do not\nassume the schema was honored just because you passed it.\n\n**Windows arg-limit corollary (measured 2026-07-18).** The same active-MCP inventory that voids\n`--output-schema` also blows the Windows 32,767-char `CreateProcess` limit: two Codex runs died\nwith `payload_len=34472` \u002F `os error 206` before executing anything. Pass `--ignore-user-config`\non every non-interactive Codex invocation, and pass the prompt BY FILE (`--prompt-file`, or stdin\nvia `-`), never inline. See `rules\u002Fcommon\u002Fcross-vendor-dispatch.md`.\n\n## 10. SDK notes (context-editing, task-budgets, canUseTool)\n\nWhen a workflow is driven through the Agent SDK rather than the in-app runtime:\n\n- **context-editing (`clear_tool_uses`)** trims tool-call history to fit long\n  fan-outs — enable it for long-running orchestrations so agents do not blow the\n  window on accumulated tool noise.\n- **task budgets** cap per-agent spend; set them on mechanical\u002Fharvest agents so\n  a runaway agent cannot drain the wave.\n- **`canUseTool`** is the SDK's per-agent tool gate — the file-scope \u002F capability\n  enforcement point for fan-out subagents (the parity for in-app subagent\n  scoping). Use it to hold a mutating agent to its declared file scope (rule 4\u002F7).\n\n## 11. Fan-out spawn-prompt template\n\nMeasured over 50 agents \u002F 6 workflows (2026-07-18): element presence in the prompt predicts\ncompliance almost perfectly, and **nothing is inherited** from the global contract. Blocks marked\nREQUIRED are the ones with measured evidence behind them. Full rationale + the fan-in gates:\n`rules\u002Fcommon\u002Ffan-out-contract.md`.\n\n```\n[REQUIRED] VERIFIED GROUND TRUTH — established by direct inspection immediately before this\nworkflow. Treat as high-confidence. Each claim is tagged [probe: \u003Chow it was measured>].\nTo confirm OR refute you MUST use a mechanically DIFFERENT probe — a different shell, a\ndifferent data field, a different measurement. Re-running the tagged probe does NOT count as\nverification and must not be reported as CONFIRMED. Report every reconciliation in a\ndedicated section, whatever the outcome.\n  1. \u003Cclaim>  [probe: Get-Command mon, PowerShell 7]\n  2. \u003Cclaim>  [probe: directory listing at 00:25 — may be stale]\n  ...\n[REQUIRED] ACCESS METHODS — for every path above that is NOT readable by Read at that literal\npath, state how to read it:\n  - tools\u002Fmonitorcore\u002F** exists ONLY on origin\u002Fmain: read via `git show origin\u002Fmain:\u003Cpath>`.\n    The working tree is on branch \u003CX> and does not contain it.\n\n[REQUIRED] ANTI-OVERFIT ANCHOR — a proposal counts only if it (a) rides the EXISTING bus\n(\u003Cname the buses>), and (b) does not reintroduce a rejected component (\u003Cname them>).\nPrefer \"enable a native feature\" and \"fold into the existing adapter\" over \"new daemon\".\n[If a deliverable deliberately waives this, say so explicitly and say why.]\n\nENVIRONMENT: \u003COS, shells, language constraints, RAM ceiling>.\n\n=== YOUR TASK (\u003Clens-slug>) ===\n\u003C2-5 sentences. Name the concrete artefacts to read. State the deliverable as numbered\noutputs. End with \"Be concrete about \u003Cthe specific axis>, not generic \u003Cdomain> advice.\">\n\n[REQUIRED] PER-FINDING RUBRIC — apply to every RECOMMENDATION (skip for pure audit findings):\n  name \u002F what it is \u002F why THIS user specifically \u002F how to build-or-adopt (file, function,\n  command) \u002F source: file:line or URL \u002F value (H|M|L) x effort (H|M|L) \u002F\n  disposition: NATIVE (already exists, just enable) | ADOPT (external, vendor it) |\n    BUILD (write it) | PRUNE (delete something) \u002F\n  confidence: almost-certain 90-99% | likely 55-80% | roughly-even 45-55% | unlikely 20-45%,\n    followed by the evidence that justifies that band.\n\n[REQUIRED] HONESTY CONTRACT — the global contract does NOT carry into this fan-out; these\napply here or not at all:\n  - Attach UNVERIFIED INLINE to the sentence it qualifies. A limitations section at the end\n    does NOT satisfy this. If a recommendation's premise is UNVERIFIED, its heading inherits\n    the tag.\n  - Every UNVERIFIED names the experiment that would settle it.\n  - \"Absent \u002F not found \u002F broken\" is a claim, not an observation. Verify your detector can\n    find a known-present item before reporting absence.\n  - Do not report CONFIRMED on a probe that is the same mechanism as the one being checked.\n  - If a tool is denied, you are in a subagent — no user will answer. Do not stop. Record the\n    gap, use an alternative, report it in your digest.\n\n[REQUIRED] OUTPUT CONTRACT\n  1. Write the full deliverable to \u003Cabs path>. Write it with the **Write** tool. If a single\n     call is too large, chunk with `Add-Content` — never build a multi-KB literal inside a\n     shell command string (Windows 32,767-char limit -> os error 206).\n  2. Then return a \u003C= 400 word structured digest as your final text (NOT the whole doc).\n     LEAD the digest with any ground-truth correction, then the top findings.\n  3. Append an EVIDENCE: block of 3-5 file:line \u002F URL anchors. It is EXEMPT from the word\n     cap — 87% of file:line refs and 100% of URLs otherwise die at the digest boundary.\n  4. Your final line must be: DELIVERED: \u003Cabs path> (\u003Cn> bytes)\n```\n\nFor a cross-vendor (Codex) unit, do NOT paste a here-string output contract and do NOT route\nthrough a `*-rescue` forwarder — see §12 and `rules\u002Fcommon\u002Fcross-vendor-dispatch.md`.\n\n## 12. agentType caveat — a fan-out is only as synchronous as its slowest lie\n\n`agent({ agentType: 'codex:codex-rescue' })` is NOT an interchangeable work-unit executor. Its\ndefinition instructs it to **prefer background execution** for tasks that are \"complicated,\nopen-ended, multi-step\" — i.e. every work unit — and it ships `model: sonnet` with `tools: Bash`\nonly, so it cannot write a report or verify its own artifacts. Measured: 3 such agents, 34 turns,\n**0 production files**; one returned only a job id, and that job then completed on disk with a\nreal finding the harness could not collect. Use `agentType: 'codex'` (the synchronous adapter).\nAny barrier that accepts a result containing `started in the background` or `Check \u002Fcodex:status`\nis accepting a receipt, not an answer — reject it at fan-in.\n\n## 13. Wire the resume manifest — the cross-session half of rule 6\n\n`plan_lint_stage0.py` has shipped a complete, `fsync`'d `ResumeManifest` since 2026-07-12 and, as\nmeasured 2026-07-29, had **zero callers**: the only reference anywhere was the `LINTER` const at\n`plan-gate.mjs:35`, which runs `lint()` and never touches the manifest. Consequence — every saved\nWorkflow that dies mid-wave redoes the whole wave.\n\n**Wire it with an `agent()` step, never a JS shim.** The runner *wraps the body* (rule 1), so a\nstatic top-level `import` is a SyntaxError; measured across all 11 saved workflows there is not one\n`import`, `require(`, `child_process` or `fs.` call — every disk read is delegated to an agent's\nshell. Copy `templates\u002Fresume-wired-fanout.md` (its `js` block is kept lint-clean and is gated by\n`scripts\u002Ftest_workflow_lint.py`). Three touch-points, nothing else:\n\n| when | command | effect |\n|---|---|---|\n| GROUND, once | `--manifest M --ids \u003Call> --next` | stdout = exactly the ids still to run, one per line |\n| BEFORE each unit | `--manifest M --mark-partial \u003Cid>` | write-ahead; an `fsync`'d `partial` record |\n| AFTER each unit | `--manifest M --mark-done \u003Cid>` \u002F `--mark-failed \u003Cid>` | terminal state |\n\n`--ids` and `--next` exist because a Workflow's unit list is a **JS array**: `resume()` derives\n`pending` from a declared work-list, and the CLI's only other source was `--plan`, which reads a\nplan *document*. So an author had to synthesise a throwaway `plan.wu.json` just to name their own\nunits — that friction is the mechanical reason the manifest shipped uncalled.\n\nDurability, measured (not assumed): with `--mark-partial WU3` written, a `taskkill \u002FF` on the unit\nprocess, and `--resume` in a fresh interpreter, WU3 lands in `partial` — process-crash recovery is\n**executable-proven** (13\u002F13 assertions, and 3\u002F3 mutants of the fold\u002Fappend\u002Ftorn-line logic go RED,\nso the probe is not vacuous). **UNVERIFIED, inline: machine-crash \u002F power-loss durability is NOT\nproven** — a process kill cannot distinguish `fsync`'d bytes from page-cache bytes, so the\nexperiment cannot see the `fsync` at all (only an instrumented counter shows the call is reached,\n`FSYNC_CALLS=1` per append). Settling experiment: run the same protocol in a VM and hard-cut power\nbetween the append and the read.\n\nTwo honest limits to design around: `--mark-done` is **asserted by the unit agent**, not verified by\nthe orchestrator, so pair it with an independent postcondition probe (artifact exists AND ends with\nyour completion marker) exactly as the accumulator workflow's AND-gate does — a unit that lies is\nskipped forever, which is worse than being re-driven. And `done.jsonl` is append-only and never\npruned; `records()` re-reads the whole file, which is fine at wave scale and is not a database.\n\n---\n\n## Governed single-shot run\n\nEverything above is how to write the workflow JS. When you instead want to GOVERN a whole run\nend-to-end from a single invocation — ground → design\u002Fplan gates → fan-out → integrate → reconcile\n→ one focused merge-when-green PR → memory sync — this skill vendors a reusable orchestrator\nmeta-prompt: [`templates\u002Fgoverned-run-orchestrator.md`](templates\u002Fgoverned-run-orchestrator.md).\n\nIt is a fill-in **template, not a second engine** — it rides the native Claude Code Workflow tool\nand this skill's bus. Use it in four steps:\n\n1. **Fill** — copy the template and replace ONLY its two authoritative slots. `\u003CGOAL>` is the\n   durable outcome + named scope + resume-state + exclusions; `\u003CDoD>` is the numbered completion\n   AND-gate. Add as many `DOD-NN` rows as the goal needs, each an externally observable state with\n   TWO mechanically independent evidence sources. Remove every `[...]` instructional sentinel; keep\n   the `\u003CGOAL>`\u002F`\u003CDoD>` tag names and the `DOD-NN` id format.\n2. **Freeze** — freeze the filled `\u003CGOAL>`\u002F`\u003CDoD>`, the artifact root, the schemas, and the prompt\n   hashes before the first unit launches, and never rewrite them mid-run (fan-out contract §A10).\n3. **Launch** — invoke `\u002Fworkflow` and paste the whole filled prompt as its single raw string\n   argument. `\u002Fworkflow` takes ONE string; do not invent `--goal`\u002F`--dod` flags (see §2,\n   args-as-string).\n4. **Terminal marker** — the run ends with exactly one mechanically derived\n   `SUCCESS:\u003Cworkflow-id>` (only when every `DOD-NN` passes with both evidence records) OR\n   `FAILED:\u003Cworkflow-id> \u003Creason>` (with every unmet id, preserved artifacts\u002Fbranches, the exact\n   blocker, and one resumable next action). A progress report, a job id, \"started in the\n   background\", or \"check status\" is NEVER terminal success.\n\nThis **complements, and does not duplicate**, the neighbouring orchestration skills: the\ncross-vendor architect\u002Feditor split, the sandbox ladder, and retry-escalation live in\n[`..\u002Forchestration-patterns\u002FSKILL.md`](..\u002Forchestration-patterns\u002FSKILL.md); the large\nparallel-agent-on-Databricks fan-out lives in\n[`..\u002Fcodex-databricks-fanout\u002FSKILL.md`](..\u002Fcodex-databricks-fanout\u002FSKILL.md). Author the workflow\nJS with §1–§12 above; reach for the template only to govern an entire run.\n\n---\n\n## Lint your workflow\n\nBefore saving, run the bundled static linter over your workflow file(s):\n\n```bash\npython skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py ~\u002F.claude\u002Fworkflows\u002Fmy-wf.mjs\npython skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py ~\u002F.claude\u002Fworkflows   # or a whole dir\n```\n\nIt **HARD-FAILS** (non-zero exit) on the determinism-breakers that silently corrupt resume + the\nprompt cache, and **WARNS** on the recorded footguns — every check maps to a rule above:\n\n| level | check | rule |\n|---|---|---|\n| ERROR | `no-date-now` · `no-math-random` · `no-argless-new-date` | 3 — banned non-determinism (`Date.now()` \u002F `Math.random()` \u002F argless `new Date()`) |\n| ERROR | `meta-missing` · `meta-not-object` · `meta-not-literal` | 1 + 8 — a pure-literal `export const meta` |\n| ERROR | `sandbox-forbidden` | 1 — `require` \u002F `import` \u002F `eval` \u002F `new Function` \u002F `process.*`, none of which exist in the vm sandbox |\n| WARN  | `args-unguarded` | 2 — `args.\u003Cfield>` with no absent-args guard (**renamed** from `args-parse-if-string`, whose premise was measured false) |\n| WARN  | `agent-schema` | 7 — an `agent()` fan-in with no `schema:` option |\n| WARN  | `parallel-filter-boolean` | 5 — a `parallel()` result that is neither `.filter(Boolean)`-ed nor null-typed by a `.map()` |\n| WARN  | `barrier-without-timeout` | 5b — a `parallel()` over `agent()` with no `withTimeout` \u002F `settle` \u002F `Promise.race` (design item H6) |\n| WARN  | `prefer-pipeline` | 6 — two barriers separated only by a pure transform; the runtime's own smell test |\n| WARN  | `budget-loop-unguarded` | 5c — `while (budget.remaining() …)` with no `budget.total` guard |\n\nMeasured against the 10 saved workflows on 2026-08-12: the pre-change linter\nreported **12** findings (11 `agent-schema` + 1 `parallel-filter-boolean`); the\nupdated one reports **27** — a delta of exactly the **15 `barrier-without-timeout`**\nhits, one for every barrier call on the box, with **no change to any pre-existing\ncheck**. `prefer-pipeline` fires **0** times on that corpus (the live workflows use\none barrier per phase, not the two-barrier-with-transform smell), so its only\ncurrent evidence is its both-states fixture — it is a prospective rule, not a\ndemonstrated catch.\n\n`--strict` also fails on warnings; `--json` emits machine-readable findings with `file:line`.\nThe linter is a **stdlib-only lexical pass** — it MASKS comment + string\u002Ftemplate bodies before\nmatching, so a banned call quoted in a string never false-fires — plus a recursive-descent\nliteral parser for `meta`. It is **not** a full JS parser; the honest limits are in the module\ndocstring (e.g. options passed to `agent()` via a variable read as a false WARN; `Date.now()`\nhidden in a nested template interpolation may be missed). Treat ERRORs as blocking, WARNs as\nadvisory. Tests live beside it (`scripts\u002Ftest_workflow_lint.py`) and run in CI at 100% branch\ncoverage.\n\n## Author quick-wins (helper snippets)\n\nCopy-paste patterns that make the checks above pass. These are DOC snippets — the runtime API\n(section 1) is unchanged.\n\n**Parse-if-string args (rule 2).** `args` arrives as ONE string. Normalise it once, then read\nfields off the parsed value — never off raw `args`:\n\n```js\nconst input = (() => {\n  if (typeof args !== 'string') return args ?? {}      \u002F\u002F already an object\n  const s = args.trim()\n  if (!s) return {}\n  try { return JSON.parse(s) } catch { return { raw: s } }  \u002F\u002F fall back to the raw string\n})()\nconst topic = input.topic ?? '(none)'   \u002F\u002F read off `input`, not `args`\n```\n\n**Schema-builder helper (rule 7).** A tiny builder keeps every fan-in schema consistent, with\n`additionalProperties: false` and a tight `required[]` (the FLOOR only — an over-broad `required`\nmakes StructuredOutput reject good work):\n\n```js\nconst obj = (properties, required = []) => ({\n  type: 'object', additionalProperties: false, required, properties,\n})\nconst DIGEST = obj(\n  { digest: { type: 'string' }, evidence: { type: 'array', items: { type: 'string' } } },\n  ['digest', 'evidence'],   \u002F\u002F fields an agent can ALWAYS fill; keep verdict\u002Fartifact optional\n)\nconst r = await agent(prompt, { label: 'lens', schema: DIGEST })\n```\n\n**`{ envelope: true }` result shape.** For a fan-in that must not lose a partial cohort, return a\ntyped envelope, not a bare array — it carries the survivors AND what failed, so a null-tolerant\nbarrier degrades visibly:\n\n```js\nconst results = (await parallel(thunks)).filter(Boolean)   \u002F\u002F rule 5: drop the nulls\nreturn {\n  envelope: true,\n  ok: results.length,\n  total: thunks.length,\n  dropped: thunks.length - results.length,   \u002F\u002F how many agents died — surfaced, not hidden\n  results,\n}\n```\n\n## Companion artifacts in this workspace\n\n- **`\u002Fplan-gate`** (`~\u002F.claude\u002Fworkflows\u002Fplan-gate.mjs`) — saved, schema-typed\n  Plan Review Gate: a mechanical stage-0 lint, then 3 fresh adversarial reviewers\n  (Feasibility, Completeness, Scope & Alignment), each returning\n  `{ verdict, blocking }`. SHADOW\u002Fadvisory — records a verdict, never blocks.\n- **`\u002Fdesign-gate`** (`~\u002F.claude\u002Fworkflows\u002Fdesign-gate.mjs`) — saved Design\n  Review Gate: 5 fresh reviewers (PM, Architect, Designer, Security, CTO) in\n  parallel, each `{ verdict, blocking }`. SHADOW\u002Fadvisory.\n- **`plan_lint_stage0.py`** (`~\u002F.claude\u002Fharness-meta\u002F`) — the mechanical stage-0\n  the gate runs FIRST: `lint(plan)` checks WU ≤ 5 files, no overlapping scopes,\n  and a valid DAG, returning `PLAN_LINT:clean|dirty|na` (SHADOW, exits 0). It\n  also ships the **cross-session resume manifest** (`done.jsonl` + write-ahead\n  `partial` records): mark a WU `partial` before it runs and `done` after it\n  commits, then on a resumed session `resume(all_wu_ids)` classifies WUs into\n  done \u002F partial (crashed → re-drive) \u002F pending — so a teammate that dies on a\n  session cut is not silently redone. **Wiring it is rule 13**; the copy-paste\n  template is `templates\u002Fresume-wired-fanout.md`.\n\nBoth gates ship in **SHADOW mode** (council C5): they surface a verdict + append\none line to a bounded, auto-rotating shadow sink; promotion to an ENFORCE gate\nthat blocks on a blocking FAIL is a separate, deferred step.\n\n## Quick checklist before saving a workflow\n\n- [ ] lint clean: `python skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py \u003Cfile>` (no ERROR findings)\n- [ ] `export const meta` with a stable `name` + `phases`\n- [ ] a `schema` (with `additionalProperties: false`) on every `agent()` fan-in\n- [ ] `.filter(Boolean)` after every `parallel()`\n- [ ] `isolation: 'worktree'` on every file-MUTATING agent; disjoint scopes\n- [ ] no `Date.now()` \u002F `Math.random()` \u002F `new Date()` in the workflow JS\n- [ ] byte-stable shared prompt prefix; volatile data only in per-agent tails\n- [ ] one level of nesting; wide barriers kept small + homogeneous\n- [ ] agents auto-discover their input (args arrive as one string)\n- [ ] no spawn prompt asserts a tool is blocked without a live probe backing it\n- [ ] every contracted output path is `Test-Path`-checked at fan-in before its digest is read\n- [ ] cross-vendor units use `agentType: 'codex'`, never a `*-rescue` forwarder\n- [ ] briefed facts carry `[probe: …]` + a timestamp + a different-mechanism re-verify rule\n- [ ] adversarial reviewers receive siblings' FULL text and their own on-disk artifact contract\n- [ ] agents flush a partial artifact on a cadence (a cohort interrupt must degrade, not zero)\n","# workflow-authoring\n\nHow to write a **saved Claude Code Workflow** — an orchestration script under\n`~\u002F.claude\u002Fworkflows\u002F*.js` (or `*.mjs`) that the runtime executes to fan out\nsubagents, collect their typed results, and return a structured answer. This is\nthe durable, re-invokable form of a multi-agent job (invoked as `\u002Fname`), as\nopposed to a one-off `Task()` subagent.\n\nThe rules below are distilled from the workspace's own recorded footguns\n(`feedback_workflow_orchestration`, `feedback_parallel_agent_worktree`, the\n\"Workflow args-as-string gotcha\") and the G.6 gem-hunt finds. Follow them and\nyour workflow will not deadlock, will reproduce, and will not contaminate the\nshared git index.\n\n---\n\n## 1. The runtime API (the whole surface)\n\nA workflow file is NOT a plain ES module. The runner injects **eight** globals —\n`agent`, `parallel`, **`pipeline`**, `phase`, `log`, `args`, `budget`, `workflow`\n(plus `console`, `setTimeout`, `clearTimeout`) — reads your `export const meta`,\nwraps the body so top-level `await` and a trailing top-level `return` are legal,\nand executes it in a Node `vm` context.\n\n> **CORRECTED 2026-08-12.** This section previously said \"injects three globals —\n> `agent`, `parallel`, `phase`\". That was measured wrong against the installed\n> runtime, and the omission of `pipeline` is not cosmetic: **0 of the 10 saved\n> workflows on this box use `pipeline()`** while 15 barrier calls do, even though\n> the runtime's own tool doc says **\"DEFAULT TO pipeline()\"**. The globals list,\n> the `args` contract (rule 2), and the effort enum below were all re-derived from\n> the installed binary; see `docs\u002Fworkflow-sota\u002FGROUNDING-NOTES.md` §3 and the\n> verbatim dump at `docs\u002Fworkflow-sota\u002FRUNTIME-API-DUMP.txt`.\n\nThe sandbox is real, not advisory: `codeGeneration.strings = false` (no `eval`,\nno `new Function`), no module loader (`require` is undefined, `import()` throws),\nno filesystem or Node API, and `Date.now()` \u002F argless `new Date()` \u002F `Math.random()`\nare **shimmed to throw** — not merely discouraged. `runInContext` caps only the\n*synchronous* body at 30 s; it does **not** bound an awaited agent, which is why\nper-agent timeouts are your problem (rule 5b).\n\n```js\nexport const meta = {\n  name: 'my-workflow',                       \u002F\u002F the \u002Fslash name; keep it stable\n  description: 'one line — shown in the picker',\n  phases: [{ title: 'Ground' }, { title: 'Fan-out' }, { title: 'Synthesize' }],\n}\n\nphase('Ground')                              \u002F\u002F declare\u002Fenter a phase (telemetry)\nconst SCHEMA = { type: 'object', additionalProperties: false,\n  required: ['answer'], properties: { answer: { type: 'string' } } }\n\nconst ground = await agent('prompt string', {\n  label: 'ground',        \u002F\u002F stable id for this agent (used in logs\u002Ftelemetry)\n  phase: 'Ground',        \u002F\u002F which declared phase this belongs to\n  schema: SCHEMA,         \u002F\u002F JSON-schema; the runtime RETURNS a parsed object\n  effort: 'high',         \u002F\u002F 'low' | 'medium' | 'high' | 'xhigh' | 'max'\n  model: 'opus',          \u002F\u002F 'opus' (default, deepest) or 'fable' for wide fan-out\n})\n\nphase('Fan-out')\nconst results = (await parallel(\n  items.map((it) => () => withTimeout(   \u002F\u002F \u003C-- paste-in helper, NOT a global; rule 5b\n    () => agent(promptFor(it), { label: `lens:${it.key}`, phase: 'Fan-out', schema: SCHEMA }),\n    600000, `lens:${it.key}`))\n)).filter(Boolean)          \u002F\u002F \u003C-- the null-tolerant barrier; see rule 5\n\nreturn { ...ground, results }   \u002F\u002F top-level return is the workflow's result\n```\n\n- `agent(prompt, opts)` → a Promise of the **parsed schema object** (not raw\n  text). Always pass a `schema` so you get typed fields, never prose to re-parse.\n- `parallel(thunks)` → a Promise of an array, one entry per thunk, in order.\n  **Pass an array of zero-arg thunks** (`() => agent(...)`), not an array of\n  already-started promises — the thunks let the runtime schedule\u002Flimit them.\n- `phase(title)` is telemetry\u002Fstructure; call it once per phase before its work.\n- `pipeline(items, stage1, stage2, ...)` → runs each item through **all** stages\n  independently, with **no barrier between stages**. Item A can be in stage 3\n  while item B is still in stage 1. Wall-clock = the slowest single-item chain,\n  not the sum of the slowest-per-stage. Every stage callback receives\n  `(prevResult, originalItem, index)`. A stage that throws drops that item to\n  `null` and skips its remaining stages. **This is the default for multi-stage\n  work.** Caveat measured elsewhere: `pipeline()` is not a cacheable resume unit —\n  resuming a stopped pipeline re-runs its first stage\n  (`skills\u002Fworkflow-foundry\u002FSKILL.md` L4).\n- `log(msg)` → a progress line in the run view. The only output channel from the\n  script body itself.\n- `budget` → `{ total: number|null, spent(): number, remaining(): number }`. The\n  target is a **HARD ceiling**: once `spent() >= total`, further `agent()` calls\n  **throw**. `total` is `null` when the user set no target, and `remaining()` is\n  then `Infinity` — so always guard a budget loop on `budget.total` (rule 5c).\n- `workflow(nameOrRef, args?)` → run another saved workflow inline. The child\n  shares this run's concurrency cap, agent counter, abort signal and budget.\n  **Nesting is one level only**; `workflow()` inside a child throws.\n- Concurrency is capped at `min(16, cores - 2)` per workflow (**14** on this\n  box), with a **1000-agent lifetime cap** as a runaway backstop.\n- Native resume: `Workflow({ scriptPath, resumeFromRunId })` replays the longest\n  unchanged prefix of `agent()` calls from cache. Same script + same args = 100%\n  cache hit. Before diagnosing an empty result, read\n  `\u003CtranscriptDir>\u002Fjournal.jsonl` — it records each agent's actual return value.\n\n---\n\n## 2. `args` is delivered VERBATIM (corrected — the old \"one string\" rule was false)\n\n> **This rule previously said the opposite** and the linter enforced it. Measured\n> against the installed runtime's own tool doc, `args` is *\"the value passed as\n> Workflow's `args` input, **verbatim** (undefined if not provided)\"*. Pass a JSON\n> array and the script receives an array. The lint check was renamed\n> `args-parse-if-string` → **`args-unguarded`** and re-messaged.\n\nTwo things are still true and are the actual footguns:\n\n1. **`args` is `undefined` when the workflow is invoked with none**, so a bare\n   `args.topic` throws a TypeError. Guard it: `const a = args ?? {}`.\n2. **A caller who passes a JSON-*encoded string* instead of a JSON value** does\n   reach the script with one string, and `args.filter` \u002F `args.map` then throw.\n   That is a caller-side mistake, not the runtime contract; add\n   `typeof args === 'string' ? JSON.parse(args) : args` when a caller may do it.\n\nIndependently of both: design for there being no reliable arg channel at all —\n**have the agents auto-discover their input from live files** (Read\u002FGlob). The\nsaved gate Workflows do exactly this: they tell each reviewer where to find the\nplan\u002Fdesign doc and let it locate + Read it.\n\n## 3. Never call `Date.now()` (or `Math.random()`, `new Date()`) in workflow JS\n\nNon-determinism in the orchestration layer breaks two things: **prompt-cache\nstability** (a changing timestamp in a shared prefix busts the cache for every\ndownstream agent — see rule 8) and **reproducibility** (two runs of the same\nworkflow diverge for no reason). If you genuinely need a timestamp or a random\nid (e.g. to name a shadow-log line), **generate it inside an agent's tool call**\n(`Get-Date -Format o` in PowerShell, `date -Is` in a POSIX shell) — never in the\nworkflow's own JavaScript. The gate Workflows produce their shadow-sink timestamp\nthis way for precisely this reason.\n\n## 4. `isolation: \"worktree\"` for any agent that MUTATES files\n\nParallel subagents share ONE git index and working tree by default. A broad\n`git add -A` in one agent then sweeps a sibling's in-flight files into the wrong\ncommit (`feedback_parallel_agent_worktree`). Any agent that WRITES to the repo\nmust run isolated:\n\n```js\nagent(prompt, { label: 'impl:WU3', schema: S, isolation: 'worktree' })\n```\n\nRead-only agents (reviewers, researchers, the gate reviewers) need no worktree —\ndeclare them read-only in the prompt and skip the overhead. Pair worktree\nisolation with **disjoint file scopes** (rule 7) so two mutating agents can never\ntarget the same file.\n\n## 5. Null-tolerant barriers: `.filter(Boolean)` after every `parallel`\n\n`parallel()` has **no per-agent timeout** and a throwing thunk **resolves to\n`null`** rather than rejecting the whole batch. Two consequences:\n\n- **Always** `.filter(Boolean)` the result so one dead agent does not poison the\n  aggregation with a `null` you then dereference.\n- One HUNG (not thrown — actually stuck) agent will still **stall the whole\n  phase** until it dies, because the barrier waits for all thunks. That is the\n  recorded barrier-deadlock (`feedback_workflow_orchestration`). The structural\n  fix is rule 6.\n\n```js\nconst reviews = (await parallel(reviewers.map((r) => () =>\n  withTimeout(() => agent(r.prompt, { label: `review:${r.role}`, schema: REVIEW_SCHEMA }),\n              600000, `review:${r.role}`)   \u002F\u002F rule 5b: bound each thunk (H6 paste-in)\n))).filter(Boolean)   \u002F\u002F a throwing reviewer becomes null -> dropped, phase completes\n```\n\n## 5b. Bound every fanned-out agent — design item H6 (SHIPPED)\n\nRule 5 says a *thrown* agent becomes `null`. A **hung** one is worse: the barrier\nwaits forever. That is not hypothetical here — a 13-hour deadlock and 11 lanes\nstalled behind one hung reviewer are both on the record, and\n`platform\u002Fworkflows\u002Fshared-prod-shard-fanout-build.json` records an 11-hour hung\nproduction unit. Measured 2026-08-12: **15 of 15 barrier calls across all 10 saved\nworkflows had zero timeout protection** (`Promise.race` 0, `setTimeout` 0,\n`AbortController` 0).\n\nThe fix ships as a paste-in block, because the sandbox has no module loader and a\nhelper therefore cannot be imported:\n\n**`templates\u002Fh6-timeout-heartbeat.md`** — `withTimeout(thunk, ms, label)` and\n`settle(thunks, ms, labels)`. `settle()` is a drop-in for `parallel()` that\nreturns an index-aligned array with **no nulls and no holes**: every slot is\neither a real result or a typed drop-receipt `{ __h6: true, __endReason:\n'timeout' | 'threw' | 'null', label, ms }`.\n\nType the drop, do not filter it away (design item H3): the runtime yields `null`\nfor at least three different causes — the user skipped the agent, the subagent\ndied on a terminal error, and (with H6) our own timeout — and `.filter(Boolean)`\nerases that distinction, which is what makes \"12 of 15 lenses returned\"\nunauditable today.\n\n**Honest scope, do not widen it.** `Promise.race` unblocks the **barrier**; it\ndoes **not** kill the agent. The dropped agent keeps running, keeps one of the 14\nconcurrency slots, and keeps spending `budget` until it dies on its own. The\ndesign doc's word \"killed\" (`B3-migration-verification.md:262`) is an overclaim\nagainst this mechanism. Settling experiment: read `budget.spent()` at the timeout\nreceipt and again at workflow end; a rise proves the agent lived on.\n\nRed-proof (`tools\u002Fwf-timeout\u002Ftest_h6_timeout.py`, 20 tests): the helper is\nextracted from the template and executed in a Node `vm` context configured like\nthe real sandbox. The **known-broken control** — a bare `parallel()` over the same\nfive thunks, one never resolving — is required to report `hung: true` (measured:\n1507.9 ms, watchdog); `settle()` over the identical thunks returns in 175.0 ms\nwith exactly one `timeout` receipt and four intact survivors.\n\n## 5c. Guard every budget loop on `budget.total`\n\n`budget.total` is `null` when the user set no token target, and `remaining()` is\nthen `Infinity`. An unguarded `while (budget.remaining() > 50_000)` therefore runs\nstraight to the **1000-agent lifetime cap**. Write\n`while (budget.total && budget.remaining() > 50_000)`. Linted as\n`budget-loop-unguarded`.\n\n## 6. Per-work-unit pipelines, not one fat barrier\n\nPrefer `pipeline()`-style per-WU flows (impl → validate → review → commit for\neach WU independently) over a single wide barrier that waits for the slowest WU\nbefore ANY progress. A per-WU pipeline means a slow or stuck WU idles only\nitself, not the whole cohort — and it composes with the cross-session resume\nmanifest (rule 10) so a died WU is re-driven while its siblings keep their\ncompleted state. When you must use a wide `parallel`, keep the batch small and\nhomogeneous (similar expected runtimes) so the barrier's tail is short.\n\n## 7. One-level nesting + a schema on EVERY fan-in\n\n- **One level of nesting.** `parallel([... agent ...])` at the top is fine.\n  Nesting `parallel` inside an `agent` inside another `parallel` makes runtimes,\n  failures, and telemetry impossible to reason about — flatten into sequential\n  phases instead.\n- **Schema on every fan-in.** Every `agent()` that feeds an aggregation MUST\n  carry a `schema` (with `additionalProperties: false` and an explicit\n  `required`). You then aggregate typed objects, never re-parse prose. The\n  aggregator agent also takes a schema. This is what makes the gates\n  \"schema-typed\": `{ verdict, blocking, ... }` on each reviewer.\n- **`required` is the FLOOR, not the wishlist — over-broad `required[]` rejects\n  good work.** List in `required` ONLY the fields an agent can ALWAYS produce\n  (typically `digest`\u002F`answer` + `evidence`). Anything conditional — a `verdict`\n  a null-result unit has no basis for, an `artifact` path a read-only lens never\n  writes, an `owner_actions` array that is legitimately empty — must be OPTIONAL.\n  A required field the agent cannot fill makes StructuredOutput reject an\n  otherwise-correct return and burn all its retries. Ledger-proven 2026-07-19\n  (`wf_00a25df9-2cd`): a `DIGEST` requiring `verdict\u002Fcorrections\u002Fevidence\u002Fartifact`\n  failed 2 agents 5× each — their real analysis (one wrote a 16 KB artifact to\n  disk) was thrown away purely on the digest shape. Belt-and-braces: have each\n  agent WRITE its full deliverable to disk (rule 11 output contract) so a rejected\n  digest never loses the work — the synthesis fan-in reads the artifact, not the\n  digest.\n- **Editing a shared schema const busts the RESUME cache — plan the schema before\n  the first launch.** Resume is prefix-cached: a completed `agent()` replays only\n  if its `(prompt, opts)` is byte-identical, and `opts.schema` is part of that.\n  A schema referenced by the FIRST phase's agents is therefore the worst thing to\n  edit on a resume — it changes every agent's `opts`, so the longest-unchanged\n  prefix is empty and ALL agents re-run live (a full \"from beginning\" run you may\n  have been explicitly told to avoid). Corollary: get the schema right up front;\n  if you must fix an over-strict schema mid-run, the cache-preserving move is a\n  ONE-OFF standalone `Task()` for the single failed lens (writing the same\n  artifact path), NOT an edit-and-resume of the shared const.\n\n## 8. Cache-cliff-aware fan-out\n\nFor a wide fan-out (many lenses\u002FWUs), keep the **shared prompt prefix\nbyte-stable** across all agents so the prompt cache is hit N-1 times instead of\npaid N times. Concretely: build one immutable `CONTEXT` \u002F `PROBLEM` string and\nprepend it identically to every agent prompt (only the per-agent tail varies);\ndo not interpolate anything volatile (timestamps, random ids, re-serialised JSON\nwith unstable key order) into that prefix — that is the cache cliff, and it is\nthe other reason rule 3 bans `Date.now()`. Pack a repo once (repomix) and let N\nagents grep the pack rather than each re-reading the tree.\n\n## 9. Typed Codex `--output-schema` landmine (#15451)\n\nWhen a workflow fans out to Codex (via the mounted `codex mcp-server` \u002F\n`codex exec`), a typed `--output-schema` is **silently ignored if any MCP tools\nare active** in that Codex invocation (OpenAI codex issue #15451). You will get\nfree-form text back and your `schema` parse will fail or mis-fill. Mitigations:\nrun the schema-typed Codex lane with MCP tools disabled for that call, or\nvalidate\u002Frepair Codex's output on the Claude side before aggregating. Do not\nassume the schema was honored just because you passed it.\n\n**Windows arg-limit corollary (measured 2026-07-18).** The same active-MCP inventory that voids\n`--output-schema` also blows the Windows 32,767-char `CreateProcess` limit: two Codex runs died\nwith `payload_len=34472` \u002F `os error 206` before executing anything. Pass `--ignore-user-config`\non every non-interactive Codex invocation, and pass the prompt BY FILE (`--prompt-file`, or stdin\nvia `-`), never inline. See `rules\u002Fcommon\u002Fcross-vendor-dispatch.md`.\n\n## 10. SDK notes (context-editing, task-budgets, canUseTool)\n\nWhen a workflow is driven through the Agent SDK rather than the in-app runtime:\n\n- **context-editing (`clear_tool_uses`)** trims tool-call history to fit long\n  fan-outs — enable it for long-running orchestrations so agents do not blow the\n  window on accumulated tool noise.\n- **task budgets** cap per-agent spend; set them on mechanical\u002Fharvest agents so\n  a runaway agent cannot drain the wave.\n- **`canUseTool`** is the SDK's per-agent tool gate — the file-scope \u002F capability\n  enforcement point for fan-out subagents (the parity for in-app subagent\n  scoping). Use it to hold a mutating agent to its declared file scope (rule 4\u002F7).\n\n## 11. Fan-out spawn-prompt template\n\nMeasured over 50 agents \u002F 6 workflows (2026-07-18): element presence in the prompt predicts\ncompliance almost perfectly, and **nothing is inherited** from the global contract. Blocks marked\nREQUIRED are the ones with measured evidence behind them. Full rationale + the fan-in gates:\n`rules\u002Fcommon\u002Ffan-out-contract.md`.\n\n```\n[REQUIRED] VERIFIED GROUND TRUTH — established by direct inspection immediately before this\nworkflow. Treat as high-confidence. Each claim is tagged [probe: \u003Chow it was measured>].\nTo confirm OR refute you MUST use a mechanically DIFFERENT probe — a different shell, a\ndifferent data field, a different measurement. Re-running the tagged probe does NOT count as\nverification and must not be reported as CONFIRMED. Report every reconciliation in a\ndedicated section, whatever the outcome.\n  1. \u003Cclaim>  [probe: Get-Command mon, PowerShell 7]\n  2. \u003Cclaim>  [probe: directory listing at 00:25 — may be stale]\n  ...\n[REQUIRED] ACCESS METHODS — for every path above that is NOT readable by Read at that literal\npath, state how to read it:\n  - tools\u002Fmonitorcore\u002F** exists ONLY on origin\u002Fmain: read via `git show origin\u002Fmain:\u003Cpath>`.\n    The working tree is on branch \u003CX> and does not contain it.\n\n[REQUIRED] ANTI-OVERFIT ANCHOR — a proposal counts only if it (a) rides the EXISTING bus\n(\u003Cname the buses>), and (b) does not reintroduce a rejected component (\u003Cname them>).\nPrefer \"enable a native feature\" and \"fold into the existing adapter\" over \"new daemon\".\n[If a deliverable deliberately waives this, say so explicitly and say why.]\n\nENVIRONMENT: \u003COS, shells, language constraints, RAM ceiling>.\n\n=== YOUR TASK (\u003Clens-slug>) ===\n\u003C2-5 sentences. Name the concrete artefacts to read. State the deliverable as numbered\noutputs. End with \"Be concrete about \u003Cthe specific axis>, not generic \u003Cdomain> advice.\">\n\n[REQUIRED] PER-FINDING RUBRIC — apply to every RECOMMENDATION (skip for pure audit findings):\n  name \u002F what it is \u002F why THIS user specifically \u002F how to build-or-adopt (file, function,\n  command) \u002F source: file:line or URL \u002F value (H|M|L) x effort (H|M|L) \u002F\n  disposition: NATIVE (already exists, just enable) | ADOPT (external, vendor it) |\n    BUILD (write it) | PRUNE (delete something) \u002F\n  confidence: almost-certain 90-99% | likely 55-80% | roughly-even 45-55% | unlikely 20-45%,\n    followed by the evidence that justifies that band.\n\n[REQUIRED] HONESTY CONTRACT — the global contract does NOT carry into this fan-out; these\napply here or not at all:\n  - Attach UNVERIFIED INLINE to the sentence it qualifies. A limitations section at the end\n    does NOT satisfy this. If a recommendation's premise is UNVERIFIED, its heading inherits\n    the tag.\n  - Every UNVERIFIED names the experiment that would settle it.\n  - \"Absent \u002F not found \u002F broken\" is a claim, not an observation. Verify your detector can\n    find a known-present item before reporting absence.\n  - Do not report CONFIRMED on a probe that is the same mechanism as the one being checked.\n  - If a tool is denied, you are in a subagent — no user will answer. Do not stop. Record the\n    gap, use an alternative, report it in your digest.\n\n[REQUIRED] OUTPUT CONTRACT\n  1. Write the full deliverable to \u003Cabs path>. Write it with the **Write** tool. If a single\n     call is too large, chunk with `Add-Content` — never build a multi-KB literal inside a\n     shell command string (Windows 32,767-char limit -> os error 206).\n  2. Then return a \u003C= 400 word structured digest as your final text (NOT the whole doc).\n     LEAD the digest with any ground-truth correction, then the top findings.\n  3. Append an EVIDENCE: block of 3-5 file:line \u002F URL anchors. It is EXEMPT from the word\n     cap — 87% of file:line refs and 100% of URLs otherwise die at the digest boundary.\n  4. Your final line must be: DELIVERED: \u003Cabs path> (\u003Cn> bytes)\n```\n\nFor a cross-vendor (Codex) unit, do NOT paste a here-string output contract and do NOT route\nthrough a `*-rescue` forwarder — see §12 and `rules\u002Fcommon\u002Fcross-vendor-dispatch.md`.\n\n## 12. agentType caveat — a fan-out is only as synchronous as its slowest lie\n\n`agent({ agentType: 'codex:codex-rescue' })` is NOT an interchangeable work-unit executor. Its\ndefinition instructs it to **prefer background execution** for tasks that are \"complicated,\nopen-ended, multi-step\" — i.e. every work unit — and it ships `model: sonnet` with `tools: Bash`\nonly, so it cannot write a report or verify its own artifacts. Measured: 3 such agents, 34 turns,\n**0 production files**; one returned only a job id, and that job then completed on disk with a\nreal finding the harness could not collect. Use `agentType: 'codex'` (the synchronous adapter).\nAny barrier that accepts a result containing `started in the background` or `Check \u002Fcodex:status`\nis accepting a receipt, not an answer — reject it at fan-in.\n\n## 13. Wire the resume manifest — the cross-session half of rule 6\n\n`plan_lint_stage0.py` has shipped a complete, `fsync`'d `ResumeManifest` since 2026-07-12 and, as\nmeasured 2026-07-29, had **zero callers**: the only reference anywhere was the `LINTER` const at\n`plan-gate.mjs:35`, which runs `lint()` and never touches the manifest. Consequence — every saved\nWorkflow that dies mid-wave redoes the whole wave.\n\n**Wire it with an `agent()` step, never a JS shim.** The runner *wraps the body* (rule 1), so a\nstatic top-level `import` is a SyntaxError; measured across all 11 saved workflows there is not one\n`import`, `require(`, `child_process` or `fs.` call — every disk read is delegated to an agent's\nshell. Copy `templates\u002Fresume-wired-fanout.md` (its `js` block is kept lint-clean and is gated by\n`scripts\u002Ftest_workflow_lint.py`). Three touch-points, nothing else:\n\n| when | command | effect |\n|---|---|---|\n| GROUND, once | `--manifest M --ids \u003Call> --next` | stdout = exactly the ids still to run, one per line |\n| BEFORE each unit | `--manifest M --mark-partial \u003Cid>` | write-ahead; an `fsync`'d `partial` record |\n| AFTER each unit | `--manifest M --mark-done \u003Cid>` \u002F `--mark-failed \u003Cid>` | terminal state |\n\n`--ids` and `--next` exist because a Workflow's unit list is a **JS array**: `resume()` derives\n`pending` from a declared work-list, and the CLI's only other source was `--plan`, which reads a\nplan *document*. So an author had to synthesise a throwaway `plan.wu.json` just to name their own\nunits — that friction is the mechanical reason the manifest shipped uncalled.\n\nDurability, measured (not assumed): with `--mark-partial WU3` written, a `taskkill \u002FF` on the unit\nprocess, and `--resume` in a fresh interpreter, WU3 lands in `partial` — process-crash recovery is\n**executable-proven** (13\u002F13 assertions, and 3\u002F3 mutants of the fold\u002Fappend\u002Ftorn-line logic go RED,\nso the probe is not vacuous). **UNVERIFIED, inline: machine-crash \u002F power-loss durability is NOT\nproven** — a process kill cannot distinguish `fsync`'d bytes from page-cache bytes, so the\nexperiment cannot see the `fsync` at all (only an instrumented counter shows the call is reached,\n`FSYNC_CALLS=1` per append). Settling experiment: run the same protocol in a VM and hard-cut power\nbetween the append and the read.\n\nTwo honest limits to design around: `--mark-done` is **asserted by the unit agent**, not verified by\nthe orchestrator, so pair it with an independent postcondition probe (artifact exists AND ends with\nyour completion marker) exactly as the accumulator workflow's AND-gate does — a unit that lies is\nskipped forever, which is worse than being re-driven. And `done.jsonl` is append-only and never\npruned; `records()` re-reads the whole file, which is fine at wave scale and is not a database.\n\n---\n\n## Governed single-shot run\n\nEverything above is how to write the workflow JS. When you instead want to GOVERN a whole run\nend-to-end from a single invocation — ground → design\u002Fplan gates → fan-out → integrate → reconcile\n→ one focused merge-when-green PR → memory sync — this skill vendors a reusable orchestrator\nmeta-prompt: [`templates\u002Fgoverned-run-orchestrator.md`](templates\u002Fgoverned-run-orchestrator.md).\n\nIt is a fill-in **template, not a second engine** — it rides the native Claude Code Workflow tool\nand this skill's bus. Use it in four steps:\n\n1. **Fill** — copy the template and replace ONLY its two authoritative slots. `\u003CGOAL>` is the\n   durable outcome + named scope + resume-state + exclusions; `\u003CDoD>` is the numbered completion\n   AND-gate. Add as many `DOD-NN` rows as the goal needs, each an externally observable state with\n   TWO mechanically independent evidence sources. Remove every `[...]` instructional sentinel; keep\n   the `\u003CGOAL>`\u002F`\u003CDoD>` tag names and the `DOD-NN` id format.\n2. **Freeze** — freeze the filled `\u003CGOAL>`\u002F`\u003CDoD>`, the artifact root, the schemas, and the prompt\n   hashes before the first unit launches, and never rewrite them mid-run (fan-out contract §A10).\n3. **Launch** — invoke `\u002Fworkflow` and paste the whole filled prompt as its single raw string\n   argument. `\u002Fworkflow` takes ONE string; do not invent `--goal`\u002F`--dod` flags (see §2,\n   args-as-string).\n4. **Terminal marker** — the run ends with exactly one mechanically derived\n   `SUCCESS:\u003Cworkflow-id>` (only when every `DOD-NN` passes with both evidence records) OR\n   `FAILED:\u003Cworkflow-id> \u003Creason>` (with every unmet id, preserved artifacts\u002Fbranches, the exact\n   blocker, and one resumable next action). A progress report, a job id, \"started in the\n   background\", or \"check status\" is NEVER terminal success.\n\nThis **complements, and does not duplicate**, the neighbouring orchestration skills: the\ncross-vendor architect\u002Feditor split, the sandbox ladder, and retry-escalation live in\n[`..\u002Forchestration-patterns\u002FSKILL.md`](..\u002Forchestration-patterns\u002FSKILL.md); the large\nparallel-agent-on-Databricks fan-out lives in\n[`..\u002Fcodex-databricks-fanout\u002FSKILL.md`](..\u002Fcodex-databricks-fanout\u002FSKILL.md). Author the workflow\nJS with §1–§12 above; reach for the template only to govern an entire run.\n\n---\n\n## Lint your workflow\n\nBefore saving, run the bundled static linter over your workflow file(s):\n\n```bash\npython skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py ~\u002F.claude\u002Fworkflows\u002Fmy-wf.mjs\npython skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py ~\u002F.claude\u002Fworkflows   # or a whole dir\n```\n\nIt **HARD-FAILS** (non-zero exit) on the determinism-breakers that silently corrupt resume + the\nprompt cache, and **WARNS** on the recorded footguns — every check maps to a rule above:\n\n| level | check | rule |\n|---|---|---|\n| ERROR | `no-date-now` · `no-math-random` · `no-argless-new-date` | 3 — banned non-determinism (`Date.now()` \u002F `Math.random()` \u002F argless `new Date()`) |\n| ERROR | `meta-missing` · `meta-not-object` · `meta-not-literal` | 1 + 8 — a pure-literal `export const meta` |\n| ERROR | `sandbox-forbidden` | 1 — `require` \u002F `import` \u002F `eval` \u002F `new Function` \u002F `process.*`, none of which exist in the vm sandbox |\n| WARN  | `args-unguarded` | 2 — `args.\u003Cfield>` with no absent-args guard (**renamed** from `args-parse-if-string`, whose premise was measured false) |\n| WARN  | `agent-schema` | 7 — an `agent()` fan-in with no `schema:` option |\n| WARN  | `parallel-filter-boolean` | 5 — a `parallel()` result that is neither `.filter(Boolean)`-ed nor null-typed by a `.map()` |\n| WARN  | `barrier-without-timeout` | 5b — a `parallel()` over `agent()` with no `withTimeout` \u002F `settle` \u002F `Promise.race` (design item H6) |\n| WARN  | `prefer-pipeline` | 6 — two barriers separated only by a pure transform; the runtime's own smell test |\n| WARN  | `budget-loop-unguarded` | 5c — `while (budget.remaining() …)` with no `budget.total` guard |\n\nMeasured against the 10 saved workflows on 2026-08-12: the pre-change linter\nreported **12** findings (11 `agent-schema` + 1 `parallel-filter-boolean`); the\nupdated one reports **27** — a delta of exactly the **15 `barrier-without-timeout`**\nhits, one for every barrier call on the box, with **no change to any pre-existing\ncheck**. `prefer-pipeline` fires **0** times on that corpus (the live workflows use\none barrier per phase, not the two-barrier-with-transform smell), so its only\ncurrent evidence is its both-states fixture — it is a prospective rule, not a\ndemonstrated catch.\n\n`--strict` also fails on warnings; `--json` emits machine-readable findings with `file:line`.\nThe linter is a **stdlib-only lexical pass** — it MASKS comment + string\u002Ftemplate bodies before\nmatching, so a banned call quoted in a string never false-fires — plus a recursive-descent\nliteral parser for `meta`. It is **not** a full JS parser; the honest limits are in the module\ndocstring (e.g. options passed to `agent()` via a variable read as a false WARN; `Date.now()`\nhidden in a nested template interpolation may be missed). Treat ERRORs as blocking, WARNs as\nadvisory. Tests live beside it (`scripts\u002Ftest_workflow_lint.py`) and run in CI at 100% branch\ncoverage.\n\n## Author quick-wins (helper snippets)\n\nCopy-paste patterns that make the checks above pass. These are DOC snippets — the runtime API\n(section 1) is unchanged.\n\n**Parse-if-string args (rule 2).** `args` arrives as ONE string. Normalise it once, then read\nfields off the parsed value — never off raw `args`:\n\n```js\nconst input = (() => {\n  if (typeof args !== 'string') return args ?? {}      \u002F\u002F already an object\n  const s = args.trim()\n  if (!s) return {}\n  try { return JSON.parse(s) } catch { return { raw: s } }  \u002F\u002F fall back to the raw string\n})()\nconst topic = input.topic ?? '(none)'   \u002F\u002F read off `input`, not `args`\n```\n\n**Schema-builder helper (rule 7).** A tiny builder keeps every fan-in schema consistent, with\n`additionalProperties: false` and a tight `required[]` (the FLOOR only — an over-broad `required`\nmakes StructuredOutput reject good work):\n\n```js\nconst obj = (properties, required = []) => ({\n  type: 'object', additionalProperties: false, required, properties,\n})\nconst DIGEST = obj(\n  { digest: { type: 'string' }, evidence: { type: 'array', items: { type: 'string' } } },\n  ['digest', 'evidence'],   \u002F\u002F fields an agent can ALWAYS fill; keep verdict\u002Fartifact optional\n)\nconst r = await agent(prompt, { label: 'lens', schema: DIGEST })\n```\n\n**`{ envelope: true }` result shape.** For a fan-in that must not lose a partial cohort, return a\ntyped envelope, not a bare array — it carries the survivors AND what failed, so a null-tolerant\nbarrier degrades visibly:\n\n```js\nconst results = (await parallel(thunks)).filter(Boolean)   \u002F\u002F rule 5: drop the nulls\nreturn {\n  envelope: true,\n  ok: results.length,\n  total: thunks.length,\n  dropped: thunks.length - results.length,   \u002F\u002F how many agents died — surfaced, not hidden\n  results,\n}\n```\n\n## Companion artifacts in this workspace\n\n- **`\u002Fplan-gate`** (`~\u002F.claude\u002Fworkflows\u002Fplan-gate.mjs`) — saved, schema-typed\n  Plan Review Gate: a mechanical stage-0 lint, then 3 fresh adversarial reviewers\n  (Feasibility, Completeness, Scope & Alignment), each returning\n  `{ verdict, blocking }`. SHADOW\u002Fadvisory — records a verdict, never blocks.\n- **`\u002Fdesign-gate`** (`~\u002F.claude\u002Fworkflows\u002Fdesign-gate.mjs`) — saved Design\n  Review Gate: 5 fresh reviewers (PM, Architect, Designer, Security, CTO) in\n  parallel, each `{ verdict, blocking }`. SHADOW\u002Fadvisory.\n- **`plan_lint_stage0.py`** (`~\u002F.claude\u002Fharness-meta\u002F`) — the mechanical stage-0\n  the gate runs FIRST: `lint(plan)` checks WU ≤ 5 files, no overlapping scopes,\n  and a valid DAG, returning `PLAN_LINT:clean|dirty|na` (SHADOW, exits 0). It\n  also ships the **cross-session resume manifest** (`done.jsonl` + write-ahead\n  `partial` records): mark a WU `partial` before it runs and `done` after it\n  commits, then on a resumed session `resume(all_wu_ids)` classifies WUs into\n  done \u002F partial (crashed → re-drive) \u002F pending — so a teammate that dies on a\n  session cut is not silently redone. **Wiring it is rule 13**; the copy-paste\n  template is `templates\u002Fresume-wired-fanout.md`.\n\nBoth gates ship in **SHADOW mode** (council C5): they surface a verdict + append\none line to a bounded, auto-rotating shadow sink; promotion to an ENFORCE gate\nthat blocks on a blocking FAIL is a separate, deferred step.\n\n## Quick checklist before saving a workflow\n\n- [ ] lint clean: `python skills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py \u003Cfile>` (no ERROR findings)\n- [ ] `export const meta` with a stable `name` + `phases`\n- [ ] a `schema` (with `additionalProperties: false`) on every `agent()` fan-in\n- [ ] `.filter(Boolean)` after every `parallel()`\n- [ ] `isolation: 'worktree'` on every file-MUTATING agent; disjoint scopes\n- [ ] no `Date.now()` \u002F `Math.random()` \u002F `new Date()` in the workflow JS\n- [ ] byte-stable shared prompt prefix; volatile data only in per-agent tails\n- [ ] one level of nesting; wide barriers kept small + homogeneous\n- [ ] agents auto-discover their input (args arrive as one string)\n- [ ] no spawn prompt asserts a tool is blocked without a live probe backing it\n- [ ] every contracted output path is `Test-Path`-checked at fan-in before its digest is read\n- [ ] cross-vendor units use `agentType: 'codex'`, never a `*-rescue` forwarder\n- [ ] briefed facts carry `[probe: …]` + a timestamp + a different-mechanism re-verify rule\n- [ ] adversarial reviewers receive siblings' FULL text and their own on-disk artifact contract\n- [ ] agents flush a partial artifact on a cadence (a cohort interrupt must degrade, not zero)\n",{"name":5,"description":7},"main","a298c929b6cbd662e1fce54302ce520d5ceea1a5",".agents\u002Fskills\u002Fworkflow-authoring","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain","https:\u002F\u002Fgithub.com\u002FPrekzursil","2026-11-02 14:30:57",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add Prekzursil\u002Fagent-skills-toolchain --skill workflow-authoring",{"tool":64,"command":65},"bunx","bunx skills add Prekzursil\u002Fagent-skills-toolchain --skill workflow-authoring",{"tool":67,"command":68},"pnpm","pnpm dlx skills add Prekzursil\u002Fagent-skills-toolchain --skill workflow-authoring","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002FSKILL.md",true,[72,78,84,88,92,96,100,104,108],{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"DESCRIPTION.md","REFERENCE","参考文档",147,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fhermes\u002Focr-and-documents\u002FDESCRIPTION.md",{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"scripts\u002Fextract_marker.py","SCRIPT","脚本",3034,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fhermes\u002Focr-and-documents\u002Fscripts\u002Fextract_marker.py",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"scripts\u002Fextract_pymupdf.py",3132,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fhermes\u002Focr-and-documents\u002Fscripts\u002Fextract_pymupdf.py",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"scripts\u002Ftest_workflow_lint.py",44438,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Fscripts\u002Ftest_workflow_lint.py",{"path":93,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":94,"sourceHtmlUrl":95},"scripts\u002Fworkflow_lint.py",39622,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Fscripts\u002Fworkflow_lint.py",{"path":97,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":98,"sourceHtmlUrl":99},"templates\u002Fgoverned-run-orchestrator.md",160862,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Ftemplates\u002Fgoverned-run-orchestrator.md",{"path":101,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":102,"sourceHtmlUrl":103},"templates\u002Fh6-timeout-heartbeat.md",10629,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Ftemplates\u002Fh6-timeout-heartbeat.md",{"path":105,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":106,"sourceHtmlUrl":107},"templates\u002Fresume-wired-fanout.md",11176,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Ftemplates\u002Fresume-wired-fanout.md",{"path":109,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":110,"sourceHtmlUrl":111},"tests\u002Ftest_governed_run_template.py",4802,"https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fworkflow-authoring\u002Ftests\u002Ftest_governed_run_template.py",[113,134,160,187,201,217],{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":47},"01M6DF7D7478R55P46REYH4QAC","clarify-requirements","Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fworkspace-hooks\u002Fskills\u002Fclarify-requirements\u002FSKILL.md",[],[120,121,122,123,124,125,126,127,128,37,129,130,131,132,133],"clarify requirements","clarify","requirements","risk","options","task","prompts","goal","scanning","context","repo","injection","execution","file",{"skillNo":135,"name":136,"nameZh":6,"description":137,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":138,"categories":139,"keywords":141,"publishedAt":47},"01M6DF79CA4BHJS66J8EZDBPWJ","openmm-md","Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein \u002F protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fmcp\u002Fopenmm-md-mcp\u002Fskill\u002Fopenmm-md\u002FSKILL.md",[140],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[142,143,144,145,146,147,148,149,150,151,152,153,30,154,37,155,156,157,158,159],"openmm md","how","decision","pick","linux","analyze","tool","stack","open","NVIDIA","gpu","license","windows","free","molecular dynamics","local","do","MCP",{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":172,"publishedAt":47},"01M6DF76C1MG85VEWP8VZCBNMN","visual-review","Take screenshots of web pages and UI using Playwright for visual review and iteration","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fvisual-review\u002FSKILL.md",[166,169],{"categoryNo":167,"categoryName":168,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":170,"categoryName":171,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[173,174,175,176,177,178,179,180,181,182,143,183,184,185,186],"review","visual","visual review","pages","web","false","iteration","take","check","slides","ui","screenshot","playwright","screenshots",{"skillNo":188,"name":189,"nameZh":6,"description":190,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":191,"categories":192,"keywords":193,"publishedAt":47},"01M6DF73AQVCBZKEN0YXEX9FXP","status","Diagnostic status report — shows metaswarm installation state, project setup, and potential issues","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstatus\u002FSKILL.md",[],[189,194,195,196,197,198,199,200],"setup","report","project","installation","project setup","issues","state",{"skillNo":202,"name":203,"nameZh":6,"description":204,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":205,"categories":206,"keywords":208,"publishedAt":47},"01M6DF6X6C2XB0P6GX76P7TTYV","start","Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstart\u002FSKILL.md",[207],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[203,209,125,210,211,212,37,213,214,215,216],"start issue","issue","ready","work on issue","user","work","work on","development",{"skillNo":218,"name":194,"nameZh":6,"description":219,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":220,"categories":221,"keywords":222,"publishedAt":47},"01M6DF6QMJ9AJKFKHHKRT005PQ","Interactive project setup — detects your project, configures metaswarm, writes project-local files","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fsetup\u002FSKILL.md",[],[194,196,157,223,198,224],"files","interactive"]