@oisincoveney/pipeline 2.3.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,6 +70,18 @@ Whichever host you are on, run the same six steps:
70
70
  5. **Learn** — Once the gates pass, run `MoKa Learner` to store durable lessons from the run (qdrant memory) when there is something worth reusing. This mirrors the canonical pipeline's LEARN phase; skip it only when the run produced nothing reusable.
71
71
  6. **Synthesize** — Report only the evidence the agents actually returned: what passed, what the diff is, what the reviewers proved. Never fabricate or assume an outcome an agent did not report.
72
72
 
73
+ ## Task sizing, reliability & token budget
74
+
75
+ Token usage is the dominant cost and quality lever — it explains the bulk of agent performance variance, and context degrades well before a model's window fills. But the first job of sizing is **reliable completion**: a lane an agent can't finish is worthless however cheap. Size the work accordingly:
76
+
77
+ - **Size for reliable completion first.** Each lane must be small enough that a single agent session finishes it cleanly. If an agent times out, stalls, or returns having only *planned* without producing its artifact, the lane was **too big** — split it into smaller lanes (one file, section, or concern each); do **not** just raise the timeout, that re-runs the same flake. **Slow is fine; flaky is not** — many small lanes that each reliably complete beat one big lane that gambles. Lanes that share a file run sequentially; only truly independent lanes fan out. Treat repeated stalls as a decomposition bug, not bad luck.
78
+ - **Under-timeouts and permission walls are the real flake sources — not a step cap.** Per opencode's docs, an agent with no `steps`/`maxSteps` set "will continue to iterate until the model chooses to stop or the user interrupts the session" — i.e. **no hard step budget by default** (the MoKa agents set none). So a Code Writer that returns having only *planned* was not hitting a step limit; it was killed by too short a dispatch timeout or blocked on a denied read (e.g. `external_directory: deny`). Fixes: give long multi-file authoring runs **generous wall-clock** (do not kill them early — they are slow, not capped); scope lanes so they don't need denied/external reads; and only if you must *bound* a runaway agent, set `steps` in its config. Smaller lanes still help (less work = faster, fewer surprises), but "multi-file authoring can't be delegated" is a timeout/scoping issue, not an opencode limit.
79
+ - **Scale fan-out to complexity, not ambition.** A trivial change is one agent (or just do it inline); a bounded change is 1–3 lanes; only go wide for genuinely independent breadth. Code parallelizes poorly — keep writer lanes narrow (the pipeline caps `green`/code fan-out at 2 for exactly this reason).
80
+ - **Keep each agent's context small and high-signal.** Pass context by path and hand over the distilled `research.json`, never raw repo dumps. A lane that needs half the repo in its context is mis-scoped — split it.
81
+ - **Distilled returns.** Expect each sub-agent to return a ~1–2k-token summary of its result, not its full transcript. Gather the summary; don't re-read the work.
82
+ - **Re-dispatch once, with evidence.** On a gate `FAIL`, re-dispatch the failing lane a *single* time with concentrated failure evidence — do not thrash. Each fresh `opencode run` re-pays the full cold-start context tax (~35k tokens of standup before any work), so a retry loop is expensive; fix the input, not the dice.
83
+ - **Smallest roster that covers the work.** Every extra lane is another cold standup. Default to the fewest specialists that close the task; add a lane only when it genuinely runs independently.
84
+
73
85
  ## Rules
74
86
 
75
87
  - **Doctrine is host-neutral; only the Dispatch section is host-specific.** Do not leak `opencode run` syntax into an OpenCode run or Task-tool talk into a Claude run.
@@ -2,6 +2,13 @@ version: 1
2
2
  default_workflow: inspect
3
3
  orchestrator:
4
4
  profile: moka-orchestrator
5
+ token_budget:
6
+ default_context_window: 200000
7
+ max_context_pct: 50
8
+ fan_out_width:
9
+ default: 4
10
+ by_category:
11
+ green: 2
5
12
  entrypoints:
6
13
  quick:
7
14
  schedule: quick-schedule
