@sema-agent/core 7.52.0 → 7.53.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 (191) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +3 -1
  3. package/dist/agents/cross-session-envelope.d.ts +8 -0
  4. package/dist/agents/cross-session-envelope.js +6 -0
  5. package/dist/agents/cross-session-judge.d.ts +21 -1
  6. package/dist/agents/cross-session-judge.js +8 -0
  7. package/dist/agents/cross-session-ref.d.ts +5 -0
  8. package/dist/agents/cross-session-ref.js +1 -0
  9. package/dist/agents/org-delivery-gate.d.ts +122 -0
  10. package/dist/agents/org-delivery-gate.js +69 -0
  11. package/dist/agents/peer-held-queue.d.ts +9 -4
  12. package/dist/agents/peer-held-queue.js +6 -5
  13. package/dist/agents/peer-session-drain.d.ts +35 -5
  14. package/dist/agents/peer-session-drain.js +271 -180
  15. package/dist/agents/read-inbox-tool.d.ts +7 -0
  16. package/dist/agents/read-inbox-tool.js +5 -1
  17. package/dist/agents/subagent.d.ts +3 -0
  18. package/dist/agents/subagent.js +99 -72
  19. package/dist/agents/tool-filter.d.ts +18 -0
  20. package/dist/agents/tool-filter.js +6 -0
  21. package/dist/brain/anthropic.d.ts +6 -1
  22. package/dist/brain/anthropic.js +4 -4
  23. package/dist/brain/model-presets.js +2 -1
  24. package/dist/brain/route-adjudicator.d.ts +6 -0
  25. package/dist/brain/routing.d.ts +24 -1
  26. package/dist/brain/routing.js +54 -19
  27. package/dist/brain/timeout.js +5 -4
  28. package/dist/core/a2a.d.ts +5 -2
  29. package/dist/core/a2a.js +30 -13
  30. package/dist/core/allow-entry.d.ts +2 -1
  31. package/dist/core/allow-entry.js +2 -2
  32. package/dist/core/auto-compaction.d.ts +57 -0
  33. package/dist/core/auto-compaction.js +28 -2
  34. package/dist/core/checkpoint-store.d.ts +29 -20
  35. package/dist/core/checkpoint-store.js +3 -3
  36. package/dist/core/compliance.js +3 -1
  37. package/dist/core/effective-path-target.d.ts +25 -6
  38. package/dist/core/effective-path-target.js +24 -2
  39. package/dist/core/engine-notice.d.ts +13 -2
  40. package/dist/core/gate-fold.js +3 -2
  41. package/dist/core/gate-lanes.js +11 -13
  42. package/dist/core/gate-outcome.d.ts +11 -6
  43. package/dist/core/governance-codes.d.ts +3 -1
  44. package/dist/core/governance-codes.js +3 -0
  45. package/dist/core/hands-band-screen.d.ts +7 -0
  46. package/dist/core/hands-band-screen.js +32 -0
  47. package/dist/core/hooks.d.ts +7 -5
  48. package/dist/core/hooks.js +5 -3
  49. package/dist/core/locked-config.d.ts +84 -32
  50. package/dist/core/locked-config.js +128 -42
  51. package/dist/core/mcp-probe.js +1 -1
  52. package/dist/core/mcp-server-spec.d.ts +88 -10
  53. package/dist/core/mcp-server-spec.js +183 -33
  54. package/dist/core/mcp.d.ts +21 -14
  55. package/dist/core/mcp.js +55 -51
  56. package/dist/core/memory-engine/engine.d.ts +14 -19
  57. package/dist/core/memory-engine/engine.js +7 -5
  58. package/dist/core/mode-policy.d.ts +3 -18
  59. package/dist/core/mode-policy.js +2 -16
  60. package/dist/core/mods/kernel/dispatch.js +13 -15
  61. package/dist/core/mods/kernel/events.d.ts +9 -6
  62. package/dist/core/mods/kernel/events.js +2 -2
  63. package/dist/core/mods/kernel/receive.d.ts +39 -10
  64. package/dist/core/mods/kernel/receive.js +119 -54
  65. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  66. package/dist/core/mods/kernel/site-rules.js +232 -40
  67. package/dist/core/mods/stations.d.ts +0 -6
  68. package/dist/core/mods/stations.js +0 -7
  69. package/dist/core/mounted-mcp.d.ts +57 -0
  70. package/dist/core/mounted-mcp.js +71 -2
  71. package/dist/core/own-property-receipt.d.ts +13 -0
  72. package/dist/core/own-property-receipt.js +3 -1
  73. package/dist/core/parent-spec-seat.d.ts +2 -2
  74. package/dist/core/permission-rule-model.d.ts +58 -9
  75. package/dist/core/permission-rule-model.js +81 -7
  76. package/dist/core/permission-rule-org.d.ts +163 -33
  77. package/dist/core/permission-rule-org.js +205 -71
  78. package/dist/core/permission-rules.d.ts +7 -1
  79. package/dist/core/permission-rules.js +37 -8
  80. package/dist/core/retention.js +7 -2
  81. package/dist/core/retired-keys.d.ts +8 -2
  82. package/dist/core/retired-keys.js +12 -1
  83. package/dist/core/roles.js +60 -10
  84. package/dist/core/rule-lane-reads.js +1 -1
  85. package/dist/core/runner/contracts.d.ts +1 -0
  86. package/dist/core/runner/gate-exit.js +4 -3
  87. package/dist/core/runner/mcp-redial.d.ts +52 -10
  88. package/dist/core/runner/mcp-redial.js +38 -9
  89. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  90. package/dist/core/runner/permission-rule-lanes.js +22 -10
  91. package/dist/core/runner/person-path-rows.d.ts +3 -1
  92. package/dist/core/runner/person-path-rows.js +2 -2
  93. package/dist/core/runner/prepare-config-doors.d.ts +48 -2
  94. package/dist/core/runner/prepare-config-doors.js +161 -145
  95. package/dist/core/runner/prepare-defer-classify.js +4 -7
  96. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  97. package/dist/core/runner/prepare-delegation-surface.js +7 -5
  98. package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
  99. package/dist/core/runner/prepare-hands-readface.js +2 -1
  100. package/dist/core/runner/prepare-inherited-gate.js +2 -2
  101. package/dist/core/runner/prepare-listings.d.ts +1 -1
  102. package/dist/core/runner/prepare-listings.js +4 -2
  103. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  104. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  105. package/dist/core/runner/prepare-memory.d.ts +3 -2
  106. package/dist/core/runner/prepare-memory.js +6 -2
  107. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  108. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  109. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  110. package/dist/core/runner/prepare-policy-chain.js +12 -12
  111. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  112. package/dist/core/runner/prepare-project-context.js +6 -4
  113. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  114. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  115. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  116. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  117. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  118. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  119. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  120. package/dist/core/runner/prepare-question-face.js +4 -3
  121. package/dist/core/runner/prepare-task.js +10 -10
  122. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
  123. package/dist/core/runner/prepare-wiring-manifest.js +8 -5
  124. package/dist/core/runner/resume-admission.d.ts +4 -1
  125. package/dist/core/runner/resume-admission.js +7 -5
  126. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  127. package/dist/core/runner/resume-internals-and-config.js +3 -8
  128. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  129. package/dist/core/runner/retired-runner-deps.js +1 -4
  130. package/dist/core/runner/run-leg.js +42 -12
  131. package/dist/core/runner/run-recovery-lanes.js +5 -3
  132. package/dist/core/runner/run-terminal-adoption.js +6 -5
  133. package/dist/core/runner/run-turn-boundary.js +9 -3
  134. package/dist/core/runner/runtask.js +1 -1
  135. package/dist/core/runner/skills-seat.d.ts +15 -0
  136. package/dist/core/runner/skills-seat.js +57 -0
  137. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  138. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  139. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  140. package/dist/core/runner/synthetic-tools.js +2 -12
  141. package/dist/core/runner-deps.d.ts +29 -14
  142. package/dist/core/session-policy-store.d.ts +19 -0
  143. package/dist/core/session-policy-store.js +22 -2
  144. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  145. package/dist/core/task-event.d.ts +38 -2
  146. package/dist/core/task-notification.js +2 -2
  147. package/dist/core/task-spec.d.ts +1 -1
  148. package/dist/core/tool-policy.d.ts +3 -0
  149. package/dist/core/tool-policy.js +10 -4
  150. package/dist/core/types.d.ts +2 -2
  151. package/dist/core/types.js +1 -1
  152. package/dist/core/untrusted-text.d.ts +16 -0
  153. package/dist/core/untrusted-text.js +7 -2
  154. package/dist/core/user-attachment.d.ts +13 -2
  155. package/dist/core/user-attachment.js +47 -41
  156. package/dist/core/wiring-manifest.d.ts +4 -2
  157. package/dist/engine/harness/agent-harness.js +2 -1
  158. package/dist/engine/harness/types.d.ts +3 -0
  159. package/dist/engine/loop/agent-loop.d.ts +12 -1
  160. package/dist/engine/loop/agent-loop.js +17 -5
  161. package/dist/engine/loop/types.d.ts +7 -1
  162. package/dist/index.d.ts +15 -9
  163. package/dist/index.js +12 -6
  164. package/dist/internal/harness.d.ts +1 -0
  165. package/dist/internal/harness.js +1 -0
  166. package/dist/orchestration/goal.d.ts +24 -1
  167. package/dist/orchestration/goal.js +3 -2
  168. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  169. package/dist/prompt-assembly/artifact-store.js +38 -12
  170. package/dist/prompt-assembly/artifact.d.ts +12 -11
  171. package/dist/prompt-assembly/artifact.js +52 -21
  172. package/dist/prompt-assembly/assemble.d.ts +2 -0
  173. package/dist/prompt-assembly/assemble.js +2 -1
  174. package/dist/prompt-assembly/catalog/index.js +2 -1
  175. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  176. package/dist/prompt-assembly/catalog/memory.js +6 -0
  177. package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
  178. package/dist/prompt-assembly/composer.d.ts +2 -0
  179. package/dist/prompt-assembly/composer.js +1 -1
  180. package/dist/prompt-assembly/overlay.d.ts +21 -11
  181. package/dist/prompt-assembly/overlay.js +55 -17
  182. package/dist/prompt-assembly/select.d.ts +21 -6
  183. package/dist/prompt-assembly/select.js +39 -2
  184. package/dist/prompts/default.d.ts +0 -11
  185. package/dist/prompts/default.js +0 -3
  186. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  187. package/dist/stores/file/permission-rule-store.js +2 -1
  188. package/dist/tools/artifact/artifact-tool.js +4 -3
  189. package/package.json +1 -1
  190. package/test/export-surface.snapshot.json +65 -5
  191. package/wire-changes/core@7.53.0.json +1946 -0
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable,
91
91
  export { withRetry } from "./core/with-retry.js";
