@sema-agent/core 5.46.0 → 5.48.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 (78) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +4 -0
  3. package/dist/agents/agent-transcript-tool.js +10 -3
  4. package/dist/agents/send-message-tool.d.ts +43 -1
  5. package/dist/agents/send-message-tool.js +50 -11
  6. package/dist/agents/subagent.d.ts +18 -0
  7. package/dist/agents/subagent.js +231 -4
  8. package/dist/config/defaults.d.ts +20 -0
  9. package/dist/config/defaults.js +5 -0
  10. package/dist/core/background-agent-store.d.ts +1 -0
  11. package/dist/core/background-agent-store.js +13 -0
  12. package/dist/core/governance-codes.d.ts +13 -0
  13. package/dist/core/governance-codes.js +33 -0
  14. package/dist/core/mcp.d.ts +6 -1
  15. package/dist/core/mcp.js +34 -7
  16. package/dist/core/memory-engine/delegation-settlement.d.ts +318 -0
  17. package/dist/core/memory-engine/delegation-settlement.js +661 -0
  18. package/dist/core/memory-engine/engine.d.ts +159 -1
  19. package/dist/core/memory-engine/engine.js +699 -15
  20. package/dist/core/memory-engine/file-backend.d.ts +1 -0
  21. package/dist/core/memory-engine/file-backend.js +3 -1
  22. package/dist/core/memory-engine/frontmatter.d.ts +46 -19
  23. package/dist/core/memory-engine/frontmatter.js +91 -77
  24. package/dist/core/memory-engine/index.d.ts +4 -3
  25. package/dist/core/memory-engine/index.js +3 -2
  26. package/dist/core/memory-engine/layout.d.ts +14 -0
  27. package/dist/core/memory-engine/layout.js +2 -2
  28. package/dist/core/memory-engine/memory-backend-contract.js +43 -0
  29. package/dist/core/memory-engine/origin-clearance.d.ts +66 -0
  30. package/dist/core/memory-engine/origin-clearance.js +84 -0
  31. package/dist/core/memory-engine/provenance-wording.d.ts +50 -0
  32. package/dist/core/memory-engine/provenance-wording.js +15 -0
  33. package/dist/core/memory-engine/tools.d.ts +61 -7
  34. package/dist/core/memory-engine/tools.js +34 -9
  35. package/dist/core/memory-engine/types.d.ts +70 -2
  36. package/dist/core/reminder-disclosure.d.ts +90 -0
  37. package/dist/core/reminder-disclosure.js +64 -0
  38. package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
  39. package/dist/core/runner/prepare-acquire-reconcile.js +1 -1
  40. package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
  41. package/dist/core/runner/prepare-hands-readface.js +1 -0
  42. package/dist/core/runner/prepare-memory.js +50 -15
  43. package/dist/core/runner/prepare-task.d.ts +39 -0
  44. package/dist/core/runner/prepare-task.js +128 -40
  45. package/dist/core/runner/runtask.js +3 -1
  46. package/dist/core/session-reconcile.js +3 -2
  47. package/dist/core/session-store.d.ts +59 -1
  48. package/dist/core/session-store.js +82 -14
  49. package/dist/core/session.d.ts +83 -1
  50. package/dist/core/task-registry-agent.d.ts +28 -0
  51. package/dist/core/task-registry-agent.js +63 -2
  52. package/dist/core/task-registry.d.ts +21 -0
  53. package/dist/core/task-registry.js +4 -1
  54. package/dist/core/types.d.ts +98 -3
  55. package/dist/core/types.js +3 -0
  56. package/dist/core/untrusted-text.d.ts +63 -0
  57. package/dist/core/untrusted-text.js +48 -0
  58. package/dist/core/wiring-manifest.d.ts +35 -0
  59. package/dist/core/wiring-manifest.js +21 -1
  60. package/dist/engine/harness/types.d.ts +36 -1
  61. package/dist/index.d.ts +7 -5
  62. package/dist/index.js +6 -4
  63. package/dist/internal/harness-types.d.ts +1 -0
  64. package/dist/stores/file/index.d.ts +19 -3
  65. package/dist/stores/file/index.js +24 -1
  66. package/dist/stores/file/session-store.d.ts +18 -4
  67. package/dist/stores/file/session-store.js +73 -12
  68. package/dist/tools/fs/fs-pdf.d.ts +12 -1
  69. package/dist/tools/fs/fs-pdf.js +17 -3
  70. package/dist/tools/fs/fs-read.d.ts +2 -1
  71. package/dist/tools/fs/fs-read.js +33 -5
  72. package/dist/tools/fs/fs-shared.d.ts +6 -2
  73. package/dist/tools/fs/index.d.ts +7 -0
  74. package/dist/tools/fs/index.js +1 -1
  75. package/dist/tools/task-list.d.ts +5 -1
  76. package/dist/tools/web.js +21 -2
  77. package/package.json +3 -2
  78. package/test/export-surface.snapshot.json +24 -2
@@ -3,6 +3,8 @@ import { isAbsolute } from "node:path";
3
3
  import { withDelegationProvenance } from "../core/tool-policy.js";
4
4
  import { isHighSurrogate, isLowSurrogate } from "../core/surrogate-safe-slice.js";
