pi-background-tasks 2.1.4 → 2.4.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.
Files changed (45) hide show
  1. package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
  2. package/PUBLISHING.md +2 -0
  3. package/README.md +16 -8
  4. package/TESTING.md +23 -14
  5. package/TEST_PLAN.md +13 -12
  6. package/THIRD_PARTY_NOTICES.md +30 -0
  7. package/docs/INDEX.md +8 -4
  8. package/docs/choose-a-workflow.md +5 -2
  9. package/docs/commands/claude-cache.md +50 -0
  10. package/docs/concepts/context-projection-and-budgeting.md +4 -2
  11. package/docs/getting-started.md +3 -0
  12. package/docs/manifest.json +86 -22
  13. package/docs/operations/configuration.md +15 -1
  14. package/docs/operations/releasing.md +6 -3
  15. package/docs/operations/troubleshooting.md +3 -1
  16. package/docs/read-before-edit.md +4 -1
  17. package/docs/reference/runtime-contracts.md +53 -53
  18. package/docs/subsystems/anthropic-attribution.md +63 -0
  19. package/docs/subsystems/attested-pi-runs.md +2 -2
  20. package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
  21. package/docs/subsystems/delegation.md +34 -17
  22. package/docs/subsystems/docs-freshness-gate.md +6 -6
  23. package/docs/subsystems/fusion.md +2 -2
  24. package/docs/tools/bg_delegate.md +33 -16
  25. package/docs/tools/bg_result.md +2 -2
  26. package/docs/tools/bg_run.md +5 -0
  27. package/extensions/anthropic-attribution.ts +1 -0
  28. package/package.json +4 -2
  29. package/src/core/anthropic-attribution-path.ts +26 -0
  30. package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
  31. package/src/core/attested-pi-run.ts +10 -1
  32. package/src/core/common.ts +2 -1
  33. package/src/core/context/token-budget.ts +16 -3
  34. package/src/core/delegate/artifacts.ts +18 -12
  35. package/src/core/delegate/budget.ts +78 -33
  36. package/src/core/delegate/launch.ts +48 -16
  37. package/src/core/delegate/result-package.ts +16 -0
  38. package/src/core/delegate/runner.ts +45 -2
  39. package/src/core/delegate/seed.ts +12 -0
  40. package/src/core/delegate/types.ts +22 -3
  41. package/src/core/fusion/config.ts +1 -1
  42. package/src/core/fusion/pi-child.ts +7 -124
  43. package/src/core/registry.ts +4 -1
  44. package/src/delegate-child-extension.ts +377 -71
  45. package/src/delegate-extension.ts +58 -6
@@ -64,13 +64,17 @@ const AUDIT_ENV = 'PIPELINE_ANTHROPIC_ATTRIBUTION_AUDIT_PATH';
64
64
  const CACHE_RETENTION_ENV = 'PI_CACHE_RETENTION';
65
65
  export const ANTHROPIC_CACHE_RETENTION_ENTRY = 'pipeline-anthropic-cache-retention';
66
66
  const ANTHROPIC_CACHE_RETENTION_SCHEMA = 'pipeline.anthropic_cache_retention.v1';
67
+ export const ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL = 'pi-anthropic-attribution:claim:v1';
68
+ const ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA = 'pi-anthropic-attribution.claim.v1';
67
69
  const NATIVE_ATTESTATION_PLACEHOLDER = '00000';
68
70
  const ANTHROPIC_CACHE_CONTROL_BREAKPOINT_LIMIT = 4;
69
71
 
70
- // Source: ravshansbox/pi-anthropic-sps. Keep exact-match semantics so the
71
- // repo-local attribution shim is idempotent with the global sanitizer.
72
+ // Sanitization behavior derived from the MIT-licensed ravshansbox/pi-anthropic-sps
73
+ // extension at commit 17409b5615f0ec0625776bc5434f92f2c55e3fd0. Keep exact-match
74
+ // semantics and all known Pi prompt variants; unrelated system text is preserved.
72
75
  const ANTHROPIC_SYSTEM_PROMPT_BAD_LINES = new Set([
73
76
  '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)',
77
+ '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)',
74
78
  '- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing',
75
79
  ]);
76
80
 
@@ -329,7 +333,13 @@ interface PiCommandConfigLike {
329
333
  readonly handler: (args: string, ctx: PiContextLike) => Promise<void> | void;
330
334
  }