92
92
  export { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "./core/git-worktree-env.js";
93
93
  export { runExecGate } from "./core/exec-gate.js";
94
- export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
94
+ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted, isMintableProbeReason } from "./core/untrusted-text.js";
95
95
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
96
96
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
97
97
  export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
@@ -219,9 +219,10 @@ export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDAT
219
219
  export { consolidateScope, advanceCursorAfterInline, } from "./core/consolidate-scope.js";
220
220
  export { resolveMemoryConsolidationDriver, autoRunOnRecommendationEffective, } from "./core/runner/memory-consolidation-driver.js";
221
221
  export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
222
- export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, executionEnvironmentBlockFor, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, } from "./prompts/default.js";
222
+ export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, executionEnvironmentBlockFor, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, } from "./prompts/default.js";
223
223
  export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
224
224
  export { OVERLAY_SOURCE_ORDER } from "./prompt-assembly/overlay.js";
225
+ export { PROMPT_BINDING_LIST_MAX, PROMPT_BINDING_MAX_CHARS } from "./prompt-assembly/overlay.js";
225
226
  export { BANDS as PROMPT_BANDS } from "./prompt-assembly/bands.js";
226
227
  export { OVERLAY_OPS, HOST_ROLES, HOST_FORMS } from "./prompt-assembly/catalog/schema.js";
227
228
  export { SEMA_DEFAULT_CATALOG, catalogText } from "./prompt-assembly/catalog/index.js";
@@ -300,6 +301,11 @@ export { TEAM_ROLES, DIRECTION_RELATIONS, teamRoleOf, mayDirect } from "./agents
300
301
  export { createListAgentsTool, LIST_AGENTS_TOOL_NAME, LIST_AGENTS_TOOL_ALIAS, LIST_AGENTS_MAX_RESULT_CHARS, PEER_LISTING_FRESH_WINDOW_MS, isFreshPeerRow } from "./agents/list-agents-tool.js";
301
302
  export { CROSS_SESSION_CLASSIFIER_RULE } from "./agents/cross-session-envelope.js";
302
303
  export { PEER_SESSION_ID_UNGRAMMATICAL_CODE, PEER_RESUPPLY_CAP } from "./agents/peer-session-drain.js";
304
+ export { ORG_DELIVERY_REFUSAL_CODES, isOrgDeliveryRefusalCode, mintCrossPrincipalRecord, isGateMintedRecord, PRINCIPAL_INBOX_TRIAGE_POLICIES, principalInboxTriagePolicy, principalInboxSideEffectKey, principalInboxConsumerOf, PRINCIPAL_INBOX_CONSUMER_INVALID_CODE, } from "./agents/org-delivery-gate.js";
305
+ export { PRINCIPAL_INBOX_HANDLE } from "./agents/cross-session-ref.js";
306
+ export { CROSS_PRINCIPAL_MESSAGE_NOTICE } from "./agents/cross-session-envelope.js";
307
+ export { principalInboundLayers } from "./agents/cross-session-judge.js";
308
+ export { PRINCIPAL_INBOX_UNMINTED_CAUSE } from "./agents/peer-session-drain.js";
303
309
  export { renderCrossSessionMessageFrame, renderCrossSessionNoticeLine } from "./core/task-notification.js";