5
5
  import { newDelegationProvenanceAggregate, reduceDelegationAttestation } from "../core/memory-engine/delegation-provenance.js";
6
+ import { registerDelegationLaunch, replayExternalSettlementEffects, settleDelegation } from "../core/memory-engine/delegation-settlement.js";
7
+ import { enqueueMemoryAnnouncement } from "../core/memory-engine/layout.js";
6
8
  import { resolveModel, resolveModelDisplayLabel } from "../core/roles.js";
7
9
  import { OUTPUT_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME } from "../core/runner/synthetic-tools.js";
8
10
  import { TOOL_SEARCH_NAME } from "../core/runner/tool-disclosure.js";
@@ -22,7 +24,7 @@ import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
22
24
  import { readDurableOrgAdmission } from "../core/memory-admission.js";
23
25
  import { extractErrorCode } from "../brain/errors.js";
24
26
  import { governanceRetryClass } from "../core/governance-codes.js";
25
- import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
27
+ import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS, DELEGATION_MAX_CONCURRENT_DEFAULT, DELEGATION_MAX_PER_SESSION_DEFAULT } from "../config/defaults.js";
26
28
  function rollupDelegatedCost(stats, nested) {
27
29
  if (stats.costMicroUsd === undefined)
28
30
  return undefined;
@@ -873,6 +875,43 @@ function parkCompletionNotify(deps) {
873
875
  };
874
876
  entry.deferredNotify = { ...(payload.seq !== undefined ? { seq: payload.seq } : {}), cancel, flush: () => deliver(false) };
875
877
  }
878
+ export function resolveDelegationEntryCaps(caps) {
879
+ const bad = (member, value) => {
880
+ const e = new Error(`RunnerDeps.delegationEntryCaps.${member} holds ${typeof value === "number" ? String(value) : value === null ? "null" : typeof value} — must be a positive integer (or absent for the default). A bad cap refuses loudly; it is never silently folded to the default.`);
881
+ e.code = "config.delegation_entry_caps";
882
+ throw e;
883
+ };
884
+ const member = (name, fallback) => {
885
+ const v = caps?.[name];
886
+ if (v === undefined)
887
+ return fallback;
888
+ if (typeof v !== "number" || !Number.isInteger(v) || v <= 0)
889
+ bad(name, v);
890
+ return v;
891
+ };
892
+ const maxConcurrent = member("maxConcurrent", DELEGATION_MAX_CONCURRENT_DEFAULT);
893
+ const maxCumulativePerSession = member("maxCumulativePerSession", DELEGATION_MAX_PER_SESSION_DEFAULT);
894
+ if (maxConcurrent > maxCumulativePerSession) {
895
+ const e = new Error(`RunnerDeps.delegationEntryCaps resolves to maxConcurrent ${maxConcurrent} > maxCumulativePerSession ${maxCumulativePerSession} — a tree cannot run more agents at once than it may ever create. Fix the pair; a contradictory configuration refuses loudly.`);
896
+ e.code = "config.delegation_entry_caps";
897
+ throw e;
898
+ }
899
+ return { maxConcurrent, maxCumulativePerSession };
900
+ }
901
+ const delegationEntryLedgers = new WeakMap();
902
+ function delegationEntryLedger(registry, key) {
903
+ let byKey = delegationEntryLedgers.get(registry);
904
+ if (byKey === undefined) {
905
+ byKey = new Map();
906
+ delegationEntryLedgers.set(registry, byKey);
907
+ }
908
+ let set = byKey.get(key);
909
+ if (set === undefined) {
910
+ set = new Set();
911
+ byKey.set(key, set);
912
+ }
913
+ return set;
914
+ }
876
915
  export function normalizeSubagentType(value) {
877
916
  return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
878
917
  }
@@ -1935,6 +1974,59 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1935
1974
  const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
1936
1975
  const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
1937
1976
  const bgScope = treeScope;
