Files
Codewhale/docs/WORKFLOW_AUTHORING.md
CodeWhale Bot 574dc76e26 fix(runtime): let delegated work finish without invented budgets (CW-7500) [skip ci]
Remove model-selected child caps, default child wall/depth ceilings, early
wrap-up guidance, report-only requests and report reserves. Preserve explicit
owner settings, cancellation, permission checks and deterministic partial-work
receipts. Continue independent workflow steps, keep dependent skips truthful,
remove cumulative child/code-call and recursive-round cutoffs, and retain
unknown usage as unknown. Preserve mode history and idle active-goal resume.
Upgrade untouched bundled delegate guidance by exact shipped-body hash while
preserving customized and deleted skills. Update existing checks and both
changelogs; no new test functions were added.

Erase the shared Engine event-loop future at run_owned without adding another
loop. Two successive warm local lib-test builds took 29m31s then 2m24s with
Rust 1.99.0 and the same cache, profile, features and target selection. Three
existing assertions also changed; this is an observation, not a cold-build or
cross-platform guarantee. Record the method in both build-performance docs.

Verification: 753 distinct existing checks passed across frozen passes.
Repair6 ran 750 passed / 3 stale assertions; those assertions were corrected,
and repair7 passed 58 relevant Engine/child/recovery/goal and corrected checks.
Reuse unchanged Workflow 237, JS library 12, VM 82 and configuration evidence.
Both builds and test passes retained 2,172 frozen inputs. After tests only
release-note text and build-performance docs changed; runtime code stayed
unchanged. git diff --check passed. Darwin linker reports oversized compact
unwind metadata. Final artifact/package, CI, RC and provider/Boat qualification
remain separate. Further request-wall-time and retry-window work remains open.
2026-10-22 22:46:36 -07:00

11 KiB

Workflow Authoring

阅读简体中文版:zh_hans/WORKFLOW_AUTHORING.md。

Ordinary multi-agent work does not require this file. In Operate, send normal messages. Small work stays direct; multiple delegated steps use a compact Workflow plan with dependencies, bounded scopes, and completion evidence. Fleet manages the same sub-agents and roles. One bounded, independent task can use a direct agent, with followup for continued work. Act/Agent may also use optional soft-auto launch. See Automatic Workflows.

Workflow has one runtime boundary: authored source lowers to typed Rust WorkflowSpec, Rust validates the IR, and the scheduler/headless worker runtime executes leaves. Authoring languages do not get hidden authority to own files, shell, network, providers, cancellation, or TUI state.

Compatibility launch paths on the workflow tool:

Input When to use
plan Structured goal / phases / children (preferred agent path)
script Short inline JS the model owns
source_path Checked-in .workflow.js / .workflow.ts in the workspace

Use agent(action="roster") to inspect the saved Fleet models and roles before assigning children. Native plan children accept model for a saved shortlist selector, or role/profile for a saved assignment. Named Exact Fleets keep their member routes fixed and reject per-step model overrides. Plan children also accept cwd, a repository-relative working directory — required in multi-repository workspaces so the child (and worktree isolation) resolves the right repository, mirroring task({cwd}).

For a guided walkthrough from fleet task specs to Workflow authoring and monitoring, see fleet + Workflow Tutorial.

Access model

The Workflow script is a coordinator only. It has no filesystem or shell of its own. Real work happens in sub-agents the script launches.

Layer What it can access
Workflow script (JS VM) Script variables, branching/loops, task() / parallel() / pipeline(), phase / log, budget / args. No direct FS, shell, network, env, imports, clock, or randomness.
Workflow-spawned sub-agents Normal tool surface (read/search/edit/write, shell, web, MCP) subject to role posture, allowlists, and parent policy. File edits for write-capable roles auto-accept under Workflow; shell / web / MCP still require parent auto-approve or fail closed.
Parent session Working directory, configured tools/MCP, permission mode, sandbox/network rules.

Scale

  • Up to 16 concurrent live agents in one run (additional spawns wait for a slot).
  • No default cumulative child limit; [workflow] max_children = 0 leaves total work uncapped.
  • Owners can set a positive max_children limit and configure max_concurrent.
  • Automatic launch is model-judged on scope; the host enforces configured owner limits and plan validation.
  • Plan the population the work needs and let the host queue and clamp it. These ceilings are enforcement, not a reason to pre-shrink a valid plan.

See the Workflow JS sandbox tests for the fail-closed host surface inventory.

Language Choice

Surface Strength Tradeoff Stance
YAML / JSON IR Simple, reviewable, no runtime Verbose for generated workflows Keep as interchange/debug format
JavaScript Familiar object syntax and easy agent generation Unsafe if executed as a general runtime First-class authoring through declarative compile-only subset
TypeScript Best editor/types story for workflow SDK Needs stripping/typechecking if full TS is supported Same compile-only subset for now; richer SDK later

The default high-capability path is TypeScript/JavaScript authoring, but only as a compile step. The compiler accepts a JSON-compatible object inside workflow({...}) from .workflow.js or .workflow.ts, lowers it to WorkflowSpec, and runs the Rust validation gate. (Starlark authoring was a bootstrap reference and has been removed; Workflow authoring is JS-only.)

Contract

Accepted source shape:

export default workflow({
  "id": "issue-audit-js",
  "goal": "Audit an issue fix with parallel agents",
  "nodes": [
    {
      "branch": {
        "id": "parallel-audit",
        "children": [
          { "agent": { "id": "code-audit", "prompt": "Review code", "agent_type": "review" } },
          { "agent": { "id": "test-audit", "prompt": "Review tests", "agent_type": "verifier" } }
        ]
      }
    },
    { "reduce": { "id": "summary", "inputs": ["code-audit", "test-audit"], "prompt": "Summarize" } }
  ]
});

Supported node wrappers: agent, branch, sequence, reduce, teacher_review, loop_until, cond, and expand. Raw WorkflowNode JSON IR with kind / spec also remains valid.

An agent node may declare "profile": "reviewer" to run as a named fleet roster profile. The name is trimmed and lowercased at compile time and must be a single token (no whitespace, quotes, or =); the saved roster is resolved at dispatch time, and explicit fields on the agent override profile defaults.

The runtime task() surface also accepts cwd for an existing repository- relative working directory. This is required when a workflow is launched from a multi-repository workspace and the child needs shell or file access. cwd is validated by the host, does not grant mutation authority, and should be paired with worktree: true when the child needs an isolated checkout.

The compiler rejects effectful constructs such as import, require, fetch, process, Deno, Bun, child_process, file reads/writes, eval, async, and await. This is intentionally stricter than JavaScript: workflow source is a familiar declaration format, not a second execution runtime. The denied effects are not denied to the run — put them in a child worker, which has the full tool surface, and keep the script to coordination.

Gates

A gate fires on a role's completion (on: role_complete) and blocks the role named in blocks_role until the gate passes. That makes blocks_role a different role from the one being gated — a gate that verifies phase 2's work must block phase 3, never phase 2:

// Correct: verify the implementer, block the next stage.
gates: [{
  id: "verify-fix",
  gate: "verify",
  on: "role_complete",
  role: "implement",
  blocks_role: "verify",
  on_fail: "escalate",
  max_retries: 1,
  require_explicit_verdict: true,
}]

Admission rejects a gate that blocks its own role, a cycle of role gates, and a gate whose explicitly named prerequisite first occurs after its blocked role in a sequential plan. Diagnostics name the contradictory gate and roles before any child starts. Parallel structure, unknown profile roles and dynamic raw JavaScript order are not assigned a total order; runtime gates still enforce the prerequisite at dispatch.

Native singleton and sequence steps use the same settled behavior as parallel steps. Ordinary failures retain partial results and let independent work continue. A step whose required input failed or is missing is skipped with a named reason; it never receives an empty substitute and never launches a child. Raw JavaScript task() remains fail-fast unless the author uses the existing settled combinators. Reducers can explicitly summarize partial inputs; they must disclose missing inputs instead of claiming that blocked work ran.

Let workers finish their assigned outcome. Legacy model task limits are accepted for compatibility but ignored, and token hints are not spend caps. Actual owner limits belong in operator, role/Fleet, parent or saved-runtime configuration. Preserve partial findings and exact blockers for a follow-up instead of discarding completed work. Schema repair cannot invent a deadline from a legacy task field.

Verification

  • cargo test -p codewhale-workflow --locked javascript

Current example: workflows/issue_audit.workflow.js.

Agent-Written fleet Workflows

The primary product flow is not "ask the user to write a script." The main agent should decide when a task deserves workflow orchestration, draft the Workflow source, show the plan for the current permission mode, and then let the runtime compile and monitor it.

Workflow owns the plan: phases, branches, loops, reducers, and intermediate results. fleet owns the durable roster, member identity, semantic role, and saved provider/model pins or inheritance. Runtime owns tool posture, launch concurrency, leases, heartbeats, logs, receipts, and resume/stop/restart controls. In other words, a workflow selects fleet members and monitors their Runtime runs; it isn't an executor, because the script has no shell or filesystem of its own — effects live in the workers.

Workflow-to-Runtime launch validation applies a conservative default shape before any Workflow IR is lowered to selected workers:

  • no default total worker limit; a positive owner-configured max_children still applies;
  • up to 16 live worker agents at once; larger populations queue (block) on the host's per-run concurrency gate until a live slot frees, then select through fleet and execute through Runtime;
  • no default structural nesting cap; positive owner workflow.max_depth values apply;
  • no default Runtime delegation depth cap; explicit owner restrictions remain independent of Workflow IR shape;
  • loops require max_iterations;
  • dynamic expand nodes require max_children and a template.

Those limits distinguish population from instantaneous launch concurrency. A long-running Workflow can still drain through a smaller Runtime worker pool. Model selection stays per member: a DeepSeek preset can suggest deepseek-v4-pro for the orchestrator and deepseek-v4-flash for nearby workers, but users and agents may override any slot when the task calls for it.

Experimental search is a Workflow option

Experimental search generalizes the existing best-of-N recipe without adding a new product mode, scheduler, or sub-agent API. The proposed search spec would freeze the objective, baseline, model request and resolved version, public evidence, evaluator hash, hard gates, scoring rule, budgets, write scope, rounds, and review-only integration policy before admission; it is a design, not shipped code.

The current JS starter supports structured generation and read-only review with strategy: "search". Runtime-owned command gates, hidden evaluation, benchmark scoring, and clean-baseline replay are an explicit host seam still to wire; a candidate's self-verdict must never be promoted into evaluator truth. See Workflow Experimental Search.