304
310
  export { PEER_HELD_QUEUE_CAP, PEER_IDLE_SUBSCRIPTION_TTL_MS, PEER_IDLE_SUBSCRIBER_TABLE_CAP, PEER_IDLE_OUTSTANDING_CAP, PEER_IDLE_PRIORS_KEPT, PEER_IDLE_FIRE_DEBOUNCE_MS, PEER_IDLE_HELD_BACKOFF_MS, PEER_IDLE_LABEL_MAX, CROSS_SESSION_DIALOG_EXPIRY_VALUES, CROSS_SESSION_DIALOG_EXPIRY_DEFAULT, resolveCrossSessionDialogExpiry, PEER_HELD_REVIEW_CAUSES, PEER_DELIVERY_RECEIPT_STATES, isPeerDeliveryReceiptState, PEER_DROP_REASONS, peerRecipientSuffix, renderCrossSessionDeliveryNotice, describePeerDropReasons, renderCrossSessionDroppedNotice, PEER_IDLE_NOTICE_KINDS, isPeerIdleNoticeKind, PEER_IDLE_UNAVAILABLE_CAUSES, peerIdleNoticeLabel, formatPeerNoticeClock, renderCrossSessionIdleNotice, describePeerIdleSubscription, } from "./agents/peer-notices.js";
305
311
  export { createPeerHeldQueue, peerHeldQueueFor, peerHeldQueueIfAny, peerHeldQueueKey, realPeerClock, } from "./agents/peer-held-queue.js";
@@ -312,17 +318,17 @@ export { builtinAgentDefinitions, BUILTIN_READONLY_DENY_TOOLS, EXPLORE_WHEN_TO_U
312
318
  export { createTaskServer, } from "./server/http.js";
313
319
  export { clearStaleToolResults, editBudget, } from "./core/context-edit.js";
314
320
  export { createOpenAIBrain } from "./brain/openai.js";
315
- export { createAnthropicBrain } from "./brain/anthropic.js";
321
+ export { createAnthropicBrain, ANTHROPIC_DEFAULT_BASE_URL } from "./brain/anthropic.js";
316
322
  export { createOpenResponsesBrain } from "./brain/open-responses.js";
317
323
  export { FABLE_5_COMPAT, fable5Model } from "./brain/model-presets.js";
318
324
  export { createFailoverBrain } from "./brain/failover.js";
319
- export { createRoutingBrain } from "./brain/routing.js";
325
+ export { createRoutingBrain, brainLaneOf } from "./brain/routing.js";
320
326
  export { repairTextToolCalls } from "./brain/tool-call-repair.js";
321
327
  export { createCircuitBreakerBrain, CIRCUIT_OPEN_MARKER, } from "./brain/circuit-breaker.js";
322
328
  export { createDegradingBrain, readDegradation, DEGRADED_DIAGNOSTIC_TYPE, } from "./brain/degrading.js";
323
329
  export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
324
330
  export { WAITING_FIRST_TOKEN_AFTER_MS, WAITING_FIRST_TOKEN_EVERY_MS } from "./brain/stream-engine.js";
325
- export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, routeKeyOf, displayUrlOf, resolveRequestRoot, resolveRequestRootOf, joinRequestUrl, hasAuthCarrier, __resetCredentialOriginBindings, } from "./brain/route-adjudicator.js";
331
+ export { adjudicateModelRoute, resolveRouteCredential, admitRouteRequest, routeRefusalText, routePairingStatus, routeKeyOf, displayUrlOf, resolveRequestRoot, resolveRequestRootOf, joinRequestUrl, hasAuthCarrier, __resetCredentialOriginBindings, } from "./brain/route-adjudicator.js";
326
332
  export { ROUTE_CREDENTIAL_SOURCES, ROUTE_CARRIERS, ROUTE_CARRIER_BAGS, REQUEST_ROOT_ORIGINS, ROUTE_CREDENTIAL_PAIRING, requestRouteOf, routePairingRowOf, } from "./brain/request-route.js";
327
333
  export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS } from "./brain/route-conformance.js";
328
334
  export {} from "./brain/timeout.js";
@@ -334,4 +340,4 @@ export { SHADOW_REASON, allowRuleShadowSentence, cardDisclosureOf } from "./core
334
340
  export { RULE_OFFERS_ABSENCES } from "./core/ask-origin.js";
335
341
  export { BOUND_GRANT_ABSENCES } from "./core/skill-grant-binding.js";
336
342
  export { DISPOSABLE_PROMPT_SECTIONS } from "./core/prompt-section-seat.js";
337
- export { readMcpToolAxes } from "./core/mcp-server-spec.js";
343
+ export { readToolAxes, readMcpToolAxes } from "./core/mcp-server-spec.js";
@@ -23,6 +23,7 @@ export { BaseSessionStorage, leafIdAfterEntry } from "../engine/session/storage-
23
23
  export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
24
24
  export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
25
25
  export { uuidv7 } from "../engine/session/uuid.js";
26
+ export { readToolCallArguments } from "../engine/loop/agent-loop.js";
26
27
  export { SessionError } from "../engine/harness/types.js";
27
28
  export type { AnnouncedListingSnapshot, AnnouncedListingSnapshotInput, AnnouncedMcpFailure } from "../engine/harness/types.js";
28
29
  export { normalizeGitAnnouncement } from "../engine/harness/types.js";
@@ -11,6 +11,7 @@ export { BaseSessionStorage, leafIdAfterEntry } from "../engine/session/storage-
11
11
  export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
12
12
  export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
13
13
  export { uuidv7 } from "../engine/session/uuid.js";
14
+ export { readToolCallArguments } from "../engine/loop/agent-loop.js";
14
15
  export { SessionError } from "../engine/harness/types.js";
15
16
  export { normalizeGitAnnouncement } from "../engine/harness/types.js";
16
17
  export { gitFrameContextVisible } from "../engine/session/session.js";
@@ -13,7 +13,8 @@
13
13
  */
14
14
  import type { RunnerSelfSeat } from "../core/runner/contracts.js";
15
15
  import type { CheckpointToken } from "../core/checkpoint-store.js";
16
- import type { TaskResult, TaskSpec } from "../core/types.js";
16
+ import type { GateKind, PAUSE_REGISTRY } from "../core/pause-registry.js";
17
+ import type { PausedCause, TaskResult, TaskSpec } from "../core/types.js";
17
18
  /** Read-only snapshot a `doneCheck` sees after one iteration. */
18
19
  export interface GoalTurnState {
19
20
  /** This iteration's full {@link TaskResult}. */
@@ -73,6 +74,18 @@ export type GoalStatus = "achieved" | "max_iterations" | "budget" | "blocked" |
73
74
  * objective / raise `budgetTokens`), `"walltime"` = `totalTimeoutMs` elapsed (give it more time).
74
75
  */
75
76
  export type GoalBudgetCause = "tokens" | "walltime";
