pi-background-tasks 1.0.4 → 1.0.7

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.
@@ -190,6 +190,7 @@ Return only JSON matching this exact closed schema:
190
190
  "limitations": ["non-blank statement of what you could not cover"]
191
191
  }
192
192
  Use an empty findings array when no issues were found; do not omit verified or limitations.
193
+ Do not wrap the JSON in Markdown fences or prose. Emit exactly one bare JSON object.
193
194
 
194
195
  Do not inflate severity and do not invent issues to appear thorough. If the work is correct, say so plainly in verified/limitations. A report with no findings that names the evidence behind that conclusion is a valid and valuable result; a padded report is not.
195
196
 
@@ -14,16 +14,19 @@ export const FUSION_MODEL_CONFIG_SCHEMA_VERSION = 'pi-background-tasks.fusion-mo
14
14
  export const FUSION_LEGACY_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v4';
15
15
  export const FUSION_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v5';
16
16
  export const FUSION_EVALUATION_SCHEMA_VERSION = 'pi-background-tasks.fusion-evaluation.v1';
17
- export const FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION = 'pi-background-tasks.fusion-validation-candidate.v1';
17
+ export const FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION =
18
+ 'pi-background-tasks.fusion-validation-candidate.v1';
18
19
  export const FUSION_LEGACY_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v4';
19
20
  export const FUSION_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v5';
20
21
  export const FUSION_LEGACY_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v3';
21
22
  export const FUSION_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v4';
22
23
  export const FUSION_CONTEXT_LEDGER_SCHEMA_VERSION = 'pi-background-tasks.fusion-context-ledger.v2';
23
24
  export const FUSION_SOURCE_POLICY_SCHEMA_VERSION = 'pi-background-tasks.fusion-source-policy.v1';
24
- export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.v3';
25
+ export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.v4';
25
26
  export const FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION =
26
- 'pi-background-tasks.fusion-calibration-violation.v1';
27
+ 'pi-background-tasks.fusion-calibration-violation.v2';
28
+ export const FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION =
29
+ 'pi-background-tasks.fusion-validation-candidate-contract-event.v1';
27
30
  export const FUSION_TOOL_CALL_LOG_SCHEMA_VERSION = 'pi-background-tasks.fusion-tool-call.v1';
28
31
 
29
32
  /**
@@ -186,6 +189,7 @@ export interface ResolvedFusionModel {
186
189
  qualifiedId: string;
187
190
  thinkingLevel: FusionThinkingLevel;
188
191
  contextWindow: number;
192
+ maxOutputTokens: number;
189
193
  }
190
194
 
191
195
  export interface ResolvedFusionModels {
@@ -556,7 +560,11 @@ export interface FusionResultDetails {
556
560
  source: FusionSource;
557
561
  status: 'completed';
558
562
  context: { kind: FusionContextKind; policy_id: string };
559
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
563
+ tool_policy: {
564
+ candidate_tools: readonly string[];
565
+ evaluation_tools: readonly [];
566
+ merge_tools: readonly [];
567
+ };
560
568
  artifact_dir: string;
561
569
  models: {
562
570
  candidates: readonly [string, string, string];
@@ -595,6 +603,8 @@ export type FusionErrorCode =
595
603
  | 'child_stdin_failed'
596
604
  | 'child_event_invalid'
597
605
  | 'child_exit_failed'
606
+ | 'child_runtime_budget_exceeded'
607
+ | 'child_cache_policy_invalid'
598
608
  | 'child_timeout'
599
609
  | 'child_output_cap'
600
610
  | 'child_cancelled'
@@ -761,7 +771,12 @@ export interface FusionChildRunResult {
761
771
  model: string;
762
772
  qualifiedId: string;
763
773
  text: string;
774
+ /** Aggregate usage across the complete child agent loop. */
764
775
  usage: FusionUsage;
776
+ /** First provider request, used for like-for-like prompt forecast calibration. */
777
+ firstRequestUsage?: FusionUsage;
778
+ /** Number of provider requests represented by aggregate usage. */
779
+ providerRequestCount?: number;
765
780
  events: Buffer;
766
781
  stderr: Buffer;
767
782
  exitCode: number;