@@ -51,6 +51,7 @@ function parsePipelineConfigParts(sources, projectRoot, sourcePaths = {
51
51
  schedules: pipeline.schedules,
52
52
  skills: profiles.skills,
53
53
  ...pipeline.task_context ? { task_context: pipeline.task_context } : {},
54
+ token_budget: pipeline.token_budget,
54
55
  version: 1,
55
56
  workflows: pipeline.workflows
56
57
  }, projectRoot, options);
@@ -116,6 +116,7 @@ declare const workflowNodeBaseSchema: z.ZodObject<{
116
116
  }, z.core.$strip>;
117
117
  type WorkflowNodeBase = z.infer<typeof workflowNodeBaseSchema>;
118
118
  type AgentWorkflowNode = WorkflowNodeBase & {
119
+ category?: string;
119
120
  kind: "agent";
120
121
  profile: string;
121
122
  };
@@ -220,8 +221,8 @@ declare const configSchema: z.ZodObject<{
220
221
  policy: z.ZodOptional<z.ZodObject<{
221
222
  commands: z.ZodOptional<z.ZodEnum<{
222
223
  allow: "allow";
223
- deny: "deny";
224
224
  "trusted-only": "trusted-only";
225
+ deny: "deny";
225
226
  }>>;
226
227
  modules: z.ZodOptional<z.ZodEnum<{
227
228
  allow: "allow";
@@ -245,8 +246,8 @@ declare const configSchema: z.ZodObject<{
245
246
  }, z.core.$strict>>>;
246
247
  default_profile: z.ZodOptional<z.ZodString>;
247
248
  mode: z.ZodEnum<{
248
- local: "local";
249
249
  hosted: "hosted";
250
+ local: "local";
250
251
  }>;
251
252
  provider: z.ZodLiteral<"toolhive">;
252
253
  authorization_env: z.ZodDefault<z.ZodString>;
@@ -289,10 +290,10 @@ declare const configSchema: z.ZodObject<{
289
290
  }, z.core.$strict>>;
290
291
  output: z.ZodOptional<z.ZodObject<{
291
292
  format: z.ZodEnum<{
292
- json_schema: "json_schema";
293
293
  text: "text";
294
294
  json: "json";
295
295
  jsonl: "jsonl";
296
+ json_schema: "json_schema";
296
297
  }>;
297
298
  repair: z.ZodOptional<z.ZodObject<{
298
299
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -310,7 +311,6 @@ declare const configSchema: z.ZodObject<{
310
311
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
311
312
  timeout_ms: z.ZodOptional<z.ZodNumber>;
312
313
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
313
- task: "task";
314
314
  read: "read";
315
315
  list: "list";
316
316
  grep: "grep";
@@ -318,6 +318,7 @@ declare const configSchema: z.ZodObject<{
318
318
  bash: "bash";
319
319
  edit: "edit";
320
320
  write: "write";
321
+ task: "task";
321
322
  }>>>;
322
323
  }, z.core.$strict>>>;
323
324
  runner_command: z.ZodDefault<z.ZodObject<{
@@ -343,8 +344,8 @@ declare const configSchema: z.ZodObject<{
343
344
  rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
344
345
  path: z.ZodString;
345
346
  source_root: z.ZodDefault<z.ZodEnum<{
346
- project: "project";
347
347
  package: "package";
348
+ project: "project";
348
349
  }>>;
349
350
  }, z.core.$strict>>>;
350
351
  runners: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -361,15 +362,14 @@ declare const configSchema: z.ZodObject<{
361
362
  disabled: "disabled";
362
363
  }>>>;
363
364
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
364
- json_schema: "json_schema";
365
365
  text: "text";
366
366
  json: "json";
367
367
  jsonl: "jsonl";
368
+ json_schema: "json_schema";
368
369
  }>>>;
369
370
  rules: z.ZodOptional<z.ZodBoolean>;
370
371
  skills: z.ZodOptional<z.ZodBoolean>;
371
372
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
372
- task: "task";
373
373
  read: "read";
374
374
  list: "list";
375
375
  grep: "grep";
@@ -377,6 +377,7 @@ declare const configSchema: z.ZodObject<{
377
377
  bash: "bash";
378
378
  edit: "edit";
379
379
  write: "write";
380
+ task: "task";
380
381
  }>>>;
381
382
  }, z.core.$strict>;
382
383
  command: z.ZodOptional<z.ZodString>;
@@ -471,8 +472,8 @@ declare const configSchema: z.ZodObject<{
471
472
  schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
472
473
  description: z.ZodOptional<z.ZodString>;
473
474
  baseline: z.ZodEnum<{
474
- quick: "quick";
475
475
  execute: "execute";
476
+ quick: "quick";
476
477
  }>;
477
478
  max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
478
479
  node_catalog: z.ZodOptional<z.ZodString>;
@@ -484,13 +485,34 @@ declare const configSchema: z.ZodObject<{
484
485
  skills: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
485
486
  path: z.ZodString;
486
487
  source_root: z.ZodDefault<z.ZodEnum<{
487
- project: "project";
488
488
  package: "package";
489
+ project: "project";
489
490
  }>>;
490
491
  }, z.core.$strict>>>;
491
492
  task_context: z.ZodOptional<z.ZodObject<{
492
493
  type: z.ZodString;
493
494
  }, z.core.$loose>>;
495
+ best_of_n: z.ZodOptional<z.ZodObject<{
496
+ categories: z.ZodDefault<z.ZodArray<z.ZodString>>;
497
+ enabled: z.ZodDefault<z.ZodBoolean>;
498
+ n: z.ZodDefault<z.ZodNumber>;
499
+ }, z.core.$strict>>;
500
+ context_handoff: z.ZodOptional<z.ZodObject<{
501
+ enabled: z.ZodDefault<z.ZodBoolean>;
502
+ model: z.ZodOptional<z.ZodString>;
503
+ }, z.core.$strict>>;
504
+ parallel_worktrees: z.ZodOptional<z.ZodObject<{
505
+ enabled: z.ZodDefault<z.ZodBoolean>;
506
+ }, z.core.$strict>>;
507
+ token_budget: z.ZodDefault<z.ZodObject<{
508
+ default_context_window: z.ZodDefault<z.ZodNumber>;
509
+ max_context_pct: z.ZodDefault<z.ZodNumber>;
510
+ model_context_windows: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
511
+ fan_out_width: z.ZodDefault<z.ZodObject<{
512
+ default: z.ZodDefault<z.ZodNumber>;
513
+ by_category: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
514
+ }, z.core.$strict>>;
515
+ }, z.core.$strict>>;
494
516
  version: z.ZodLiteral<1>;
495
517
  workflows: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
496
518
  description: z.ZodOptional<z.ZodString>;
@@ -380,6 +380,7 @@ const workflowNodeBaseSchema = z.object({
380
380
  });
381
381
  const workflowNodeSchema = z.lazy(() => z.discriminatedUnion("kind", [
382
382
  workflowNodeBaseSchema.extend({
383
+ category: z.string().min(1).optional(),
383
384
  kind: z.literal("agent"),
384
385
  profile: z.string()
385
386
  }).strict(),
@@ -438,6 +439,38 @@ const profilesFileSchema = z.object({
438
439
  skills: strictRecord(pathRefSchema).default({}),
439
440
  version: z.literal(1)
440
441
  }).strict();
442
+ const fanOutWidthSchema = z.object({
443
+ default: z.number().int().positive().default(4),
444
+ by_category: strictRecord(z.number().int().positive()).default({})
445
+ }).strict();
446
+ const tokenBudgetSchema = z.object({
447
+ default_context_window: z.number().int().positive().default(2e5),
448
+ max_context_pct: z.number().positive().max(100).default(50),
449
+ model_context_windows: strictRecord(z.number().int().positive()).default({}),
450
+ fan_out_width: fanOutWidthSchema.default({
451
+ default: 4,
452
+ by_category: {}
453
+ })
454
+ }).strict();
455
+ const DEFAULT_TOKEN_BUDGET = {
456
+ default_context_window: 2e5,
457
+ max_context_pct: 50,
458
+ model_context_windows: {},
459
+ fan_out_width: {
460
+ default: 4,
461
+ by_category: {}
462
+ }
463
+ };
464
+ const contextHandoffSchema = z.object({
465
+ enabled: z.boolean().default(false),
466
+ model: z.string().optional()
467
+ }).strict();
468
+ const parallelWorktreesSchema = z.object({ enabled: z.boolean().default(false) }).strict();
469
+ const bestOfNSchema = z.object({
470
+ categories: z.array(z.string()).default(["green"]),
471
+ enabled: z.boolean().default(false),
472
+ n: z.number().int().positive().default(1)
473
+ }).strict();
441
474
  const pipelineFileSchema = z.object({
442
475
  default_workflow: z.string(),
443
476
  entrypoints: strictRecord(entrypointSchema).default({}),
@@ -459,6 +492,10 @@ const pipelineFileSchema = z.object({
459
492
  }),
460
493
  schedules: strictRecord(schedulePolicySchema).default({}),
461
494
  task_context: taskContextResolverSchema.optional(),
495
+ best_of_n: bestOfNSchema.optional(),
496
+ context_handoff: contextHandoffSchema.optional(),
497
+ parallel_worktrees: parallelWorktreesSchema.optional(),
498
+ token_budget: tokenBudgetSchema.default(DEFAULT_TOKEN_BUDGET),
462
499
  workflows: strictRecord(workflowSchema).default({}),
463
500
  version: z.literal(1)
464
501
  }).strict();
@@ -489,6 +526,10 @@ const configSchema = z.object({
489
526
  schedules: strictRecord(schedulePolicySchema).default({}),
490
527
  skills: strictRecord(pathRefSchema).default({}),
491
528
  task_context: taskContextResolverSchema.optional(),
529
+ best_of_n: bestOfNSchema.optional(),
530
+ context_handoff: contextHandoffSchema.optional(),
531
+ parallel_worktrees: parallelWorktreesSchema.optional(),
532
+ token_budget: tokenBudgetSchema.default(DEFAULT_TOKEN_BUDGET),
492
533
  version: z.literal(1),
493
534
  workflows: strictRecord(workflowSchema).default({})
494
535
  }).strict().superRefine(validateConfigReferences);
@@ -35,12 +35,28 @@ function validatePipelineConfig(rawConfig, projectRoot, options = {}) {
35
35
  validateProfile(profileId, profile, runner, config, issues, projectRoot, options);
36
36
  }
37
37
  validateHookConfig(config, issues, projectRoot, options);
38
+ validateTokenBudget(config, issues);
38
39
  for (const [ruleId, rule] of Object.entries(config.rules)) validatePath(`rules.${ruleId}.path`, rule, projectRoot, issues, options);
39
40
  for (const [skillId, skill] of Object.entries(config.skills)) validatePath(`skills.${skillId}.path`, skill, projectRoot, issues, options);
40
41
  for (const [workflowId, workflow] of Object.entries(config.workflows)) validateWorkflow(workflowId, workflow, config, issues, projectRoot, options);
41
42
  if (issues.length > 0) throw validationError(issues);
42
43
  return config;
43
44
  }
45
+ function knownNodeCategories(config) {
46
+ const categories = /* @__PURE__ */ new Set();
47
+ for (const catalog of Object.values(config.scheduler.node_catalogs)) {
48
+ for (const category of catalog.required_categories) categories.add(category);
49
+ for (const node of Object.values(catalog.nodes)) categories.add(node.category);
50
+ }
51
+ return categories;
52
+ }
53
+ function validateTokenBudget(config, issues) {
54
+ const known = knownNodeCategories(config);
55
+ for (const category of Object.keys(config.token_budget.fan_out_width.by_category)) if (!known.has(category)) issues.push({
56
+ path: `token_budget.fan_out_width.by_category.${category}`,
57
+ message: `fan-out width cap references unknown node category '${category}'`
58
+ });
59
+ }
44
60
  function validateRegistryIds(name, registry, issues) {
45
61
  for (const id of Object.keys(registry)) if (!ID_RE.test(id)) issues.push({
46
62
  path: `${name}.${id}`,
@@ -1,21 +1,42 @@
1
1
  //#region src/model-resolver.ts
2
2
  const DISABLED_MODELS_ENV = "PIPELINE_DISABLED_MODELS";
3
- function selectNodeModel(node) {
4
- return fallbackModelSelection(node.models ?? []);
3
+ function selectNodeModel(node, options) {
4
+ return fallbackModelSelection(node.models ?? [], options);
5
5
  }
6
- function fallbackModelSelection(models) {
6
+ function fallbackModelSelection(models, options) {
7
7
  if (models.length === 0) return {
8
8
  reason: "node declares no model fallback array",
9
9
  skipped: []
10
10
  };
11
- return enabledModelSelection(models, disabledModels());
11
+ const disabled = disabledModels();
12
+ const enabled = models.filter((candidate) => !disabled.has(candidate));
13
+ const disabledSkipped = models.filter((candidate) => disabled.has(candidate));
14
+ if (!options) {
15
+ const model = enabled[0];
16
+ return {
17
+ model,
18
+ reason: selectionReason(model),
19
+ skipped: disabledSkipped
20
+ };
21
+ }
22
+ return sizedSelection(enabled, disabledSkipped, options);
12
23
  }
13
- function enabledModelSelection(models, disabled) {
14
- const model = models.find((candidate) => !disabled.has(candidate));
24
+ function sizedSelection(enabled, disabledSkipped, options) {
25
+ const { estimatedTokens, budget } = options;
26
+ const required = estimatedTokens / (budget.max_context_pct / 100);
27
+ const tooSmall = [];
28
+ for (const candidate of enabled) {
29
+ const window = budget.model_context_windows[candidate] ?? budget.default_context_window;
30
+ if (window >= required) return {
31
+ model: candidate,
32
+ reason: `selected '${candidate}' (window ${window}) — holds estimated ${estimatedTokens} tokens within the ${budget.max_context_pct}% context cap`,
33
+ skipped: [...disabledSkipped, ...tooSmall]
34
+ };
35
+ tooSmall.push(candidate);
36
+ }
15
37
  return {
16
- model,
17
- reason: selectionReason(model),
18
- skipped: models.filter((candidate) => disabled.has(candidate))
38
+ reason: `estimated context ${estimatedTokens} tokens exceeds ${budget.max_context_pct}% of every available model window`,
39
+ skipped: [...disabledSkipped, ...tooSmall]
19
40
  };
20
41
  }
21
42
  function selectionReason(model) {
@@ -5,13 +5,13 @@ import { z } from "zod";
5
5
  //#region src/moka-submit.d.ts
6
6
  declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
7
7
  "workflow.start": "workflow.start";
8
+ "node.finish": "node.finish";
9
+ "node.start": "node.start";
8
10
  "workflow.success": "workflow.success";
9
11
  "workflow.failure": "workflow.failure";
10
12
  "workflow.complete": "workflow.complete";
11
- "node.start": "node.start";
12
13
  "node.success": "node.success";
13
14
  "node.error": "node.error";
14
- "node.finish": "node.finish";
15
15
  "gate.failure": "gate.failure";
16
16
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
17
17
  failure: z.ZodDefault<z.ZodEnum<{
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
94
94
  }, z.core.$strict>>;
95
95
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
96
96
  "workflow.start": "workflow.start";
97
+ "node.finish": "node.finish";
98
+ "node.start": "node.start";
97
99
  "workflow.success": "workflow.success";
98
100
  "workflow.failure": "workflow.failure";
99
101
  "workflow.complete": "workflow.complete";
100
- "node.start": "node.start";
101
102
  "node.success": "node.success";
102
103
  "node.error": "node.error";
103
- "node.finish": "node.finish";
104
104
  "gate.failure": "gate.failure";
105
105
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
106
106
  failure: z.ZodDefault<z.ZodEnum<{
@@ -160,8 +160,8 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
160
160
  }, z.core.$strict>>;
161
161
  serviceAccountName: z.ZodOptional<z.ZodString>;
162
162
  mode: z.ZodEnum<{
163
- full: "full";
164
163
  quick: "quick";
164
+ full: "full";
165
165
  }>;
166
166
  schedulePath: z.ZodOptional<z.ZodString>;
167
167
  scheduleYaml: z.ZodOptional<z.ZodString>;
@@ -206,13 +206,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
206
206
  }, z.core.$strict>>;
207
207
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
208
208
  "workflow.start": "workflow.start";
209
+ "node.finish": "node.finish";
210
+ "node.start": "node.start";
209
211
  "workflow.success": "workflow.success";
210
212
  "workflow.failure": "workflow.failure";
211
213
  "workflow.complete": "workflow.complete";
212
- "node.start": "node.start";
213
214
  "node.success": "node.success";
214
215
  "node.error": "node.error";
215
- "node.finish": "node.finish";
216
216
  "gate.failure": "gate.failure";
217
217
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
218
218
  failure: z.ZodDefault<z.ZodEnum<{
@@ -628,6 +628,7 @@ async function executeNodeAttemptCycle(node, context, attempt, previous) {
628
628
  const beforeSnapshot = context.nodeStateStore.getSnapshot(node.id);
629
629
  if (beforeSnapshot) context.nodeStateStore.setSnapshot(node.id, diffChangedFiles(beforeSnapshot, afterSnapshot, context.worktreePath));
630
630
  context.nodeStateStore.recordOutput(node.id, last.output);
631
+ context.nodeStateStore.recordHandoff(node.id, last.handoff);
631
632
  emitNodeOutputRecorded(context, node, attempt, last.output);
632
633
  recordNodeEvent(context, node.id, {
633
634
  at: now(),
@@ -15,6 +15,7 @@ declare class WorkflowPlannerError extends Error {
15
15
  interface PlannedWorkflowNode {
16
16
  artifacts?: WorkflowNode["artifacts"];
17
17
  builtin?: string;
18
+ category?: string;
18
19
  children?: PlannedWorkflowNode[];
19
20
  command?: string[];
20
21
  dependents: string[];
@@ -185,10 +185,14 @@ function createWorkflowGraph(nodes, nodeIds = new Set(nodes.map((node) => node.i
185
185
  function uniqueExistingNeeds(node, nodeIds) {
186
186
  return uniqueStrings(node.needs.filter((need) => nodeIds.has(need)));
187
187
  }
188
+ function agentNodeCategory(node) {
189
+ return node.kind === "agent" ? node.category : void 0;
190
+ }
188
191
  function toPlannedNode(node, index) {
189
192
  const planned = {
190
193
  artifacts: node.artifacts,
191
194
  builtin: "builtin" in node ? node.builtin : void 0,
195
+ category: agentNodeCategory(node),
192
196
  command: "command" in node ? node.command : void 0,
193
197
  children: node.kind === "parallel" ? node.nodes.map((child, childIndex) => toPlannedNode(child, childIndex)) : void 0,
194
198
  dependents: [],
@@ -94,6 +94,7 @@ declare const scheduleArtifactSchema: z.ZodObject<{
94
94
  } | undefined;
95
95
  timeout_ms?: number | undefined;
96
96
  } & {
97
+ category?: string;
97
98
  kind: "agent";
98
99
  profile: string;
99
100
  }) | ({
@@ -478,6 +479,7 @@ declare const scheduleArtifactSchema: z.ZodObject<{
478
479
  } | undefined;
479
480
  timeout_ms?: number | undefined;
480
481
  } & {
482
+ category?: string;
481
483
  kind: "agent";
482
484
  profile: string;
483
485
  }) | ({
@@ -861,6 +863,7 @@ declare const scheduleArtifactSchema: z.ZodObject<{
861
863
  } | undefined;
862
864
  timeout_ms?: number | undefined;
863
865
  } & {
866
+ category?: string;
864
867
  kind: "agent";
865
868
  profile: string;
866
869
  }) | ({
@@ -1245,6 +1248,7 @@ declare const scheduleArtifactSchema: z.ZodObject<{
1245
1248
  } | undefined;
1246
1249
  timeout_ms?: number | undefined;
1247
1250
  } & {
1251
+ category?: string;
1248
1252
  kind: "agent";
1249
1253
  profile: string;
1250
1254
  }) | ({
@@ -5,6 +5,7 @@ import { createRunnerLaunchPlan, runLaunchPlan } from "../runner.js";
5
5
  import { normalizeRunnerOutput } from "../runner-output.js";
6
6
  import { loadBacklogPlanningContext } from "../schedule/backlog-context.js";
7
7
  import { baselineScheduleArtifact } from "../schedule/baseline.js";
8
+ import { expandBestOfNCandidates } from "../schedule/passes/candidates.js";
8
9
  import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.js";
9
10
  import { isCoverageNode, isImplementationNode } from "../schedule/scheduling-roles.js";
10
11
  import { addGeneratedImplementationCoverage } from "../schedule/passes/coverage.js";
@@ -91,7 +92,7 @@ async function generateScheduleArtifact(options) {
91
92
  const planningContext = { ...loadBacklogPlanningContext(options.task, options.worktreePath) };
92
93
  const generatedArtifact = await planScheduleArtifact(baseline, policy.planner_profile, options, planningContext);
93
94
  assertSchedulePassOrder();
94
- const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, addGeneratedImplementationCoverage(options.config, generatedArtifact))), planningContext);
95
+ const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, expandBestOfNCandidates(options.config, addGeneratedImplementationCoverage(options.config, generatedArtifact)))), planningContext);
95
96
  validateScheduleArtifact(options.config, artifact, planningContext);
96
97
  compileScheduleArtifact(options.config, artifact, options.worktreePath);
97
98
  return {
@@ -102,6 +103,7 @@ async function generateScheduleArtifact(options) {
102
103
  function assertSchedulePassOrder() {
103
104
  if (SCHEDULE_PASS_ORDER.join("\0") !== [
104
105
  "coverage",
106
+ "candidates",
105
107
  "models",
106
108
  "ids",
107
109
  "references"
@@ -43,8 +43,8 @@ declare const runnerDeliverySchema: z.ZodObject<{
43
43
  declare const mokaSubmissionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
44
44
  kind: z.ZodLiteral<"graph">;
45
45
  mode: z.ZodEnum<{
46
- full: "full";
47
46
  quick: "quick";
47
+ full: "full";
48
48
  }>;
49
49
  }, z.core.$strict>, z.ZodObject<{
50
50
  argv: z.ZodArray<z.ZodString>;
@@ -104,8 +104,8 @@ declare const runnerCommandPayloadSchema: z.ZodObject<{
104
104
  submission: z.ZodDefault<z.ZodDiscriminatedUnion<[z.ZodObject<{
105
105
  kind: z.ZodLiteral<"graph">;
106
106
  mode: z.ZodEnum<{
107
- full: "full";
108
107
  quick: "quick";
108
+ full: "full";
109
109
  }>;
110
110
  }, z.core.$strict>, z.ZodObject<{
111
111
  argv: z.ZodArray<z.ZodString>;
@@ -10,8 +10,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
10
10
  at: z.ZodOptional<z.ZodString>;
11
11
  sequence: z.ZodNumber;
12
12
  type: z.ZodEnum<{
13
- "workflow.start": "workflow.start";
14
13
  "workflow.planned": "workflow.planned";
14
+ "workflow.start": "workflow.start";
15
15
  }>;
16
16
  workflowPlan: z.ZodObject<{
17
17
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -55,10 +55,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
55
55
  }>;
56
56
  }, z.core.$strip>;
57
57
  type: z.ZodEnum<{
58
- "node.start": "node.start";
59
- "node.finish": "node.finish";
60
58
  "agent.finish": "agent.finish";
61
59
  "agent.start": "agent.start";
60
+ "node.finish": "node.finish";
61
+ "node.start": "node.start";
62
62
  }>;
63
63
  }, z.core.$strip>, z.ZodObject<{
64
64
  at: z.ZodOptional<z.ZodString>;
@@ -180,8 +180,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
180
180
  at: z.ZodOptional<z.ZodString>;
181
181
  sequence: z.ZodNumber;
182
182
  type: z.ZodEnum<{
183
- "workflow.start": "workflow.start";
184
183
  "workflow.planned": "workflow.planned";
184
+ "workflow.start": "workflow.start";
185
185
  }>;
186
186
  workflowPlan: z.ZodObject<{
187
187
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -225,10 +225,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
225
225
  }>;
226
226
  }, z.core.$strip>;
227
227
  type: z.ZodEnum<{
228
- "node.start": "node.start";
229
- "node.finish": "node.finish";
230
228
  "agent.finish": "agent.finish";
231
229
  "agent.start": "agent.start";
230
+ "node.finish": "node.finish";
231
+ "node.start": "node.start";
232
232
  }>;
233
233
  }, z.core.$strip>, z.ZodObject<{
234
234
  at: z.ZodOptional<z.ZodString>;