pi-background-tasks 0.7.7 → 0.9.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.
@@ -13,10 +13,6 @@ import {
13
13
  } from './evaluation.js';
14
14
  import { FusionChildRunError, runPiChild, type RunPiChildOptions } from './pi-child.js';
15
15
  import {
16
- FUSION_CANDIDATE_SYSTEM_PROMPT,
17
- FUSION_EVALUATION_REPAIR_SYSTEM_PROMPT,
18
- FUSION_EVALUATOR_SYSTEM_PROMPT,
19
- FUSION_MERGER_SYSTEM_PROMPT,
20
16
  buildBlindEvaluationInput,
21
17
  buildCandidatePrompt,
22
18
  buildEvaluationPrompt,
@@ -26,11 +22,18 @@ import {
26
22
  type AnonymousFusionCandidate,
27
23
  } from './prompts.js';
28
24
  import {
25
+ FUSION_BRAINSTORM_WORKFLOW,
26
+ resolveWorkflowCapability,
27
+ type FusionWorkflowProfile,
28
+ } from './workflows.js';
29
+ import {
30
+ FUSION_DEFAULT_CAPABILITY,
29
31
  FUSION_RESULT_SCHEMA_VERSION,
30
32
  FusionError,
31
33
  addFusionUsage,
32
34
  createEmptyFusionUsage,
33
35
  type FusionCalibrationViolation,
36
+ type FusionCapability,
34
37
  type FusionCanonicalInputV3,
35
38
  type FusionCandidateId,
36
39
  type FusionContextOmissionLedgerV2,
@@ -62,6 +65,9 @@ export interface FusionWorkflowInput {
62
65
  contextLedger: FusionContextOmissionLedgerV2;
63
66
  config: FusionModelConfigV1;
64
67
  models: ResolvedFusionModels;
68
+ candidateCapability?: FusionCapability | undefined;
69
+ /** Stage framing and capability policy. Defaults to the brainstorm workflow. */
70
+ profile?: FusionWorkflowProfile | undefined;
65
71
  signal?: AbortSignal | undefined;
66
72
  onProgress?: FusionProgressSink | undefined;
67
73
  }
@@ -179,21 +185,25 @@ function childOptions(
179
185
  model: ResolvedFusionModel,
180
186
  stage: FusionStage,
181
187
  attempt: number,
188
+ capability: FusionCapability,
182
189
  systemPrompt: string,
183
190
  userPrompt: string,
184
191
  signal: AbortSignal,
185
192
  slot?: CandidateSlot,
193
+ toolCallLogPath?: string,
186
194
  ): RunPiChildOptions {
187
195
  const out: RunPiChildOptions = {
188
196
  stage,
189
197
  attempt,
190
198
  cwd: input.cwd,
191
199
  model,
200
+ capability,
192
201
  systemPrompt,
193
202
  userPrompt,
194
203
  signal,
195
204
  };
196
205
  if (slot !== undefined) out.slot = slot;
206
+ if (toolCallLogPath !== undefined) out.toolCallLogPath = toolCallLogPath;
197
207
  return out;
198
208
  }
199
209
 
@@ -324,11 +334,22 @@ export class FusionOrchestrator {
324
334
  }
325
335
 
326
336
  async run(input: FusionWorkflowInput): Promise<FusionRunResult> {
337
+ const profile = input.profile ?? FUSION_BRAINSTORM_WORKFLOW;
338
+ // Workflow policy, not caller input: a fixed-capability workflow rejects each
339
+ // other capability here, before a single child exists, rather than silently
340
+ // substituting its own and running a review that never read the code.
341
+ const candidateCapability = resolveWorkflowCapability(profile, input.candidateCapability);
327
342
  const storeOptions: CreateFusionArtifactStoreOptions = {
328
343
  cwd: input.cwd,
344
+ profile,
329
345
  source: input.source,
330
346
  config: input.config,
331
347
  models: input.models,
348
+ capabilities: {
349
+ candidate: candidateCapability,
350
+ evaluation: FUSION_DEFAULT_CAPABILITY,
351
+ merge: FUSION_DEFAULT_CAPABILITY,
352
+ },
332
353
  };
333
354
  if (input.sessionId !== undefined) storeOptions.sessionId = input.sessionId;
334
355
  if (this.now !== undefined) storeOptions.now = this.now;
@@ -344,6 +365,8 @@ export class FusionOrchestrator {
344
365
  const budget = new FusionBudget(
345
366
  input.models,
346
367
  input.canonicalInput.conversation_projection.policy.id,
368
+ candidateCapability,
369
+ profile,
347
370
  );
348
371
  const budgetPlan = budget.plan(input.canonicalInput);
349
372
  await store.writeBudgetPlan(budgetPlan);
@@ -363,6 +386,8 @@ export class FusionOrchestrator {
363
386
  usage,
364
387
  budget,
365
388
  calibrationWarnings,
389
+ profile,
390
+ candidateCapability,
366
391
  );
367
392
  await store.transition('candidates_complete');
368
393
  input.onProgress?.({ type: 'state', state: 'candidates_complete' });
@@ -381,6 +406,7 @@ export class FusionOrchestrator {
381
406
  blindInput,
382
407
  budget,
383
408
  calibrationWarnings,
409
+ profile,
384
410
  );
385
411
  await store.writeEvaluationJson(evaluation);
386
412
  await store.transition('evaluation_complete');
@@ -390,7 +416,7 @@ export class FusionOrchestrator {
390
416
  input.onProgress?.({ type: 'state', state: 'merging' });
391
417
  const mergeInput = buildMergeInput(input.canonicalInput, shuffled.candidates, evaluation);
392
418
  const mergePrompt = buildMergePrompt(mergeInput);
393
- budget.assertStagePrompt('merge', FUSION_MERGER_SYSTEM_PROMPT, mergePrompt);
419
+ budget.assertStagePrompt('merge', profile.mergerSystemPrompt, mergePrompt);
394
420
  input.onProgress?.({ type: 'merge_started' });
395
421
  const merged = await this.runChildWithRetry(
396
422
  input,
@@ -398,9 +424,11 @@ export class FusionOrchestrator {
398
424
  usage,
399
425
  input.models.merger,
400
426
  'merge',
401
- FUSION_MERGER_SYSTEM_PROMPT,
427
+ profile.mergerSystemPrompt,
402
428
  mergePrompt,
403
429
  input.signal ?? new AbortController().signal,
430
+ // Stage policy, not caller input: evaluator and merger are always reasoning-only.
431
+ FUSION_DEFAULT_CAPABILITY,
404
432
  undefined,
405
433
  'md',
406
434
  );
@@ -412,7 +440,7 @@ export class FusionOrchestrator {
412
440
  budget,
413
441
  calibrationWarnings,
414
442
  'merge',
415
- FUSION_MERGER_SYSTEM_PROMPT,
443
+ profile.mergerSystemPrompt,
416
444
  mergePrompt,
417
445
  merged,
418
446
  );
@@ -426,6 +454,7 @@ export class FusionOrchestrator {
426
454
  details: {
427
455
  schema_version: FUSION_RESULT_SCHEMA_VERSION,
428
456
  run_id: store.runId,
457
+ workflow: profile.id,
429
458
  source: input.source,
430
459
  status: 'completed',
431
460
  artifact_dir: store.artifactDir,
@@ -480,14 +509,17 @@ export class FusionOrchestrator {
480
509
  usage: FusionUsage,
481
510
  budget: FusionBudget,
482
511
  calibrationWarnings: FusionCalibrationViolation[],
512
+ profile: FusionWorkflowProfile,
513
+ candidateCapability: FusionCapability,
483
514
  ): Promise<readonly CandidateResult[]> {
484
515
  const controller = new AbortController();
485
516
  const abortListener = () => controller.abort();
486
517
  input.signal?.addEventListener('abort', abortListener, { once: true });
487
518
  if (input.signal?.aborted) controller.abort();
519
+ const systemPrompt = profile.candidateSystemPrompt(candidateCapability);
488
520
  const prompt = buildCandidatePrompt(input.canonicalInput);
489
521
  for (const slot of [1, 2, 3] as const) {
490
- budget.assertStagePrompt('candidate', FUSION_CANDIDATE_SYSTEM_PROMPT, prompt, slot);
522
+ budget.assertStagePrompt('candidate', systemPrompt, prompt, slot);
491
523
  }
492
524
  let primaryError: unknown;
493
525
  let completed = 0;
@@ -507,9 +539,10 @@ export class FusionOrchestrator {
507
539
  usage,
508
540
  model,
509
541
  'candidate',
510
- FUSION_CANDIDATE_SYSTEM_PROMPT,
542
+ systemPrompt,
511
543
  prompt,
512
544
  controller.signal,
545
+ candidateCapability,
513
546
  slot,
514
547
  'md',
515
548
  ).then(async (result) => {
@@ -520,7 +553,7 @@ export class FusionOrchestrator {
520
553
  budget,
521
554
  calibrationWarnings,
522
555
  'candidate',
523
- FUSION_CANDIDATE_SYSTEM_PROMPT,
556
+ systemPrompt,
524
557
  prompt,
525
558
  result,
526
559
  slot,
@@ -562,9 +595,10 @@ export class FusionOrchestrator {
562
595
  blindInput: Parameters<typeof buildEvaluationPrompt>[0],
563
596
  budget: FusionBudget,
564
597
  calibrationWarnings: FusionCalibrationViolation[],
598
+ profile: FusionWorkflowProfile,
565
599
  ): Promise<FusionEvaluationV1> {
566
600
  const firstPrompt = buildEvaluationPrompt(blindInput);
567
- budget.assertStagePrompt('evaluation', FUSION_EVALUATOR_SYSTEM_PROMPT, firstPrompt);
601
+ budget.assertStagePrompt('evaluation', profile.evaluatorSystemPrompt, firstPrompt);
568
602
  const first = await this.runEvaluationAttempt(
569
603
  input,
570
604
  store,
@@ -574,6 +608,7 @@ export class FusionOrchestrator {
574
608
  firstPrompt,
575
609
  1,
576
610
  false,
611
+ profile,
577
612
  );
578
613
  if (first.evaluation !== undefined) return first.evaluation;
579
614
  const errors = boundedEvaluationErrors(first.errors);
@@ -586,7 +621,7 @@ export class FusionOrchestrator {
586
621
  });
587
622
  budget.assertStagePrompt(
588
623
  'evaluation_repair',
589
- FUSION_EVALUATION_REPAIR_SYSTEM_PROMPT,
624
+ profile.evaluationRepairSystemPrompt,
590
625
  repairPrompt,
591
626
  );
592
627
  const second = await this.runEvaluationAttempt(
@@ -598,6 +633,7 @@ export class FusionOrchestrator {
598
633
  repairPrompt,
599
634
  2,
600
635
  true,
636
+ profile,
601
637
  );
602
638
  if (second.evaluation !== undefined) return second.evaluation;
603
639
  throw new FusionError(
@@ -619,11 +655,12 @@ export class FusionOrchestrator {
619
655
  prompt: string,
620
656
  attempt: 1 | 2,
621
657
  repair: boolean,
658
+ profile: FusionWorkflowProfile,
622
659
  ): Promise<EvaluationAttemptResult> {
623
660
  input.onProgress?.({ type: 'evaluation_started', attempt, repair });
624
661
  const systemPrompt = repair
625
- ? FUSION_EVALUATION_REPAIR_SYSTEM_PROMPT
626
- : FUSION_EVALUATOR_SYSTEM_PROMPT;
662
+ ? profile.evaluationRepairSystemPrompt
663
+ : profile.evaluatorSystemPrompt;
627
664
  const result = await this.runChildWithRetry(
628
665
  input,
629
666
  store,
@@ -633,6 +670,8 @@ export class FusionOrchestrator {
633
670
  systemPrompt,
634
671
  prompt,
635
672
  input.signal ?? new AbortController().signal,
673
+ // Stage policy, not caller input: evaluator and merger are always reasoning-only.
674
+ FUSION_DEFAULT_CAPABILITY,
636
675
  undefined,
637
676
  'txt',
638
677
  attempt,
@@ -700,6 +739,7 @@ export class FusionOrchestrator {
700
739
  systemPrompt: string,
701
740
  userPrompt: string,
702
741
  signal: AbortSignal,
742
+ capability: FusionCapability,
703
743
  slot: CandidateSlot | undefined,
704
744
  responseKind: 'md' | 'txt',
705
745
  fixedAttempt?: 1 | 2,
@@ -709,9 +749,24 @@ export class FusionOrchestrator {
709
749
  if (stage === 'candidate' && slot !== undefined) {
710
750
  input.onProgress?.({ type: 'candidate_started', slot, attempt: logicalAttempt });
711
751
  }
752
+ const toolCallLogPath =
753
+ capability !== 'reason'
754
+ ? store.childToolCallLogPath(stage, slot, logicalAttempt)
755
+ : undefined;
712
756
  try {
713
757
  return await this.childRunner(
714
- childOptions(input, model, stage, logicalAttempt, systemPrompt, userPrompt, signal, slot),
758
+ childOptions(
759
+ input,
760
+ model,
761
+ stage,
762
+ logicalAttempt,
763
+ capability,
764
+ systemPrompt,
765
+ userPrompt,
766
+ signal,
767
+ slot,
768
+ toolCallLogPath,
769
+ ),
715
770
  );
716
771
  } catch (error) {
717
772
  if (!signal.aborted && retryableSpawn(error, launchTry) && launchTry === 1) continue;