pi-background-tasks 1.0.4 → 1.0.6

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.
@@ -14,14 +14,15 @@ 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
27
  'pi-background-tasks.fusion-calibration-violation.v1';
27
28
  export const FUSION_TOOL_CALL_LOG_SCHEMA_VERSION = 'pi-background-tasks.fusion-tool-call.v1';
@@ -186,6 +187,7 @@ export interface ResolvedFusionModel {
186
187
  qualifiedId: string;
187
188
  thinkingLevel: FusionThinkingLevel;
188
189
  contextWindow: number;
190
+ maxOutputTokens: number;
189
191
  }
190
192
 
191
193
  export interface ResolvedFusionModels {
@@ -556,7 +558,11 @@ export interface FusionResultDetails {
556
558
  source: FusionSource;
557
559
  status: 'completed';
558
560
  context: { kind: FusionContextKind; policy_id: string };
559
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
561
+ tool_policy: {
562
+ candidate_tools: readonly string[];
563
+ evaluation_tools: readonly [];
564
+ merge_tools: readonly [];
565
+ };
560
566
  artifact_dir: string;
561
567
  models: {
562
568
  candidates: readonly [string, string, string];
@@ -595,6 +601,8 @@ export type FusionErrorCode =
595
601
  | 'child_stdin_failed'
596
602
  | 'child_event_invalid'
597
603
  | 'child_exit_failed'
604
+ | 'child_runtime_budget_exceeded'
605
+ | 'child_cache_policy_invalid'
598
606
  | 'child_timeout'
599
607
  | 'child_output_cap'
600
608
  | 'child_cancelled'
@@ -815,8 +823,17 @@ export interface FusionArtifactManifest {
815
823
  evaluation: FusionCapability;
816
824
  merge: FusionCapability;
817
825
  };
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 [] };
826
+ context: {
827
+ kind: FusionContextKind;
828
+ policy_id: string;
829
+ ledger_artifact?: string;
830
+ source_policy_artifact?: string;
831
+ };
832
+ tool_policy: {
833
+ candidate_tools: readonly string[];
834
+ evaluation_tools: readonly [];
835
+ merge_tools: readonly [];
836
+ };
820
837
  usage: FusionUsage;
821
838
  attempts: readonly FusionAttemptArtifactRecord[];
822
839
  artifacts: Readonly<Record<string, FusionArtifactRef>>;
@@ -900,7 +917,11 @@ export interface FusionBudgetPlanV1 {
900
917
  workflow: FusionWorkflowId;
901
918
  context: { kind: FusionContextKind; policy_id: string };
902
919
  fixed_candidate_policy: { capability: FusionCapability; tools: readonly string[] };
903
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
920
+ tool_policy: {
921
+ candidate_tools: readonly string[];
922
+ evaluation_tools: readonly [];
923
+ merge_tools: readonly [];
924
+ };
904
925
  policy: FusionBudgetPolicyDescriptor;
905
926
  routes: readonly FusionRouteCapacity[];
906
927
  stages: readonly FusionStageBudgetPlanEntry[];
@@ -913,7 +934,8 @@ export interface FusionBudgetPlanV1 {
913
934
 
914
935
  /** Documented, versioned budget policy. */
915
936
  export interface FusionBudgetPolicyDescriptor {
916
- id: 'fusion-budget-policy-v3';
937
+ id: 'fusion-budget-policy-v4';
938
+ route_output_reserve_strategy: 'max_fusion_contract_or_model_max';
917
939
  calibration_version: string;
918
940
  calibration_table: Readonly<Record<TokenBudgetFamily, TokenBudgetFamilyCalibration>>;
919
941
  reserved_output_tokens: number;
@@ -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
  }