pi-background-tasks 1.0.6 → 2.0.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/README.md +7 -7
- package/TESTING.md +3 -3
- package/TEST_PLAN.md +2 -2
- package/docs/INDEX.md +25 -25
- package/docs/choose-a-workflow.md +4 -4
- package/docs/commands/bg-clear.md +1 -1
- package/docs/commands/bg-update.md +1 -1
- package/docs/commands/bg.md +1 -1
- package/docs/commands/fusion-models.md +1 -1
- package/docs/commands/fusion.md +5 -8
- package/docs/commands/jobs.md +1 -1
- package/docs/commands/kill.md +1 -1
- package/docs/commands/logs.md +1 -1
- package/docs/commands/task-manager.md +2 -2
- package/docs/concepts/completion-delivery.md +1 -0
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +59 -50
- package/docs/read-before-edit.md +1 -0
- package/docs/reference/runtime-contracts.md +55 -51
- package/docs/reference/shortcuts-and-dock.md +2 -2
- package/docs/subsystems/background-task-runtime.md +7 -1
- package/docs/subsystems/docs-freshness-gate.md +4 -4
- package/docs/subsystems/fusion.md +15 -11
- package/docs/subsystems/host-ui-and-telemetry.md +1 -1
- package/docs/tools/bg_delegate.md +1 -1
- package/docs/tools/bg_kill.md +1 -1
- package/docs/tools/bg_logs.md +1 -1
- package/docs/tools/bg_result.md +14 -10
- package/docs/tools/bg_run.md +1 -1
- package/docs/tools/bg_run_pi_attested.md +1 -1
- package/docs/tools/bg_status.md +1 -1
- package/docs/tools/fusion_investigate.md +6 -4
- package/docs/tools/fusion_reason.md +5 -5
- package/docs/tools/fusion_research.md +6 -2
- package/docs/tools/fusion_validate.md +5 -3
- package/package.json +1 -1
- package/src/core/common.ts +50 -2
- package/src/core/fusion/artifacts.ts +106 -13
- package/src/core/fusion/budget.ts +12 -4
- package/src/core/fusion/evaluation.ts +61 -0
- package/src/core/fusion/orchestrator.ts +270 -73
- package/src/core/fusion/pi-child.ts +6 -0
- package/src/core/fusion/prompts.ts +1 -0
- package/src/core/fusion/result-package.ts +385 -0
- package/src/core/fusion/types.ts +19 -1
- package/src/core/registry.ts +187 -20
- package/src/delegate-extension.ts +130 -24
- package/src/extension.ts +17 -6
- package/src/fusion-extension.ts +308 -154
|
@@ -6,7 +6,9 @@ import { sanitizePathSegment } from '../common.js';
|
|
|
6
6
|
import { replaceFileDurable } from '../durable-fs.js';
|
|
7
7
|
import {
|
|
8
8
|
EMPTY_FUSION_USAGE,
|
|
9
|
+
FUSION_COMMITTED_RESULT_SCHEMA_VERSION,
|
|
9
10
|
FUSION_MANIFEST_SCHEMA_VERSION,
|
|
11
|
+
FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
|
|
10
12
|
FusionError,
|
|
11
13
|
cloneFusionUsage,
|
|
12
14
|
type FusionArtifactManifest,
|
|
@@ -18,6 +20,8 @@ import {
|
|
|
18
20
|
type FusionCapability,
|
|
19
21
|
type FusionContextOmissionLedgerV2,
|
|
20
22
|
type FusionChildRunResult,
|
|
23
|
+
type FusionCommittedResultV1,
|
|
24
|
+
type FusionResultDetails,
|
|
21
25
|
type FusionModelConfigV1,
|
|
22
26
|
type FusionSource,
|
|
23
27
|
type FusionStage,
|
|
@@ -56,8 +60,17 @@ interface MutableFusionArtifactManifest {
|
|
|
56
60
|
evaluation: FusionCapability;
|
|
57
61
|
merge: FusionCapability;
|
|
58
62
|
};
|
|
59
|
-
context: {
|
|
60
|
-
|
|
63
|
+
context: {
|
|
64
|
+
kind: import('./types.js').FusionContextKind;
|
|
65
|
+
policy_id: string;
|
|
66
|
+
ledger_artifact?: string;
|
|
67
|
+
source_policy_artifact?: string;
|
|
68
|
+
};
|
|
69
|
+
tool_policy: {
|
|
70
|
+
candidate_tools: readonly string[];
|
|
71
|
+
evaluation_tools: readonly [];
|
|
72
|
+
merge_tools: readonly [];
|
|
73
|
+
};
|
|
61
74
|
usage: FusionUsage;
|
|
62
75
|
attempts: FusionAttemptArtifactRecord[];
|
|
63
76
|
artifacts: Record<string, FusionArtifactRef>;
|
|
@@ -83,14 +96,39 @@ export interface CreateFusionArtifactStoreOptions {
|
|
|
83
96
|
|
|
84
97
|
export interface RecordFusionChildAttemptInput {
|
|
85
98
|
result: FusionChildRunResult;
|
|
99
|
+
systemPrompt: string;
|
|
86
100
|
prompt: string;
|
|
87
101
|
responseKind: 'md' | 'txt';
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
export type RecordValidationCandidateContractEventInput =
|
|
105
|
+
| {
|
|
106
|
+
candidateId: FusionCandidateId;
|
|
107
|
+
slot: 1 | 2 | 3;
|
|
108
|
+
status: 'normalized';
|
|
109
|
+
detail: {
|
|
110
|
+
normalization: 'markdown_json_fence' | 'prose_then_markdown_json_fence';
|
|
111
|
+
original_sha256: string;
|
|
112
|
+
forwarded_sha256: string;
|
|
113
|
+
warning: string;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
| {
|
|
117
|
+
candidateId: FusionCandidateId;
|
|
118
|
+
slot: 1 | 2 | 3;
|
|
119
|
+
status: 'dropped';
|
|
120
|
+
detail: {
|
|
121
|
+
response_sha256: string;
|
|
122
|
+
error: string;
|
|
123
|
+
warning: string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
90
127
|
export interface RecordFusionFailedAttemptInput {
|
|
91
128
|
stage: FusionStage;
|
|
92
129
|
slot?: 1 | 2 | 3;
|
|
93
130
|
attempt: number;
|
|
131
|
+
systemPrompt: string;
|
|
94
132
|
prompt: string;
|
|
95
133
|
events: Buffer;
|
|
96
134
|
partialResponse: Buffer;
|
|
@@ -180,7 +218,11 @@ function publicManifest(manifest: MutableFusionArtifactManifest): FusionArtifact
|
|
|
180
218
|
models: manifest.models,
|
|
181
219
|
capabilities: manifest.capabilities,
|
|
182
220
|
context: { ...manifest.context },
|
|
183
|
-
tool_policy: {
|
|
221
|
+
tool_policy: {
|
|
222
|
+
candidate_tools: [...manifest.tool_policy.candidate_tools],
|
|
223
|
+
evaluation_tools: [],
|
|
224
|
+
merge_tools: [],
|
|
225
|
+
},
|
|
184
226
|
usage: cloneFusionUsage(manifest.usage),
|
|
185
227
|
attempts: [...manifest.attempts],
|
|
186
228
|
artifacts: { ...manifest.artifacts },
|
|
@@ -269,8 +311,18 @@ export class FusionArtifactStore {
|
|
|
269
311
|
evaluation: 'reason',
|
|
270
312
|
merge: 'reason',
|
|
271
313
|
},
|
|
272
|
-
context: {
|
|
273
|
-
|
|
314
|
+
context: {
|
|
315
|
+
kind: profile.contextKind,
|
|
316
|
+
policy_id:
|
|
317
|
+
profile.contextKind === 'session_projection'
|
|
318
|
+
? 'fusion-session-projection-v1'
|
|
319
|
+
: 'fusion-clean-task-v1',
|
|
320
|
+
},
|
|
321
|
+
tool_policy: {
|
|
322
|
+
candidate_tools: profile.candidateTools,
|
|
323
|
+
evaluation_tools: [],
|
|
324
|
+
merge_tools: [],
|
|
325
|
+
},
|
|
274
326
|
usage: cloneFusionUsage(EMPTY_FUSION_USAGE),
|
|
275
327
|
attempts: [],
|
|
276
328
|
artifacts: {},
|
|
@@ -313,11 +365,18 @@ export class FusionArtifactStore {
|
|
|
313
365
|
childCreated: false,
|
|
314
366
|
});
|
|
315
367
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
368
|
+
if (
|
|
369
|
+
to === 'completed' &&
|
|
370
|
+
(manifest.artifacts['merged.md'] === undefined ||
|
|
371
|
+
manifest.artifacts['result.json'] === undefined)
|
|
372
|
+
) {
|
|
373
|
+
throw new FusionError(
|
|
374
|
+
'fusion cannot complete before merged.md and result.json are durable',
|
|
375
|
+
{
|
|
376
|
+
code: 'state_transition_invalid',
|
|
377
|
+
childCreated: false,
|
|
378
|
+
},
|
|
379
|
+
);
|
|
321
380
|
}
|
|
322
381
|
manifest.state = to;
|
|
323
382
|
});
|
|
@@ -377,8 +436,21 @@ export class FusionArtifactStore {
|
|
|
377
436
|
await this.writeArtifact('evaluation.json', canonicalJson(value));
|
|
378
437
|
}
|
|
379
438
|
|
|
380
|
-
async writeMerged(text: string): Promise<
|
|
381
|
-
|
|
439
|
+
async writeMerged(text: string): Promise<FusionArtifactRef> {
|
|
440
|
+
return this.writeArtifact('merged.md', text);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async writeCommittedResult(
|
|
444
|
+
merged: FusionArtifactRef,
|
|
445
|
+
details: FusionResultDetails,
|
|
446
|
+
): Promise<FusionArtifactRef> {
|
|
447
|
+
const value: FusionCommittedResultV1 = {
|
|
448
|
+
schema_version: FUSION_COMMITTED_RESULT_SCHEMA_VERSION,
|
|
449
|
+
run_id: this.runId,
|
|
450
|
+
merged,
|
|
451
|
+
details,
|
|
452
|
+
};
|
|
453
|
+
return this.writeArtifact('result.json', `${canonicalJson(value)}\n`);
|
|
382
454
|
}
|
|
383
455
|
|
|
384
456
|
async writeError(state: Exclude<FusionTerminalState, 'completed'>, error: string): Promise<void> {
|
|
@@ -398,6 +470,7 @@ export class FusionArtifactStore {
|
|
|
398
470
|
|
|
399
471
|
async recordChildAttempt(input: RecordFusionChildAttemptInput): Promise<void> {
|
|
400
472
|
const prefix = attemptPrefix(input.result.stage, input.result.slot, input.result.attempt);
|
|
473
|
+
await this.writeArtifact(`${prefix}.system-prompt.txt`, input.systemPrompt);
|
|
401
474
|
const promptRef = await this.writeArtifact(`${prefix}.prompt.txt`, input.prompt);
|
|
402
475
|
const eventsRef = await this.writeArtifact(`${prefix}.events.jsonl`, input.result.events);
|
|
403
476
|
const stderrRef = await this.writeArtifact(`${prefix}.stderr.txt`, input.result.stderr);
|
|
@@ -439,11 +512,31 @@ export class FusionArtifactStore {
|
|
|
439
512
|
violation: FusionCalibrationViolation;
|
|
440
513
|
}): Promise<FusionArtifactRef> {
|
|
441
514
|
const prefix = attemptPrefix(input.stage, input.slot, input.attempt);
|
|
442
|
-
return this.writeArtifact(
|
|
515
|
+
return this.writeArtifact(
|
|
516
|
+
calibrationViolationName(prefix),
|
|
517
|
+
`${canonicalJson(input.violation)}\n`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async recordValidationCandidateContractEvent(
|
|
522
|
+
input: RecordValidationCandidateContractEventInput,
|
|
523
|
+
): Promise<FusionArtifactRef> {
|
|
524
|
+
const name = `candidate-${String(input.slot)}.output-contract-${input.status}.json`;
|
|
525
|
+
return this.writeArtifact(
|
|
526
|
+
name,
|
|
527
|
+
`${canonicalJson({
|
|
528
|
+
schema_version: FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
|
|
529
|
+
...input.detail,
|
|
530
|
+
candidate_id: input.candidateId,
|
|
531
|
+
slot: input.slot,
|
|
532
|
+
status: input.status,
|
|
533
|
+
})}\n`,
|
|
534
|
+
);
|
|
443
535
|
}
|
|
444
536
|
|
|
445
537
|
async recordFailedAttempt(input: RecordFusionFailedAttemptInput): Promise<void> {
|
|
446
538
|
const prefix = attemptPrefix(input.stage, input.slot, input.attempt);
|
|
539
|
+
await this.writeArtifact(`${prefix}.system-prompt.txt`, input.systemPrompt);
|
|
447
540
|
const promptRef = await this.writeArtifact(`${prefix}.prompt.txt`, input.prompt);
|
|
448
541
|
const eventsRef = await this.writeArtifact(`${prefix}.events.jsonl`, input.events);
|
|
449
542
|
const stderrRef = await this.writeArtifact(`${prefix}.stderr.txt`, input.stderr);
|
|
@@ -1122,7 +1122,13 @@ export class FusionBudget {
|
|
|
1122
1122
|
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1123
1123
|
const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1124
1124
|
const estimate = estimateRouteInput(route, inputSegments);
|
|
1125
|
-
|
|
1125
|
+
// The forecast is a one-request admission estimate. Compare it only with
|
|
1126
|
+
// the first provider request, never with aggregate agent-loop/cache usage.
|
|
1127
|
+
// Custom child runners predating this observation field remain compatible,
|
|
1128
|
+
// but cannot produce a calibration verdict without like-for-like evidence.
|
|
1129
|
+
const observedUsage = result.firstRequestUsage;
|
|
1130
|
+
if (observedUsage === undefined) return undefined;
|
|
1131
|
+
const billedInput = observedUsage.input + observedUsage.cacheRead + observedUsage.cacheWrite;
|
|
1126
1132
|
if (billedInput <= estimate.tokens) return undefined;
|
|
1127
1133
|
const violation: FusionCalibrationViolation = {
|
|
1128
1134
|
schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
@@ -1137,12 +1143,14 @@ export class FusionBudget {
|
|
|
1137
1143
|
rate_source: estimate.rateSource,
|
|
1138
1144
|
prompt_utf8_bytes: promptUtf8Bytes,
|
|
1139
1145
|
prompt_sha256: sha256Hex(`${systemPrompt}\u0000${userPrompt}`),
|
|
1146
|
+
observation_scope: 'first_provider_request',
|
|
1147
|
+
provider_request_count: result.providerRequestCount ?? 1,
|
|
1140
1148
|
forecast_input_tokens: estimate.tokens,
|
|
1141
1149
|
billed_input_tokens: billedInput,
|
|
1142
1150
|
billed_input_breakdown: {
|
|
1143
|
-
input:
|
|
1144
|
-
cache_read:
|
|
1145
|
-
cache_write:
|
|
1151
|
+
input: observedUsage.input,
|
|
1152
|
+
cache_read: observedUsage.cacheRead,
|
|
1153
|
+
cache_write: observedUsage.cacheWrite,
|
|
1146
1154
|
},
|
|
1147
1155
|
under_forecast_tokens: billedInput - estimate.tokens,
|
|
1148
1156
|
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
@@ -525,6 +525,67 @@ export interface ParsedFusionValidationCandidateReport {
|
|
|
525
525
|
limitations: readonly string[];
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
+
export type FusionValidationCandidateNormalization =
|
|
529
|
+
| 'markdown_json_fence'
|
|
530
|
+
| 'prose_then_markdown_json_fence';
|
|
531
|
+
|
|
532
|
+
export interface RecoveredFusionValidationCandidateReport {
|
|
533
|
+
report: ParsedFusionValidationCandidateReport;
|
|
534
|
+
/** Bare JSON forwarded to the evaluator after explicit, audited recovery. */
|
|
535
|
+
response: string;
|
|
536
|
+
normalization: FusionValidationCandidateNormalization;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Recognize exactly one complete Markdown JSON fence, optionally preceded by a
|
|
541
|
+
* short prose preamble. This is deliberately narrower than generic substring
|
|
542
|
+
* extraction: trailing prose, nested fences, unlabelled fences, and oversized
|
|
543
|
+
* preambles remain contract failures.
|
|
544
|
+
*/
|
|
545
|
+
function fencedValidationCandidateJson(text: string): {
|
|
546
|
+
payload: string;
|
|
547
|
+
normalization: FusionValidationCandidateNormalization;
|
|
548
|
+
} | undefined {
|
|
549
|
+
const trimmed = text.trim();
|
|
550
|
+
const openingPattern = /```json[ \t]*\r?\n/giu;
|
|
551
|
+
const openings = [...trimmed.matchAll(openingPattern)];
|
|
552
|
+
if (openings.length !== 1) return undefined;
|
|
553
|
+
const opening = openings[0];
|
|
554
|
+
if (opening === undefined) return undefined;
|
|
555
|
+
const headerEnd = opening.index + opening[0].length;
|
|
556
|
+
const closing = trimmed.indexOf('```', headerEnd);
|
|
557
|
+
if (closing < 0 || trimmed.slice(closing + 3).includes('```')) return undefined;
|
|
558
|
+
if (trimmed.slice(closing + 3).trim().length > 0) return undefined;
|
|
559
|
+
const preamble = trimmed.slice(0, opening.index).trim();
|
|
560
|
+
if (Buffer.byteLength(preamble, 'utf8') > 2_000 || preamble.includes('```')) return undefined;
|
|
561
|
+
const payload = trimmed.slice(headerEnd, closing).trim();
|
|
562
|
+
if (payload.length === 0 || payload.includes('```')) return undefined;
|
|
563
|
+
return {
|
|
564
|
+
payload,
|
|
565
|
+
normalization: preamble.length === 0
|
|
566
|
+
? 'markdown_json_fence'
|
|
567
|
+
: 'prose_then_markdown_json_fence',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Defensive recovery for the one observed contract violation shape. Callers
|
|
573
|
+
* must persist/surface the returned normalization; this function intentionally
|
|
574
|
+
* does not make the strict parser permissive.
|
|
575
|
+
*/
|
|
576
|
+
export function recoverFencedFusionValidationCandidateReport(
|
|
577
|
+
text: string,
|
|
578
|
+
candidateId: FusionCandidateId,
|
|
579
|
+
): RecoveredFusionValidationCandidateReport | undefined {
|
|
580
|
+
const recovered = fencedValidationCandidateJson(text);
|
|
581
|
+
if (recovered === undefined) return undefined;
|
|
582
|
+
return {
|
|
583
|
+
report: parseFusionValidationCandidateReport(recovered.payload, candidateId),
|
|
584
|
+
response: recovered.payload,
|
|
585
|
+
normalization: recovered.normalization,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
528
589
|
export function parseFusionValidationCandidateReport(text: string, candidateId: FusionCandidateId): ParsedFusionValidationCandidateReport {
|
|
529
590
|
let parsed: unknown;
|
|
530
591
|
try {
|