@@ -815,8 +830,17 @@ export interface FusionArtifactManifest {
815
830
  evaluation: FusionCapability;
816
831
  merge: FusionCapability;
817
832
  };
818
- context: { kind: FusionContextKind; policy_id: string; ledger_artifact?: string; source_policy_artifact?: string };
819
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
833
+ context: {
834
+ kind: FusionContextKind;
835
+ policy_id: string;
836
+ ledger_artifact?: string;
837
+ source_policy_artifact?: string;
838
+ };
839
+ tool_policy: {
840
+ candidate_tools: readonly string[];
841
+ evaluation_tools: readonly [];
842
+ merge_tools: readonly [];
843
+ };
820
844
  usage: FusionUsage;
821
845
  attempts: readonly FusionAttemptArtifactRecord[];
822
846
  artifacts: Readonly<Record<string, FusionArtifactRef>>;
@@ -900,7 +924,11 @@ export interface FusionBudgetPlanV1 {
900
924
  workflow: FusionWorkflowId;
901
925
  context: { kind: FusionContextKind; policy_id: string };
902
926
  fixed_candidate_policy: { capability: FusionCapability; tools: readonly string[] };
903
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
927
+ tool_policy: {
928
+ candidate_tools: readonly string[];
929
+ evaluation_tools: readonly [];
930
+ merge_tools: readonly [];
931
+ };
904
932
  policy: FusionBudgetPolicyDescriptor;
905
933
  routes: readonly FusionRouteCapacity[];
906
934
  stages: readonly FusionStageBudgetPlanEntry[];
@@ -913,7 +941,8 @@ export interface FusionBudgetPlanV1 {
913
941
 
914
942
  /** Documented, versioned budget policy. */
915
943
  export interface FusionBudgetPolicyDescriptor {
916
- id: 'fusion-budget-policy-v3';
944
+ id: 'fusion-budget-policy-v4';
945
+ route_output_reserve_strategy: 'max_fusion_contract_or_model_max';
917
946
  calibration_version: string;
918
947
  calibration_table: Readonly<Record<TokenBudgetFamily, TokenBudgetFamilyCalibration>>;
919
948
  reserved_output_tokens: number;
@@ -940,6 +969,8 @@ export interface FusionCalibrationViolation {
940
969
  rate_source: TokenBudgetRateSource;
941
970
  prompt_utf8_bytes: number;
942
971
  prompt_sha256: string;
972
+ observation_scope: 'first_provider_request';
973
+ provider_request_count: number;
943
974
  forecast_input_tokens: number;
944
975
  billed_input_tokens: number;
945
976
  billed_input_breakdown: {
@@ -9,8 +9,14 @@ import {
9
9
  writeSync,
10
10
  } from 'node:fs';
11
11
  import { dirname } from 'node:path';
12
+ import { parseJsonText } from './core/common.js';
12
13
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
13
14
  import { Type, type Static } from 'typebox';
15
+ import {
16
+ estimateInputTokens,
17
+ knownJsonSegment,
18
+ resolveTokenBudgetFamily,
19
+ } from './core/context/token-budget.js';
14
20
  import {
15
21
  FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
16
22
  FUSION_WEB_FETCH_TOOL_NAME,
@@ -26,32 +32,75 @@ import {
26
32
  parseFusionSourcePolicy,
27
33
  } from './core/fusion/source-policy.js';
28
34
  import {
35
+ nonAnthropicFusionCacheObservation,
36
+ normalizeFusionClaudeCachePayload,
37
+ type FusionClaudeCacheObservation,
38
+ } from './core/fusion/claude-cache.js';
39
+ import {
40
+ FUSION_CHILD_MAX_PROVIDER_REQUESTS,
41
+ FUSION_CHILD_MAX_TOOL_CALLS,
29
42
  FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
43
+ FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
30
44
  FUSION_CHILD_RESULT_PREFIX,
45
+ FUSION_CHILD_SAFETY_RESERVE_TOKENS,
46
+ FUSION_CHILD_SETTLEMENT_PREFIX,
31
47
  FUSION_RESEARCH_ENABLED_ENV,
48
+ FUSION_RUNTIME_GUARD_PREFIX,
49
+ FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
32
50
  FUSION_SOURCE_POLICY_PATH_ENV,
33
51
  FUSION_SOURCE_POLICY_SHA256_ENV,
34
52
  FUSION_TOOL_CALL_LOG_PATH_ENV,
35
53
  FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION,
36
54
  FUSION_TOOL_CALL_SEAL_SUFFIX,
37
55
  buildFusionChildResultMetadata,
56
+ buildFusionChildSettlement,
38
57
  type FusionChildResultMetadata,
58
+ type FusionChildSettlementRecord,
59
+ type FusionRuntimeGuardCode,
60
+ type FusionRuntimeGuardRecord,
39
61
  } from './core/fusion/child-protocol.js';
40
62
 
41
63
  export {
64
+ FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT,
65
+ FUSION_CLAUDE_CACHE_DEFAULT_RETENTION,
66
+ FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
67
+ FUSION_CLAUDE_CACHE_RETENTION_ENV,
68
+ nonAnthropicFusionCacheObservation,
69
+ normalizeFusionClaudeCachePayload,
70
+ resolveFusionClaudeCachePolicy,
71
+ type FusionClaudeCacheNormalization,
72
+ type FusionClaudeCacheObservation,
73
+ type FusionClaudeCachePolicySource,
74
+ type FusionClaudeCacheRetention,
75
+ } from './core/fusion/claude-cache.js';
76
+
77
+ export {
78
+ FUSION_CHILD_MAX_PROVIDER_REQUESTS,
79
+ FUSION_CHILD_MAX_TOOL_CALLS,
42
80
  FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
81
+ FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
43
82
  FUSION_CHILD_RESULT_PREFIX,
44
83
  FUSION_CHILD_RESULT_SCHEMA_VERSION,
84
+ FUSION_CHILD_SAFETY_RESERVE_TOKENS,
85
+ FUSION_CHILD_SETTLEMENT_PREFIX,
86
+ FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
45
87
  FUSION_RESEARCH_ENABLED_ENV,
88
+ FUSION_RUNTIME_GUARD_PREFIX,
89
+ FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
46
90
  FUSION_SOURCE_POLICY_PATH_ENV,
47
91
  FUSION_SOURCE_POLICY_SHA256_ENV,
48
92
  FUSION_TOOL_CALL_LOG_PATH_ENV,
49
93
  FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION,
50
94
  FUSION_TOOL_CALL_SEAL_SUFFIX,
51
95
  buildFusionChildResultMetadata,
96
+ buildFusionChildSettlement,
52
97
  type FusionChildResultMetadata,
53
98
  type FusionChildResultUsageMetadata,
99
+ type FusionChildSettlementFailureReason,
100
+ type FusionChildSettlementRecord,
54
101
  type FusionChildTextBlockMetadata,
102
+ type FusionRuntimeGuardCode,
103
+ type FusionRuntimeGuardRecord,
55
104
  } from './core/fusion/child-protocol.js';
56
105
 
57
106
  const FUSION_CHILD_O_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
@@ -263,6 +312,237 @@ async function writeMetadata(record: FusionChildResultMetadata): Promise<void> {
263
312
  });
264
313
  }
265
314
 
315
+ async function writeSettlement(record: FusionChildSettlementRecord): Promise<void> {
316
+ const line = `${FUSION_CHILD_SETTLEMENT_PREFIX}${JSON.stringify(record)}\n`;
317
+ await new Promise<void>((resolve, reject) => {
318
+ process.stderr.write(line, (error) => {
319
+ if (error) reject(error);
320
+ else resolve();
321
+ });
322
+ });
323
+ }
324
+
325
+ async function writeRuntimeGuard(record: FusionRuntimeGuardRecord): Promise<void> {
326
+ const line = `${FUSION_RUNTIME_GUARD_PREFIX}${JSON.stringify(record)}\n`;
327
+ await new Promise<void>((resolve, reject) => {
328
+ process.stderr.write(line, (error) => {
329
+ if (error) reject(error);
330
+ else resolve();
331
+ });
332
+ });
333
+ }
334
+
335
+ export interface FusionRuntimeRequestEvaluationInput {
336
+ payload: unknown;
337
+ provider: string | undefined;
338
+ model: string | undefined;
339
+ contextWindowTokens: number | undefined;
340
+ maxOutputTokens: number | undefined;
341
+ requestOrdinal: number;
342
+ toolCallCount: number;
343
+ }
344
+
345
+ function invalidFusionRuntimeRequest(
346
+ input: FusionRuntimeRequestEvaluationInput,
347
+ detail: string,
348
+ code: Extract<
349
+ FusionRuntimeGuardCode,
350
+ 'provider_payload_invalid' | 'claude_cache_policy'
351
+ > = 'provider_payload_invalid',
352
+ ): FusionRuntimeGuardRecord {
353
+ const contextWindowTokens =
354
+ input.contextWindowTokens !== undefined &&
355
+ Number.isSafeInteger(input.contextWindowTokens) &&
356
+ input.contextWindowTokens > 0
357
+ ? input.contextWindowTokens
358
+ : 0;
359
+ const modelOutputTokens =
360
+ input.maxOutputTokens !== undefined &&
361
+ Number.isSafeInteger(input.maxOutputTokens) &&
362
+ input.maxOutputTokens > 0
363
+ ? input.maxOutputTokens
364
+ : 0;
365
+ const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
366
+ const allowedInputTokens = Math.max(
367
+ 0,
368
+ contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
369
+ );
370
+ const emptyPayload = Buffer.alloc(0);
371
+ return {
372
+ schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
373
+ code,
374
+ provider: input.provider ?? 'unknown',
375
+ model: input.model ?? 'unknown',
376
+ request_ordinal: input.requestOrdinal,
377
+ tool_call_count: input.toolCallCount,
378
+ payload_bytes: 0,
379
+ payload_sha256: sha256(emptyPayload),
380
+ estimated_input_tokens: 0,
381
+ context_window_tokens: contextWindowTokens,
382
+ reserved_output_tokens: reservedOutputTokens,
383
+ safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
384
+ allowed_input_tokens: allowedInputTokens,
385
+ message: `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${detail}`,
386
+ };
387
+ }
388
+
389
+ export interface PreparedFusionRuntimeRequest {
390
+ payload: unknown;
391
+ guard: FusionRuntimeGuardRecord | undefined;
392
+ }
393
+
394
+ export function prepareFusionRuntimeRequest(
395
+ input: FusionRuntimeRequestEvaluationInput,
396
+ ): PreparedFusionRuntimeRequest {
397
+ try {
398
+ const serialized: unknown = JSON.stringify(input.payload);
399
+ if (typeof serialized !== 'string') {
400
+ throw new Error('provider payload serialized to a non-string value');
401
+ }
402
+ const payload = parseJsonText(serialized);
403
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
404
+ throw new Error('provider payload must serialize to a JSON object');
405
+ }
406
+ if (JSON.stringify(payload) !== serialized) {
407
+ throw new Error('provider payload does not have a stable JSON serialization');
408
+ }
409
+ return { payload, guard: evaluateFusionRuntimeRequest({ ...input, payload }) };
410
+ } catch (error) {
411
+ return {
412
+ payload: input.payload,
413
+ guard: invalidFusionRuntimeRequest(
414
+ input,
415
+ error instanceof Error ? error.message : String(error),
416
+ ),
417
+ };
418
+ }
419
+ }
420
+
421
+ export function evaluateFusionRuntimeRequest(
422
+ input: FusionRuntimeRequestEvaluationInput,
423
+ ): FusionRuntimeGuardRecord | undefined {
424
+ let code: FusionRuntimeGuardCode | undefined;
425
+ let message = '';
426
+ let payloadBytes = Buffer.alloc(0);
427
+ let estimatedInputTokens = 0;
428
+ let allowedInputTokens = 0;
429
+ const provider = input.provider ?? 'unknown';
430
+ const model = input.model ?? 'unknown';
431
+ const contextWindowTokens = input.contextWindowTokens ?? 0;
432
+ const reservedOutputTokens = Math.max(
433
+ FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
434
+ input.maxOutputTokens ?? 0,
435
+ );
436
+ try {
437
+ if (input.provider === undefined || input.model === undefined) {
438
+ throw new Error('active model is unavailable');
439
+ }
440
+ if (!Number.isSafeInteger(contextWindowTokens) || contextWindowTokens <= 0) {
441
+ throw new Error('active model context window is unavailable');
442
+ }
443
+ if (
444
+ input.maxOutputTokens === undefined ||
445
+ !Number.isSafeInteger(input.maxOutputTokens) ||
446
+ input.maxOutputTokens <= 0
447
+ ) {
448
+ throw new Error('active model maximum output tokens are unavailable');
449
+ }
450
+ allowedInputTokens =
451
+ contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS;
452
+ if (allowedInputTokens <= 0) {
453
+ throw new Error('active model has no safe provider input capacity');
454
+ }
455
+ const payloadText = JSON.stringify(input.payload);
456
+ if (payloadText === undefined) throw new Error('provider payload serialized to undefined');
457
+ payloadBytes = Buffer.from(payloadText, 'utf8');
458
+ const family = resolveTokenBudgetFamily({ provider, model });
459
+ estimatedInputTokens = estimateInputTokens({
460
+ family: family.family,
461
+ calibrationBacked: family.backed,
462
+ familyResolution: family.resolution,
463
+ allowedInputTokens,
464
+ scope: 'fusion',
465
+ segments: [knownJsonSegment(payloadText)],
466
+ }).tokens;
467
+ if (input.requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
468
+ code = 'provider_request_limit';
469
+ message = `fusion child reached provider request ${String(input.requestOrdinal)}, exceeding the ${String(FUSION_CHILD_MAX_PROVIDER_REQUESTS)}-request execution limit`;
470
+ } else if (estimatedInputTokens > allowedInputTokens) {
471
+ code = 'provider_request_budget';
472
+ message = `fusion child blocked provider request ${String(input.requestOrdinal)} before transport: exact final payload is ${String(payloadBytes.length)} UTF-8 bytes (estimated <= ${String(estimatedInputTokens)} input tokens), exceeding ${String(allowedInputTokens)} allowed input tokens after reserving ${String(reservedOutputTokens)} model output + ${String(FUSION_CHILD_SAFETY_RESERVE_TOKENS)} safety from the ${String(contextWindowTokens)}-token context window`;
473
+ }
474
+ } catch (error) {
475
+ code = 'provider_payload_invalid';
476
+ message = `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${error instanceof Error ? error.message : String(error)}`;
477
+ }
478
+ if (code === undefined) return undefined;
479
+ return {
480
+ schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
481
+ code,
482
+ provider,
483
+ model,
484
+ request_ordinal: input.requestOrdinal,
485
+ tool_call_count: input.toolCallCount,
486
+ payload_bytes: payloadBytes.length,
487
+ payload_sha256: sha256(payloadBytes),
488
+ estimated_input_tokens: estimatedInputTokens,
489
+ context_window_tokens: contextWindowTokens,
490
+ reserved_output_tokens: reservedOutputTokens,
491
+ safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
492
+ allowed_input_tokens: allowedInputTokens,
493
+ message,
494
+ };
495
+ }
496
+
497
+ export interface FusionRuntimeToolLimitEvaluationInput {
498
+ provider: string | undefined;
499
+ model: string | undefined;
500
+ contextWindowTokens: number | undefined;
501
+ maxOutputTokens: number | undefined;
502
+ requestOrdinal: number;
503
+ toolCallCount: number;
504
+ }
505
+
506
+ export function evaluateFusionRuntimeToolLimit(
507
+ input: FusionRuntimeToolLimitEvaluationInput,
508
+ ): FusionRuntimeGuardRecord | undefined {
509
+ if (input.toolCallCount <= FUSION_CHILD_MAX_TOOL_CALLS) return undefined;
510
+ const contextWindowTokens =
511
+ input.contextWindowTokens !== undefined &&
512
+ Number.isSafeInteger(input.contextWindowTokens) &&
513
+ input.contextWindowTokens > 0
514
+ ? input.contextWindowTokens
515
+ : 0;
516
+ const modelOutputTokens =
517
+ input.maxOutputTokens !== undefined &&
518
+ Number.isSafeInteger(input.maxOutputTokens) &&
519
+ input.maxOutputTokens > 0
520
+ ? input.maxOutputTokens
521
+ : 0;
522
+ const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
523
+ const allowedInputTokens = Math.max(
524
+ 0,
525
+ contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
526
+ );
527
+ const emptyPayload = Buffer.alloc(0);
528
+ return {
529
+ schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
530
+ code: 'tool_call_limit',
531
+ provider: input.provider ?? 'unknown',
532
+ model: input.model ?? 'unknown',
533
+ request_ordinal: input.requestOrdinal,
534
+ tool_call_count: input.toolCallCount,
535
+ payload_bytes: 0,
536
+ payload_sha256: sha256(emptyPayload),
537
+ estimated_input_tokens: 0,
538
+ context_window_tokens: contextWindowTokens,
539
+ reserved_output_tokens: reservedOutputTokens,
540
+ safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
541
+ allowed_input_tokens: allowedInputTokens,
542
+ message: `fusion child reached tool call ${String(input.toolCallCount)}, exceeding the ${String(FUSION_CHILD_MAX_TOOL_CALLS)}-call execution limit`,
543
+ };
544
+ }
545
+
266
546
  function strictFusionWebFetchArgs(args: unknown): FusionWebFetchParamsValue {
267
547
  if (typeof args !== 'object' || args === null || Array.isArray(args)) {
268
548
  throw new Error(`${FUSION_WEB_FETCH_TOOL_NAME} arguments must be an object`);
@@ -401,6 +681,82 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
401
681
  }
402
682
  const declaredResearchUrls = researchEnabled === '1' ? loadDeclaredResearchUrls() : undefined;
403
683
  const fetchAuditMetadata = new Map<string, FusionWebFetchAuditMetadata>();
684
+ let providerRequestCount = 0;
685
+ let toolCallCount = 0;
686
+ let runtimeGuardFailed = false;
687
+ let settlementPublished = false;
688
+ const childResultRecords: FusionChildResultMetadata[] = [];
689
+ let pendingCacheObservation: FusionClaudeCacheObservation | undefined;
690
+
691
+ pi.on('before_provider_request', async (event, ctx) => {
692
+ providerRequestCount += 1;
693
+ if (runtimeGuardFailed) {
694
+ ctx.abort();
695
+ return event.payload;
696
+ }
697
+
698
+ const model = ctx.model;
699
+ let cacheNormalizedPayload = event.payload;
700
+ let cacheObservation: FusionClaudeCacheObservation;
701
+ try {
702
+ if (model?.provider === 'anthropic') {
703
+ const supportsLongCacheRetention =
704
+ model.compat !== undefined && 'supportsLongCacheRetention' in model.compat
705
+ ? model.compat.supportsLongCacheRetention
706
+ : undefined;
707
+ const normalized = normalizeFusionClaudeCachePayload({
708
+ payload: event.payload,
709
+ requestOrdinal: providerRequestCount,
710
+ supportsLongCacheRetention:
711
+ typeof supportsLongCacheRetention === 'boolean'
712
+ ? supportsLongCacheRetention
713
+ : undefined,
714
+ });
715
+ cacheNormalizedPayload = normalized.payload;
716
+ cacheObservation = normalized.observation;
717
+ } else {
718
+ cacheObservation = nonAnthropicFusionCacheObservation(providerRequestCount);
719
+ }
720
+ } catch (error) {
721
+ const guard = invalidFusionRuntimeRequest(
722
+ {
723
+ payload: event.payload,
724
+ provider: model?.provider,
725
+ model: model?.id,
726
+ contextWindowTokens: model?.contextWindow,
727
+ maxOutputTokens: model?.maxTokens,
728
+ requestOrdinal: providerRequestCount,
729
+ toolCallCount,
730
+ },
731
+ `Claude cache policy rejected the final payload: ${error instanceof Error ? error.message : String(error)}`,
732
+ 'claude_cache_policy',
733
+ );
734
+ runtimeGuardFailed = true;
735
+ latchAuditProcessFailure();
736
+ ctx.abort();
737
+ await writeRuntimeGuard(guard);
738
+ return event.payload;
739
+ }
740
+ pendingCacheObservation = cacheObservation;
741
+
742
+ const prepared = prepareFusionRuntimeRequest({
743
+ payload: cacheNormalizedPayload,
744
+ provider: model?.provider,
745
+ model: model?.id,
746
+ contextWindowTokens: model?.contextWindow,
747
+ maxOutputTokens: model?.maxTokens,
748
+ requestOrdinal: providerRequestCount,
749
+ toolCallCount,
750
+ });
751
+ const guard = prepared.guard;
752
+ if (guard === undefined) return prepared.payload;
753
+ runtimeGuardFailed = true;
754
+ latchAuditProcessFailure();
755
+ ctx.abort();
756
+ await writeRuntimeGuard(guard);
757
+ return prepared.payload;
758
+ });
759
+
404
760
  if (toolCallLogPath !== undefined) {
405
761
  // Establish the audit file before tools can run. Exclusive creation makes a reused
406
762
  // attempt path or redirected file loud instead of appending to untrusted history.
@@ -441,7 +797,8 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
441
797
  }
442
798
  phase = 'finalizing';
443
799
  const unmatchedStarts = starts.size;
444
- const complete = normalSettlement && !auditFailed && unmatchedStarts === 0;
800
+ const complete =
801
+ normalSettlement && !auditFailed && !runtimeGuardFailed && unmatchedStarts === 0;
445
802
  if (!complete) {
446
803
  auditFailed = true;
447
804
  latchAuditProcessFailure();
@@ -460,9 +817,30 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
460
817
  }
461
818
  };
462
819
 
463
- pi.on('tool_call', (event) => {
820
+ pi.on('tool_call', async (event, ctx) => {
464
821
  try {
465
822
  requireOpen('tool_call');
823
+ if (runtimeGuardFailed) {
824
+ ctx.abort();
825
+ return { block: true, reason: 'fusion child runtime guard already refused the run' };
826
+ }
827
+ toolCallCount += 1;
828
+ const model = ctx.model;
829
+ const guard = evaluateFusionRuntimeToolLimit({
830
+ provider: model?.provider,
831
+ model: model?.id,
832
+ contextWindowTokens: model?.contextWindow,
833
+ maxOutputTokens: model?.maxTokens,
834
+ requestOrdinal: providerRequestCount,
835
+ toolCallCount,
836
+ });
837
+ if (guard !== undefined) {
838
+ runtimeGuardFailed = true;
839
+ latchAuditProcessFailure();
840
+ ctx.abort();
841
+ await writeRuntimeGuard(guard);
842
+ return { block: true, reason: guard.message };
843
+ }
466
844
  if (starts.has(event.toolCallId)) {
467
845
  throw new Error(`fusion tool-call log duplicate start for ${event.toolCallId}`);
468
846
  }
@@ -470,8 +848,9 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
470
848
  startedAt: Date.now(),
471
849
  toolName: event.toolName,
472
850
  });
851
+ return undefined;
473
852
  } catch (error) {
474
- failAudit(error);
853
+ return failAudit(error);
475
854
  }
476
855
  });
477
856
  pi.on('tool_result', (event) => {
@@ -597,6 +976,36 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
597
976
 
598
977
  pi.on('message_end', async (event) => {
599
978
  if (event.message.role !== 'assistant') return;
600
- await writeMetadata(buildFusionChildResultMetadata(event.message));
979
+ const cacheObservation = pendingCacheObservation;
980
+ if (cacheObservation === undefined) {
981
+ latchAuditProcessFailure();
982
+ throw new Error('fusion child assistant result has no matching cache-policy observation');
983
+ }
984
+ pendingCacheObservation = undefined;
985
+ const record = buildFusionChildResultMetadata(event.message, cacheObservation);
986
+ await writeMetadata(record);
987
+ childResultRecords.push(record);
988
+ });
989
+ pi.on('agent_settled', async (_event, ctx) => {
990
+ if (settlementPublished) {
991
+ latchAuditProcessFailure();
992
+ throw new Error('fusion child received duplicate agent_settled for result settlement');
993
+ }
994
+ if (!ctx.isIdle()) {
995
+ latchAuditProcessFailure();
996
+ throw new Error('fusion child result settlement observed agent_settled while not idle');
997
+ }
998
+ settlementPublished = true;
999
+ const cacheObservationFailed = pendingCacheObservation !== undefined;
1000
+ const settlement = buildFusionChildSettlement(
1001
+ childResultRecords,
1002
+ runtimeGuardFailed,
1003
+ cacheObservationFailed,
1004
+ );
1005
+ if (settlement.status !== 'complete') latchAuditProcessFailure();
1006
+ await writeSettlement(settlement);
1007
+ });
1008
+ pi.on('session_shutdown', () => {
1009
+ if (!settlementPublished) latchAuditProcessFailure();
601
1010
  });
602
1011
  }