返回 CodeWhale
wf_a3_partial_failure_synthesis.workflow.js
根目录 / docs / examples / dogfood-automatic / wf_a3_partial_failure_synthesis.workflow.js
1 /**
2 * #4131 WF-A3 — partial failure + synthesis.
3 *
4 * parallel() uses all-settled semantics: a failed slot becomes null; the run
5 * continues so a synthesizer can still produce an operator summary. The slot
6 * is null but not anonymous — `slots.errors` carries `{ index, kind, message }`
7 * for every drop, so the summary can name what was lost instead of guessing.
8 *
9 * Run: /workflow run docs/examples/dogfood-automatic/wf_a3_partial_failure_synthesis.workflow.js
10 *
11 * For pure VM proof without model spend, use workflow-js unit tests:
12 * cargo test -p codewhale-workflow-js --locked parallel_fan_out_maps_one_failure_to_null_slot
13 */
14 export default async function () {
15 phase("Parallel scouts");
16 const slots = await parallel([
17 () =>
18 task({
19 description: "Healthy scout A",
20 label: "scout-a",
21 type: "explore",
22 prompt: "Return the string READY_A. Read-only.",
23 }),
24 // Give this child an intentionally tiny budget so it starts, then fails
25 // deterministically at the runtime boundary. A model refusal is still a
26 // successful transport-level completion, while response-schema failures
27 // intentionally abort the whole workflow so they remain loud.
28 () =>
29 task({
30 description: "Deliberately failing scout B",
31 label: "scout-b-fail",
32 type: "explore",
33 tokenBudget: 1,
34 prompt:
35 "Inspect Cargo.toml and return a detailed workspace summary. This child intentionally has a one-token budget so parallel() exercises a failed null slot.",
36 }),
37 () =>
38 task({
39 description: "Healthy scout C",
40 label: "scout-c",
41 type: "explore",
42 prompt: "Return the string READY_C. Read-only.",
43 }),
44 ]);
45
46 phase("Synthesize");
47 const surviving = (slots || []).filter((s) => s != null);
48 // The typed failure ledger: which slot died, and of what.
49 const dropped = (slots.errors || []).map(
50 (e) => `slot ${e.index} (${e.kind}): ${e.message}`,
51 );
52 for (const line of dropped) {
53 log(`dropped: ${line}`);
54 }
55 const summary = await task({
56 description: "Synthesize from surviving parallel slots",
57 label: "synthesizer",
58 type: "general",
59 prompt: [
60 "Build one operator-facing summary from the surviving scout results.",
61 "Explicitly note which parallel slot failed, and why.",
62 `slot_count=${(slots || []).length} surviving=${surviving.length}`,
63 "dropped_slots:",
64 dropped.length ? dropped.join("\n") : "(none)",
65 "slots_json:",
66 JSON.stringify(slots),
67 ].join("\n"),
68 });
69
70 return {
71 scenario: "WF-A3",
72 slots,
73 surviving_count: surviving.length,
74 dropped_slots: dropped,
75 summary,
76 };
77 }
78
78 lines JAVASCRIPT