@sema-agent/core 5.12.0 → 5.13.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.
@@ -1,3 +1,4 @@
1
+ import { admitMemoryScopes } from "../memory-admission.js";
1
2
  import { adoptLegacyRepoDirs, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
2
3
  import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
3
4
  import { normalizeMemorySpec } from "../memory.js";
@@ -5,8 +6,33 @@ import { MemoryEngine } from "../memory-engine/engine.js";
5
6
  import { assertScopeContractPlacement, parseScopeKey, resolveProjectId } from "../memory-engine/scope-contract.js";
6
7
  import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
7
8
  export async function prepareMemory(input) {
8
- const { spec, deps, sessionId, taskRootPath, memoryWriteGateRef } = input;
9
+ const { spec, deps, sessionId, taskRootPath, memoryWriteGateRef, admissionCtx } = input;
9
10
  const memorySpec = normalizeMemorySpec(spec.memory);
11
+ let admittedOrgScopes = [];
12
+ let ownOrgVerdict;
13
+ if (memorySpec && memorySpec.enabled && deps.memoryBackend) {
14
+ const outcome = await admitMemoryScopes({
15
+ memorySpec,
16
+ principal: spec.principal,
17
+ admission: deps.memoryScopeAdmission,
18
+ deploymentScopes: new Set(deps.deploymentMemoryScopes ?? []),
19
+ orgMemoryDenied: admissionCtx.orgMemoryDenied,
20
+ complianceDegraded: admissionCtx.complianceDegraded,
21
+ parentAdmittedOrgScopes: admissionCtx.parentAdmittedOrgScopes,
22
+ priorOwnVerdict: admissionCtx.priorOwnVerdict,
23
+ governedProvenance: admissionCtx.governedProvenance,
24
+ });
25
+ admittedOrgScopes = outcome.admittedOrgScopes;
26
+ ownOrgVerdict = outcome.ownVerdict;
27
+ for (const droppedScope of outcome.droppedDeploymentScopes) {
28
+ deps.onError?.(new Error(`org memory scope "${droppedScope}" (deployment-declared) was narrowed away (admission resolver or the session's frozen verdict) — the layer is not mounted this session.`), { phase: "memory", sessionId });
29
+ }
30
+ if (outcome.writeScopeNarrowed) {
31
+ deps.onError?.(new Error(`org memory writeScope "${String(memorySpec.writeScope)}" was not explicitly granted by admission — the session's memory write face is read-only (org layers default read-only).`), { phase: "memory", sessionId });
32
+ }
33
+ memorySpec.scopes = outcome.scopes;
34
+ memorySpec.writeScope = outcome.writeScope;
35
+ }
10
36
  const useMemoryEngine = Boolean(memorySpec && memorySpec.enabled && deps.memoryBackend);
11
37
  let memoryEngineSession;
12
38
  if (useMemoryEngine && memorySpec) {
@@ -193,5 +219,5 @@ export async function prepareMemory(input) {
193
219
  if (memoryBlock !== undefined && injection.indexSeed !== undefined)
194
220
  seedFiles = [injection.indexSeed];
195
221
  }
196
- return { memoryEngineSession, memoryBlock, ...(seedFiles !== undefined ? { seedFiles } : {}) };
222
+ return { memoryEngineSession, memoryBlock, admittedOrgScopes, ownOrgVerdict, ...(seedFiles !== undefined ? { seedFiles } : {}) };
197
223
  }
@@ -304,6 +304,8 @@ export interface InheritedGate {
304
304
  rules: SessionPermissionRules;
305
305
  }>;
306
306
  shellGate?: "off" | "always" | "classify";
307
+ admittedOrgScopes?: readonly string[];
308
+ orgAdmissionGoverned?: true;
307
309
  parentConstraints?: ReadonlyArray<{
308
310
  policy: ToolPolicy;
309
311
  onAsk?: OnAsk;
@@ -38,6 +38,10 @@ import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, S
38
38
  import { compileOutputSchema } from "./strict-output-schema.js";
39
39
  import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, } from "./tool-disclosure.js";
40
40
  import { composeMemoryBlock } from "../memory.js";
41
+ import { preflightLockedConfig } from "../locked-config.js";
42
+ import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
43
+ import { assertRetentionCapability } from "../retention.js";
44
+ import { foldAdmissionFreeze } from "../memory-admission.js";
41
45
  import { prepareMemory } from "./prepare-memory.js";
42
46
  import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
43
47
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
@@ -69,7 +73,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
69
73
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
70
74
  import { resolveKey } from "../../tools/fs/safety.js";
71
75
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
72
- import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
76
+ import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
73
77
  import { boundInputHashOf } from "../canonical-json.js";
74
78
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
75
79
  const RECONCILE_MAX_RETRIES = 3;
@@ -337,6 +341,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
337
341
  e.code = "config.empty_objective";
338
342
  throw e;
339
343
  }
344
+ const lockedPreflight = preflightLockedConfig(spec, deps);
345
+ assertRetentionCapability({
346
+ policy: deps.retentionPolicy,
347
+ locked: lockedPreflight.lockedKeys.has("retentionPolicy"),
348
+ stores: [
349
+ { name: "sessionStore", store: sessions },
350
+ { name: "checkpointStore", store: resolveCheckpointStore(spec, deps) },
351
+ { name: "toolResultStore", store: deps.toolResultStore },
352
+ ],
353
+ });
340
354
  resolveTaskLimits(spec.limits);
341
355
  if (spec.resourceSuspend !== undefined) {
342
356
  const rsus = spec.resourceSuspend;
@@ -940,9 +954,24 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
940
954
  ? liveShellGate
941
955
  : seedShellGate;
942
956
  const inheritedParentConstraints = liveInheritedGate?.parentConstraints;
957
+ const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
958
+ const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
959
+ const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
960
+ const inheritedOrgGoverned = liveInheritedGate?.orgAdmissionGoverned === true || seedInheritedGate?.orgAdmissionGoverned === true;
961
+ const priorOwnOrgVerdict = resume?.seed.inheritedGate?.ownAdmittedOrgScopes !== undefined
962
+ ? { scopes: resume.seed.inheritedGate.ownAdmittedOrgScopes, writeScope: resume.seed.inheritedGate.ownAdmittedOrgWriteScope ?? null }
963
+ : undefined;
964
+ const orgGovernedProvenance = deps.memoryScopeAdmission !== undefined ||
965
+ (deps.deploymentMemoryScopes !== undefined && deps.deploymentMemoryScopes.length > 0) ||
966
+ deps.compliancePostureResolver !== undefined ||
967
+ inheritedOrgGoverned ||
968
+ priorOwnOrgVerdict !== undefined;
943
969
  const specShellGate = spec.shellGate ?? "off";
944
970
  const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
945
971
  const ownSessionRulesRef = {};
972
+ const memoryAdmittedOrgScopesRef = { current: [] };
973
+ const ownOrgVerdictRef = { current: undefined };
974
+ const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
946
975
  const frozenOnAsk = spec.onAsk ?? deps.onAsk;
947
976
  const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
948
977
  const inheritedGateForChildren = () => {
@@ -950,7 +979,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
950
979
  ...(inheritedAncestorRules ?? []),
951
980
  ...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
952
981
  ];
953
- const ownCallerPolicy = spec.toolPolicy ?? deps.toolPolicy;
982
+ const ownCallerPolicy = lockedPreflight.toolPolicy;
954
983
  const durableMandate = runtimeCaps?.forceDurableGate === true || (spec.durableApproval !== undefined && frozenOnAsk === undefined);
955
984
  const parentConstraints = [
956
985
  ...(inheritedParentConstraints ?? []),
@@ -968,6 +997,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
968
997
  ...(ancestorRules.length > 0 ? { ancestorRules } : {}),
969
998
  ...(effectiveShellGate !== "off" ? { shellGate: effectiveShellGate } : {}),
970
999
  ...(parentConstraints.length > 0 ? { parentConstraints } : {}),
1000
+ admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
1001
+ ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
971
1002
  };
972
1003
  };
973
1004
  let agentForkDenial;
@@ -1072,6 +1103,39 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1072
1103
  runtimeCaps = { allowWorkflows: false, allowFork: false };
1073
1104
  }
1074
1105
  }
1106
+ let complianceDenies = new Set();
1107
+ let complianceDegraded = false;
1108
+ if (deps.compliancePostureResolver) {
1109
+ try {
1110
+ const posture = (await deps.compliancePostureResolver(spec.principal)) ?? undefined;
1111
+ if (posture !== undefined)
1112
+ complianceDenies = resolveComplianceDenies(posture);
1113
+ }
1114
+ catch (err) {
1115
+ deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1116
+ complianceDenies = new Set(COMPLIANCE_CAPABILITIES);
1117
+ complianceDegraded = true;
1118
+ }
1119
+ const complianceRefuse = (capability, requested) => {
1120
+ const e = new Error(`${requested} is denied for this principal by the compliance posture (capability "${capability}"` +
1121
+ `${complianceDegraded ? "; the posture resolver is currently failing, so every managed capability is denied fail-closed" : ""}) — ` +
1122
+ `the task is refused rather than silently narrowed.`);
1123
+ e.code = complianceDegraded ? "config.compliance_required" : "config.compliance_denied";
1124
+ throw e;
1125
+ };
1126
+ if (complianceDenies.has("mcp_servers") && lockedPreflight.mcp?.length) {
1127
+ complianceRefuse("mcp_servers", "TaskSpec.mcp (MCP server materialization)");
1128
+ }
1129
+ if (complianceDenies.has("workflows") && spec.selfOrchestration === true) {
1130
+ complianceRefuse("workflows", "TaskSpec.selfOrchestration (workflow self-orchestration)");
1131
+ }
1132
+ if (complianceDenies.has("web_fetch")) {
1133
+ const webTool = (spec.tools ?? []).find((t) => t.name === WEB_FETCH_TOOL_NAME || (t.aliases ?? []).includes(WEB_FETCH_TOOL_NAME));
1134
+ if (webTool !== undefined) {
1135
+ complianceRefuse("web_fetch", `TaskSpec.tools["${webTool.name}"] (the WebFetch tool face)`);
1136
+ }
1137
+ }
1138
+ }
1075
1139
  agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
1076
1140
  observersActive = runtimeCaps?.allowObservers === true;
1077
1141
  if (runtimeCaps?.autoMode === true && deps.autoMode !== undefined) {
@@ -1220,8 +1284,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1220
1284
  }
1221
1285
  tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
1222
1286
  }
1223
- mcp = spec.mcp?.length
1224
- ? await materializeMcpTools(spec.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer)
1287
+ mcp = lockedPreflight.mcp?.length
1288
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer)
1225
1289
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1226
1290
  for (const w of mcp.warnings)
1227
1291
  deps.onError?.(w, { phase: "mcp", sessionId });
@@ -1241,7 +1305,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1241
1305
  tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
1242
1306
  const rebuildHarnessToolsRef = {};
1243
1307
  const toolCallGateArmedRef = { armed: false };
1244
- if (spec.mcp?.length) {
1308
+ if (lockedPreflight.mcp?.length) {
1245
1309
  tools.push({
1246
1310
  name: "RefreshMcpTools",
1247
1311
  label: "RefreshMcpTools",
@@ -1632,7 +1696,31 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1632
1696
  .catch(() => undefined);
1633
1697
  }
1634
1698
  : undefined;
1635
- const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, seedFiles: memorySeedFiles } = await prepareMemory({ spec, deps, sessionId, taskRootPath, memoryWriteGateRef });
1699
+ const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, } = await prepareMemory({
1700
+ spec,
1701
+ deps,
1702
+ sessionId,
1703
+ taskRootPath,
1704
+ memoryWriteGateRef,
1705
+ admissionCtx: {
1706
+ orgMemoryDenied: complianceDenies.has("org_memory_mount"),
1707
+ complianceDegraded,
1708
+ parentAdmittedOrgScopes: foldAdmissionFreeze({
1709
+ delegated: internals?.isDelegatedChild === true ||
1710
+ internals?.inheritedGate !== undefined ||
1711
+ (resume?.seed.inheritedGate !== undefined &&
1712
+ (resume.seed.inheritedGate.ancestorRules !== undefined ||
1713
+ resume.seed.inheritedGate.shellGate !== undefined ||
1714
+ resume.seed.inheritedGate.admittedOrgScopes !== undefined ||
1715
+ resume.seed.inheritedGate.requiresParentConstraint)),
1716
+ inherited: inheritedAdmittedOrgScopes,
1717
+ }),
1718
+ priorOwnVerdict: priorOwnOrgVerdict,
1719
+ governedProvenance: orgGovernedProvenance,
1720
+ },
1721
+ });
1722
+ memoryAdmittedOrgScopesRef.current = memoryAdmittedOrgScopes;
1723
+ ownOrgVerdictRef.current = memoryOwnOrgVerdict ?? priorOwnOrgVerdict;
1636
1724
  let memoryBlock = memoryBlockFromEngine;
1637
1725
  if (memorySeedFiles?.length && seedContextFiles) {
1638
1726
  try {
@@ -1737,7 +1825,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1737
1825
  { entries: [], seedAnnounced: true }
1738
1826
  : undefined;
1739
1827
  const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
1740
- const promptPolicyEnabled = Boolean(spec.toolPolicy ?? deps.toolPolicy);
1828
+ const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
1741
1829
  const promptHooks = spec.hooks ?? deps.hooks;
1742
1830
  const promptHooksEnabled = Boolean(promptHooks?.preToolUse || promptHooks?.postToolUse);
1743
1831
  const failClosedReason = selfOrchestrationFailClosedReason(spec, deps);
@@ -2328,7 +2416,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2328
2416
  }));
2329
2417
  },
2330
2418
  ...(spec.streamingToolExecution === true ? { streamingToolExecution: true } : {}),
2331
- followUpMode: "all",
2332
2419
  env: executionEnv,
2333
2420
  session,
2334
2421
  tools: harnessTools,
@@ -2368,7 +2455,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2368
2455
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
2369
2456
  }
2370
2457
  }
2371
- const policy = spec.toolPolicy ?? deps.toolPolicy;
2458
+ const policy = lockedPreflight.toolPolicy;
2372
2459
  const auditPolicyNames = (auditee) => {
2373
2460
  const nameGroups = toolPolicyNameSets(auditee);
2374
2461
  if (nameGroups.length > 0) {
@@ -2456,7 +2543,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2456
2543
  for (const layer of narrowingLayers)
2457
2544
  auditPolicyNames(layer);
2458
2545
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
2459
- const basePolicyForResumeEdit = spec.toolPolicy ?? deps.toolPolicy;
2546
+ const basePolicyForResumeEdit = lockedPreflight.toolPolicy;
2460
2547
  const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0);
2461
2548
  const sharedFirstDecision = new Map();
2462
2549
  const SHARED_FIRST_DECISION_CAP = 256;
@@ -2994,10 +3081,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2994
3081
  inheritedGate: (() => {
2995
3082
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
2996
3083
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
2997
- return inheritedAncestorRules !== undefined || inheritedShellGate !== undefined || requiresParentConstraint
3084
+ return inheritedAncestorRules !== undefined ||
3085
+ inheritedShellGate !== undefined ||
3086
+ inheritedAdmittedOrgScopes !== undefined ||
3087
+ ownOrgVerdictRef.current !== undefined ||
3088
+ orgGovernedProvenance ||
3089
+ requiresParentConstraint
2998
3090
  ? {
2999
3091
  ...(inheritedAncestorRules !== undefined ? { ancestorRules: structuredClone(inheritedAncestorRules) } : {}),
3000
3092
  ...(inheritedShellGate !== undefined ? { shellGate: inheritedShellGate } : {}),
3093
+ ...(inheritedAdmittedOrgScopes !== undefined ? { admittedOrgScopes: [...inheritedAdmittedOrgScopes] } : {}),
3094
+ ...(ownOrgVerdictRef.current !== undefined
3095
+ ? { ownAdmittedOrgScopes: [...ownOrgVerdictRef.current.scopes], ownAdmittedOrgWriteScope: ownOrgVerdictRef.current.writeScope }
3096
+ : {}),
3097
+ ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
3001
3098
  requiresParentConstraint,
3002
3099
  ...(requiresParentConstraint && parentConstraintCount !== undefined ? { parentConstraintCount } : {}),
3003
3100
  }
@@ -3112,7 +3209,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3112
3209
  const mintedAt = Date.now();
3113
3210
  const cp = {
3114
3211
  token,
3115
- version: resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3212
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3116
3213
  scope,
3117
3214
  sessionId,
3118
3215
  leafId,
@@ -3194,7 +3291,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3194
3291
  const mintedAt = Date.now();
3195
3292
  const cp = {
3196
3293
  token,
3197
- version: reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3294
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3198
3295
  scope,
3199
3296
  sessionId,
3200
3297
  leafId,
@@ -3230,8 +3327,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3230
3327
  : undefined;
3231
3328
  const suspendAsk = checkpointStore && (durableApproval || irreversibleTools.size > 0 || egressTools.size > 0)
3232
3329
  ? async (req, postHookArgs, safety, approverUnavailable) => {
3233
- if (safety === undefined &&
3234
- onAsk !== undefined &&
3330
+ if (onAsk !== undefined &&
3235
3331
  runtimeCaps?.forceDurableGate !== true &&
3236
3332
  approverUnavailable !== true &&
3237
3333
  req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
@@ -3320,7 +3416,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3320
3416
  const mintedAt = Date.now();
3321
3417
  cp = {
3322
3418
  token,
3323
- version: approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3419
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3324
3420
  scope,
3325
3421
  sessionId,
3326
3422
  leafId,
@@ -3381,11 +3477,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3381
3477
  if (eff !== undefined && !toolEffects.has(t.name))
3382
3478
  toolEffects.set(t.name, eff);
3383
3479
  }
3384
- toolCallGateArmedRef.armed = effectivePolicy !== undefined || hooks?.preToolUse !== undefined || egressTools.size > 0 || irreversibleTools.size > 0 || spec.enablePlanMode === true;
3480
+ toolCallGateArmedRef.armed =
3481
+ effectivePolicy !== undefined ||
3482
+ hooks?.preToolUse !== undefined ||
3483
+ egressTools.size > 0 ||
3484
+ irreversibleTools.size > 0 ||
3485
+ spec.enablePlanMode === true ||
3486
+ complianceDenies.has("web_fetch");
3385
3487
  if (toolCallGateArmedRef.armed) {
3386
3488
  harness.on("tool_call", async (e) => {
3387
3489
  blockedToolCalls.delete(e.toolCallId);
3388
3490
  inheritedAskGrants.delete(e.toolCallId);
3491
+ {
3492
+ const complianceDeny = complianceCallDenial(complianceDenies, e.toolName);
3493
+ if (complianceDeny !== undefined) {
3494
+ if (blockedTracked)
3495
+ blockedToolCalls.add(e.toolCallId);
3496
+ if (notifyPermissionDenied) {
3497
+ await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, source: "safety" });
3498
+ }
3499
+ return { block: true, reason: formatHookFeedback(complianceDeny), preToolContext: [] };
3500
+ }
3501
+ }
3389
3502
  if (planModeRef.active && (toolEffects.get(e.toolName) ?? "write") !== "read") {
3390
3503
  if (blockedTracked)
3391
3504
  blockedToolCalls.add(e.toolCallId);
@@ -1450,6 +1450,12 @@ export class Runner {
1450
1450
  errorMessage: err instanceof Error ? err.message : String(err),
1451
1451
  errorCode: code,
1452
1452
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1453
+ ...(() => {
1454
+ const hinted = err.retryAfterMs;
1455
+ return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
1456
+ ? { retryAfterMs: hinted }
1457
+ : {};
1458
+ })(),
1453
1459
  stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0 },
1454
1460
  };
1455
1461
  emitTrace(spec.tracer ?? this.deps.tracer, () => ({
@@ -1703,9 +1709,7 @@ export class Runner {
1703
1709
  queue.push({ type: "task_notification", notification: item.payload, ...notificationIdent() });
1704
1710
  if (notificationHarness) {
1705
1711
  const xml = renderTaskNotificationXml(item.payload);
1706
- const deliver = item.priority === "later"
1707
- ? notificationHarness.followUp(xml, { provenance: "engine-note", enginePayload: item.payload })
1708
- : notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
1712
+ const deliver = notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
1709
1713
  void deliver.then(() => item.onDisposition?.("queued"), () => {
1710
1714
  parkTaskNotification(item.payload, item.priority);
1711
1715
  item.onDisposition?.("parked");
@@ -2323,6 +2327,22 @@ export class Runner {
2323
2327
  });
2324
2328
  const stopHook = (spec.hooks ?? this.deps.hooks)?.stop;
2325
2329
  const finalVerificationOn = spec.finalVerification === true;
2330
+ const finalVerifyBudgetFill = () => {
2331
+ let worst = 0;
2332
+ if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0)
2333
+ worst = Math.max(worst, stats.tokens / rs.budget.maxTokensWindow);
2334
+ if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0)
2335
+ worst = Math.max(worst, stats.costMicroUsd / rs.budget.maxCostMicroUsd);
2336
+ if (walltimeMonotonicDeadline !== undefined && prepared.suspendForResource === undefined) {
2337
+ const windowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
2338
+ if (windowMs > 0)
2339
+ worst = Math.max(worst, (performance.now() - rs.telemetry.taskStartMonotonic) / windowMs);
2340
+ }
2341
+ return worst;
2342
+ };
2343
+ const emitFinalVerifyEcho = (body) => {
2344
+ queue.push({ type: "steering_injected", source: "final_verification", preview: body.slice(0, 220), ...ident() });
2345
+ };
2326
2346
  if (stopHook || finalVerificationOn) {
2327
2347
  let consecutiveBlocks = 0;
2328
2348
  prepared.harness.setStopGate(async () => {
@@ -2332,58 +2352,69 @@ export class Runner {
2332
2352
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
2333
2353
  rs.counters.wroteThisRun &&
2334
2354
  prepared.outputRef.set !== true &&
2335
- !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1)) {
2355
+ !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1) &&
2356
+ finalVerifyBudgetFill() < 0.9) {
2336
2357
  rs.counters.finalVerifyInjections += 1;
2337
2358
  if (rs.counters.finalVerifyInjections === 2) {
2359
+ const reentryBody = "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2360
+ "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
2361
+ "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
2362
+ "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
2363
+ "declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
2364
+ "reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
2365
+ "A closing statement without a reported check result is not verification. If you already ran such a check, state " +
2366
+ "its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
2367
+ "this verification gate — it will not intervene again.</system-reminder>";
2368
+ emitFinalVerifyEcho(reentryBody);
2338
2369
  return [
2339
2370
  {
2340
2371
  role: "user",
2341
2372
  engineMinted: true,
2342
- content: "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2343
- "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
2344
- "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
2345
- "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
2346
- "declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
2347
- "reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
2348
- "A closing statement without a reported check result is not verification. If you already ran such a check, state " +
2349
- "its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
2350
- "this verification gate — it will not intervene again.</system-reminder>",
2373
+ content: reentryBody,
2351
2374
  timestamp: Date.now(),
2352
2375
  },
2353
2376
  ];
2354
2377
  }
2378
+ const nudgeBody = "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2379
+ "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
2380
+ "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
2381
+ "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
2382
+ "finishing. " +
2383
+ "Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
2384
+ "state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
2385
+ "do not change that state merely to test it. This constrains HOW you verify — it is never a " +
2386
+ "license to skip the real acceptance path or to check a substitute of your own making: expected " +
2387
+ "values must come from the task's requirements, never from content you generated. If the real " +
2388
+ "acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
2389
+ "required final state, and verify that final state before finishing. " +
2390
+ "If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
2391
+ "(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
2392
+ "it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
2393
+ "produced it matches the task's requirements. " +
2394
+ "Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
2395
+ "task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
2396
+ "correctness — and cross-check through an independent second path where the task or environment offers one " +
2397
+ "(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
2398
+ "not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
2399
+ "lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
2400
+ "If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
2401
+ "reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
2402
+ "that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
2403
+ "no reported check result means the work is not finished. " +
2404
+ "If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
2405
+ "oracle to check against, briefly confirm completion and stop. " +
2406
+ "Residue YOUR OWN testing created (scratch files, running processes, generated outputs the task does not ask for) " +
2407
+ "is not protected state — if the task's required final state is a clean target, removing your own residue is part " +
2408
+ "of delivering it. " +
2409
+ "Verification must never LAUNDER uncertainty: if part of your conclusion was uncertain before this check, keep " +
2410
+ "reporting it as uncertain unless the check you actually ran resolved it — a re-stated conclusion is not new " +
2411
+ "evidence.</system-reminder>";
2412
+ emitFinalVerifyEcho(nudgeBody);
2355
2413
  return [
2356
2414
  {
2357
2415
  role: "user",
2358
2416
  engineMinted: true,
2359
- content: "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2360
- "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
2361
- "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
2362
- "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
2363
- "finishing. " +
2364
- "Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
2365
- "state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
2366
- "do not change that state merely to test it. This constrains HOW you verify — it is never a " +
2367
- "license to skip the real acceptance path or to check a substitute of your own making: expected " +
2368
- "values must come from the task's requirements, never from content you generated. If the real " +
2369
- "acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
2370
- "required final state, and verify that final state before finishing. " +
2371
- "If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
2372
- "(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
2373
- "it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
2374
- "produced it matches the task's requirements. " +
2375
- "Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
2376
- "task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
2377
- "correctness — and cross-check through an independent second path where the task or environment offers one " +
2378
- "(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
2379
- "not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
2380
- "lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
2381
- "If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
2382
- "reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
2383
- "that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
2384
- "no reported check result means the work is not finished. " +
2385
- "If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
2386
- "oracle to check against, briefly confirm completion and stop.</system-reminder>",
2417
+ content: nudgeBody,
2387
2418
  timestamp: Date.now(),
2388
2419
  },
2389
2420
  ];
@@ -10,6 +10,7 @@ export interface TtlSessionStoreOptions {
10
10
  evict?: EvictPolicy;
11
11
  }
12
12
  export declare class TtlSessionStore implements SessionStore {
13
+ readonly retention: "none";
13
14
  private repo;
14
15
  private entries;
15
16
  private owners;
@@ -4,6 +4,7 @@ const DAY_MS = 24 * 60 * 60 * 1000;
4
4
  import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
5
5
  export { SESSION_DEFAULT_TTL_DAYS };
6
6
  export class TtlSessionStore {
7
+ retention = "none";
7
8
  repo;
8
9
  entries = new Map();
9
10
  owners = new Map();
@@ -1,5 +1,6 @@
1
1
  import type { AgentTool } from "../internal/harness-types.js";
2
2
  export interface ToolResultStore {
3
+ readonly retention?: import("./retention.js").RetentionDeclaration;
3
4
  put(ref: string, content: string): Promise<void> | void;
4
5
  get(ref: string, opts?: {
5
6
  offset?: number;
@@ -15,6 +16,7 @@ export interface ToolResultSlice {
15
16
  }
16
17
  export declare class InMemoryToolResultStore implements ToolResultStore {
17
18
  private readonly opts?;
19
+ readonly retention: "none";
18
20
  private readonly map;
19
21
  private totalChars;
20
22
  constructor(opts?: {
@@ -25,6 +25,7 @@ function refSegment(raw) {
25
25
  }
26
26
  export class InMemoryToolResultStore {
27
27
  opts;
28
+ retention = "none";
28
29
  map = new Map();
29
30
  totalChars = 0;
30
31
  constructor(opts) {
@@ -584,7 +584,7 @@ export type TaskEvent = ({
584
584
  reason?: string;
585
585
  } & TaskEventIdentity) | ({
586
586
  type: "steering_injected";
587
- source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
587
+ source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification";
588
588
  preview: string;
589
589
  } & TaskEventIdentity) | ({
590
590
  type: "diagnostics";
@@ -737,6 +737,11 @@ export interface RunnerDeps {
737
737
  fileSnapshotStore?: import("./file-snapshot-store.js").FileSnapshotStore;
738
738
  sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
739
739
  runtimeCapsResolver?: (principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>;
740
+ lockedConfig?: import("./locked-config.js").LockedConfig;
741
+ compliancePostureResolver?: (principal: string | undefined) => import("./compliance.js").CompliancePosture | undefined | Promise<import("./compliance.js").CompliancePosture | undefined>;
742
+ memoryScopeAdmission?: import("./memory-admission.js").MemoryScopeAdmission;
743
+ deploymentMemoryScopes?: readonly string[];
744
+ retentionPolicy?: import("./retention.js").RetentionPolicy;
740
745
  autoMode?: {
741
746
  rules?: import("./auto-mode-prompt.js").AutoModeRules;
742
747
  settingsDenyRules?: readonly string[];
@@ -27,6 +27,7 @@ function createUserMessage(text, images, provenance) {
27
27
  };
28
28
  }
29
29
  const engineNotePayloads = new WeakMap();
30
+ const ENGINE_NOTE_STEER_BACKLOG_CAP = 50;
30
31
  function createFailureMessage(model, error, aborted) {
31
32
  return {
32
33
  role: "assistant",
@@ -418,7 +419,13 @@ export class AgentHarness {
418
419
  };
419
420
  }
420
421
  async drainQueuedMessages(queue, mode) {
421
- const messages = mode === "all" ? queue.splice(0) : queue.splice(0, 1);
422
+ let count = mode === "all" ? queue.length : 1;
423
+ if (mode !== "all" && queue.length > 1 && engineNotePayloads.has(queue[0])) {
424
+ count = 1;
425
+ while (count < queue.length && engineNotePayloads.has(queue[count]))
426
+ count++;
427
+ }
428
+ const messages = queue.splice(0, count);
422
429
  if (messages.length === 0) {
423
430
  return messages;
424
431
  }
@@ -713,6 +720,9 @@ export class AgentHarness {
713
720
  async enqueueInjection(queue, text, options) {
714
721
  if (AgentHarness.emptyInjection(text, options))
715
722
  return;
723
+ if (options?.enginePayload !== undefined && queue.filter((q) => engineNotePayloads.has(q)).length >= ENGINE_NOTE_STEER_BACKLOG_CAP) {
724
+ throw new AgentHarnessError("invalid_state", `engine-note backlog at cap (${ENGINE_NOTE_STEER_BACKLOG_CAP}) — park the payload for the session's next run`);
725
+ }
716
726
  const m = createUserMessage(text, options?.images, options);
717
727
  if (options?.enginePayload !== undefined)
718
728
  engineNotePayloads.set(m, options.enginePayload);
@@ -288,5 +288,15 @@ export function validateToolArguments(tool, toolCall) {
288
288
  .Errors(args)
289
289
  .map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)
290
290
  .join("\n") || "Unknown validation error";
291
- throw new Error(`Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}`);
291
+ const schemaJson = (() => {
292
+ try {
293
+ const s = JSON.stringify(tool.parameters);
294
+ return s.length > VALIDATION_ERROR_SCHEMA_MAX_CHARS ? `${s.slice(0, VALIDATION_ERROR_SCHEMA_MAX_CHARS)}… (schema truncated)` : s;
295
+ }
296
+ catch {
297
+ return "(schema not serializable)";
298
+ }
299
+ })();
300
+ throw new Error(`Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}\n\nExpected parameter schema:\n${schemaJson}`);
292
301
  }
302
+ const VALIDATION_ERROR_SCHEMA_MAX_CHARS = 4_000;