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
@@ -21,6 +21,7 @@ import {
21
21
  import {
22
22
  DELEGATE_AUTO_DELIVER_MODES,
23
23
  DELEGATE_CAPABILITIES,
24
+ DELEGATE_EXTENSION_MODES,
24
25
  DELEGATE_RESULT_TOOL_NAME,
25
26
  DELEGATE_TOOL_NAME,
26
27
  DelegateError,
@@ -28,6 +29,7 @@ import {
28
29
  type DelegateCapability,
29
30
  type DelegateDeliveryMode,
30
31
  type DelegateBudgetRouteSource,
32
+ type DelegateExtensionMode,
31
33
  type DelegateRoute,
32
34
  } from './core/delegate/types.js';
33
35
  import {
@@ -67,7 +69,7 @@ const HOOK_EVIDENCE_PATH = fileURLToPath(
67
69
  new URL('./core/delegate/hook-contract-evidence.json', import.meta.url),
68
70
  );
69
71
 
70
- const DelegateParams = Type.Object(
72
+ export const DelegateParams = Type.Object(
71
73
  {
72
74
  name: Type.String({
73
75
  description: 'Short human-readable task name shown in the bg footer dock. Use 2-6 words.',
@@ -93,6 +95,12 @@ const DelegateParams = Type.Object(
93
95
  description: `Capability profile. Only "inspect" (read/search/list, no shell, no writes, no network, no recursion) is supported.`,
94
96
  }),
95
97
  ),
98
+ extensionMode: Type.Optional(
99
+ Type.String({
100
+ description:
101
+ 'Extension discovery: isolated | ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation.',
102
+ }),
103
+ ),
96
104
  maxTurns: Type.Optional(
97
105
  Type.Number({
98
106
  description: `Maximum agent turns. Default ${String(DELEGATE_DEFAULT_MAX_TURNS)}.`,
@@ -142,6 +150,20 @@ const ResultParams = Type.Object(
142
150
  type DelegateParamsValue = Static<typeof DelegateParams>;
143
151
  type ResultParamsValue = Static<typeof ResultParams>;
144
152
 
153
+ const DELEGATE_PARAM_KEYS = new Set([
154
+ 'name',
155
+ 'prompt',
156
+ 'route',
157
+ 'capability',
158
+ 'extensionMode',
159
+ 'maxTurns',
160
+ 'maxToolCalls',
161
+ 'timeoutSeconds',
162
+ 'autoDeliver',
163
+ 'notifyOnCompletion',
164
+ 'triggerOnCompletion',
165
+ ]);
166
+
145
167
  export interface DelegateLaunchDetails {
146
168
  schema_version: 'pi-background-tasks.delegate-launch.v1';
147
169
  task: BgTaskSnapshot;
@@ -151,6 +173,7 @@ export interface DelegateLaunchDetails {
151
173
  seed_sha256: string;
152
174
  seed_utf8_bytes: number;
153
175
  budget: DelegateBudgetRouteSource;
176
+ extension_mode: DelegateExtensionMode;
154
177
  auto_deliver: DelegateAutoDeliverMode;
155
178
  notify_on_completion: boolean;
156
179
  trigger_on_completion: boolean;
@@ -187,6 +210,7 @@ export interface DelegateResultDetails {
187
210
  delivery: DelegateDeliveryMode | 'none';
188
211
  route?: { provider: string; model: string } | undefined;
189
212
  budget?: DelegateBudgetRouteSource | undefined;
213
+ extension_mode?: DelegateExtensionMode | undefined;
190
214
  answer_bytes?: number | undefined;
191
215
  answer_sha256?: string | undefined;
192
216
  turns?: number | undefined;
@@ -213,6 +237,15 @@ function requireCapability(value: unknown): DelegateCapability {
213
237
  );
214
238
  }
215
239
 
240
+ function requireExtensionMode(value: unknown): DelegateExtensionMode {
241
+ if (value === undefined) return 'isolated';
242
+ if (value === 'isolated' || value === 'ambient') return value;
243
+ throw new DelegateError(
244
+ `bg_delegate extensionMode must be one of ${DELEGATE_EXTENSION_MODES.join(', ')}`,
245
+ { code: 'invalid_arguments', childCreated: false },
246
+ );
247
+ }
248
+
216
249
  function requireAutoDeliver(value: unknown): DelegateAutoDeliverMode {
217
250
  if (value === undefined) return 'never';
218
251
  if (value === 'never' || value === 'when_small' || value === 'always') return value;
@@ -308,13 +341,15 @@ export function registerDelegateExtension(
308
341
  name: DELEGATE_TOOL_NAME,
309
342
  label: 'Background Delegate',
310
343
  description:
311
- 'Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Retrieve its verified answer with bg_result.',
344
+ 'Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Extension discovery is isolated by default; ambient mode supports extension-registered providers but executes arbitrary discovered extension code. Retrieve its verified answer with bg_result.',
312
345
  promptSnippet:
313
346
  'Delegate an investigation to a background agent that already has this conversation as context',
314
347
  promptGuidelines: [
315
348
  'Use bg_delegate when work should continue in the background and the worker needs what you already know: it is seeded with a projection of this conversation.',
316
349
  'The prompt is authoritative. State exactly what you want investigated and what the answer should contain.',
317
- 'The delegate is inspect-only: it can read, search, and list files, but cannot run shell commands, edit or write files, use the network, or delegate further.',
350
+ 'The delegate is inspect-only at the model-visible tool boundary: it can read, search, and list files, but cannot run shell commands, edit or write files, use the network, or delegate further.',
351
+ 'Extension discovery is isolated by default. Use extensionMode:"ambient" only when the pinned provider is registered by an ambient user/project extension.',
352
+ 'Ambient mode executes arbitrary discovered extension code in the child process. Tool allowlists do not sandbox extension code, so ambient mode weakens inspect-only process isolation.',
318
353
  'Facts that exist only inside omitted tool output are not available to the delegate. Restate such findings in the prompt.',
319
354
  'bg_delegate returns immediately. Do not poll; retrieve the answer with bg_result after the terminal notification arrives.',
320
355
  ],
@@ -325,6 +360,13 @@ export function registerDelegateExtension(
325
360
  code: 'invalid_arguments',
326
361
  childCreated: false,
327
362
  });
363
+ const unknownKeys = Object.keys(args).filter((key) => !DELEGATE_PARAM_KEYS.has(key));
364
+ if (unknownKeys.length > 0) {
365
+ throw new DelegateError(
366
+ `bg_delegate contains unsupported key(s): ${unknownKeys.sort().join(', ')}`,
367
+ { code: 'invalid_arguments', childCreated: false },
368
+ );
369
+ }
328
370
  const name = args['name'];
329
371
  const prompt = args['prompt'];
330
372
  if (typeof name !== 'string' || name.trim().length === 0)
@@ -341,6 +383,7 @@ export function registerDelegateExtension(
341
383
  const route = requireRoute(args['route']);
342
384
  if (route !== undefined) prepared.route = route;
343
385
  prepared.capability = requireCapability(args['capability']);
386
+ prepared.extensionMode = requireExtensionMode(args['extensionMode']);
344
387
  prepared.autoDeliver = requireAutoDeliver(args['autoDeliver']);
345
388
  const maxTurns = optionalPositiveInteger(args['maxTurns'], 'maxTurns');
346
389
  if (maxTurns !== undefined) prepared.maxTurns = maxTurns;
@@ -356,6 +399,7 @@ export function registerDelegateExtension(
356
399
  },
357
400
  async execute(toolCallId, params, _signal, _onUpdate, ctx) {
358
401
  const capability = requireCapability(params.capability);
402
+ const extensionMode = requireExtensionMode(params.extensionMode);
359
403
  const autoDeliver = requireAutoDeliver(params.autoDeliver);
360
404
  const hookEvidence = await loadEvidence();
361
405
  const route = resolveDelegateRoute({
@@ -385,6 +429,7 @@ export function registerDelegateExtension(
385
429
  toolCallId,
386
430
  prompt: params.prompt,
387
431
  capability,
432
+ extensionMode,
388
433
  route,
389
434
  limitOverrides: {
390
435
  maxTurns: params.maxTurns,
@@ -421,8 +466,9 @@ export function registerDelegateExtension(
421
466
  child_session_id: prepared.preflight.childSessionId,
422
467
  artifact_dir: prepared.facts.artifactDir,
423
468
  seed_sha256: prepared.facts.seedSha256,
424
- seed_utf8_bytes: prepared.preflight.plan.seed_utf8_bytes,
469
+ seed_utf8_bytes: Buffer.byteLength(prepared.preflight.seed.serialized, 'utf8'),
425
470
  budget: prepared.facts.budget,
471
+ extension_mode: extensionMode,
426
472
  auto_deliver: autoDeliver,
427
473
  notify_on_completion: launchOptions.notifyOnCompletion,
428
474
  trigger_on_completion: launchOptions.triggerOnCompletion,
@@ -434,9 +480,11 @@ export function registerDelegateExtension(
434
480
  `Route pinned: ${route.qualified_id} (${route.origin}); it is never substituted.`,
435
481
  `Child session: ${prepared.preflight.childSessionId} (separate from this session)`,
436
482
  `Artifacts: ${prepared.facts.artifactDir}`,
437
- `Seed: ${String(prepared.preflight.plan.seed_utf8_bytes)} bytes, sha256 ${prepared.facts.seedSha256}`,
483
+ `Seed: ${String(Buffer.byteLength(prepared.preflight.seed.serialized, 'utf8'))} bytes, sha256 ${prepared.facts.seedSha256}`,
484
+ `Child prompt: ${String(prepared.preflight.plan.child_prompt_utf8_bytes)} bytes; launch estimate ${String(prepared.preflight.plan.launch_input_tokens_upper_bound)} / ${String(prepared.preflight.plan.route.allowed_input_tokens)} allowed input tokens; protected retained-growth runway ${String(prepared.preflight.plan.retained_growth_budget_tokens)} tokens.`,
438
485
  `Estimator: family ${prepared.facts.budget.family}, source ${prepared.facts.budget.rate_source.source}, rate ${String(prepared.facts.budget.rate_source.effective_rate_bytes_per_token_x100)}/100 B/tok + ${String(prepared.facts.budget.rate_source.affine_f_tokens)} tokens${prepared.facts.budget.rate_source.warning === null ? '' : `; warning: ${prepared.facts.budget.rate_source.warning}`}`,
439
486
  `Capability: ${capability} (read/search/list only)`,
487
+ `Extension mode: ${extensionMode}${extensionMode === 'ambient' ? ' — WARNING: arbitrary discovered extension code executes in the child; the tool allowlist does not sandbox it, so inspect-only process isolation is weakened.' : ' (ambient extension discovery disabled)'}`,
440
488
  `Limits: ${String(prepared.preflight.limits.max_turns)} turns, ${String(prepared.preflight.limits.max_tool_calls)} tool calls, ${String(prepared.preflight.limits.timeout_seconds)}s`,
441
489
  `Auto-deliver: ${autoDeliver}`,
442
490
  launchOptions.notifyOnCompletion
@@ -458,7 +506,7 @@ export function registerDelegateExtension(
458
506
  renderResult(result, _options, theme) {
459
507
  const details = result.details;
460
508
  return new Text(
461
- `${theme.fg('success', '✓ delegated')} ${theme.fg('accent', details.task.id)}\n${theme.fg('dim', `route ${details.route.qualified_id} · seed ${String(details.seed_utf8_bytes)}B · ${details.artifact_dir}`)}`,
509
+ `${theme.fg('success', '✓ delegated')} ${theme.fg('accent', details.task.id)}\n${theme.fg('dim', `route ${details.route.qualified_id} · extensions ${details.extension_mode} · seed ${String(details.seed_utf8_bytes)}B · ${details.artifact_dir}`)}`,
462
510
  0,
463
511
  0,
464
512
  );
@@ -633,6 +681,7 @@ export function registerDelegateExtension(
633
681
  delivery: 'none',
634
682
  artifact_dir: facts.artifactDir,
635
683
  budget: facts.budget,
684
+ extension_mode: facts.extensionMode,
636
685
  };
637
686
  return {
638
687
  content: textContent(
@@ -650,6 +699,8 @@ export function registerDelegateExtension(
650
699
  route: { provider: facts.route.provider, model: facts.route.model },
651
700
  taskStatus: task.status === 'completed' ? 'completed' : task.status,
652
701
  taskError: task.error,
702
+ taskOutputPath: task.outputPath,
703
+ taskOutputAbsPath: task.outputAbsPath,
653
704
  });
654
705
 
655
706
  if (terminal.error !== undefined || terminal.result === undefined) {
@@ -686,6 +737,7 @@ export function registerDelegateExtension(
686
737
  delivery: decision.mode,
687
738
  route: verified.package.route,
688
739
  budget: facts.budget,
740
+ extension_mode: facts.extensionMode,
689
741
  answer_bytes: verified.package.answer.byte_length,
690
742
  answer_sha256: verified.package.answer.sha256,
691
743
  turns: verified.package.turns,