From ef4d22f4960527a52ab92477404cd42de414549d Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sun, 14 Jun 2026 10:56:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20maxConcurrency=E2=89=A03=20?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E5=85=88=20AskUserQuestion=EF=BC=88=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=203=20=E6=8E=A8=E8=8D=90=E5=80=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 fan-out 时才问改成任何 maxConcurrency≠3 都必须问。 唯一例外:用户在当前会话已明确说过并发数("use 6" / "maxConcurrency 9")。 prompt (WorkflowTool.ts) + skill (ultracode.ts) + audit spec 三处同步。 Co-Authored-By: glm-5.2 --- packages/workflow-engine/src/tool/WorkflowTool.ts | 2 +- src/skills/bundled/ultracode.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/workflow-engine/src/tool/WorkflowTool.ts b/packages/workflow-engine/src/tool/WorkflowTool.ts index d044de914..72f284a9f 100644 --- a/packages/workflow-engine/src/tool/WorkflowTool.ts +++ b/packages/workflow-engine/src/tool/WorkflowTool.ts @@ -43,7 +43,7 @@ Provide the script inline via "script", or reference a named workflow via "name" Use "resumeFromRunId" to resume a prior run — completed agent() calls replay from the journal instantly. -Concurrency: default is 3 (hard ceiling 16). Pass "maxConcurrency" to override. If the user hasn't specified a concurrency and the workflow fans out (parallel/pipeline with many items, multi-dimensional audit, etc.), use AskUserQuestion to confirm the desired concurrency before launching — e.g. offer 3 / 6 / 9 as choices for a 9-dimension review. +Concurrency: default is 3 (hard ceiling 16). OMIT maxConcurrency to use 3. To set maxConcurrency to ANY value other than 3, you MUST first ask the user via AskUserQuestion — propose 3 / 6 / 9 (or other tiers matching the fan-out width) with 3 marked "(Recommended)". The ONLY exception: the user has ALREADY specified a concurrency number in this session ("use 6", "maxConcurrency 9") — then honor it without re-asking. Never silently raise concurrency above 3 just because the workflow fans out; 3 is the recommended default. Script execution model (common pitfalls — getting these wrong is the #1 cause of script errors): the script is the body of \`new AsyncFunction\` — NOT an ESM module, and TypeScript is NOT transpiled. Therefore: - Do NOT use \`import\` — \`agent\`, \`parallel\`, \`pipeline\`, \`phase\`, \`log\`, \`workflow\`, \`args\`, and \`budget\` are injected as parameters; reference them directly. diff --git a/src/skills/bundled/ultracode.ts b/src/skills/bundled/ultracode.ts index 00b6a212b..e47ed2295 100644 --- a/src/skills/bundled/ultracode.ts +++ b/src/skills/bundled/ultracode.ts @@ -74,7 +74,7 @@ Script body hooks: - \`budget: {total: number|null, spent(): number, remaining(): number}\` — the turn's token target from the user's "+500k"-style directive. \`budget.total\` is null if no target was set. \`budget.spent()\` returns output tokens spent this turn across the main loop and all workflows — the pool is shared, not per-workflow. \`budget.remaining()\` returns \`max(0, total - spent())\`, or \`Infinity\` if no target. The target is a HARD ceiling, not advisory: once \`spent()\` reaches \`total\`, further \`agent()\` calls throw. Use for dynamic loops: \`while (budget.total && budget.remaining() > 50_000) { ... }\`, or static scaling: \`const FLEET = budget.total ? Math.floor(budget.total / 100_000) : 5\`. - \`workflow(nameOrRef: string | {scriptPath: string}, args?: any): Promise\` — run another workflow inline as a sub-step and return whatever it returns. Pass a name to invoke a saved workflow (same registry as {name: "..."}), or {scriptPath} to run a script file you Wrote earlier. The child shares this run's concurrency cap, agent counter, abort signal, and token budget — its agents appear under a "▸ name" group in /workflows and its tokens count toward budget.spent(). The args param becomes the child's \`args\` global. Nesting is one level only: workflow() inside a child throws. Throws on unknown name / unreadable scriptPath / child syntax error; catch to handle gracefully. -Concurrent agent() calls are capped at 3 by default per workflow — excess calls queue and run as slots free up. The Workflow tool accepts an optional \`maxConcurrency\` input (1–16) to override per-run; if the user hasn't specified and the workflow fans out (large parallel/pipeline, multi-dimensional review), ask them via AskUserQuestion before launching — e.g. offer 3 / 6 / 9 as choices. You can still pass 100 items to parallel()/pipeline() and they all complete; only the configured number run at any moment. Total agent count across a workflow's lifetime is capped at 1000 — a runaway-loop backstop set far above any real workflow. A single parallel()/pipeline() call accepts at most 4096 items; passing more is an explicit error, not a silent truncation. +Concurrent agent() calls are capped at 3 by default per workflow — excess calls queue and run as slots free up. The Workflow tool accepts an optional \`maxConcurrency\` input (1–16) to override per-run. OMIT it to use 3. To set maxConcurrency to ANY value other than 3, you MUST first ask the user via AskUserQuestion (offer 3 / 6 / 9 with 3 marked "(Recommended)") — the ONLY exception is when the user has already specified a number this session ("use 6", "maxConcurrency 9"). Never silently raise concurrency above 3 just because the workflow fans out; 3 is the recommended default. You can still pass 100 items to parallel()/pipeline() and they all complete; only the configured number run at any moment. Total agent count across a workflow's lifetime is capped at 1000 — a runaway-loop backstop set far above any real workflow. A single parallel()/pipeline() call accepts at most 4096 items; passing more is an explicit error, not a silent truncation. Model tier per task — when you DO override opts.model. Valid aliases: 'haiku' | 'sonnet' | 'opus' | 'best' | 'sonnet[1m]' | 'opus[1m]' | 'opusplan'. The main loop already runs on the user's chosen tier (usually sonnet), so omit model for most agents. Override only when the task clearly fits a different tier: