@sema-agent/core 5.44.0 → 5.46.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 (79) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/agents/subagent.d.ts +3 -1
  3. package/dist/agents/subagent.js +1 -1
  4. package/dist/brain/reasoning.d.ts +50 -4
  5. package/dist/brain/reasoning.js +28 -7
  6. package/dist/brain/request-params.d.ts +0 -12
  7. package/dist/brain/request-params.js +1 -1
  8. package/dist/core/checkpoint-store.d.ts +12 -0
  9. package/dist/core/governance-codes.js +1 -0
  10. package/dist/core/hooks.d.ts +9 -2
  11. package/dist/core/hooks.js +6 -5
  12. package/dist/core/memory-engine/content-origin.d.ts +9 -4
  13. package/dist/core/memory-engine/delegation-provenance.d.ts +12 -7
  14. package/dist/core/memory-engine/engine.d.ts +50 -3
  15. package/dist/core/memory-engine/engine.js +194 -32
  16. package/dist/core/memory-engine/export-bundle.d.ts +10 -1
  17. package/dist/core/memory-engine/export-bundle.js +21 -0
  18. package/dist/core/memory-engine/file-backend.d.ts +33 -4
  19. package/dist/core/memory-engine/file-backend.js +165 -39
  20. package/dist/core/memory-engine/frontmatter.d.ts +42 -1
  21. package/dist/core/memory-engine/frontmatter.js +141 -1
  22. package/dist/core/memory-engine/header-hints.d.ts +17 -0
  23. package/dist/core/memory-engine/header-hints.js +6 -0
  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 +25 -2
  27. package/dist/core/memory-engine/layout.js +25 -12
  28. package/dist/core/memory-engine/memory-backend-contract.js +65 -0
  29. package/dist/core/memory-engine/sync-client.d.ts +1 -1
  30. package/dist/core/memory-engine/sync-client.js +33 -1
  31. package/dist/core/memory-engine/tools.d.ts +7 -0
  32. package/dist/core/memory-engine/tools.js +3 -0
  33. package/dist/core/memory-engine/types.d.ts +75 -1
  34. package/dist/core/memory-engine/types.js +1 -1
  35. package/dist/core/reminder-mint.d.ts +70 -0
  36. package/dist/core/reminder-mint.js +25 -0
  37. package/dist/core/runner/assemble-result.d.ts +5 -0
  38. package/dist/core/runner/assemble-result.js +1 -1
  39. package/dist/core/runner/git-status-frame.d.ts +3 -14
  40. package/dist/core/runner/git-status-frame.js +39 -14
  41. package/dist/core/runner/prepare-config-doors.d.ts +10 -0
  42. package/dist/core/runner/prepare-config-doors.js +32 -0
  43. package/dist/core/runner/prepare-hands-readface.d.ts +5 -11
  44. package/dist/core/runner/prepare-hands-readface.js +26 -0
  45. package/dist/core/runner/prepare-memory.d.ts +11 -0
  46. package/dist/core/runner/prepare-memory.js +12 -10
  47. package/dist/core/runner/prepare-task.d.ts +25 -2
  48. package/dist/core/runner/prepare-task.js +71 -14
  49. package/dist/core/runner/runtask.js +82 -72
  50. package/dist/core/side-query.d.ts +11 -1
  51. package/dist/core/side-query.js +3 -0
  52. package/dist/core/trace.d.ts +17 -2
  53. package/dist/core/types.d.ts +119 -8
  54. package/dist/engine/harness/types.d.ts +46 -1
  55. package/dist/engine/harness/types.js +11 -0
  56. package/dist/engine/session/import-validate.js +6 -1
  57. package/dist/engine/session/session.d.ts +20 -0
  58. package/dist/engine/session/session.js +26 -1
  59. package/dist/index.d.ts +2 -1
  60. package/dist/index.js +2 -1
  61. package/dist/orchestration/run-workflow-tool.d.ts +16 -0
  62. package/dist/orchestration/run-workflow-tool.js +23 -3
  63. package/dist/orchestration/workflow-governance.d.ts +8 -1
  64. package/dist/prompt-assembly/epoch.js +2 -0
  65. package/dist/prompt-assembly/types.d.ts +6 -0
  66. package/dist/prompts/default.d.ts +13 -1
  67. package/dist/prompts/default.js +5 -1
  68. package/dist/tools/fs/fs-bash.d.ts +4 -0
  69. package/dist/tools/fs/fs-bash.js +1 -1
  70. package/dist/tools/fs/fs-read.d.ts +1 -1
  71. package/dist/tools/fs/fs-read.js +8 -7
  72. package/dist/tools/fs/fs-shared.d.ts +10 -4
  73. package/dist/tools/fs/fs-shared.js +6 -3
  74. package/dist/tools/fs/gh-rate-limit.d.ts +4 -1
  75. package/dist/tools/fs/gh-rate-limit.js +3 -2
  76. package/dist/tools/fs/index.d.ts +10 -2
  77. package/dist/tools/fs/index.js +2 -1
  78. package/package.json +1 -1
  79. package/test/export-surface.snapshot.json +12 -1
@@ -1,5 +1,6 @@
1
1
  import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
2
2
  import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
3
+ import { mintSystemReminder, openSystemReminder } from "../reminder-mint.js";
3
4
  import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
4
5
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
5
6
  import { snapshotActorAssertion } from "../../internal/llm.js";
@@ -122,12 +123,12 @@ const SNAPSHOT_TIMEOUT_MS = 30_000;
122
123
  const SNAPSHOT_TOO_LARGE_TTL_MS = 30 * 60_000;
123
124
  const LIMIT_APPROACH_DEFAULT_THRESHOLDS = [0.8, 0.95];
124
125
  const DEFAULT_PRECALL_OUTPUT_TOKENS = 4096;
125
- function limitApproachFrames(index, axis, percent) {
126
+ function limitApproachFrames(index, axis, percent, mark) {
126
127
  return index === 0
127
- ? `<system-reminder>[limit approach] This task has used about ${percent}% of its ${axis}. ` +
128
+ ? `${openSystemReminder(mark)}[limit approach] This task has used about ${percent}% of its ${axis}. ` +
128
129
  `Start converging: finish the step you are on, drop work that is not required for the deliverable, and ` +
129
130
  `avoid starting anything you cannot complete within what is left.</system-reminder>`
130
- : `<system-reminder>[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` +
131
+ : `${openSystemReminder(mark)}[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` +
131
132
  `stop exploring, write up what you have — including what is incomplete and what you would do next — ` +
132
133
  `and end the task. Work that is not reported before the allowance runs out is lost.</system-reminder>`;
133
134
  }
@@ -244,16 +245,16 @@ function assertOutcomeText(value, field) {
244
245
  throw new CheckpointError("checkpoint.invalid_outcome", `resume \`${field}\` is not a plain string (got ${typeof value}) — a decide's text payload is an operator's plain data, not a live object; refusing pre-CAS, the checkpoint stays pending`);
245
246
  }
246
247
  }
247
- function resumeContinuation(resume) {
248
+ function resumeContinuation(resume, mark) {
248
249
  if (resume.outcome.gate === "wake") {
249
250
  return formatHookFeedback("You were WOKEN from a parked pause by an operator. Before continuing, re-orient from the workspace: " +
250
251
  "run `git status` and review your recent changes / last commits to confirm what is already done, then " +
251
- "continue the remaining work. Do NOT restart the task or re-run work that is already committed.");
252
+ "continue the remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
252
253
  }
253
254
  if (resume.outcome.gate === "resource_limit") {
254
255
  return formatHookFeedback("You were resumed after a pause. Before continuing, re-orient from the workspace: run `git status` and " +
255
256
  "review your recent changes / last commits to confirm what is already done, then continue the " +
256
- "remaining work. Do NOT restart the task or re-run work that is already committed.");
257
+ "remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
257
258
  }
258
259
  if (resume.outcome.gate === "dry_run_review") {
259
260
  const verdict = resume.outcome.decision === "approve"
@@ -261,31 +262,31 @@ function resumeContinuation(resume) {
261
262
  : `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}; it was NOT applied`;
262
263
  return formatHookFeedback(`You were resumed after a dry-run review. ${verdict}. Before continuing, re-orient from the workspace ` +
263
264
  "(run `git status` and review your recent changes) to confirm the current state, then continue the " +
264
- "remaining work. Do NOT restart the task or re-run work that is already done.");
265
+ "remaining work. Do NOT restart the task or re-run work that is already done.", mark);
265
266
  }
266
267
  if (resume.outcome.gate === "plan_review") {
267
268
  if (resume.outcome.decision === "approve") {
268
269
  return formatHookFeedback("Your proposed PLAN was REVIEWED and APPROVED. Proceed with that plan now — begin executing it. " +
269
- "This is a RESUMED task; do NOT re-plan or restart from scratch, just carry out the approved plan.");
270
+ "This is a RESUMED task; do NOT re-plan or restart from scratch, just carry out the approved plan.", mark);
270
271
  }
271
272
  if (resume.outcome.decision === "edit") {
272
273
  if (!resume.outcome.editedPlan) {
273
274
  return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED, but no revised plan text was supplied — proceed with your " +
274
- "ORIGINAL plan as-is, begin executing it now. This is a RESUMED task; do NOT re-plan or restart from scratch.");
275
+ "ORIGINAL plan as-is, begin executing it now. This is a RESUMED task; do NOT re-plan or restart from scratch.", mark);
275
276
  }
276
277
  return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED by a human reviewer. Proceed with the REVISED plan below " +
277
278
  "(it supersedes your earlier plan); begin executing it now. This is a RESUMED task; do NOT re-plan " +
278
279
  "or restart from scratch, just carry out the revised plan.\n\nThe REVISED plan to follow is:\n" +
279
- delimitUntrusted("revised plan", resume.outcome.editedPlan));
280
+ delimitUntrusted("revised plan", resume.outcome.editedPlan), mark);
280
281
  }
281
282
  const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : "";
282
283
  return formatHookFeedback(`Your proposed PLAN was REVIEWED and REJECTED; it was NOT executed.${why} Produce a NEW plan that ` +
283
284
  "addresses the concern, then continue. This is a RESUMED task — re-plan from the current state; do " +
284
- "NOT execute the rejected plan.");
285
+ "NOT execute the rejected plan.", mark);
285
286
  }
286
287
  const { pendingAction } = resume.cp;
287
288
  if (pendingAction.kind !== "tool_approval") {
288
- return formatHookFeedback("This is a RESUMED task — continue from where you left off without restarting or re-running prior work.");
289
+ return formatHookFeedback("This is a RESUMED task — continue from where you left off without restarting or re-running prior work.", mark);
289
290
  }
290
291
  const decided = resume.outcome.decision === "allow"
291
292
  ? "was APPROVED and has now been executed — its result is in the tool results above"
@@ -297,7 +298,7 @@ function resumeContinuation(resume) {
297
298
  return formatHookFeedback(`The pending tool call "${pendingAction.toolName}" ${decided}.${batchNote} ` +
298
299
  `This is a RESUMED task — every step before this point is already done and its results are in the ` +
299
300
  `conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
300
- `exact point, building on the existing results, and finish the remaining work.`);
301
+ `exact point, building on the existing results, and finish the remaining work.`, mark);
301
302
  }
302
303
  const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
303
304
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
@@ -514,7 +515,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
514
515
  if (tightest !== undefined && tightest.ratio >= thresholds[index]) {
515
516
  rs.counters.approachNoticesSent += 1;
516
517
  const percent = Math.min(100, Math.round(tightest.ratio * 100));
517
- const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent);
518
+ const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent, prepared.reminderMark);
518
519
  void prepared.harness.steer(body, { engineMinted: true }).catch(() => undefined);
519
520
  queue.push({ type: "steering_injected", source: "limit_approach", preview: body.slice(0, 220), ...ident() });
520
521
  boundarySteered = true;
@@ -739,7 +740,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
739
740
  }
740
741
  }
741
742
  if (due.length > 0) {
742
- attachmentsPayload = due.map((a) => `<system-reminder>\n${sanitizeUntrustedText(a.body)}\n</system-reminder>`).join("\n");
743
+ attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body), prepared.reminderMark)).join("\n");
743
744
  for (const a of due)
744
745
  boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
745
746
  rs.attach.attachmentsInjected += due.length;
@@ -766,7 +767,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
766
767
  const clipped = clipToBytes(body, budget) === body
767
768
  ? body
768
769
  : `${clipToBytes(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
769
- batchContextBlock = `<system-reminder>\n${clipped}\n</system-reminder>`;
770
+ batchContextBlock = mintSystemReminder(clipped, prepared.reminderMark);
770
771
  }
771
772
  }
772
773
  }
@@ -1000,8 +1001,8 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
1000
1001
  };
1001
1002
  return onTurnBoundary;
1002
1003
  }
1003
- function wrapGitFrame(body) {
1004
- return `<system-reminder>\n${sanitizeUntrustedText(body)}\n</system-reminder>`;
1004
+ function wrapGitFrame(body, mark) {
1005
+ return mintSystemReminder(sanitizeUntrustedText(body), mark);
1005
1006
  }
1006
1007
  async function resolveGitLegDelivery(prepared, cpMirror, report) {
1007
1008
  const ref = prepared.gitStatusRef;
@@ -1044,7 +1045,7 @@ async function resolveGitLegDelivery(prepared, cpMirror, report) {
1044
1045
  }
1045
1046
  if (prior !== undefined && !pending && prior.entryId !== undefined) {
1046
1047
  try {
1047
- const newest = newestEngineGitFrame(await prepared.session.getBranch());
1048
+ const newest = newestEngineGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
1048
1049
  if (newest === undefined || newest.entryId !== prior.entryId)
1049
1050
  pending = true;
1050
1051
  }
@@ -1060,7 +1061,7 @@ async function resolveGitLegDelivery(prepared, cpMirror, report) {
1060
1061
  if (prior === undefined && !pending) {
1061
1062
  let mustDisown;
1062
1063
  try {
1063
- mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch());
1064
+ mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
1064
1065
  }
1065
1066
  catch (err) {
1066
1067
  mustDisown = true;
@@ -1073,9 +1074,9 @@ async function resolveGitLegDelivery(prepared, cpMirror, report) {
1073
1074
  return frame.body;
1074
1075
  }
1075
1076
  if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
1076
- ref.protectedText = wrapGitFrame(frame.body);
1077
+ ref.protectedText = wrapGitFrame(frame.body, prepared.reminderMark);
1077
1078
  if (frame.kind === "full" && frame.shrunk !== undefined) {
1078
- ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body) };
1079
+ ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
1079
1080
  }
1080
1081
  return undefined;
1081
1082
  }
@@ -1661,6 +1662,7 @@ export class Runner {
1661
1662
  errorCode: code,
1662
1663
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1663
1664
  ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
1665
+ ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
1664
1666
  ...(() => {
1665
1667
  const hinted = err.retryAfterMs;
1666
1668
  return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
@@ -1758,9 +1760,9 @@ export class Runner {
1758
1760
  const actorIn = options?.actor;
1759
1761
  const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
1760
1762
  const projected = projectHumanInput({ text, actor, source: "steer" });
1761
- const payload = trusted ? formatHookFeedback(projected) : projected;
1762
- const mintsAFrame = payload.trim().length !== 0;
1763
- const replay = { payload, trusted, ...(actor !== undefined ? { actor } : {}) };
1763
+ let payload;
1764
+ let mintsAFrame;
1765
+ let replay;
1764
1766
  const noteAccepted = (h) => {
1765
1767
  if (!mintsAFrame)
1766
1768
  return;
@@ -1789,6 +1791,9 @@ export class Runner {
1789
1791
  const h = handle ?? (await orTimeout(ready));
1790
1792
  if (!h)
1791
1793
  throw steeringError("the task is not running");
1794
+ payload = trusted ? formatHookFeedback(projected, h.reminderMark) : projected;
1795
+ mintsAFrame = payload.trim().length !== 0;
1796
+ replay = { payload, trusted, ...(actor !== undefined ? { actor } : {}) };
1792
1797
  if (typeof inputId === "string") {
1793
1798
  const prior = acceptedSteerInputs.get(inputId);
1794
1799
  if (prior !== undefined) {
@@ -2098,7 +2103,7 @@ export class Runner {
2098
2103
  }
2099
2104
  }
2100
2105
  const loopLatch = { ended: false };
2101
- onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch });
2106
+ onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark });
2102
2107
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
2103
2108
  prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
2104
2109
  if (resume &&
@@ -2312,12 +2317,12 @@ export class Runner {
2312
2317
  return [];
2313
2318
  const full = textOf(e.message);
2314
2319
  if (m.engineMinted === true)
2315
- return [stripGitStatusUnits(full)];
2320
+ return [stripGitStatusUnits(full, prepared.reminderMark)];
2316
2321
  if (Array.isArray(m.engineSegments) && m.engineSegments.length > 0) {
2317
- return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end))));
2322
+ return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end)), prepared.reminderMark));
2318
2323
  }
2319
2324
  if (typeof m.enginePrefixChars === "number" && m.enginePrefixChars > 0)
2320
- return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars))];
2325
+ return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars), prepared.reminderMark)];
2321
2326
  return [];
2322
2327
  });
2323
2328
  }
@@ -2406,7 +2411,8 @@ export class Runner {
2406
2411
  const text = typeof m.content === "string"
2407
2412
  ? m.content
2408
2413
  : m.content.map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : "")).join("\n");
2409
- if (text.includes("<system-reminder>[final verification]") && rs.counters.finalVerifyInjections < 2)
2414
+ const fvMarked = m.engineMinted === true && text.includes(`<system-reminder mark="${prepared.reminderMark}">[final verification]`);
2415
+ if ((fvMarked || text.includes("<system-reminder>[final verification]")) && rs.counters.finalVerifyInjections < 2)
2410
2416
  rs.counters.finalVerifyInjections += 1;
2411
2417
  }
2412
2418
  else if (m.role === "toolResult" && !rs.counters.wroteThisRun && (prepared.toolEffects.get(m.toolName) ?? "write") !== "read") {
@@ -2468,23 +2474,24 @@ export class Runner {
2468
2474
  ts: Date.now(),
2469
2475
  }));
2470
2476
  }
2471
- if (prepared.thinking && prepared.thinking !== "off" && prepared.model.reasoning === true) {
2472
- emitTrace(rs.telemetry.tracer, () => {
2473
- const r = resolveReasoning(prepared.thinking, prepared.model);
2474
- return {
2475
- kind: "reasoning.resolved",
2476
- version: 1,
2477
- taskId: rs.telemetry.taskId,
2478
- model: prepared.model.id,
2479
- requested: r.requested,
2480
- effective: r.effective,
2481
- graded: r.graded,
2482
- clamped: r.clamped,
2483
- format: r.format,
2484
- endpoint: r.endpoint,
2485
- ts: Date.now(),
2486
- };
2487
- });
2477
+ const reasoningResolution = prepared.thinking && prepared.thinking !== "off" ? resolveReasoning(prepared.thinking, prepared.model) : undefined;
2478
+ if (taskIdRef && reasoningResolution !== undefined)
2479
+ taskIdRef.effectiveReasoning = reasoningResolution;
2480
+ if (reasoningResolution !== undefined) {
2481
+ emitTrace(rs.telemetry.tracer, () => ({
2482
+ kind: "reasoning.resolved",
2483
+ version: 1,
2484
+ taskId: rs.telemetry.taskId,
2485
+ model: prepared.model.id,
2486
+ requested: reasoningResolution.requested,
2487
+ effective: reasoningResolution.effective,
2488
+ graded: reasoningResolution.graded,
2489
+ clamped: reasoningResolution.clamped,
2490
+ format: reasoningResolution.format,
2491
+ endpoint: reasoningResolution.endpoint,
2492
+ ...(reasoningResolution.dropped === true ? { dropped: true } : {}),
2493
+ ts: Date.now(),
2494
+ }));
2488
2495
  }
2489
2496
  const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
2490
2497
  const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
@@ -2685,7 +2692,8 @@ export class Runner {
2685
2692
  finalVerifyBudgetFill() < 0.9) {
2686
2693
  rs.counters.finalVerifyInjections += 1;
2687
2694
  if (rs.counters.finalVerifyInjections === 2) {
2688
- const reentryBody = "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2695
+ const reentryBody = openSystemReminder(prepared.reminderMark) +
2696
+ "[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2689
2697
  "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
2690
2698
  "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
2691
2699
  "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
@@ -2704,7 +2712,8 @@ export class Runner {
2704
2712
  },
2705
2713
  ];
2706
2714
  }
2707
- const nudgeBody = "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2715
+ const nudgeBody = openSystemReminder(prepared.reminderMark) +
2716
+ "[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2708
2717
  "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
2709
2718
  "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
2710
2719
  "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
@@ -2768,7 +2777,7 @@ export class Runner {
2768
2777
  messages.push({
2769
2778
  role: "user",
2770
2779
  engineMinted: true,
2771
- content: formatHookFeedback(`Stop hook additional context: ${result.additionalContext}`),
2780
+ content: formatHookFeedback(`Stop hook additional context: ${result.additionalContext}`, prepared.reminderMark),
2772
2781
  timestamp: Date.now(),
2773
2782
  });
2774
2783
  }
@@ -2788,7 +2797,7 @@ export class Runner {
2788
2797
  messages.push({
2789
2798
  role: "user",
2790
2799
  engineMinted: true,
2791
- content: formatHookFeedback(`Stop hook stopped continuation: ${result.block}`),
2800
+ content: formatHookFeedback(`Stop hook stopped continuation: ${result.block}`, prepared.reminderMark),
2792
2801
  timestamp: Date.now(),
2793
2802
  });
2794
2803
  }
@@ -2910,7 +2919,7 @@ export class Runner {
2910
2919
  const use = ref.overBudgetShrunk && frame.shrunk !== undefined
2911
2920
  ? { kind: "degraded", body: frame.shrunk.body, hash: frame.shrunk.hash }
2912
2921
  : { kind: frame.kind, body: frame.body, hash: frame.hash };
2913
- const wrapped = wrapGitFrame(use.body);
2922
+ const wrapped = wrapGitFrame(use.body, prepared.reminderMark);
2914
2923
  try {
2915
2924
  const entryId = await prepared.session.appendMessage({
2916
2925
  role: "user",
@@ -2921,7 +2930,7 @@ export class Runner {
2921
2930
  ref.announced = { kind: use.kind, hash: use.hash, entryId };
2922
2931
  ref.protectedText = wrapped;
2923
2932
  if (use.kind === "full" && frame.shrunk !== undefined) {
2924
- ref.wrappedShrink = { find: wrapped, replace: wrapGitFrame(frame.shrunk.body) };
2933
+ ref.wrappedShrink = { find: wrapped, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
2925
2934
  }
2926
2935
  else {
2927
2936
  delete ref.wrappedShrink;
@@ -3043,13 +3052,13 @@ export class Runner {
3043
3052
  rs.limits.budgetAxis = "cost";
3044
3053
  }
3045
3054
  else {
3046
- let continuation = resumeContinuation(resume);
3055
+ let continuation = resumeContinuation(resume, prepared.reminderMark);
3047
3056
  const bgSnapshot = resume.cp.state.runningBackgroundTasks;
3048
3057
  if (bgSnapshot !== undefined && bgSnapshot.length > 0) {
3049
3058
  const alive = new Set(prepared.listBackgroundTasks().map((t) => t.id));
3050
3059
  const orphans = bgSnapshot.filter((t) => !alive.has(t.id));
3051
3060
  if (orphans.length > 0) {
3052
- continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans));
3061
+ continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans), prepared.reminderMark);
3053
3062
  }
3054
3063
  }
3055
3064
  let gitResumeDelivered;
@@ -3063,11 +3072,11 @@ export class Runner {
3063
3072
  });
3064
3073
  const gitFrame = prepared.gitStatusRef.frame;
3065
3074
  if (gitBody !== undefined && gitFrame !== undefined) {
3066
- const wrappedGit = wrapGitFrame(gitBody);
3075
+ const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
3067
3076
  continuation += "\n\n" + wrappedGit;
3068
3077
  gitResumeDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, wrapped: wrappedGit };
3069
3078
  if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
3070
- prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
3079
+ prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
3071
3080
  }
3072
3081
  }
3073
3082
  }
@@ -3081,7 +3090,7 @@ export class Runner {
3081
3090
  const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
3082
3091
  continuation +=
3083
3092
  "\n\n" +
3084
- (steer.trusted ? formatHookFeedback(projected) : delimitUntrusted("supervisor steering message", projected));
3093
+ (steer.trusted ? formatHookFeedback(projected, prepared.reminderMark) : delimitUntrusted("supervisor steering message", projected));
3085
3094
  if (steer.trusted)
3086
3095
  engineSegments.push({ start, end: continuation.length });
3087
3096
  queue.push({
@@ -3129,11 +3138,11 @@ export class Runner {
3129
3138
  try {
3130
3139
  const decision = await userPromptSubmit(spec.objective, { identity: prepared.hookIdentity });
3131
3140
  if (decision?.block) {
3132
- prepared.blockedRef.reason = formatHookFeedback(decision.block);
3141
+ prepared.blockedRef.reason = formatHookFeedback(decision.block, prepared.reminderMark);
3133
3142
  promptBlocked = true;
3134
3143
  }
3135
3144
  else if (decision?.additionalContext) {
3136
- effectiveObjective = `${formatHookFeedback(decision.additionalContext)}\n\n${projectedObjective}`;
3145
+ effectiveObjective = `${formatHookFeedback(decision.additionalContext, prepared.reminderMark)}\n\n${projectedObjective}`;
3137
3146
  }
3138
3147
  }
3139
3148
  catch (hookErr) {
@@ -3144,7 +3153,7 @@ export class Runner {
3144
3153
  catch {
3145
3154
  }
3146
3155
  prepared.blockedRef.reason = formatHookFeedback(`the deployment's userPromptSubmit hook crashed while screening this prompt (${err.message}); ` +
3147
- `the prompt was NOT submitted (fail-closed)`);
3156
+ `the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
3148
3157
  promptBlocked = true;
3149
3158
  }
3150
3159
  }
@@ -3159,13 +3168,13 @@ export class Runner {
3159
3168
  });
3160
3169
  const gitFrame = prepared.gitStatusRef.frame;
3161
3170
  if (gitBody !== undefined && gitFrame !== undefined) {
3162
- const wrappedGit = wrapGitFrame(gitBody);
3171
+ const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
3163
3172
  const standalone = spec.images !== undefined && spec.images.length > 0;
3164
3173
  if (!standalone)
3165
3174
  effectiveObjective = `${wrappedGit}\n${effectiveObjective}`;
3166
3175
  gitLegDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, standalone, wrapped: wrappedGit };
3167
3176
  if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
3168
- prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
3177
+ prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
3169
3178
  }
3170
3179
  }
3171
3180
  }
@@ -3203,7 +3212,7 @@ export class Runner {
3203
3212
  }
3204
3213
  }
3205
3214
  if (firstFrames.length > 0) {
3206
- effectiveObjective = `${firstFrames.map((f) => `<system-reminder>\n${sanitizeUntrustedText(f.body)}\n</system-reminder>`).join("\n")}\n${effectiveObjective}`;
3215
+ effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
3207
3216
  }
3208
3217
  }
3209
3218
  const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
@@ -3458,6 +3467,7 @@ export class Runner {
3458
3467
  effectiveReadFace: prepared.effectiveReadFace,
3459
3468
  effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
3460
3469
  effectiveMemoryScopes: prepared.effectiveMemoryScopes,
3470
+ effectiveReasoning: reasoningResolution,
3461
3471
  retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
3462
3472
  abortedForTimeout: timeout.fired,
3463
3473
  abortedForTurns: rs.limits.turnsExceeded,
@@ -4382,8 +4392,8 @@ export class Runner {
4382
4392
  return l !== undefined && l !== name ? { label: l } : {};
4383
4393
  })();
4384
4394
  emit({ type: "tool_start", toolCallId: id, toolName: name, ...displayLabel, args: deferredArgs.get(id) ?? {} });
4385
- emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
4386
- const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
4395
+ emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE, prepared.reminderMark) }, true) });
4396
+ const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE, prepared.reminderMark), true));
4387
4397
  emitCommitted(eid, "toolResult", id);
4388
4398
  }
4389
4399
  }
@@ -4404,8 +4414,8 @@ export class Runner {
4404
4414
  ? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
4405
4415
  : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
4406
4416
  const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
4407
- emitEnd(true, { content: formatHookFeedback(reason) });
4408
- const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
4417
+ emitEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
4418
+ const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
4409
4419
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
4410
4420
  return;
4411
4421
  }
@@ -4428,7 +4438,7 @@ export class Runner {
4428
4438
  const beltUnsafe = rechecked.action === "deny" ||
4429
4439
  (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
4430
4440
  if (beltUnsafe) {
4431
- const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${rechecked.action === "deny" && rechecked.message ? `: ${rechecked.message}` : " (the policy rewrote the edit to a value that was not approved)"}.`);
4441
+ const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${rechecked.action === "deny" && rechecked.message ? `: ${rechecked.message}` : " (the policy rewrote the edit to a value that was not approved)"}.`, prepared.reminderMark);
4432
4442
  emitEnd(true, { content: editedDenial });
4433
4443
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
4434
4444
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
@@ -4438,7 +4448,7 @@ export class Runner {
4438
4448
  const gateRealApproval = resume.cp.gate.kind === "irreversible_ask" ? resume.cp.gate.realApproval : undefined;
4439
4449
  const gateOrgGoverned = gateRealApproval?.origin === "org_rule" || gateRealApproval?.origin === "org_unavailable";
4440
4450
  if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
4441
- const unwiredDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its approval was minted under organization governance (${gateRealApproval.origin}), and this worker has no org adjudication wiring — a governed approval may only be redeemed where governance can be enforced. This approval is spent; re-issue the call on an org-wired worker.`);
4451
+ const unwiredDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its approval was minted under organization governance (${gateRealApproval.origin}), and this worker has no org adjudication wiring — a governed approval may only be redeemed where governance can be enforced. This approval is spent; re-issue the call on an org-wired worker.`, prepared.reminderMark);
4442
4452
  emitEnd(true, { content: unwiredDenial });
4443
4453
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
4444
4454
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
@@ -4469,7 +4479,7 @@ export class Runner {
4469
4479
  const orgDisclosures = org.status === "unavailable" && org.disclosures.length > 0
4470
4480
  ? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
4471
4481
  : "";
4472
- const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`);
4482
+ const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`, prepared.reminderMark);
4473
4483
  emitEnd(true, { content: orgDenial });
4474
4484
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
4475
4485
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
@@ -4485,7 +4495,7 @@ export class Runner {
4485
4495
  ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
4486
4496
  }, prepared.abortController.signal));
4487
4497
  if (narrowed.action === "deny") {
4488
- const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`);
4498
+ const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
4489
4499
  emitEnd(true, { content: narrowedDenial });
4490
4500
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
4491
4501
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
@@ -1,5 +1,5 @@
1
1
  import type { AssistantMessage, StopReason, Tool, ToolResultMessage, Usage, UserMessage } from "../internal/llm.js";
2
- import type { Brain, Model, ModelRef, ModelRole, ModelRoles, ThinkingLevel } from "./types.js";
2
+ import type { Brain, Model, ModelRef, ModelRole, ModelRoles, TaskSpec, ThinkingLevel } from "./types.js";
3
3
  import { type DegradationInfo } from "../brain/degrading.js";
4
4
  /** A tool DEFINITION exposed to the model in a side query (never executed by the engine). */
5
5
  export interface SideQueryToolDef {
@@ -54,6 +54,16 @@ export interface SideQuerySpec {
54
54
  tools?: SideQueryToolDef[];
55
55
  /** Per-request output cap (provider max_tokens). */
56
56
  maxOutputTokens?: number;
57
+ /**
58
+ * Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (same signature, resolved per
59
+ * call against the RESOLVED model, exactly like the task path's per-call hook). The brain
60
+ * contract is `options.apiKey ?? config.apiKey`, and a model's own `baseUrl` outranks the
61
+ * brain's — so before this seat existed, a side query routed to a model carrying its own
62
+ * `baseUrl` + per-model key fell back to the brain's construction-time credential and sent the
63
+ * GATEWAY key to the per-model (possibly external) URL: a credential leak the task path already
64
+ * prevents. Absent ⇒ construction-time credentials apply, options byte-identical to before.
65
+ */
66
+ getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
57
67
  signal?: AbortSignal;
58
68
  }
59
69
  export interface SideQueryResult {
@@ -44,10 +44,13 @@ export async function runSideQuery(spec, deps) {
44
44
  : {}),
45
45
  };
46
46
  const thinking = resolved.thinking ?? resolved.model.defaultThinking;
47
+ const auth = await spec.getApiKeyAndHeaders?.(resolved.model);
47
48
  const options = {
48
49
  ...(spec.signal !== undefined ? { signal: spec.signal } : {}),
49
50
  ...(spec.maxOutputTokens !== undefined ? { maxTokens: spec.maxOutputTokens } : {}),
50
51
  ...(thinking !== undefined ? { reasoning: thinking } : {}),
52
+ ...(auth?.apiKey !== undefined ? { apiKey: auth.apiKey } : {}),
53
+ ...(auth?.headers !== undefined ? { headers: auth.headers } : {}),
51
54
  };
52
55
  const stream = await Promise.resolve(deps.brain.stream(resolved.model, context, options));
53
56
  const msg = (await stream.result());
@@ -143,8 +143,13 @@ export type TraceEvent = {
143
143
  /**
144
144
  * How a task's requested reasoning intensity RESOLVED against the model's real capability (design/96 S6).
145
145
  * Emitted once at task start when thinking is on, so a deployment can SEE — not silently swallow (§E
146
- * honesty red-line) — that a binary provider ignored the tier (`graded:false`) or that an effort endpoint
147
- * clamped it down (`clamped:true`). Metadata-only (tiers + format + endpoint, never prompt content).
146
+ * honesty red-line) — that a binary provider ignored the tier (`graded:false`), that an effort endpoint
147
+ * clamped it down (`clamped:true`), or that a NON-reasoning model dropped the request entirely
148
+ * (`dropped:true` — the frame fires for that model too; it used to be the one arm with no report).
149
+ * Metadata-only (tiers + format + endpoint, never prompt content). The resolution is against the
150
+ * task's PRIMARY serving model at leg entry (same law as `TaskResult.model`): a mid-run
151
+ * degradation does not re-emit this frame — the switch is observed on its own seats
152
+ * (`TaskResult.degraded`).
148
153
  */
149
154
  kind: "reasoning.resolved";
150
155
  version: 1;
@@ -163,6 +168,16 @@ export type TraceEvent = {
163
168
  format: string;
164
169
  /** Coarse endpoint label (`model.api`, e.g. `openai-completions` / `anthropic-messages`). */
165
170
  endpoint: string;
171
+ /**
172
+ * Present (true) only when the model declares NO reasoning capability (`Model.reasoning` falsy): the
173
+ * brains send no thinking parameter at all, so the requested tier was DROPPED — not clamped.
174
+ * `effective:"off"` states the ENGINE side (nothing was requested), not a measured gateway state:
175
+ * on the binary enable-only formats (qwen/zai/qwen-chat-template) parameter absence is
176
+ * provider-default and a default-on gateway may still reason at its own tier — read `format` to
177
+ * know which family applies. `format`/`endpoint` report the family that WOULD have carried the
178
+ * tier. Absent on every reasoning-capable resolution (old consumers see identical frames).
179
+ */
180
+ dropped?: true;
166
181
  /** design/148 S3 (additive, §10.2): the nine-element cache identity — digests only. */
167
182
  snapshot?: {
168
183
  cacheIdentity: string;