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.
- package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/PUBLISHING.md +2 -0
- package/README.md +16 -8
- package/TESTING.md +23 -14
- package/TEST_PLAN.md +13 -12
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/docs/INDEX.md +8 -4
- package/docs/choose-a-workflow.md +5 -2
- package/docs/commands/claude-cache.md +50 -0
- package/docs/concepts/context-projection-and-budgeting.md +4 -2
- package/docs/getting-started.md +3 -0
- package/docs/manifest.json +86 -22
- package/docs/operations/configuration.md +15 -1
- package/docs/operations/releasing.md +6 -3
- package/docs/operations/troubleshooting.md +3 -1
- package/docs/read-before-edit.md +4 -1
- package/docs/reference/runtime-contracts.md +53 -53
- package/docs/subsystems/anthropic-attribution.md +63 -0
- package/docs/subsystems/attested-pi-runs.md +2 -2
- package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
- package/docs/subsystems/delegation.md +34 -17
- package/docs/subsystems/docs-freshness-gate.md +6 -6
- package/docs/subsystems/fusion.md +2 -2
- package/docs/tools/bg_delegate.md +33 -16
- package/docs/tools/bg_result.md +2 -2
- package/docs/tools/bg_run.md +5 -0
- package/extensions/anthropic-attribution.ts +1 -0
- package/package.json +4 -2
- package/src/core/anthropic-attribution-path.ts +26 -0
- package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
- package/src/core/attested-pi-run.ts +10 -1
- package/src/core/common.ts +2 -1
- package/src/core/context/token-budget.ts +16 -3
- package/src/core/delegate/artifacts.ts +18 -12
- package/src/core/delegate/budget.ts +78 -33
- package/src/core/delegate/launch.ts +48 -16
- package/src/core/delegate/result-package.ts +16 -0
- package/src/core/delegate/runner.ts +45 -2
- package/src/core/delegate/seed.ts +12 -0
- package/src/core/delegate/types.ts +22 -3
- package/src/core/fusion/config.ts +1 -1
- package/src/core/fusion/pi-child.ts +7 -124
- package/src/core/registry.ts +4 -1
- package/src/delegate-child-extension.ts +377 -71
- 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
|
-
//
|
|
71
|
-
//
|
|
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(
|
|
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;
|
package/src/core/common.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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:
|
|
258
|
-
remediation:
|
|
259
|
-
'
|
|
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
|
-
*
|
|
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
|
|
35
|
-
* fit
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
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: '
|
|
187
|
-
segments
|
|
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 =
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
*
|
|
336
|
+
* Advisory runtime measurement for one prospective model call.
|
|
295
337
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
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: '
|
|
355
|
+
scope: 'delegate_launch',
|
|
311
356
|
segments: [
|
|
312
357
|
{
|
|
313
358
|
kind: 'known_text',
|