77
+ /**
78
+ * The gates a pause can carry when the status word reads `suspended`: the pause registry's rows whose `taskStatus` column is `suspended` —
79
+ * the column {@link pauseFamily} projects the status word from. The type reads the same table the status word does, so a row added to the
80
+ * table moves both, and a gate that reaches the mint below with that word is always one of these. Of the four kinds, a run's own pause is
81
+ * `human`, `irreversible_ask` or `resource_limit`; `task_done` is the handle a deployment parks, which the engine mints in no run but which
82
+ * a custom runner seat may return as a paused terminal, and which the table reads `suspended` too. The review kinds are `needs_review`.
83
+ */
84
+ type SuspendedGate = Extract<PausedCause["gate"], {
85
+ kind: {
86
+ [K in GateKind]: (typeof PAUSE_REGISTRY)[K]["taskStatus"] extends "suspended" ? K : never;
87
+ }[GateKind];
88
+ }>;
76
89
  export interface GoalResult {
77
90
  readonly status: GoalStatus;
78
91
  /** Last iteration's result; absent on a preflight-terminate (a stop BEFORE iteration 1 ran). */
@@ -93,6 +106,15 @@ export interface GoalResult {
93
106
  /** #111 — present IFF `status === "budget"` (the KEY is absent otherwise, so presence alone reads
94
107
  * as "stopped on a ceiling"). See {@link GoalBudgetCause}. */
95
108
  readonly budgetCause?: GoalBudgetCause;
109
+ /** Present IFF `status === "suspended"` — every suspended result carries it, no other carries the KEY: WHY the run is parked, as the
110
+ * gate it parked at (the checkpoint's own gate, the same object as `result.terminal.gate`; its words are the closed sets of
111
+ * {@link import("../core/checkpoint-store.js").CheckpointGate}, narrowed to the kinds whose status word is `suspended`: `human`,
112
+ * `irreversible_ask`, `resource_limit` — and `task_done`, the handle a deployment parks, which a run of this engine never mints but a custom
113
+ * runner seat may return). The cause is read from the gate alone, never from the caller's signal:
114
+ * `{ kind: "resource_limit", reason: "usage_window" }` waits for quota (`resumeAfterMs`); `reason: "preempt"` is the host's own resumable
115
+ * stop (`TaskSpec.preemptSignal`); `human` / `irreversible_ask` wait on an approval; another `resource_limit` reason waits on an
116
+ * allowance. A caller's abort that ended the run is `status: "aborted"` (no checkpoint); a park that won the race with it is that park. */
117
+ readonly suspendGate?: SuspendedGate;
96
118
  }
97
119
  /** Reserved name of the goal-completion signal tool (injected by `runGoal` when there is no outputSchema). */
98
120
  export declare const DECLARE_DONE_TOOL_NAME = "DeclareDone";
@@ -104,3 +126,4 @@ export declare const DECLARE_DONE_TOOL_NAME = "DeclareDone";
104
126
  * cancel; a durable pause (suspended/needs_review) is a hard boundary that surfaces the checkpointToken.
105
127
  */
106
128
  export declare function runGoal(runner: RunnerSelfSeat, spec: GoalSpec): Promise<GoalResult>;
129
+ export {};
@@ -100,9 +100,10 @@ export async function runGoal(runner, spec) {
100
100
  verificationKind,
101
101
  accepted: false,
102
102
  cumulativeTokens,
103
- checkpointToken: over?.checkpointToken,
103
+ checkpointToken: over?.paused?.token,
104
104
  errorCode: over?.errorCode,
105
105
  ...(status === "budget" && over?.budgetCause !== undefined ? { budgetCause: over.budgetCause } : {}),
106
+ ...(status === "suspended" && over?.paused !== undefined ? { suspendGate: over.paused.gate } : {}),
106
107
  };
107
108
  if (verificationKind === "mechanical" && status !== "suspended" && status !== "needs_review") {
108
109
  runner.emitTaskOutcome?.({
@@ -145,7 +146,7 @@ export async function runGoal(runner, spec) {
145
146
  spentCostMicroUsd += (result.stats.costMicroUsd ?? 0) + (result.stats.nested?.costMicroUsd ?? 0);
146
147
  spentTurns += (result.stats.turns ?? 0) + (result.stats.nested?.turns ?? 0);
147
148
  if (isDurablePause(result.terminal)) {
148
- return mk(pauseFamily(result.terminal), iteration, { result, checkpointToken: result.terminal.token });
149
+ return mk(pauseFamily(result.terminal), iteration, { result, paused: result.terminal });
149
150
  }
150
151
  const midStop = classifyStop();
151
152
  if (midStop)
@@ -1,13 +1,35 @@
1
+ import type { RunnerDeps } from "../core/runner-deps.js";
1
2
  import { type ArtifactVerifyCode, type PromptArtifact, type PublishedPromptArtifact } from "./artifact.js";
3
+ /** Why a copy a store holds could not be used: the verifier refused it (its code and reason — a retired schema's reason
4
+ * names the republish rewrite), or the store could not read it back (`unreadable`, the reason naming the read error). */
5
+ export interface StoredArtifactRefusal {
6
+ readonly code: ArtifactVerifyCode | "unreadable";
7
+ readonly reason: string;
8
+ }
9
+ /** What a read tells its caller when a copy was there and could not be used. */
10
+ export type OnArtifactRefused = (refusal: StoredArtifactRefusal) => void;
11
+ /** The clause every reader says of a copy that could not be used, by its cause — one wording, wherever it is said. */
12
+ export declare function refusalClause(refusal: StoredArtifactRefusal): string;
13
+ /**
14
+ * The operator line for a stored center artifact that could not be read back: which copy, why — refused or unreadable
15
+ * (the clause above), or no copy at all — and what follows from it. The one sentence a boot restore and a pinned resume
16
+ * say.
17
+ */
18
+ export declare function storedArtifactLine(artifactDigest: string, refusal: StoredArtifactRefusal | undefined, consequence: string): string;
2
19
  export interface PromptArtifactStore {
3
- /** Verified read by digest — undefined on a miss OR on any verification failure of the stored bytes. */
4
- get(artifactDigest: string): Promise<PublishedPromptArtifact | undefined>;
20
+ /**
21
+ * Verified read by digest — undefined on a miss OR on a copy that cannot be used. When a copy was there and could not
22
+ * be used — the verifier refused its bytes, or the store could not read them — `onRefused` is told once before the
23
+ * read answers undefined, so a caller can tell a refused or unreadable copy (a retired schema, a corrupt file, a file
24
+ * the process may not read) from a missing one. A store that holds a copy it cannot use calls it.
25
+ */
26
+ get(artifactDigest: string, onRefused?: OnArtifactRefused): Promise<PublishedPromptArtifact | undefined>;
5
27
  /** Write by digest (the caller verified before putting). */
6
28
  put(artifact: PublishedPromptArtifact): Promise<void>;
7
29
  }
8
30
  export declare class MemoryPromptArtifactStore implements PromptArtifactStore {
9
31
  private readonly byDigest;
10
- get(artifactDigest: string): Promise<PublishedPromptArtifact | undefined>;
32
+ get(artifactDigest: string, onRefused?: OnArtifactRefused): Promise<PublishedPromptArtifact | undefined>;
11
33
  put(artifact: PublishedPromptArtifact): Promise<void>;
12
34
  }
13
35
  /** `artifacts/<hex>.json` under a caller-owned directory; atomic tmp+rename writes. */
@@ -15,7 +37,9 @@ export declare class FilePromptArtifactStore implements PromptArtifactStore {
15
37
  private readonly dir;
16
38
  constructor(dir: string);
17
39
  private fileFor;
18
- get(artifactDigest: string): Promise<PublishedPromptArtifact | undefined>;
40
+ /** A file that is not there is a miss; one that is there and cannot be read (a directory in its place, a file the
41
+ * process may not open, too many open files) is told to `onRefused` as `unreadable`, by its error code. */
42
+ get(artifactDigest: string, onRefused?: OnArtifactRefused): Promise<PublishedPromptArtifact | undefined>;
19
43
  put(artifact: PublishedPromptArtifact): Promise<void>;
20
44
  }
21
45
  export type PromptSourceState = {
@@ -47,8 +71,10 @@ export declare class FilePromptSourceStateStore implements PromptSourceStateStor
47
71
  export interface PromptArtifactSource {
48
72
  /** The current verified candidate new sessions (and every top-level session at a compaction) adopt; undefined = none. */
49
73
  current(): PublishedPromptArtifact | undefined;
50
- /** Resolve a PINNED digest (the resume path). Undefined = unavailable — the caller fails loud. */
51
- get(artifactDigest: string): Promise<PromptArtifact | undefined>;
74
+ /** Resolve a PINNED digest (the resume path). Undefined = unavailable — the caller fails loud. `onRefused` is the
75
+ * store's (see {@link PromptArtifactStore.get}): told when a copy was there and could not be used, so the caller says
76
+ * why. */
77
+ get(artifactDigest: string, onRefused?: OnArtifactRefused): Promise<PromptArtifact | undefined>;
52
78
  /** The tri-state every adoption decision keys on: `"active"` = a verified candidate is live; `"disabled"` = an
53
79
  * EXPLICIT verified revocation (`applyDisabled`) — the one state that empties a pinned center layer;
54
80
  * `"unavailable"` = no verified state (never configured, or a store miss at boot) — an outage never strips policy,
@@ -61,12 +87,16 @@ export interface PromptArtifactSource {
61
87
  * in-memory swap. Any verification failure leaves EVERYTHING unchanged.
62
88
  * - `applyDisabled(sourceRevision)` — an explicit, successful disable/delete response: persist disabled state +
63
89
  * drop the candidate. (Network failure/304 = simply don't call anything.)
64
- * - `restore()` — boot: re-resolve the persisted active state through the verified store; a miss or verify failure
65
- * boots stateless (no candidate) without touching the persisted record.
90
+ * - `restore()` — boot: re-resolve the persisted active state through the verified store; a miss, an unreadable copy
91
+ * or a verify failure boots stateless (no candidate) without touching the persisted record, and says which on
92
+ * `onError` (a refused copy — a retired schema's reason names the republish rewrite — and an unreadable one are told
93
+ * apart from no copy at all).
66
94
  */
67
95
  export declare class CenterPromptSource implements PromptArtifactSource {
68
96
  private readonly store;
69
97
  private readonly stateStore;
98
+ private readonly onError;
99
+ private readonly notifier;
70
100
  private candidate;
71
101
  private state;
72
102
  /** Serialization tail for the three STATE-MUTATING entry points. Each is a multi-await sequence ending in a
@@ -78,12 +108,17 @@ export declare class CenterPromptSource implements PromptArtifactSource {
78
108
  private tail;
79
109
  /** Run `fn` after every previously-queued mutation settles (success OR failure — a failed apply must not wedge the queue). */
80
110
  private exclusive;
111
+ /** `onError` is the operator channel of the source's own acts (a boot restore that finds its persisted artifact
112
+ * refused, unreadable or gone) — the Runner's sink, in its shape (`RunnerDeps.onError`): pass the Runner's. A boot
113
+ * restore belongs to no session: its context is `{ phase: "prompt-constitution", sessionId: "", classification:
114
+ * "center-source-restore" }`. Omitted = those acts are silent. */
81
115
  constructor(opts: {
82
116
  store: PromptArtifactStore;
83
117
  stateStore?: PromptSourceStateStore;
118
+ onError?: RunnerDeps["onError"];
84
119
  });
85
120
  current(): PublishedPromptArtifact | undefined;
86
- get(artifactDigest: string): Promise<PromptArtifact | undefined>;
121
+ get(artifactDigest: string, onRefused?: OnArtifactRefused): Promise<PromptArtifact | undefined>;
87
122
  restore(): Promise<void>;
88
123
  sourceState(): "active" | "disabled" | "unavailable";
89
124
  applySnapshot(raw: unknown): Promise<{
@@ -1,22 +1,37 @@
1
1
  import { mkdirSync, promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
+ import { createSafeNotifier, observeThenableRejection } from "../core/safe-notify.js";
3
4
  import { verifyPromptArtifact } from "./artifact.js";
4
- function verifiedRead(bytes, artifactDigest) {
5
+ export function refusalClause(refusal) {
6
+ return refusal.code === "unreadable" ? `unreadable (${refusal.reason})` : `refused by this engine (${refusal.code}: ${refusal.reason})`;
7
+ }
8
+ export function storedArtifactLine(artifactDigest, refusal, consequence) {
9
+ return `center prompt artifact ${artifactDigest.slice(0, 23)}… ${refusal !== undefined ? `is ${refusalClause(refusal)}` : "has no stored copy"} — ${consequence}`;
10
+ }
11
+ function tellRefused(onRefused, refusal) {
12
+ const notifier = createSafeNotifier();
13
+ notifier.notify(() => observeThenableRejection(onRefused?.(refusal), notifier, "artifact-store.onRefused"), "artifact-store.onRefused");
14
+ return undefined;
15
+ }
16
+ function verifiedRead(bytes, artifactDigest, onRefused) {
17
+ const refuse = (refusal) => tellRefused(onRefused, refusal);
5
18
  let parsed;
6
19
  try {
7
20
  parsed = JSON.parse(bytes);
8
21
  }
9
22
  catch {
10
- return undefined;
23
+ return refuse({ code: "shape", reason: "the stored bytes are not JSON" });
11
24
  }
12
25
  const v = verifyPromptArtifact(parsed);
13
- return v.ok && v.artifact.artifactDigest === artifactDigest ? v.artifact : undefined;
26
+ if (!v.ok)
27
+ return refuse({ code: v.code, reason: v.reason });
28
+ return v.artifact.artifactDigest === artifactDigest ? v.artifact : refuse({ code: "digest", reason: "the stored artifact is not the one its digest names" });
14
29
  }
15
30
  export class MemoryPromptArtifactStore {
16
31
  byDigest = new Map();
17
- async get(artifactDigest) {
32
+ async get(artifactDigest, onRefused) {
18
33
  const bytes = this.byDigest.get(artifactDigest);
19
- return bytes === undefined ? undefined : verifiedRead(bytes, artifactDigest);
34
+ return bytes === undefined ? undefined : verifiedRead(bytes, artifactDigest, onRefused);
20
35
  }
21
36
  async put(artifact) {
22
37
  this.byDigest.set(artifact.artifactDigest, JSON.stringify(artifact));
@@ -32,7 +47,7 @@ export class FilePromptArtifactStore {
32
47
  const m = /^sha256:([0-9a-f]{64})$/.exec(artifactDigest);
33
48
  return m ? path.join(this.dir, "artifacts", `${m[1]}.json`) : undefined;
34
49
  }
35
- async get(artifactDigest) {
50
+ async get(artifactDigest, onRefused) {
36
51
  const file = this.fileFor(artifactDigest);
37
52
  if (file === undefined)
38
53
  return undefined;
@@ -40,10 +55,13 @@ export class FilePromptArtifactStore {
40
55
  try {
41
56
  bytes = await fs.readFile(file, "utf8");
42
57
  }
43
- catch {
44
- return undefined;
58
+ catch (err) {
59
+ const code = typeof err === "object" && err !== null ? err.code : undefined;
60
+ if (code === "ENOENT")
61
+ return undefined;
62
+ return tellRefused(onRefused, { code: "unreadable", reason: `the store could not read its file: ${typeof code === "string" ? code : "an error with no code"}` });
45
63
  }
46
- return verifiedRead(bytes, artifactDigest);
64
+ return verifiedRead(bytes, artifactDigest, onRefused);
47
65
  }
48
66
  async put(artifact) {
49
67
  const file = this.fileFor(artifact.artifactDigest);
@@ -99,6 +117,8 @@ export class FilePromptSourceStateStore {
99
117
  export class CenterPromptSource {
100
118
  store;
101
119
  stateStore;
120
+ onError;
121
+ notifier = createSafeNotifier();
102
122
  candidate;
103
123
  state = "unavailable";
104
124
  tail = Promise.resolve();
@@ -110,14 +130,15 @@ export class CenterPromptSource {
110
130
  constructor(opts) {
111
131
  this.store = opts.store;
112
132
  this.stateStore = opts.stateStore ?? new MemoryPromptSourceStateStore();
133
+ this.onError = opts.onError;
113
134
  }
114
135
  current() {
115
136
  return this.candidate;
116
137
  }
117
- async get(artifactDigest) {
138
+ async get(artifactDigest, onRefused) {
118
139
  if (this.candidate?.artifactDigest === artifactDigest)
119
140
  return this.candidate;
120
- return this.store.get(artifactDigest);
141
+ return this.store.get(artifactDigest, onRefused);
121
142
  }
122
143
  async restore() {
123
144
  return this.exclusive(async () => {
@@ -132,9 +153,14 @@ export class CenterPromptSource {
132
153
  this.state = "unavailable";
133
154
  return;
134
155
  }
135
- const stored = await this.store.get(state.artifactDigest);
156
+ const refused = {};
157
+ const stored = await this.store.get(state.artifactDigest, (r) => void (refused.current = r));
136
158
  this.candidate = stored !== undefined ? { ...stored, sourceRevision: state.sourceRevision } : undefined;
137
159
  this.state = stored !== undefined ? "active" : "unavailable";
160
+ if (stored === undefined) {
161
+ const line = new Error(storedArtifactLine(state.artifactDigest, refused.current, "the source boots unavailable: a new session has no center layer until a verified artifact is applied"));
162
+ this.notifier.notify(() => observeThenableRejection(this.onError?.(line, { phase: "prompt-constitution", sessionId: "", classification: "center-source-restore" }), this.notifier, "center-source.onError"), "center-source.onError");
163
+ }
138
164
  });
139
165
  }
140
166
  sourceState() {
@@ -1,7 +1,7 @@
1
1
  import { type OverlayOp, type PromptTrait } from "./catalog/schema.js";
2
2
  import { type PromptOverlay, type SourcedOverlay } from "./overlay.js";
3
3
  /** What an artifact needs from the engine that composes it: the targets it names (each with what kind of target it
4
- * is), the operations it uses, the traits its `when` conditions read, and the bindings its overlays are scoped to. */
4
+ * is), the operations it uses, the traits its `when` conditions read, and the bindings they name. */
5
5
  export interface PromptArtifactRequires {
6
6
  readonly targets: readonly {
7
7
  readonly id: string;
@@ -18,9 +18,9 @@ export interface PromptArtifactEvidence {
18
18
  }
19
19
  /** The digested part of an artifact. */
20
20
  export interface PromptArtifactPayload {
21
- readonly schemaVersion: 2;
21
+ readonly schemaVersion: 3;
22
22
  readonly requires: PromptArtifactRequires;
23
- /** Every overlay is of the `center` class; `scope.binding` absent ⇒ applies under every binding. */
23
+ /** Every overlay is of the `center` class; one with no `when.binding` applies under every binding. */
24
24
  readonly overlays: readonly PromptOverlay[];
25
25
  readonly evidence: PromptArtifactEvidence;
26
26
  }
@@ -33,7 +33,8 @@ export interface PromptArtifact {
33
33
  export interface PublishedPromptArtifact extends PromptArtifact {
34
34
  readonly sourceRevision: string;
35
35
  }
36
- /** Validator bounds: they bound abuse and runaway publishes, not legitimate artifacts. */
36
+ /** Validator bounds: they bound abuse and runaway publishes, not legitimate artifacts. A `…Chars` bound counts Unicode
37
+ * code points (`codePointsOver`, the verifier's one character unit); a `…Bytes` bound counts UTF-8 bytes. */
37
38
  export declare const ARTIFACT_LIMITS: Readonly<{
38
39
  maxOverlays: 64;
39
40
  maxOverlayTextBytes: 32000;
@@ -85,17 +86,17 @@ export type CenterAdoption = {
85
86
  artifact: PromptArtifact;
86
87
  sourceRevision?: string;
87
88
  };
88
- /**
89
- * The center class's overlays for an adopted artifact under a binding: every overlay with no `scope.binding`, and
90
- * every one whose `scope.binding` equals the run's binding, in the artifact's order.
91
- */
92
- export declare function centerOverlaysOf(adoption: CenterAdoption | undefined, binding: string | undefined): readonly SourcedOverlay[];
89
+ /** The center class's overlays for an adopted artifact, in the artifact's order. Which of them apply to a run is their
90
+ * `when` (the binding included), read where every class's is — at the composition. */
91
+ export declare function centerOverlaysOf(adoption: CenterAdoption | undefined): readonly SourcedOverlay[];
93
92
  /** An artifact a source handed a run, re-checked by this engine before the run adopts it; undefined when it fails. */
94
93
  export declare function adoptCenter(artifact: PromptArtifact, sourceRevision: string | undefined): CenterAdoption | undefined;
95
94
  /**
96
95
  * Build a published artifact from overlays and evidence — the one builder (a center's publish pipeline calls it):
97
- * `requires` is derived, the digest computed over the payload's JSON form (what travels), and the result goes
98
- * through the verifier, so the caller holds either a valid artifact or the reason it is not one.
96
+ * `requires` is derived, the digest computed over the payload's JSON form (what travels) with each overlay as the
97
+ * overlay door leaves it (its `when` in normal form, so one meaning is one digest), and the result goes through the
98
+ * verifier, so the caller holds either a valid artifact or the reason it is not one. An overlay the door refuses goes on
99
+ * as it was given: the verifier names it, in its own order of checks.
99
100
  */
100
101
  export declare function buildPromptArtifact(opts: {
101
102
  overlays: readonly PromptOverlay[];
@@ -3,7 +3,7 @@ import { isBandId } from "./bands.js";
3
3
  import { SEMA_DEFAULT_CATALOG } from "./catalog/index.js";
4
4
  import { OVERLAY_OPS, PROMPT_TRAITS, isWordOf } from "./catalog/schema.js";
5
5
  import { canonical } from "./epoch.js";
6
- import { sectionOfMessage, validateOverlay } from "./overlay.js";
6
+ import { codePointsOver, sectionOfMessage, validateOverlay } from "./overlay.js";
7
7
  export const ARTIFACT_LIMITS = Object.freeze({
8
8
  maxOverlays: 64,
9
9
  maxOverlayTextBytes: 32_000,
@@ -37,8 +37,8 @@ export function requiresOf(overlays) {
37
37
  need(o.message, "message");
38
38
  for (const t of [...(o.when?.traits?.allOf ?? []), ...(o.when?.traits?.noneOf ?? [])])
39
39
  traits.add(t);
40
- if (o.scope?.binding !== undefined)
41
- bindings.add(o.scope.binding);
40
+ for (const b of [...(o.when?.binding?.anyOf ?? []), ...(o.when?.binding?.noneOf ?? [])])
41
+ bindings.add(b);
42
42
  }
43
43
  const byText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
44
44
  return {
@@ -77,24 +77,49 @@ function requiresShaped(v) {
77
77
  strings(v.traits) &&
78
78
  strings(v.bindings));
79
79
  }
80
+ const bindingListOf = (b) => {
81
+ if (b === undefined)
82
+ return undefined;
83
+ const names = b.anyOf !== undefined ? b.anyOf : b.noneOf;
84
+ return { anyOf: b.anyOf !== undefined, names, set: new Set(names) };
85
+ };
86
+ function bindingsMeet(a, b) {
87
+ if (a === undefined || b === undefined)
88
+ return true;
89
+ if (a.anyOf && b.anyOf)
90
+ return a.names.some((x) => b.set.has(x));
91
+ if (a.anyOf)
92
+ return a.names.some((x) => !b.set.has(x));
93
+ if (b.anyOf)
94
+ return b.names.some((x) => !a.set.has(x));
95
+ return true;
96
+ }
80
97
  function replaceConflict(overlays) {
81
- const seen = new Map();
98
+ const groups = new Map();
82
99
  for (const o of overlays) {
83
100
  if (o.op !== "replace")
84
101
  continue;
85
- const key = canonical({ target: o.target, binding: o.scope?.binding ?? null, when: o.when ?? null });
86
- const prior = seen.get(key);
87
- if (prior !== undefined)
88
- return `overlays "${prior}" and "${o.id}" both replace "${o.target}" under the same binding and condition`;
89
- seen.set(key, o.id);
102
+ const { binding, ...rest } = o.when ?? {};
103
+ const key = canonical({ target: o.target, when: rest });
104
+ groups.set(key, [...(groups.get(key) ?? []), { o, list: bindingListOf(binding) }]);
105
+ }
106
+ for (const group of groups.values()) {
107
+ for (let i = 0; i < group.length; i++) {
108
+ for (let j = i + 1; j < group.length; j++) {
109
+ const [{ o: p, list: pl }, { o: q, list: ql }] = [group[i], group[j]];
110
+ if (bindingsMeet(pl, ql)) {
111
+ return `overlays "${p.id}" and "${q.id}" both replace "${p.target}" under one condition that can hold in the same run (write exclusive binding conditions: "noneOf": [x] beside "anyOf": [x])`;
112
+ }
113
+ }
114
+ }
90
115
  }
91
116
  return undefined;
92
117
  }
93
118
  export function verifyPromptArtifact(raw) {
94
119
  if (!isRecord(raw))
95
120
  return { ok: false, code: "shape", reason: "the published artifact is not an object" };
96
- if (typeof raw.sourceRevision !== "string" || raw.sourceRevision.length === 0 || raw.sourceRevision.length > ARTIFACT_LIMITS.maxSourceRevisionChars) {
97
- return { ok: false, code: "shape", reason: "sourceRevision missing or oversized" };
121
+ if (typeof raw.sourceRevision !== "string" || raw.sourceRevision.length === 0 || codePointsOver(raw.sourceRevision, ARTIFACT_LIMITS.maxSourceRevisionChars)) {
122
+ return { ok: false, code: "shape", reason: `sourceRevision missing or over ${ARTIFACT_LIMITS.maxSourceRevisionChars} characters (code points)` };
98
123
  }
99
124
  const v = verifyPromptArtifactBody(raw);
100
125
  return v.ok ? { ok: true, artifact: { ...v.artifact, sourceRevision: raw.sourceRevision } } : v;
@@ -111,8 +136,10 @@ export function verifyPromptArtifactBody(raw) {
111
136
  const payload = raw.payload;
112
137
  if (!isRecord(payload))
113
138
  return fail("shape", "payload is not an object");
114
- if (payload.schemaVersion !== 2)
115
- return fail("schema", `unsupported schemaVersion ${JSON.stringify(payload.schemaVersion)?.slice(0, 40)} (this engine reads schemaVersion 2 only)`);
139
+ if (payload.schemaVersion !== 3) {
140
+ const republish = payload.schemaVersion === 2 ? `; republish under schemaVersion 3 — an overlay's "scope": { "binding": b } becomes "when": { "binding": { "anyOf": [b] } }` : "";
141
+ return fail("schema", `unsupported schemaVersion ${JSON.stringify(payload.schemaVersion)?.slice(0, 40)} (this engine reads schemaVersion 3 only${republish})`);
142
+ }
116
143
  for (const k of Object.keys(payload))
117
144
  if (k !== "schemaVersion" && k !== "requires" && k !== "overlays" && k !== "evidence")
118
145
  return fail("shape", `payload carries ${JSON.stringify(k).slice(0, 60)}`);
@@ -151,11 +178,11 @@ export function verifyPromptArtifactBody(raw) {
151
178
  if (conflict !== undefined)
152
179
  return fail("overlay", conflict);
153
180
  const evidence = payload.evidence;
154
- if (!isRecord(evidence) || typeof evidence.intent !== "string" || evidence.intent.trim() === "" || evidence.intent.length > ARTIFACT_LIMITS.maxIntentChars) {
155
- return fail("evidence", `evidence.intent is a non-blank string of at most ${ARTIFACT_LIMITS.maxIntentChars} characters`);
181
+ if (!isRecord(evidence) || typeof evidence.intent !== "string" || evidence.intent.trim() === "" || codePointsOver(evidence.intent, ARTIFACT_LIMITS.maxIntentChars)) {
182
+ return fail("evidence", `evidence.intent is a non-blank string of at most ${ARTIFACT_LIMITS.maxIntentChars} characters (code points)`);
156
183
  }
157
- if (!Array.isArray(evidence.refs) || evidence.refs.length > ARTIFACT_LIMITS.maxRefs || evidence.refs.some((r) => typeof r !== "string" || r.trim() === "" || r.length > ARTIFACT_LIMITS.maxRefChars)) {
158
- return fail("evidence", `evidence.refs is a list of at most ${ARTIFACT_LIMITS.maxRefs} non-blank references`);
184
+ if (!Array.isArray(evidence.refs) || evidence.refs.length > ARTIFACT_LIMITS.maxRefs || evidence.refs.some((r) => typeof r !== "string" || r.trim() === "" || codePointsOver(r, ARTIFACT_LIMITS.maxRefChars))) {
185
+ return fail("evidence", `evidence.refs is a list of at most ${ARTIFACT_LIMITS.maxRefs} non-blank references of at most ${ARTIFACT_LIMITS.maxRefChars} characters (code points) each`);
159
186
  }
160
187
  for (const k of Object.keys(evidence))
161
188
  if (k !== "intent" && k !== "refs")
@@ -165,7 +192,7 @@ export function verifyPromptArtifactBody(raw) {
165
192
  return fail("evidence", `overlay "${behaviorReplace.id}" replaces core-behavior text; evidence.refs names at least one evaluation or golden`);
166
193
  }
167
194
  const normalized = {
168
- schemaVersion: 2,
195
+ schemaVersion: 3,
169
196
  requires: requiresOf(overlays),
170
197
  overlays,
171
198
  evidence: { intent: evidence.intent, refs: [...evidence.refs] },
@@ -175,16 +202,20 @@ export function verifyPromptArtifactBody(raw) {
175
202
  return { ok: true, artifact: { payload: normalized, artifactDigest: raw.artifactDigest } };
176
203
  }
177
204
  const CENTER_ORIGIN = Object.freeze({ source: "center", by: "center" });
178
- export function centerOverlaysOf(adoption, binding) {
205
+ export function centerOverlaysOf(adoption) {
179
206
  if (adoption === undefined)
180
207
  return [];
181
- return adoption.artifact.payload.overlays.filter((o) => o.scope?.binding === undefined || o.scope.binding === binding).map((overlay) => ({ overlay, origin: CENTER_ORIGIN }));
208
+ return adoption.artifact.payload.overlays.map((overlay) => ({ overlay, origin: CENTER_ORIGIN }));
182
209
  }
183
210
  export function adoptCenter(artifact, sourceRevision) {
184
211
  const v = verifyPromptArtifactBody(artifact);
185
212
  return v.ok ? { artifact: v.artifact, ...(sourceRevision !== undefined ? { sourceRevision } : {}) } : undefined;
186
213
  }
187
214
  export function buildPromptArtifact(opts) {
188
- const payload = JSON.parse(JSON.stringify({ schemaVersion: 2, requires: requiresOf(opts.overlays), overlays: opts.overlays, evidence: opts.evidence }));
215
+ const overlays = opts.overlays.map((o) => {
216
+ const v = validateOverlay(o, { source: "center" });
217
+ return "overlay" in v ? v.overlay : o;
218
+ });
219
+ const payload = JSON.parse(JSON.stringify({ schemaVersion: 3, requires: requiresOf(overlays), overlays, evidence: opts.evidence }));
189
220
  return verifyPromptArtifact({ payload, artifactDigest: sha256Hex(canonical(payload)), sourceRevision: opts.sourceRevision });
190
221
  }
@@ -24,6 +24,8 @@ export interface AssembleInputs {
24
24
  role?: HostRole;
25
25
  form?: HostForm;
26
26
  };
27
+ /** The run's binding (`TaskSpec.promptBinding`), read by every overlay's `when.binding`. */
28
+ binding?: string;
27
29
  tools: readonly AgentTool[];
28
30
  facts: PromptRuntimeFacts;
29
31
  /** The task's working directory (the Cowork role base's `{{workspaceFolder}}`). */
@@ -3,7 +3,7 @@ import { composeCatalog, layoutSystemBlocks } from "./composer.js";
3
3
  import { ABSENT_THIS_RUN } from "./overlay.js";
4
4
  function supersededLine(s) {
5
5
  return s.supersededBy === ABSENT_THIS_RUN
6
- ? `prompt overlay "${s.id}" (${s.source}, ${s.by}) on "${s.target}" changed nothing: the selector left its target, or the catalog message it takes its text from, out of this run`
6
+ ? `prompt overlay "${s.id}" (${s.source}, ${s.by}) on "${s.target}" changed nothing: what it names is not in this run (the selector chose no variant of its target, or of the catalog message it takes its text from; or the lower entry it drops was never added — its own condition left it out, or the layer that added it is not in force)`
7
7
  : `prompt overlay "${s.id}" (${s.source}, ${s.by}) on "${s.target}" is superseded by "${s.supersededBy}", applied after it (source order task < center < deploy; within a source, declaration order)`;
8
8
  }
9
9
  export function assemblePrompt(inputs) {
@@ -17,6 +17,7 @@ export function assemblePrompt(inputs) {
17
17
  const plan = {
18
18
  catalog: SEMA_DEFAULT_CATALOG,
19
19
  ...(inputs.host !== undefined ? { host: inputs.host } : {}),
20
+ ...(inputs.binding !== undefined ? { binding: inputs.binding } : {}),
20
21
  overlays: [...inputs.taskOverlays, ...inputs.centerOverlays, ...inputs.deployOverlays],
21
22
  };
22
23
  const toolNames = inputs.tools.map((t) => t.name);
@@ -1,5 +1,6 @@
1
1
  import { CONSTITUTION_MESSAGES } from "./constitution.js";
2
2
  import { CORE_BEHAVIOR_MESSAGES } from "./core-behavior.js";
3
+ import { MEMORY_MESSAGES } from "./memory.js";
3
4
  import { MODE_MESSAGES } from "./modes.js";
4
5
  import { ROLE_MESSAGES } from "./role.js";
5
6
  import { SECTIONS } from "./sections.js";
@@ -9,7 +10,7 @@ import { RENDERER_IDS, isWordOf } from "./schema.js";
9
10
  export const SEMA_DEFAULT_CATALOG = {
10
11
  id: "sema-default@2",
11
12
  bands: BANDS,
12
- messages: [...ROLE_MESSAGES, ...CONSTITUTION_MESSAGES, ...CORE_BEHAVIOR_MESSAGES, ...MODE_MESSAGES, ...TEAM_MESSAGES],
13
+ messages: [...ROLE_MESSAGES, ...CONSTITUTION_MESSAGES, ...CORE_BEHAVIOR_MESSAGES, ...MODE_MESSAGES, ...TEAM_MESSAGES, ...MEMORY_MESSAGES],
13
14
  sections: SECTIONS,
14
15
  };
15
16
  export function validateCatalog(catalog) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Catalog messages of the `memory` band. No section places them: the band's bytes are the memory renderer's
3
+ * input, which the memory phase composes (prepare-memory.ts `composeInjection`); these are the texts it reads.
4
+ *
5
+ * - `core/memory.no-persistent-store`: the run has no memory store configured and no channel to persist through
6
+ * (the roster-wide persistence verdict is false). Without it the model, asked "remember X", answers with a
7
+ * receipt for a save that has nowhere to land. A store that is configured but did not mount this run does not
8
+ * get this sentence (it is categorical, and a mount can succeed next time): it gets the engine's
9
+ * writes-unavailable-right-now notice, with the failed mount as its cause. Name-free: it names no tool, so no
10
+ * toolkit can make it stale.
11
+ */
12
+ import type { CatalogMessage } from "./schema.js";
13
+ export declare const MEMORY_MESSAGES: readonly CatalogMessage[];