331
335
 
336
+ interface PiEventBusLike {
337
+ emit(channel: string, data: unknown): void;
338
+ on(channel: string, handler: (data: unknown) => void): () => void;
339
+ }
340
+
332
341
  export interface PiExtensionHost extends PiProviderRegistrationHost {
342
+ readonly events: PiEventBusLike;
333
343
  on(
334
344
  eventName: 'session_start' | 'session_shutdown' | 'session_tree' | 'before_agent_start',
335
345
  handler: (event: unknown, ctx: PiContextLike) => void,
@@ -1867,11 +1877,54 @@ function cacheRetentionLabel(retention: CacheRetention): string {
1867
1877
  }
1868
1878
  }
1869
1879
 
1880
+ interface AnthropicAttributionClaimProbe {
1881
+ readonly schema_version: typeof ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA;
1882
+ readonly acknowledge: () => void;
1883
+ }
1884
+
1885
+ function isAnthropicAttributionClaimProbe(value: unknown): value is AnthropicAttributionClaimProbe {
1886
+ return (
1887
+ isPlainObject(value) &&
1888
+ value['schema_version'] === ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA &&
1889
+ typeof value['acknowledge'] === 'function'
1890
+ );
1891
+ }
1892
+
1893
+ /**
1894
+ * Prevent two independently installed copies from registering duplicate provider
1895
+ * hooks and `/claude-cache` commands in one Pi runtime. Pi loads extension factories
1896
+ * sequentially and its EventBus dispatches listeners synchronously, so an existing
1897
+ * owner acknowledges this probe before emit() returns. The winning extension only
1898
+ * publishes ownership after every registration below succeeds; a factory that throws
1899
+ * cannot strand a false claim that suppresses a healthy later copy.
1900
+ */
1870
1901
  export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
1902
+ const acknowledgements: true[] = [];
1903
+ const probe: AnthropicAttributionClaimProbe = {
1904
+ schema_version: ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA,
1905
+ acknowledge: () => {
1906
+ acknowledgements.push(true);
1907
+ },
1908
+ };
1909
+ pi.events.emit(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, probe);
1910
+ if (acknowledgements.length > 0) return;
1911
+
1871
1912
  let sessionCacheRetention: Exclude<CacheRetention, 'none'> | undefined;
1872
1913
  const getSessionOverride = (): Exclude<CacheRetention, 'none'> | undefined =>
1873
1914
  sessionCacheRetention;
1874
1915
 
1916
+ // Registration is global but route-scoped by provider name. Keeping it at
1917
+ // factory scope avoids lifecycle-dependent provider availability; the custom
1918
+ // transport derives session/model headers from the attributed payload.
1919
+ pi.registerProvider('anthropic', {
1920
+ api: 'anthropic-messages',
1921
+ streamSimple: (model, context, options) =>
1922
+ streamAnthropicViaBetaMessages(model, context, {
1923
+ ...(options ?? {}),
1924
+ cacheRetention: resolveCacheRetentionPreference(options, getSessionOverride()),
1925
+ }),
1926
+ });
1927
+
1875
1928
  pi.registerCommand('claude-cache', {
1876
1929
  description: 'Show or set Claude cache retention for this session (short, long, default)',
1877
1930
  handler: (args, ctx) => {
@@ -1902,7 +1955,6 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
1902
1955
 
1903
1956
  pi.on('session_start', (_event, ctx) => {
1904
1957
  sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
1905
- registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
1906
1958
  });
1907
1959
 
1908
1960
  pi.on('session_shutdown', () => {
@@ -1913,13 +1965,8 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
1913
1965
  sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
1914
1966
  });
1915
1967
 
1916
- pi.on('before_agent_start', (_event, ctx) => {
1917
- registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
1918
- });
1919
-
1920
1968
  pi.on('before_provider_request', (event, ctx) => {
1921
1969
  if (!isAnthropicContext(ctx)) return undefined;
1922
- registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
1923
1970
  return rewriteAnthropicRequestPayload({
1924
1971
  payload: event.payload,
1925
1972
  ctx,
@@ -1927,4 +1974,10 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
1927
1974
  headerRegistered: true,
1928
1975
  });
1929
1976
  });
1977
+
1978
+ // Publish ownership last. Extension loading is sequential, so later independent
1979
+ // copies probe this responder and become inert instead of registering duplicates.
1980
+ pi.events.on(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, (value) => {
1981
+ if (isAnthropicAttributionClaimProbe(value)) value.acknowledge();
1982
+ });
1930
1983
  }
@@ -150,9 +150,18 @@ export function attestedPiChildEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
150
150
  return out;
151
151
  }
152
152
 
153
- export function buildAttestedPiArgv(input: StructuredPiLaunchRequest): string[] {
153
+ export function buildAttestedPiArgv(
154
+ input: StructuredPiLaunchRequest,
155
+ attributionExtensionPath?: string,
156
+ ): string[] {
154
157
  validateStructuredPiLaunchRequest(input);
155
158
  const args = ['pi', '--mode', 'json', '--provider', input.provider, '--model', input.model];
159
+ if (input.provider === 'anthropic') {
160
+ if (!attributionExtensionPath?.trim()) {
161
+ throw new Error('Anthropic attested Pi tasks require the package attribution extension');
162
+ }
163
+ args.push('--extension', attributionExtensionPath);
164
+ }
156
165
  if (input.thinking?.trim()) args.push('--thinking', input.thinking.trim());
157
166
  args.push(...(input.extraPiArgs ?? []), input.prompt);
158
167
  return args;
@@ -3,7 +3,7 @@ import { open } from 'node:fs/promises';
3
3
  import { extname, isAbsolute, join, win32 } from 'node:path';
4
4
  import { DEFAULT_MAX_BYTES } from '@earendil-works/pi-coding-agent';
5
5
  import type { BackgroundTaskChildProcess } from './registry.js';
6
- import type { DelegateBudgetRouteSource } from './delegate/types.js';
6
+ import type { DelegateBudgetRouteSource, DelegateExtensionMode } from './delegate/types.js';
7
7
  import type { FusionResultDetails, FusionUsage, FusionWorkflowId } from './fusion/types.js';
8
8
 
9
9
  export const TASK_STATUS_VALUES = ['running', 'completed', 'failed', 'killed'] as const;
@@ -87,6 +87,7 @@ export interface DelegateTaskFacts {
87
87
  childSessionId: string;
88
88
  route: { provider: string; model: string; qualifiedId: string };
89
89
  budget: DelegateBudgetRouteSource;
90
+ extensionMode: DelegateExtensionMode;
90
91
  autoDeliver: 'never' | 'when_small' | 'always';
91
92
  /** Set once the run reaches a terminal state and its result has been evaluated. */
92
93
  outcome?: DelegateTaskOutcome | undefined;
@@ -45,7 +45,11 @@ export const TOKEN_BUDGET_SEGMENT_KINDS = [
45
45
  ] as const;
46
46
  export type TokenBudgetSegmentKind = (typeof TOKEN_BUDGET_SEGMENT_KINDS)[number];
47
47
 
48
- export type TokenBudgetEstimatorScope = 'fusion' | 'delegate' | 'conservative';
48
+ export type TokenBudgetEstimatorScope =
49
+ | 'fusion'
50
+ | 'delegate_launch'
51
+ | 'delegate'
52
+ | 'conservative';
49
53
  export type TokenBudgetDominantByteClass =
50
54
  | 'normal'
51
55
  | 'dense_ascii'
@@ -526,7 +530,7 @@ function rateSourceWarning(input: {
526
530
  return `model is not in the exact calibration backing set for family ${input.family}; using the provable 1.00 B/tok floor`;
527
531
  }
528
532
  if (input.source === 'delegate_conservative') {
529
- return 'delegate scope withholds calibrated rates and uses the provable 1.00 B/tok profile for launch/runtime headroom';
533
+ return 'delegate launch/runtime uses the provable 1.00 B/tok profile when the prompt or route is below the backed large-prompt calibration domain';
530
534
  }
531
535
  if (input.source === 'explicit_conservative') {
532
536
  return 'explicit conservative scope uses the provable 1.00 B/tok profile';
@@ -569,7 +573,16 @@ function effectiveRateSource(input: {
569
573
  ? 'unbacked_model_floor'
570
574
  : 'unknown_provider_floor';
571
575
  effective = TOKEN_BUDGET_PROVABLE_RATE_X100;
572
- } else if (input.scope === 'delegate') {
576
+ } else if (
577
+ input.scope === 'delegate' ||
578
+ (input.scope === 'delegate_launch' &&
579
+ (input.profile.concrete_known_bytes < TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES ||
580
+ (input.allowedInputTokens !== undefined &&
581
+ Math.floor(
582
+ (input.allowedInputTokens * TOKEN_BUDGET_DELEGATE_CONSERVATIVE_RATE_X100) /
583
+ TOKEN_BUDGET_RATE_SCALE,
584
+ ) < TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES)))
585
+ ) {
573
586
  source = 'delegate_conservative';
574
587
  effective = Math.min(configured, TOKEN_BUDGET_DELEGATE_CONSERVATIVE_RATE_X100);
575
588
  } else if (input.scope === 'conservative') {
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
2
3
  import { chmod, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
3
4
  import { basename, isAbsolute, join, relative, sep } from 'node:path';
4
5
  import { canonicalJson } from '../attested-pi-run.js';
@@ -8,6 +9,7 @@ import {
8
9
  DELEGATE_MANIFEST_SCHEMA_VERSION,
9
10
  DELEGATE_RECEIPT_SCHEMA_VERSION,
10
11
  DelegateError,
12
+ type DelegateExtensionMode,
11
13
  type DelegateLimits,
12
14
  type DelegatePinnedRoute,
13
15
  type DelegateSpillReceipt,
@@ -44,8 +46,7 @@ export const DELEGATE_ARTIFACT_NAMES = {
44
46
  outcome: 'outcome.json',
45
47
  result: DELEGATE_RESULT_PACKAGE_FILENAME,
46
48
  childPrompt: 'child-prompt.txt',
47
- childStderr: 'child.stderr.txt',
48
- childStdout: 'child.stdout.txt',
49
+ runtimeBudget: 'runtime-budget.json',
49
50
  error: 'error.json',
50
51
  } as const;
51
52
 
@@ -67,6 +68,7 @@ export interface DelegateManifestV1 {
67
68
  cwd: string;
68
69
  child_session_id: string;
69
70
  child_session_dir: string;
71
+ extension_mode: DelegateExtensionMode;
70
72
  route: DelegatePinnedRoute;
71
73
  limits: DelegateLimits;
72
74
  seed_sha256: string;
@@ -111,6 +113,7 @@ export interface CreateDelegateArtifactStoreOptions {
111
113
  sessionId?: string | undefined;
112
114
  childSessionId: string;
113
115
  childSessionDir: string;
116
+ extensionMode: DelegateExtensionMode;
114
117
  route: DelegatePinnedRoute;
115
118
  limits: DelegateLimits;
116
119
  seedSha256: string;
@@ -175,6 +178,7 @@ export class DelegateArtifactStore {
175
178
  cwd: options.cwd,
176
179
  child_session_id: options.childSessionId,
177
180
  child_session_dir: options.childSessionDir,
181
+ extension_mode: options.extensionMode,
178
182
  route: options.route,
179
183
  limits: options.limits,
180
184
  seed_sha256: options.seedSha256,
@@ -228,11 +232,6 @@ export class DelegateArtifactStore {
228
232
  return this.write(DELEGATE_ARTIFACT_NAMES.budgetPlan, `${canonicalJson(plan)}\n`);
229
233
  }
230
234
 
231
- async writeChildStreams(stdout: Buffer, stderr: Buffer): Promise<void> {
232
- await this.write(DELEGATE_ARTIFACT_NAMES.childStdout, stdout);
233
- await this.write(DELEGATE_ARTIFACT_NAMES.childStderr, stderr);
234
- }
235
-
236
235
  /** Commit the run. The rename performed here is the single success point. */
237
236
  async commitResult(pkg: DelegateResultPackageV1): Promise<DelegateArtifactRef> {
238
237
  const ref = await this.write(
@@ -247,17 +246,23 @@ export class DelegateArtifactStore {
247
246
  try {
248
247
  return await readFile(this.resultPathAbs, 'utf8');
249
248
  } catch (error) {
249
+ const diagnosticNames = [
250
+ DELEGATE_ARTIFACT_NAMES.error,
251
+ DELEGATE_ARTIFACT_NAMES.outcome,
252
+ DELEGATE_ARTIFACT_NAMES.runtimeBudget,
253
+ DELEGATE_ARTIFACT_NAMES.manifest,
254
+ ].filter((name) => existsSync(join(this.rootAbs, name)));
250
255
  throw new DelegateError(
251
- `delegate result package is not present at ${join(this.rootDisplay, DELEGATE_ARTIFACT_NAMES.result)}; the child produced no committed answer`,
256
+ `delegate result package could not be read at ${join(this.rootDisplay, DELEGATE_ARTIFACT_NAMES.result)}; no committed answer is available (${error instanceof Error ? error.message : String(error)})`,
252
257
  {
253
258
  code: 'result_unavailable',
254
259
  childCreated: true,
255
260
  taskId: this.manifest.task_id,
256
261
  artifactDir: this.rootDisplay,
257
- preserved: [this.rootDisplay],
258
- remediation: [
259
- 'Inspect child.stderr.txt and error.json in the artifact directory for the recorded failure.',
260
- ],
262
+ preserved: diagnosticNames,
263
+ remediation: diagnosticNames.length === 0
264
+ ? ['No diagnostic control artifact exists; inspect the background task merged output if one was created.']
265
+ : [`Inspect the existing delegate control artifacts: ${diagnosticNames.join(', ')}.`],
261
266
  },
262
267
  );
263
268
  }
@@ -349,6 +354,7 @@ export class DelegateArtifactStore {
349
354
  source_call_index: input.sourceCallIndex,
350
355
  byte_length: input.payload.length,
351
356
  sha256: sha256Bytes(input.payload),
357
+ content_format: 'opaque_bytes',
352
358
  };
353
359
  }
354
360
 
@@ -4,7 +4,6 @@ import {
4
4
  TOKEN_BUDGET_RATE_SCALE,
5
5
  estimateInputTokens,
6
6
  knownTextSegment,
7
- maxKnownTextBytesForTokens,
8
7
  resolveTokenBudgetFamily,
9
8
  utf8ByteClassBreakdown,
10
9
  allowedInputTokens,
@@ -28,11 +27,15 @@ import {
28
27
  * A delegate child is a multi-turn, tool-using agent, so its budget has two
29
28
  * distinct phases rather than Fusion's single-shot stage forecast:
30
29
  *
31
- * 1. Launch admission checks the frozen seed, framing, and child system prompt.
32
- * 2. The runtime governor checks the complete retained input before each call.
30
+ * 1. Launch admission checks the frozen seed, framing, and child system prompt
31
+ * with the same backed family calibration used by Fusion for large prompts.
32
+ * 2. A separate provable 1 B/token forecast sizes the transcript-growth runway
33
+ * used for explicit tool-result spilling.
34
+ * 3. Runtime measurements are advisory. Package-owned growth is controlled
35
+ * before transcript entry; Pi and the provider own live context handling.
33
36
  *
34
- * Nothing here clamps, downgrades, or silently reduces. An input that does not
35
- * fit is a typed refusal.
37
+ * Nothing here clips, substitutes, or silently reduces content. Tool bytes that
38
+ * do not fit the retained-growth runway are preserved as hashed spill artifacts.
36
39
  */
37
40
 
38
41
  /** Output tokens reserved so the child can always finish an answer. */
@@ -54,10 +57,14 @@ export const DELEGATE_DEFAULT_TIMEOUT_SECONDS = 1200;
54
57
  export const DELEGATE_MAX_TOOL_RESULT_BYTES = 64 * 1024;
55
58
  export const DELEGATE_MAX_TOTAL_TOOL_OUTPUT_BYTES = 64 * 1024 * 1024;
56
59
  export const DELEGATE_MAX_ANSWER_BYTES = 4 * 1024 * 1024;
60
+ /** Input runway held back for a final no-tool answer after investigation. */
61
+ export const DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS = 32 * 1024;
62
+ /** Remaining retained-growth runway at which the child disables tools. */
63
+ export const DELEGATE_FINALIZATION_TRIGGER_TOKENS = 8 * 1024;
57
64
  /** Answers at or under this serialize inline; larger ones degrade explicitly. */
58
65
  export const DELEGATE_INLINE_ANSWER_BYTES = 48 * 1024;
59
66
 
60
- export const DELEGATE_BUDGET_POLICY_ID = 'delegate-budget-policy-v2';
67
+ export const DELEGATE_BUDGET_POLICY_ID = 'delegate-budget-policy-v3';
61
68
 
62
69
  export interface DelegateBudgetPolicyDescriptor {
63
70
  id: typeof DELEGATE_BUDGET_POLICY_ID;
@@ -68,7 +75,11 @@ export interface DelegateBudgetPolicyDescriptor {
68
75
  safety_reserve_tokens: number;
69
76
  min_usable_input_tokens: number;
70
77
  inline_answer_bytes: number;
71
- estimator_scope: 'delegate';
78
+ finalization_input_reserve_tokens: number;
79
+ finalization_trigger_tokens: number;
80
+ launch_estimator_scope: 'calibrated_large_prompt';
81
+ retained_growth_estimator_scope: 'provable_1_byte_per_token';
82
+ live_provider_context_owner: 'pi_and_provider';
72
83
  }
73
84
 
74
85
  export const DELEGATE_BUDGET_POLICY: DelegateBudgetPolicyDescriptor = {
@@ -80,7 +91,11 @@ export const DELEGATE_BUDGET_POLICY: DelegateBudgetPolicyDescriptor = {
80
91
  safety_reserve_tokens: DELEGATE_SAFETY_RESERVE_TOKENS,
81
92
  min_usable_input_tokens: DELEGATE_MIN_USABLE_INPUT_TOKENS,
82
93
  inline_answer_bytes: DELEGATE_INLINE_ANSWER_BYTES,
83
- estimator_scope: 'delegate',
94
+ finalization_input_reserve_tokens: DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
95
+ finalization_trigger_tokens: DELEGATE_FINALIZATION_TRIGGER_TOKENS,
96
+ launch_estimator_scope: 'calibrated_large_prompt',
97
+ retained_growth_estimator_scope: 'provable_1_byte_per_token',
98
+ live_provider_context_owner: 'pi_and_provider',
84
99
  };
85
100
 
86
101
  export interface DelegateAdmissionPlanV1 {
@@ -97,17 +112,22 @@ export interface DelegateAdmissionPlanV1 {
97
112
  rate_source: TokenBudgetRateSource;
98
113
  byte_capacity_utf8_bytes: number;
99
114
  };
100
- seed_utf8_bytes: number;
101
- seed_multibyte_utf8_bytes: number;
115
+ child_prompt_utf8_bytes: number;
116
+ child_prompt_multibyte_utf8_bytes: number;
102
117
  system_prompt_utf8_bytes: number;
103
118
  system_prompt_multibyte_utf8_bytes: number;
104
119
  launch_utf8_bytes: number;
105
120
  launch_input_tokens_upper_bound: number;
121
+ conservative_launch_input_tokens: number;
122
+ conservative_launch_fits: boolean;
106
123
  signed_headroom_tokens: number;
107
124
  utilization_basis_points: number;
125
+ retained_growth_budget_tokens: number;
126
+ finalization_input_reserve_tokens: number;
108
127
  byte_class_breakdown: TokenBudgetByteClassBreakdown;
109
128
  dominant_byte_class: EstimateInputTokensResult['rateSource']['dominant_byte_class'];
110
129
  estimate: EstimateInputTokensResult;
130
+ conservative_estimate: EstimateInputTokensResult;
111
131
  fits: boolean;
112
132
  limits: DelegateLimits;
113
133
  }
@@ -167,7 +187,7 @@ export function delegateAllowedInputTokens(route: DelegatePinnedRoute): number {
167
187
 
168
188
  export interface DelegateAdmissionInput {
169
189
  route: DelegatePinnedRoute;
170
- seedSerialized: string;
190
+ childPrompt: string;
171
191
  childSystemPrompt: string;
172
192
  limits: DelegateLimits;
173
193
  }
@@ -176,20 +196,39 @@ export interface DelegateAdmissionInput {
176
196
  export function planDelegateAdmission(input: DelegateAdmissionInput): DelegateAdmissionPlanV1 {
177
197
  const allowed = delegateAllowedInputTokens(input.route);
178
198
  const family = routeFamily(input.route);
179
- const seed = utf8ByteClassBreakdown(input.seedSerialized);
199
+ const childPrompt = utf8ByteClassBreakdown(input.childPrompt);
180
200
  const system = utf8ByteClassBreakdown(input.childSystemPrompt);
201
+ const segments = [knownTextSegment(input.childPrompt), knownTextSegment(input.childSystemPrompt)];
181
202
  const estimate = estimateInputTokens({
182
203
  family: family.family,
183
204
  calibrationBacked: family.backed,
184
205
  familyResolution: family.resolution,
185
206
  allowedInputTokens: allowed,
186
- scope: 'delegate',
187
- segments: [knownTextSegment(input.seedSerialized), knownTextSegment(input.childSystemPrompt)],
207
+ scope: 'delegate_launch',
208
+ segments,
188
209
  });
210
+ const conservativeEstimate = estimateInputTokens({
211
+ family: family.family,
212
+ calibrationBacked: family.backed,
213
+ familyResolution: family.resolution,
214
+ allowedInputTokens: allowed,
215
+ scope: 'conservative',
216
+ segments,
217
+ });
218
+ // `conservativeEstimate.tokens` intentionally uses the shared estimator's
219
+ // calibrated multibyte diagnostic rate. The counter-forecast published as
220
+ // "provable" must instead use that estimator's explicit 1 B/token ceiling
221
+ // for multibyte bytes as well as normal/dense bytes.
222
+ const provableConservativeLaunchTokens =
223
+ conservativeEstimate.advisory.input_tokens_if_multibyte_used_provable_ceiling;
189
224
  const byteCapacity = Math.floor(
190
225
  (allowed * estimate.rateSource.effective_rate_bytes_per_token_x100) / TOKEN_BUDGET_RATE_SCALE,
191
226
  );
192
- const launchBytes = seed.bytes + system.bytes;
227
+ const launchBytes = childPrompt.bytes + system.bytes;
228
+ const retainedGrowthBudget = Math.max(
229
+ 0,
230
+ allowed - estimate.tokens - DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
231
+ );
193
232
  return {
194
233
  schema_version: DELEGATE_BUDGET_PLAN_SCHEMA_VERSION,
195
234
  policy: DELEGATE_BUDGET_POLICY,
@@ -204,17 +243,22 @@ export function planDelegateAdmission(input: DelegateAdmissionInput): DelegateAd
204
243
  rate_source: estimate.rateSource,
205
244
  byte_capacity_utf8_bytes: byteCapacity,
206
245
  },
207
- seed_utf8_bytes: seed.bytes,
208
- seed_multibyte_utf8_bytes: seed.multibyteBytes,
246
+ child_prompt_utf8_bytes: childPrompt.bytes,
247
+ child_prompt_multibyte_utf8_bytes: childPrompt.multibyteBytes,
209
248
  system_prompt_utf8_bytes: system.bytes,
210
249
  system_prompt_multibyte_utf8_bytes: system.multibyteBytes,
211
250
  launch_utf8_bytes: launchBytes,
212
251
  launch_input_tokens_upper_bound: estimate.tokens,
252
+ conservative_launch_input_tokens: provableConservativeLaunchTokens,
253
+ conservative_launch_fits: provableConservativeLaunchTokens <= allowed,
213
254
  signed_headroom_tokens: allowed - estimate.tokens,
214
255
  utilization_basis_points: utilizationBasisPoints(estimate.tokens, allowed),
256
+ retained_growth_budget_tokens: retainedGrowthBudget,
257
+ finalization_input_reserve_tokens: DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
215
258
  byte_class_breakdown: estimate.byte_class_breakdown,
216
259
  dominant_byte_class: estimate.rateSource.dominant_byte_class,
217
260
  estimate,
261
+ conservative_estimate: conservativeEstimate,
218
262
  fits: estimate.tokens <= allowed,
219
263
  limits: input.limits,
220
264
  };
@@ -226,17 +270,15 @@ function rateWarningText(rateSource: TokenBudgetRateSource, qualifiedId: string)
226
270
  }
227
271
 
228
272
  function requiredByteReduction(plan: DelegateAdmissionPlanV1): number {
229
- return Math.max(
230
- 0,
231
- plan.launch_utf8_bytes -
232
- maxKnownTextBytesForTokens({
233
- family: plan.route.family,
234
- calibrationBacked: plan.route.rate_source.backed,
235
- familyResolution: plan.route.rate_source.model_resolution,
236
- allowedInputTokens: plan.route.allowed_input_tokens,
237
- scope: 'delegate',
238
- }),
239
- );
273
+ const variableTokens =
274
+ plan.route.allowed_input_tokens - plan.route.rate_source.affine_f_tokens;
275
+ const maximumBytes = variableTokens <= 0
276
+ ? 0
277
+ : Math.floor(
278
+ (variableTokens * plan.route.rate_source.effective_rate_bytes_per_token_x100) /
279
+ TOKEN_BUDGET_RATE_SCALE,
280
+ );
281
+ return Math.max(0, plan.launch_utf8_bytes - maximumBytes);
240
282
  }
241
283
 
242
284
  /**
@@ -249,7 +291,7 @@ export function assertDelegateAdmission(plan: DelegateAdmissionPlanV1): void {
249
291
  if (plan.fits) return;
250
292
  const overage = plan.launch_input_tokens_upper_bound - plan.route.allowed_input_tokens;
251
293
  throw new DelegateError(
252
- `bg_delegate seed does not fit the pinned route before launch. Route ${plan.route.qualified_id} allows ${String(plan.route.allowed_input_tokens)} input tokens; the frozen seed plus the child system prompt measure ${String(plan.launch_utf8_bytes)} UTF-8 bytes (<= ${String(plan.launch_input_tokens_upper_bound)} input tokens), over by ${String(overage)} tokens. Estimator family ${plan.route.family}, source ${plan.route.rate_source.source}, backed=${String(plan.route.rate_source.backed)}, dominant_byte_class=${plan.dominant_byte_class}, rate ${String(plan.route.rate_source.effective_rate_bytes_per_token_x100)}/100 B/tok + ${String(plan.route.rate_source.affine_f_tokens)} tokens.${rateWarningText(plan.route.rate_source, plan.route.qualified_id)} Required reduction is at least ${String(requiredByteReduction(plan))} UTF-8 bytes. No child process, child session, or artifact was created. Nothing was clipped, dropped, or substituted.`,
294
+ `bg_delegate child prompt does not fit the pinned route before launch. Route ${plan.route.qualified_id} allows ${String(plan.route.allowed_input_tokens)} input tokens; the exact child prompt plus child system prompt measure ${String(plan.launch_utf8_bytes)} UTF-8 bytes (<= ${String(plan.launch_input_tokens_upper_bound)} input tokens), over by ${String(overage)} tokens. Estimator family ${plan.route.family}, source ${plan.route.rate_source.source}, backed=${String(plan.route.rate_source.backed)}, dominant_byte_class=${plan.dominant_byte_class}, rate ${String(plan.route.rate_source.effective_rate_bytes_per_token_x100)}/100 B/tok + ${String(plan.route.rate_source.affine_f_tokens)} tokens.${rateWarningText(plan.route.rate_source, plan.route.qualified_id)} Required reduction is at least ${String(requiredByteReduction(plan))} UTF-8 bytes. No child process, child session, or artifact was created. Nothing was clipped, dropped, or substituted.`,
253
295
  {
254
296
  code: 'seed_budget_exceeded',
255
297
  childCreated: false,
@@ -291,10 +333,13 @@ export interface DelegateGovernorVerdict {
291
333
  }
292
334
 
293
335
  /**
294
- * Runtime governor decision for one prospective model call.
336
+ * Advisory runtime measurement for one prospective model call.
295
337
  *
296
- * Pure and total, so the child guard can call it from inside a hook with no
297
- * possibility of throwing where a throw would be swallowed.
338
+ * This deliberately uses the calibrated large-prompt policy and never decides
339
+ * whether transport may occur. Fusion's BUG-185 proved that a package-local
340
+ * estimator must not reject a live provider payload by subtracting hypothetical
341
+ * output. The delegate child uses this result for evidence and graceful
342
+ * finalization while proactive spilling controls package-owned growth.
298
343
  */
299
344
  export function evaluateDelegateRuntimeBudget(
300
345
  measurement: DelegateRuntimeMeasurement,
@@ -307,7 +352,7 @@ export function evaluateDelegateRuntimeBudget(
307
352
  calibrationBacked: family.backed,
308
353
  familyResolution: family.resolution,
309
354
  allowedInputTokens: allowedTokens,
310
- scope: 'delegate',
355
+ scope: 'delegate_launch',
311
356
  segments: [
312
357
  {
313
358
  kind: 'known_text',