1977
+ const forkSettlementSeat = ctx.delegationSettlement?.();
1978
+ const forkSettleId = forkSettlementSeat !== undefined ? `bg-${uuidv7()}` : undefined;
1979
+ if (forkSettlementSeat !== undefined && forkSettleId !== undefined) {
1980
+ try {
1981
+ registerDelegationLaunch(forkSettlementSeat.controlDir, { settleId: forkSettleId, sessionId: forkSettlementSeat.sessionId, now: Date.now, ...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}) });
1982
+ }
1983
+ catch (e) {
1984
+ dropHostAbortListener();
1985
+ const wt = await finishWorktree();
1986
+ return {
1987
+ isError: true,
1988
+ content: `Sub-agent not started in background: the delegation settlement account could not record the launch (${(e instanceof Error ? e.message : String(e)).slice(0, 300)}) — fail-closed; repair the memory control plane or retry.${wt ? `\n${wt}` : ""}`,
1989
+ details: { error: "settlement_write_ahead_failed" },
1990
+ };
1991
+ }
1992
+ }
1993
+ const settleForkRow = (status) => {
1994
+ if (forkSettlementSeat === undefined || forkSettleId === undefined)
1995
+ return;
1996
+ const att = status !== undefined ? childAttestation(status) : undefined;
1997
+ const verdict = att === "external" ? "external" : att === "clean" ? "clean" : "unattestable";
1998
+ let settled = false;
1999
+ let lastErr;
2000
+ for (let attempt = 0; attempt < 3 && !settled; attempt++) {
2001
+ try {
2002
+ settleDelegation(forkSettlementSeat.controlDir, { settleId: forkSettleId, status: verdict, now: Date.now });
2003
+ settled = true;
2004
+ }
2005
+ catch (e) {
2006
+ lastErr = e;
2007
+ }
2008
+ }
2009
+ if (settled && verdict === "external") {
2010
+ try {
2011
+ replayExternalSettlementEffects(forkSettlementSeat.controlDir, { carry: true, now: Date.now });
2012
+ }
2013
+ catch {
2014
+ }
2015
+ }
2016
+ if (!settled) {
2017
+ const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
2018
+ try {
2019
+ enqueueMemoryAnnouncement(forkSettlementSeat.controlDir, {
2020
+ kind: "gate",
2021
+ at: Date.now(),
2022
+ items: [`delegation settlement: the terminal observation for a background fork of session ${JSON.stringify(forkSettlementSeat.sessionId)} could NOT be recorded (verdict ${verdict}) — the row stays pending and expires as UNPROVEN at the settlement window (fail-closed floor): ${detail.slice(0, 200)}`],
2023
+ });
2024
+ }
2025
+ catch {
2026
+ console.warn(`[sema] delegation settlement terminal write failed (fork, verdict ${verdict}, session ${forkSettlementSeat.sessionId}): ${detail}`);
2027
+ }
2028
+ }
2029
+ };
1938
2030
  let taskId;
1939
2031
  try {
1940
2032
  taskId = bg.registry.registerBackgroundAgent({
@@ -1962,6 +2054,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1962
2054
  catch {
1963
2055
  }
1964
2056
  const wt = await finishWorktree();
2057
+ if (forkSettlementSeat !== undefined && forkSettleId !== undefined) {
2058
+ try {
2059
+ settleDelegation(forkSettlementSeat.controlDir, { settleId: forkSettleId, status: "void", now: Date.now, note: "registration failed before invoke (not dispatched)" });
2060
+ }
2061
+ catch {
2062
+ }
2063
+ }
1965
2064
  return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
1966
2065
  }
1967
2066
  childInternals.peerSelfRef?.addAxis("h", taskId);
@@ -2106,6 +2205,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2106
2205
  void opts.runner
2107
2206
  .runTask(bgForkSpec, bgForkInternals)
2108
2207
  .then(async (child) => {
2208
+ settleForkRow(child.status);
2109
2209
  dropHostAbortListener();
2110
2210
  await finishWorktree();
2111
2211
  try {
@@ -2202,6 +2302,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2202
2302
  }
2203
2303
  })
2204
2304
  .catch((e) => {
2305
+ settleForkRow("failed");
2205
2306
  dropHostAbortListener();
2206
2307
  void finishWorktree();
2207
2308
  const killed = abort.signal.aborted;
@@ -2268,7 +2369,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2268
2369
  : `Note: it is stopped automatically (killed) if still running when this task ends — do not promise the user results beyond this task.`,
2269
2370
  ],
2270
2371
  }),
2271
- details: { type: "agent", subagent_type: FORK_SUBAGENT_TYPE, status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt },
2372
+ details: { type: "agent", subagent_type: FORK_SUBAGENT_TYPE, status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt, ...(forkSettleId !== undefined ? { settle_id: forkSettleId } : {}) },
2272
2373
  };
2273
2374
  }
2274
2375
  const bgIgnoredNote = a.run_in_background === true
@@ -2368,6 +2469,107 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2368
2469
  const shortDesc = reviveRow?.description ?? String(a.description ?? "sub-agent").slice(0, 200);
2369
2470
  const bgOwner = reviveRow !== undefined ? reviveRow.owner : sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
2370
2471
  const bgScope = treeScope;
2472
+ const capScope = bgScope;
2473
+ const capRoot = reviveRow !== undefined
2474
+ ? (reviveRow.rootSessionId ?? reviveRow.parentSessionId ?? (reviveRow.sessionScoped ? reviveRow.owner : undefined))
2475
+ : (ctx.rootSessionId ?? ctx.sessionId);
2476
+ const entryCaps = ctx.delegationEntryCaps ?? { maxConcurrent: DELEGATION_MAX_CONCURRENT_DEFAULT, maxCumulativePerSession: DELEGATION_MAX_PER_SESSION_DEFAULT };
2477
+ const capLedgerKey = capScope !== undefined && capRoot !== undefined ? JSON.stringify([capScope, capRoot]) : undefined;
2478
+ if (capScope !== undefined && capRoot !== undefined) {
2479
+ let storedHandles = [];
2480
+ let storedEnumerationOk = false;
2481
+ if (bg.agentStore !== undefined && reviveRow === undefined) {
2482
+ try {
2483
+ storedHandles = (await bg.agentStore.listBySession(capScope, capRoot)).map((r) => r.handle);
2484
+ storedEnumerationOk = true;
2485
+ }
2486
+ catch {
2487
+ storedHandles = [];
2488
+ }
2489
+ }
2490
+ const capRefusal = async (code, text) => {
2491
+ dropHostAbortListener();
2492
+ await cancelObserver();
2493
+ const wt = await finishWorktree();
2494
+ return { isError: true, content: `Sub-agent not started in background: ${text}${wt ? `\n${wt}` : ""}`, details: { error: code } };
2495
+ };
2496
+ const activeFace = bg.registry.activeDelegationHandles;
2497
+ const active = typeof activeFace === "function" ? activeFace.call(bg.registry, capScope, capRoot) : [];
2498
+ if (typeof activeFace === "function" && active.length >= entryCaps.maxConcurrent) {
2499
+ return await capRefusal("delegation.concurrency_cap", `this session tree already has ${active.length} background agents running — the concurrency cap (${entryCaps.maxConcurrent}; RunnerDeps.delegationEntryCaps.maxConcurrent) refuses another. Wait for one to complete (you will be notified) or stop one, then relaunch.`);
2500
+ }
2501
+ if (reviveRow === undefined) {
2502
+ const ledger = delegationEntryLedger(bg.registry, capLedgerKey);
2503
+ let cumulative;
2504
+ if (bg.agentStore !== undefined && storedEnumerationOk) {
2505
+ const keep = new Set([...storedHandles, ...active]);
2506
+ for (const h of [...ledger])
2507
+ if (!keep.has(h))
2508
+ ledger.delete(h);
2509
+ cumulative = new Set([...storedHandles, ...ledger]).size;
2510
+ }
2511
+ else {
2512
+ cumulative = ledger.size;
2513
+ }
2514
+ if (cumulative >= entryCaps.maxCumulativePerSession) {
2515
+ return await capRefusal("delegation.session_cap", `this session tree has already launched ${cumulative} background agents in its retained window — the cumulative cap (${entryCaps.maxCumulativePerSession}; RunnerDeps.delegationEntryCaps.maxCumulativePerSession) refuses more. Continue an existing agent (SendMessage) instead of launching new ones.`);
2516
+ }
2517
+ }
2518
+ }
2519
+ const settlementSeat = ctx.delegationSettlement?.();
2520
+ const bgSettleId = settlementSeat !== undefined ? `bg-${uuidv7()}` : undefined;
2521
+ if (settlementSeat !== undefined && bgSettleId !== undefined) {
2522
+ try {
2523
+ registerDelegationLaunch(settlementSeat.controlDir, { settleId: bgSettleId, sessionId: settlementSeat.sessionId, now: Date.now, ...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}) });
2524
+ }
2525
+ catch (e) {
2526
+ dropHostAbortListener();
2527
+ await cancelObserver();
2528
+ const wt = await finishWorktree();
2529
+ return {
2530
+ isError: true,
2531
+ content: `Sub-agent not started in background: the delegation settlement account could not record the launch (${(e instanceof Error ? e.message : String(e)).slice(0, 300)}) — fail-closed; repair the memory control plane or retry.${wt ? `\n${wt}` : ""}`,
2532
+ details: { error: "settlement_write_ahead_failed" },
2533
+ };
2534
+ }
2535
+ }
2536
+ const settleDelegationRow = (status) => {
2537
+ if (settlementSeat === undefined || bgSettleId === undefined)
2538
+ return;
2539
+ const att = status !== undefined ? childAttestation(status) : undefined;
2540
+ const verdict = att === "external" ? "external" : att === "clean" ? "clean" : "unattestable";
2541
+ let settled = false;
2542
+ let lastErr;
2543
+ for (let attempt = 0; attempt < 3 && !settled; attempt++) {
2544
+ try {
2545
+ settleDelegation(settlementSeat.controlDir, { settleId: bgSettleId, status: verdict, now: Date.now });
2546
+ settled = true;
2547
+ }
2548
+ catch (e) {
2549
+ lastErr = e;
2550
+ }
2551
+ }
2552
+ if (settled && verdict === "external") {
2553
+ try {
2554
+ replayExternalSettlementEffects(settlementSeat.controlDir, { carry: true, now: Date.now });
2555
+ }
2556
+ catch {
2557
+ }
2558
+ }
2559
+ if (!settled) {
2560
+ const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
2561
+ try {
2562
+ enqueueMemoryAnnouncement(settlementSeat.controlDir, {
2563
+ kind: "gate",
2564
+ at: Date.now(),
2565
+ items: [`delegation settlement: the terminal observation for a background delegation of session ${JSON.stringify(settlementSeat.sessionId)} could NOT be recorded (verdict ${verdict}) — the row stays pending and expires as UNPROVEN at the settlement window (fail-closed floor): ${detail.slice(0, 200)}`],
2566
+ });
2567
+ }
2568
+ catch {
2569
+ console.warn(`[sema] delegation settlement terminal write failed (verdict ${verdict}, session ${settlementSeat.sessionId}): ${detail} — the pending row expires as unproven at the settlement window`);
2570
+ }
2571
+ }
2572
+ };
2371
2573
  let taskId;
2372
2574
  try {
2373
2575
  taskId = bg.registry.registerBackgroundAgent(reviveRow !== undefined
@@ -2427,8 +2629,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2427
2629
  dropHostAbortListener();
2428
2630
  await cancelObserver();
2429
2631
  const wt = await finishWorktree();
2632
+ if (settlementSeat !== undefined && bgSettleId !== undefined) {
2633
+ try {
2634
+ settleDelegation(settlementSeat.controlDir, { settleId: bgSettleId, status: "void", now: Date.now, note: "registration failed before invoke (not dispatched)" });
2635
+ }
2636
+ catch {
2637
+ }
2638
+ }
2430
2639
  return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
2431
2640
  }
2641
+ if (capLedgerKey !== undefined && reviveRow === undefined)
2642
+ delegationEntryLedger(bg.registry, capLedgerKey).add(taskId);
2432
2643
  childInternals.peerSelfRef?.addAxis("h", taskId);
2433
2644
  if (agentName !== undefined) {
2434
2645
  recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
@@ -2566,10 +2777,22 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2566
2777
  usage: { toolUses: toolStarts },
2567
2778
  });
2568
2779
  }, () => bg.registry.noteBackgroundAgentActivity(taskId));
2780
+ const bgPlacementRoot = reviveRow !== undefined ? (reviveRow.rootSessionId ?? reviveRow.parentSessionId) : (ctx.rootSessionId ?? ctx.sessionId);
2781
+ const bgPlacementParent = reviveRow !== undefined ? reviveRow.parentSessionId : ctx.sessionId;
2782
+ const bgPlacement = bg.agentStore !== undefined && opts.runner.sessions.placements?.subagent !== undefined
2783
+ ? {
2784
+ kind: "subagent",
2785
+ ...(bgScope !== undefined ? { scope: bgScope } : {}),
2786
+ ...(bgPlacementParent !== undefined ? { parentSessionId: bgPlacementParent } : {}),
2787
+ ...(bgPlacementRoot !== undefined ? { rootSessionId: bgPlacementRoot } : {}),
2788
+ handle: taskId,
2789
+ }
2790
+ : undefined;
2569
2791
  const bgInternals = bgSink
2570
2792
  ? {
2571
2793
  ...childInternals,
2572
2794
  ...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
2795
+ ...(bgPlacement !== undefined ? { sessionPlacement: bgPlacement } : {}),
2573
2796
  onNotifyInjectorReady: s2NotifyReady,
2574
2797
  delegationTaskType: "background_agent",
2575
2798
  ...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
@@ -2605,6 +2828,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2605
2828
  : {
2606
2829
  ...childInternals,
2607
2830
  ...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
2831
+ ...(bgPlacement !== undefined ? { sessionPlacement: bgPlacement } : {}),
2608
2832
  onNotifyInjectorReady: s2NotifyReady,
2609
2833
  delegationTaskType: "background_agent",
2610
2834
  ...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
@@ -2635,7 +2859,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2635
2859
  };
2636
2860
  try {
2637
2861
  await opts.runner.sessions.acquire(bgChildSessionId, { requireExisting: true });
2638
- await opts.runner.sessions.release(bgChildSessionId);
2862
+ if (opts.runner.sessions.forget)
2863
+ await opts.runner.sessions.forget(bgChildSessionId);
2639
2864
  }
2640
2865
  catch (e) {
2641
2866
  const code = e.code;
@@ -2744,6 +2969,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2744
2969
  : opts.runner.runTask(bgSpec, bgInternals);
2745
2970
  void bgChildPromise
2746
2971
  .then(async (child) => {
2972
+ settleDelegationRow(child.status);
2747
2973
  closeObserverWindow(child.status);
2748
2974
  dropHostAbortListener();
2749
2975
  await finishWorktree();
@@ -2938,6 +3164,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2938
3164
  }
2939
3165
  })
2940
3166
  .catch(async (e) => {
3167
+ settleDelegationRow("failed");
2941
3168
  closeObserverWindow("failed");
2942
3169
  dropHostAbortListener();
2943
3170
  void finishWorktree();
@@ -3043,7 +3270,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3043
3270
  : `Note: it is stopped automatically (killed) if still running when this task ends — do not promise the user results beyond this task.`,
3044
3271
  ],
3045
3272
  }),
3046
- details: { type: "agent", status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt },
3273
+ details: { type: "agent", status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt, ...(bgSettleId !== undefined ? { settle_id: bgSettleId } : {}) },
3047
3274
  };
3048
3275
  }
3049
3276
  let child;
@@ -21,6 +21,26 @@
21
21
  */
22
22
  export declare const RETAIN_DEFAULT_TTL_MS: number;
23
23
  export declare const RETAIN_DEFAULT_MAX = 16;
24
+ /** Subagent transcript persistence — the delegation ENTRY caps (CC values: 20 concurrent /
25
+ * 200 per session tree). The INLET bound family (`RunnerDeps.delegationEntryCaps`), as opposed to
26
+ * the retain pair above, which is a pure in-memory fast-lane handle bound (outlet — armed
27
+ * deployments lose nothing when it evicts). Concurrent = running/pending a* handles under one
28
+ * (scope, rootSessionId) key in THIS process's registry; cumulative = retained-window count under
29
+ * the same key (see the enforcement site for the exact retained-window semantics). */
30
+ export declare const DELEGATION_MAX_CONCURRENT_DEFAULT = 20;
31
+ export declare const DELEGATION_MAX_PER_SESSION_DEFAULT = 200;
32
+ /** Subagent transcript persistence — orphan adoption (CC parity: after a restart a deployment
33
+ * auto-adopts at most this many stale rows whose transcript mtime is inside the window; older ones
34
+ * stay MANUALLY continuable for the whole retention period — never deleted by the window). The
35
+ * TRIGGER is deployment-owned (core has no daemon and never revives runs nobody asked for); these
36
+ * constants are the shared vocabulary so every deployment adopts by the same numbers. */
37
+ export declare const ORPHAN_ADOPT_WINDOW_MS_DEFAULT: number;
38
+ export declare const ORPHAN_ADOPT_MAX_DEFAULT = 20;
39
+ /** Subagent transcript persistence — the default transcript retention period (CC
40
+ * cleanupPeriodDays parity). Core ships NO sweeper (design/151 ruling 2): the blessed path is the
41
+ * deployment calling `TaskRegistry.reapDurableAgents` with `maxAgeMs` derived from this (boot +
42
+ * every 24h is the reference cadence). */
43
+ export declare const SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT = 30;
24
44
  /** Default idle TTL (days) before a cached session is evicted (config-catalog `session.idleTtlDays`). */
25
45
  export declare const SESSION_DEFAULT_TTL_DAYS = 7;
26
46
  /**
@@ -1,4 +1,9 @@
1
1
  export const RETAIN_DEFAULT_TTL_MS = 30 * 60 * 1000;
2
2
  export const RETAIN_DEFAULT_MAX = 16;
3
+ export const DELEGATION_MAX_CONCURRENT_DEFAULT = 20;
4
+ export const DELEGATION_MAX_PER_SESSION_DEFAULT = 200;
5
+ export const ORPHAN_ADOPT_WINDOW_MS_DEFAULT = 48 * 60 * 60 * 1000;
6
+ export const ORPHAN_ADOPT_MAX_DEFAULT = 20;
7
+ export const SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT = 30;
3
8
  export const SESSION_DEFAULT_TTL_DAYS = 7;
4
9
  export const RUNNING_AGENT_OBSERVE_EVERY_BEATS = 4;
@@ -170,6 +170,7 @@ export interface BackgroundAgentRecord {
170
170
  export declare const REVIVED_ROW_CLEARED_FIELDS: readonly ["settledAt", "stoppedBy", "completionId", "finalOutput", "finalOutputFull", "error", "errorCode", "errorRetryable", "errorKind", "errorRetryAfterMs", "resultIsPartial", "summary", "recentSteps", "editedFiles", "usage"];
171
171
  /** Erase {@link REVIVED_ROW_CLEARED_FIELDS} from a record a revival is about to write back. */
172
172
  export declare function clearRevivedRowTerminalPayload(record: BackgroundAgentRecord): void;
173
+ export declare function announceTranscriptIntegrityGapOnce(handle: string, sink: ((handle: string) => void) | undefined): void;
173
174
  /** Content-free projection for list reads (design/151 HIGH-1: `summary`/`finalOutput`/`recentSteps`
174
175
  * and friends NEVER ride a list — content is get-by-handle only, behind the full predicate). */
175
176
  export interface BackgroundAgentRowSummary {
@@ -21,6 +21,19 @@ export function clearRevivedRowTerminalPayload(record) {
21
21
  for (const field of REVIVED_ROW_CLEARED_FIELDS)
22
22
  delete record[field];
23
23
  }
24
+ const transcriptIntegrityAnnounced = new Set();
25
+ export function announceTranscriptIntegrityGapOnce(handle, sink) {
26
+ if (sink === undefined)
27
+ return;
28
+ if (transcriptIntegrityAnnounced.has(handle))
29
+ return;
30
+ transcriptIntegrityAnnounced.add(handle);
31
+ try {
32
+ sink(handle);
33
+ }
34
+ catch {
35
+ }
36
+ }
24
37
  export class BackgroundAgentStoreError extends Error {
25
38
  code;
26
39
  constructor(code, message) {
@@ -67,3 +67,16 @@ export type RuleSyncDropReason = keyof typeof RULE_SYNC_DROP_CODES;
67
67
  /** The subset that may appear on a LOCAL quarantined row (design/182 §8.1 `quarantine` instruction /
68
68
  * fence arm / local screening). `own_actor_forged` is inbound-only by construction. */
69
69
  export type RuleQuarantineReason = Exclude<RuleSyncDropReason, "own_actor_forged">;
70
+ /**
71
+ * The presentation-tier registry (the server's wire whitelist retires into this
72
+ * table once shipped): WHO a notice code is for. `"user"` = a session-scoped disclosure the end
73
+ * user of that session should see (safe to project onto the session's event stream); `"operator"`
74
+ * = a deployment/config/ops fact for whoever runs the process. Presentation tier is a property of
75
+ * the CODE (closed set, one code one tier) — never of an individual emission, which is why this is
76
+ * a registry and not an EngineNotice field. Codes absent from the table read as `"operator"`
77
+ * (the conservative default: never push an unclassified code at an end user).
78
+ * Orthogonal to {@link NON_GOVERNANCE_MEMORY_CODES} (a retry-semantics table, not presentation).
79
+ */
80
+ export declare const NOTICE_AUDIENCE: Readonly<Record<string, "user" | "operator">>;
81
+ /** The audience for `code` — table lookup with the conservative `"operator"` default. */
82
+ export declare function noticeAudienceOf(code: string): "user" | "operator";
@@ -29,6 +29,28 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
29
29
  "memory.erasure_index_residue",
30
30
  "memory.export_incomplete",
31
31
  "memory.import_rejected",
32
+ "memory.hold_opened",
33
+ "memory.hold_released",
34
+ "memory.hold_disposed",
35
+ "memory.settlement_resolve_unattributed",
36
+ "memory.settlement_resolve_invalid",
37
+ "memory.settlement_resolve_unknown",
38
+ "memory.session_account_resolve_unattributed",
39
+ "memory.session_account_resolve_unknown",
40
+ "memory.hold_resolve_unattributed",
41
+ "memory.hold_resolve_invalid",
42
+ "memory.hold_resolve_unknown",
43
+ "memory.hold_resolve_invalid_state",
44
+ "memory.settlement_record_failed",
45
+ "memory.session_account_failed",
46
+ "memory.origin_clear_unattributed",
47
+ "memory.origin_clear_invalid",
48
+ "memory.origin_clear_unknown",
49
+ "memory.origin_clear_not_marked",
50
+ "memory.origin_clear_challenged",
51
+ "memory.origin_clear_pending",
52
+ "memory.origin_clear_conflict",
53
+ "memory.origin_clear_failed",
32
54
  ]);
33
55
  export function governanceRetryClass(code) {
34
56
  if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
@@ -46,3 +68,14 @@ export const RULE_SYNC_DROP_CODES = {
46
68
  below_gc_frontier: "local-quarantined",
47
69
  server_rejected: "local-quarantined",
48
70
  };
71
+ export const NOTICE_AUDIENCE = {
72
+ "memory.session_polluted": "user",
73
+ "memory.harvest_quarantined": "user",
74
+ "memory.delegation_static_mark_waived": "user",
75
+ "memory.hold_opened": "user",
76
+ "memory.hold_released": "user",
77
+ "memory.hold_disposed": "user",
78
+ };
79
+ export function noticeAudienceOf(code) {
80
+ return NOTICE_AUDIENCE[code] ?? "operator";
81
+ }
@@ -36,6 +36,7 @@ import type { AgentTool } from "../internal/harness-types.js";
36
36
  import type { ImageContent, TextContent } from "../internal/llm.js";
37
37
  import { type McpImageResizer } from "./image-downsample.js";
38
38
  import type { McpServerSpec, OnElicit, ToolEffect } from "./types.js";
39
+ import { type ReminderDisclosureCounts } from "./reminder-disclosure.js";
39
40
  /**
40
41
  * The safety axes (design/77 §4 irreversibility, design/70 egress) derived from one materialized MCP
41
42
  * tool's server-advertised `annotations`. These ride alongside the {@link AgentTool} (which is vendored
@@ -462,7 +463,11 @@ export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormal
462
463
  * combinator's own structure is legal JSON Schema ⇒ passes validateJsonSchemaShape too).
463
464
  */
464
465
  export declare function mcpToolSchemaProblem(schema: unknown): string | undefined;
465
- export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer): Promise<MaterializedMcp>;
466
+ export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer, // design/116 CONFIRM-1 seam: deployment-injected; default = auto-detected sharp
467
+ reminderDisclosure?: {
468
+ reminderMark?: string;
469
+ counts?: ReminderDisclosureCounts;
470
+ }): Promise<MaterializedMcp>;
466
471
  /**
467
472
  * Fold the caller's AUTHORITATIVE per-tool override (design F: caller = trust root) over the server-hint axis.
468
473
  * Unlike server hints, the caller may RAISE or LOWER any axis: `effect` sets the repeat-safety class (lower to
package/dist/core/mcp.js CHANGED
@@ -11,6 +11,7 @@ import { MCP_IMAGE_MAX_BASE64, sharpImageResizer } from "./image-downsample.js";
11
11
  import { sliceHeadSafe, sliceTailSafe } from "./surrogate-safe-slice.js";
12
12
  import { truncateError } from "./tool-errors.js";
13
13
  import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
14
+ import { discloseReminderShaped } from "./reminder-disclosure.js";
14
15
  import { withContentOrigin } from "./memory-engine/content-origin.js";
15
16
  import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
16
17
  export const MCP_PREFIX = MCP_NAMESPACE.prefix;
@@ -649,7 +650,10 @@ export function mcpToolSchemaProblem(schema) {
649
650
  }
650
651
  return undefined;
651
652
  }
652
- export async function materializeMcpTools(specs, principal, onElicit, imageResizer) {
653
+ export async function materializeMcpTools(specs, principal, onElicit, imageResizer, reminderDisclosure) {
654
+ const mcpDisclosure = reminderDisclosure?.reminderMark !== undefined
655
+ ? { mark: reminderDisclosure.reminderMark, windows: new Map(), ...(reminderDisclosure.counts !== undefined ? { counts: reminderDisclosure.counts } : {}) }
656
+ : undefined;
653
657
  const clients = [];
654
658
  const tools = [];
655
659
  const toolAxes = [];
@@ -661,7 +665,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
661
665
  const droppedTools = [];
662
666
  let disposing = false;
663
667
  const serverHandles = [];
664
- const settled = await Promise.allSettled(specs.map((spec) => connectServer(spec, principal, onElicit, imageResizer)));
668
+ const settled = await Promise.allSettled(specs.map((spec) => connectServer(spec, principal, onElicit, imageResizer, mcpDisclosure)));
665
669
  try {
666
670
  for (let i = 0; i < specs.length; i++) {
667
671
  const r = settled[i];
@@ -728,7 +732,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
728
732
  try {
729
733
  const listed = await listToolsLenient(h.client);
730
734
  cacheMcpToolMetadata(h.client, listed.tools);
731
- const { serverTools, serverAxes, dropped } = intakeListedTools(listed, h.spec, h.client, h.health, imageResizer);
735
+ const { serverTools, serverAxes, dropped } = intakeListedTools(listed, h.spec, h.client, h.health, imageResizer, mcpDisclosure);
732
736
  const newNames = serverTools.map((t) => t.name);
733
737
  const added = newNames.filter((n) => !h.toolNames.includes(n));
734
738
  const removed = h.toolNames.filter((n) => !newNames.includes(n));
@@ -1119,7 +1123,7 @@ const LENIENT_CALL_TOOL_RESULT_SCHEMA = { safeParse: parseCallToolResultLenient
1119
1123
  async function listToolsLenient(client, options) {
1120
1124
  return client.request({ method: "tools/list", params: {} }, LenientListToolsResultSchema, options);
1121
1125
  }
1122
- async function connectServer(spec, principal, onElicit, imageResizer) {
1126
+ async function connectServer(spec, principal, onElicit, imageResizer, reminderDisclosure) {
1123
1127
  const elicitOn = spec.elicitation === true && onElicit !== undefined;
1124
1128
  const health = { dead: false, pendingElicitations: 0, lastElicitationClosedAt: 0 };
1125
1129
  const client = new Client({ name: `sema-core/${spec.name}`, version: "0.1.0" }, { capabilities: elicitOn ? { elicitation: { form: {} } } : {} });
@@ -1155,7 +1159,7 @@ async function connectServer(spec, principal, onElicit, imageResizer) {
1155
1159
  };
1156
1160
  const listed = await listToolsLenient(client, startupOpts);
1157
1161
  cacheMcpToolMetadata(client, listed.tools);
1158
- const { serverTools, serverAxes, dropped } = intakeListedTools(listed, spec, client, health, imageResizer);
1162
+ const { serverTools, serverAxes, dropped } = intakeListedTools(listed, spec, client, health, imageResizer, reminderDisclosure);
1159
1163
  const caps = client.getServerCapabilities();
1160
1164
  const resourceInfo = caps?.resources
1161
1165
  ? {
@@ -1188,7 +1192,7 @@ async function connectServer(spec, principal, onElicit, imageResizer) {
1188
1192
  throw err;
1189
1193
  }
1190
1194
  }
1191
- function intakeListedTools(listed, spec, client, health, imageResizer) {
1195
+ function intakeListedTools(listed, spec, client, health, imageResizer, reminderDisclosure) {
1192
1196
  const serverTools = [];
1193
1197
  const serverAxes = [];
1194
1198
  const dropped = [];
@@ -1276,8 +1280,31 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1276
1280
  type: "text",
1277
1281
  text: `[structuredContent] JSON with schema: ${inferCompactSchema(sc)}\n${truncateError(JSON.stringify(sc))}`,
1278
1282
  });
1279
- return { content, details: { type: "mcp", structuredContent: sc }, terminate: false };
1280
1283
  }
1284
+ if (reminderDisclosure !== undefined) {
1285
+ const textIdx = [];
1286
+ const segments = [];
1287
+ content.forEach((b, i) => {
1288
+ if (b.type === "text" && typeof b.text === "string") {
1289
+ textIdx.push(i);
1290
+ segments.push(b.text);
1291
+ }
1292
+ });
1293
+ const d = discloseReminderShaped({
1294
+ segments,
1295
+ mark: reminderDisclosure.mark,
1296
+ outlet: "mcp",
1297
+ defuseExactMark: true,
1298
+ throttle: { key: `${spec.name}:${remoteName}`, windows: reminderDisclosure.windows },
1299
+ ...(reminderDisclosure.counts !== undefined ? { counts: reminderDisclosure.counts } : {}),
1300
+ });
1301
+ if (d.defused)
1302
+ textIdx.forEach((ci, si) => (content[ci] = { type: "text", text: d.segments[si] }));
1303
+ if (d.trailer !== undefined)
1304
+ content.push({ type: "text", text: d.trailer });
1305
+ }
1306
+ if (sc !== undefined)
1307
+ return { content, details: { type: "mcp", structuredContent: sc }, terminate: false };
1281
1308
  return { content, details: res, terminate: false };
1282
1309
  },
1283
1310
  });