@sema-agent/core 5.44.0 → 5.45.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.45.0 — 2026-08-19
4
+
5
+ ### Added
6
+ - `RunnerDeps.memoryDelegationEvidence?: "static-face" | "attested-only"` (design/324, ruling-1
7
+ stopgap): the deployment's evidence standard for delegation-driven memory pollution.
8
+ `"attested-only"` retires ONLY the static capability-face mark (the "a child COULD reach
9
+ external content" over-approximation); the attestation-observed mark (`att === "external"`),
10
+ the chain's `incomplete` honesty, `recordExternal`, non-delegation classification and the
11
+ pre-call direct-class mark all stay. Key absent ≡ `"static-face"` ≡ byte-identical behavior.
12
+ A malformed value is refused loudly (`config.memory_delegation_evidence`, exact-spelling gate).
13
+ ACCEPTED COST (the deployment's to own): with `"attested-only"`, a BACKGROUND child's real
14
+ external contact does not mark this session — its content re-enters via TaskOutput /
15
+ task-notification / AgentTranscript, none of which carries an attestation.
16
+ - `memory.delegation_static_mark_waived` EngineNotice — mandatory companion: minted at most once
17
+ per prepared leg when the knob actually waives a static-face mark on a pollution-armed chain
18
+ (`detail: {subagentType?, sessionId?}`; reason carries the waived mark's own sentence, bounded).
19
+ - `TaskResult.effectiveReasoning?: ResolvedReasoning` (#327b): the leg's effective reasoning
20
+ resolution echoed on the result — same object the `reasoning.resolved` trace event carries
21
+ (two faces, one mint); rides the backstop/`resume.tool_unavailable` carrier like its
22
+ precedents. `ReasoningFormat` gains `"responses"` (dispatched by api family, single-source
23
+ `RESPONSES_APIS`).
24
+ - Cross-session recall pins (#260 verdict: engine clean): the consumer contract is pinned —
25
+ the model's final answer text exists BEFORE the terminal harvest commits, `TaskStream.result()`
26
+ is the commit boundary, and a consumer that prints the answer and kills the worker without
27
+ awaiting `result()` strands the entry id-less on disk (the incident's exact evidence shape).
28
+
29
+ ### Changed
30
+ - The reasoning resolver mirrors `thinkingLevelMap`'s null arm (5.45 rescan): a clamped tier the
31
+ model maps to `null` is reported tier-not-honored (`graded:false`, intent echo) — matching what
32
+ the wire appliers actually send (nothing) instead of claiming "honored exactly"; the r3/r4
33
+ mirror comment now names all three arms. `resolveEffort` reads a malformed non-array
34
+ `reasoningEffortLevels` as undeclared (member-level filtering: garbage/sparse/"off" entries can
35
+ no longer become clamp targets) — a catalog typo no longer fails the whole thinking-on leg with
36
+ a raw TypeError (5.44.0 regression window closed before release).
37
+ - The #260 pins tell the bounded-teardown truth: `result()` settles after the harvest LEG settled
38
+ (committed, or its 15s timeout REPORTED — never silent), the discriminative arm polls 50 ticks
39
+ (the abandon arm sat inside the old 1-tick slack), and the incident-scale argument is stated.
40
+ - `reasoning.resolved` now fires a LOUD DROP frame (`dropped: true`, `effective: "off"`) on
41
+ models whose `Model.reasoning` is falsy — a user-requested effort tier no longer evaporates
42
+ silently (#325). The public `resolveReasoning` reports the drop arm for such models; the
43
+ capable arm is byte-identical. The capability predicate is truthiness (matching every adapter),
44
+ and `supportsReasoningEffort === false` mirrors the ungraded intent-only arm.
45
+
46
+ ### Fixed
47
+ - The two 5.44.0 pollution notices are DISPOSITIONED in the governance registry and the
48
+ error-surface cap vocabulary gained `inlineUntrusted`/`pollutionSentence` (both bounded by
49
+ contract) — landed as gate-family debts caught by the full-suite leg.
50
+
51
+ ### Notes
52
+ - Commit `2f78edb7` (the delegation-evidence knob) carries the `TaskResult.effectiveReasoning`
53
+ declaration hunk of the parallel effort car (shared-checkout staging race; HEAD is consistent).
54
+ Do not cherry-pick that commit alone.
55
+
3
56
  ## 5.44.0 — 2026-08-19
4
57
 
5
58
  ### Added
@@ -297,7 +297,9 @@ export declare function completedAgentCard(child: {
297
297
  * recorder aggregate at delivery. Present only when the parent chain armed the recorder; the
298
298
  * parent-side judgment (the content-origin wrap) reads it off this card — `clean` retires the
299
299
  * static face verdict for this call, `external` marks with the observed-event reason, `unknown`
300
- * (or absence) keeps the static floor. Deliberate whitelist admission (see the boundary note). */
300
+ * (or absence) keeps the static floor, whose mark action follows the parent run's deployment
301
+ * evidence standard (design/324, `RunnerDeps.memoryDelegationEvidence`). Deliberate whitelist
302
+ * admission (see the boundary note). */
301
303
  attestation?: import("../core/memory-engine/delegation-provenance.js").DelegationAttestation;
302
304
  }): Record<string, unknown>;
303
305
  /**
@@ -34,7 +34,20 @@ export type ReasoningFormat = "openai" | "openrouter" | "deepseek" | "together"
34
34
  /** Anthropic `output_config.effort` (CC 2.1.198 / Fable-5 class, design/119): the tier is a named
35
35
  * effort string, clamped to `compat.effortLevels` — the anthropic-side twin of the OpenAI
36
36
  * `reasoning_effort` path (thinking FORM is `thinkingMode`; effort carries the intensity). */
37
- | "effort";
37
+ | "effort"
38
+ /** OpenAI Responses API (`openai-responses` / `azure-openai-responses`, design/163): the tier rides
39
+ * the NESTED `reasoning: { effort }` request field (open-responses.ts `resolveWireEffort`), clamped
40
+ * by the same {@link resolveEffort} against `compat.reasoningEffortLevels` — NOT the completions
41
+ * top-level `reasoning_effort` the `"openai"` tag names. #325 review r3: before this member the
42
+ * resolver fell through to the completions default and labeled Responses resolutions `"openai"`. */
43
+ | "responses";
44
+ /**
45
+ * The Open Responses API family — every `Api` value `createOpenResponsesBrain` serves (design/163).
46
+ * The SINGLE family discriminant: request-params.ts (reserved-key set selection) and
47
+ * {@link resolveReasoning} (format dispatch, #325 review r4) both key off THIS set, so a new
48
+ * Responses api value added here reaches every family-scoped behavior instead of one of two twins.
49
+ */
50
+ export declare const RESPONSES_APIS: ReadonlySet<string>;
38
51
  /** How a requested intensity resolved against one endpoint's real capability. */
39
52
  export interface ReasoningResolution {
40
53
  /** The level the caller asked for. */
@@ -43,9 +56,10 @@ export interface ReasoningResolution {
43
56
  effective: ThinkingLevel;
44
57
  /**
45
58
  * True when the endpoint varies behavior by intensity (effort tiers / token budget). False = binary
46
- * enable-only (qwen / zai / qwen-chat-template, or an effort endpoint that declares
47
- * `supportsReasoningEffort:false`): thinking is on or off and the tier is NOT honored read this alongside
48
- * {@link effective}, which then echoes the request as intent only.
59
+ * enable-only (qwen / zai / qwen-chat-template, an effort endpoint that declares
60
+ * `supportsReasoningEffort:false`, or a tier `Model.thinkingLevelMap` maps to `null`the wire then
61
+ * carries NO effort value for it): thinking is on or off and the tier is NOT honored — read this
62
+ * alongside {@link effective}, which then echoes the request as intent only.
49
63
  */
50
64
  graded: boolean;
51
65
  /** True when {@link effective} differs from {@link requested} (the request couldn't be honored exactly). */
@@ -62,6 +76,23 @@ export interface ResolvedReasoning extends ReasoningResolution {
62
76
  format: ReasoningFormat;
63
77
  /** A coarse endpoint label for the trace (`model.api` — e.g. `openai-completions`, `anthropic-messages`). */
64
78
  endpoint: string;
79
+ /**
80
+ * Present (true) only when the model declares NO reasoning capability (`Model.reasoning` falsy): every
81
+ * brain early-returns on that flag (openai.ts applyThinking / anthropic.ts / open-responses.ts
82
+ * resolveWireEffort), so NO thinking
83
+ * parameter reaches the wire at all — the requested tier is DROPPED entirely, not clamped or
84
+ * downgraded-to-binary. `effective:"off"` here states the ENGINE side of that fact (nothing was
85
+ * requested), NOT a measured gateway state: on the binary enable-only formats (qwen / zai /
86
+ * qwen-chat-template) parameter ABSENCE is provider-default, and a default-on gateway may still
87
+ * reason at its own tier — the brains deliberately send no disable key for a model declared
88
+ * non-reasoning, so the engine cannot claim a measured off there; `format` tells a consumer which
89
+ * family applies (on every other format, absence is the closest the wire has to off — see
90
+ * applyThinking's own contract). `format`/`endpoint` report the family that WOULD have carried the
91
+ * tier (the why-shape a consumer diagnoses with, not a claim about the request). Absent on every
92
+ * reasoning-capable resolution, so existing consumers see byte-identical objects. Loud-bad-values
93
+ * rule: the one arm where the request evaporates must not be the one arm with no report.
94
+ */
95
+ dropped?: true;
65
96
  }
66
97
  /**
67
98
  * Resolve a requested intensity against a model END-TO-END, dispatching on its wire format the SAME way the
@@ -74,6 +105,17 @@ export interface ResolvedReasoning extends ReasoningResolution {
74
105
  * - Binary enable-only formats (qwen / zai / qwen-chat-template) → `graded:false` (tier not honored).
75
106
  * - An effort endpoint with `supportsReasoningEffort:false` → `graded:false` (enable key only, no effort tier).
76
107
  * - Otherwise effort-based → clamp DOWN to the endpoint's `reasoningEffortLevels` (default minimal|low|medium|high).
108
+ * - A CLAMPED tier that `model.thinkingLevelMap` maps to `null` (declared unsupported on this model) →
109
+ * `graded:false`: both effort-wire appliers key the map by the post-clamp tier and then carry NO effort
110
+ * value (thinking still enables via the format's own enable key where one exists), so the tier is not
111
+ * honored. Previously this resolver never read the map and reported such a request as exactly honored
112
+ * (`graded:true`, `clamped:false`) while the wire dropped the value — trace/result-face drift.
113
+ *
114
+ * - A model whose `reasoning` capability flag is FALSY drops the request ENTIRELY (neither brain emits any
115
+ * thinking parameter, whatever the format) → `effective:"off"`, `graded:false`, `clamped:true`,
116
+ * `dropped:true` — the loud-drop arm. Previously this resolver described the capability dispatch for such
117
+ * a model (a resolution the request never carried), and the runner's trace guard skipped the frame — the
118
+ * one arm where the request evaporates was the one arm with no disclosure.
77
119
  *
78
120
  * `off`/falsy never enables thinking, so it resolves trivially (no clamp, graded:true) — the caller decides
79
121
  * whether to emit at all.
@@ -82,12 +124,16 @@ export declare function resolveReasoning(requested: ThinkingLevel, model: {
82
124
  api?: string;
83
125
  reasoning?: boolean;
84
126
  compat?: unknown;
127
+ thinkingLevelMap?: Readonly<Partial<Record<ThinkingLevel, string | null>>>;
85
128
  }): ResolvedReasoning;
86
129
  /**
87
130
  * Resolve a requested intensity for an effort-based endpoint (`reasoning_effort` / `reasoning.effort`). Picks
88
131
  * the requested tier when supported; otherwise the highest supported tier ≤ requested (clamp DOWN, never
89
132
  * silently up); if none is ≤ requested (the endpoint's set is entirely higher — unusual), the lowest supported
90
133
  * tier. Never returns a value outside `allowed`, so the brain can't emit a tier the endpoint would 422.
134
+ * A malformed `allowed` reads as UNDECLARED → the conservative default set (non-array, empty, or no
135
+ * valid member); individually invalid MEMBERS (garbage strings, sparse holes, `"off"` — not a legal
136
+ * member of the 6-tier compat effort sets) are dropped, so they can never become a clamp target.
91
137
  */
92
138
  export declare function resolveEffort(requested: ThinkingLevel, allowed?: readonly ThinkingLevel[]): ReasoningResolution;
93
139
  /**
@@ -15,7 +15,18 @@ export function rankOf(level) {
15
15
  }
16
16
  export const DEFAULT_EFFORT_LEVELS = ["minimal", "low", "medium", "high"];
17
17
  const BINARY_FORMATS = new Set(["qwen", "zai", "qwen-chat-template"]);
18
+ export const RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses", "openai-chatgpt-responses"]);
18
19
  export function resolveReasoning(requested, model) {
20
+ const resolved = dispatchReasoning(requested, model);
21
+ if (requested !== "off" && !model.reasoning) {
22
+ return { requested, effective: "off", graded: false, clamped: true, format: resolved.format, endpoint: resolved.endpoint, dropped: true };
23
+ }
24
+ return resolved;
25
+ }
26
+ function effortTierUnmapped(requested, effective, model) {
27
+ return requested !== "off" && model.thinkingLevelMap?.[effective] === null;
28
+ }
29
+ function dispatchReasoning(requested, model) {
19
30
  const endpoint = model.api ?? "unknown";
20
31
  const compat = (model.compat ?? {});
21
32
  if (model.api === "anthropic-messages") {
@@ -24,6 +35,16 @@ export function resolveReasoning(requested, model) {
24
35
  }
25
36
  return { requested, effective: requested, graded: true, clamped: false, format: "budget", endpoint };
26
37
  }
38
+ if (model.api !== undefined && RESPONSES_APIS.has(model.api)) {
39
+ if (compat.supportsReasoningEffort === false) {
40
+ return { requested, effective: requested, graded: false, clamped: false, format: "responses", endpoint };
41
+ }
42
+ const responsesResolved = resolveEffort(requested, compat.reasoningEffortLevels ?? DEFAULT_EFFORT_LEVELS);
43
+ if (effortTierUnmapped(requested, responsesResolved.effective, model)) {
44
+ return { requested, effective: requested, graded: false, clamped: false, format: "responses", endpoint };
45
+ }
46
+ return { ...responsesResolved, format: "responses", endpoint };
47
+ }
27
48
  const format = compat.thinkingFormat ?? "openai";
28
49
  if (BINARY_FORMATS.has(format)) {
29
50
  return { ...resolveBinary(requested), format, endpoint };
@@ -32,15 +53,15 @@ export function resolveReasoning(requested, model) {
32
53
  if (!supportsEffort && format !== "openrouter") {
33
54
  return { requested, effective: requested, graded: false, clamped: false, format, endpoint };
34
55
  }
35
- return {
36
- ...resolveEffort(requested, compat.reasoningEffortLevels ?? DEFAULT_EFFORT_LEVELS),
37
- format,
38
- endpoint,
39
- };
56
+ const resolved = resolveEffort(requested, compat.reasoningEffortLevels ?? DEFAULT_EFFORT_LEVELS);
57
+ if (effortTierUnmapped(requested, resolved.effective, model)) {
58
+ return { requested, effective: requested, graded: false, clamped: false, format, endpoint };
59
+ }
60
+ return { ...resolved, format, endpoint };
40
61
  }
41
62
  export function resolveEffort(requested, allowed = DEFAULT_EFFORT_LEVELS) {
42
- if (allowed.length === 0)
43
- allowed = DEFAULT_EFFORT_LEVELS;
63
+ const declared = Array.isArray(allowed) ? allowed.filter((lvl) => isThinkingLevel(lvl) && lvl !== "off") : [];
64
+ allowed = declared.length > 0 ? declared : DEFAULT_EFFORT_LEVELS;
44
65
  if (requested === "off" || allowed.includes(requested)) {
45
66
  return { requested, effective: requested, graded: true, clamped: false };
46
67
  }
@@ -1,15 +1,3 @@
1
- /**
2
- * Request-parameter passthrough (design/40). A deployment can set official provider params it wants but
3
- * core doesn't model as first-class fields (OpenAI `frequency_penalty`/`presence_penalty`/`top_p`/`seed`/
4
- * `logit_bias`; Anthropic `top_k`/`top_p`/`stop_sequences`; …) via `Model.extraBody`, and core merges them
5
- * into the brain's request body. Anchored at the **Model** level (not per-turn) so the serialized body
6
- * stays byte-stable across turns → the prefix cache holds (design/9/31).
7
- *
8
- * Symmetric: each brain owns its structural + tuning keys (`reserved`) and ALWAYS wins — `extraBody` can
9
- * only supply the long tail, never clobber `messages`/`tools`/`temperature`/etc. Stripping is the safety
10
- * enforcement (the brains are standalone-usable); `prepareTask` separately warns on a collision (it has
11
- * `onError`, the brain doesn't — council design/40).
12
- */
13
1
  /** OpenAI request-body keys core owns; `extraBody` cannot override these. */
14
2
  export declare const OPENAI_RESERVED: ReadonlySet<string>;
15
3
  /** Anthropic request-body keys core owns; `extraBody` cannot override these. (Note the asymmetry vs
@@ -1,3 +1,4 @@
1
+ import { RESPONSES_APIS } from "./reasoning.js";
1
2
  export const OPENAI_RESERVED = new Set([
2
3
  "model",
3
4
  "messages",
@@ -38,7 +39,6 @@ export const RESPONSES_RESERVED = new Set([
38
39
  "previous_response_id",
39
40
  "conversation",
40
41
  ]);
41
- const RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses", "openai-chatgpt-responses"]);
42
42
  export function reservedFor(api) {
43
43
  if (RESPONSES_APIS.has(api))
44
44
  return RESPONSES_RESERVED;
@@ -16,6 +16,7 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
16
16
  "memory.pollution_mark_failed",
17
17
  "memory.session_polluted",
18
18
  "memory.harvest_quarantined",
19
+ "memory.delegation_static_mark_waived",
19
20
  "memory.challenge_sweep_failed",
20
21
  "memory.lineage_settle_failed",
21
22
  "memory.challenge_ledger_corrupt",
@@ -66,9 +66,12 @@ export interface AgentPoolTool {
66
66
  * A child that can reach a network or protocol tool returns content from outside the trust boundary
67
67
  * just as surely as calling that tool in this session would, and the parent's transcript is where it
68
68
  * lands. The judgment is STATIC — the child's face is known from its definition before the call — so
69
- * nothing here tracks what the child actually did (runtime provenance is the v2 ticket). That makes
70
- * it conservative in the safe direction: a child that COULD reach external content counts as having
71
- * done so.
69
+ * nothing here tracks what the child actually did (runtime provenance is design/180 half A). That
70
+ * makes it conservative in the safe direction: a child that COULD reach external content counts as
71
+ * having done so. Whether this verdict MARKS the judging session's memory when no attestation
72
+ * arrives is the deployment's evidence standard (design/324,
73
+ * `RunnerDeps.memoryDelegationEvidence`) — the classification itself is value-independent, and the
74
+ * wrap computes it on every standard (the waiver announcement keys on it).
72
75
  *
73
76
  * `pool` is the CHILD'S pool, not the parent's roster: a delegation tool carries its own tool set, and
74
77
  * the two are explicitly allowed to differ — a parent mounting nothing but the delegation tool can
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * design/180 half A — delegation RUNTIME provenance: the three-value attestation and its monotonic
3
3
  * aggregate. The static tool-face judgment ({@link import("./content-origin.js").delegationCallIsExternal})
4
- * stays the PERMANENT FLOOR: runtime evidence may only retire the static verdict when a COMPLETE
5
- * clean proof is in hand; any evidence gap falls back to the floor. "We could not prove it clean"
6
- * and "it is clean" are different answers — replacing the floor outright would fail OPEN on every
7
- * evidence gap (old child vintages, crashes, un-armed sessions).
4
+ * stays the FLOOR of the attestation reduction: runtime evidence may only retire the static verdict
5
+ * when a COMPLETE clean proof is in hand; any evidence gap falls back to the floor. "We could not
6
+ * prove it clean" and "it is clean" are different answers — replacing the floor outright would fail
7
+ * OPEN on every evidence gap (old child vintages, crashes, un-armed sessions). Whether the floor's
8
+ * verdict MARKS the judging session's memory is a separate, deployment-governed question
9
+ * (design/324, `RunnerDeps.memoryDelegationEvidence`): the reduction semantics here never change
10
+ * with that key — only the mark action at the judging run's wrap does.
8
11
  *
9
12
  * The aggregate is two MONOTONIC bits plus versioning:
10
13
  * - `sawExternal` — an actually-observed polluting-class invocation (own recorder, or a delivered
@@ -17,8 +20,9 @@
17
20
  * "clean" unprovable; it can never make an observed event disappear).
18
21
  */
19
22
  /** The three-value delegation attestation. Only `clean` changes behavior (the parent skips the
20
- * static mark); `unknown` is byte-equivalent to absence (static floor governs); `external` marks
21
- * unconditionally, with the observed-event reason. */
23
+ * static mark); `unknown` is byte-equivalent to absence (the static floor governs its mark
24
+ * action subject to the judging run's deployment evidence standard, design/324); `external` marks
25
+ * unconditionally on every evidence standard, with the observed-event reason. */
22
26
  export type DelegationAttestation = "clean" | "external" | "unknown";
23
27
  /** The monotonic per-run provenance aggregate (in-process this slice; the durable-checkpoint
24
28
  * persistence leg rides the background-face slice — absence after a resume reads `incomplete`,
@@ -58,5 +62,6 @@ export declare function reduceDelegationAttestation(agg: DelegationProvenanceAgg
58
62
  completed: boolean;
59
63
  }): DelegationAttestation;
60
64
  /** Read a delivered card's attestation field (the explicit-whitelist handback seat). Anything but
61
- * the three spellings — including absence — reads as undefined, i.e. the static floor governs. */
65
+ * the three spellings — including absence — reads as undefined, i.e. the static floor governs
66
+ * (whether that floor MARKS is the judging run's deployment evidence standard, design/324). */
62
67
  export declare function readCardAttestation(details: unknown): DelegationAttestation | undefined;
@@ -133,6 +133,11 @@ export interface ResultFlags {
133
133
  * read-posture seats above: present iff prepare completed (the memory-less states are their own
134
134
  * values — absence means only "prepare never ran to completion"). */
135
135
  effectiveMemoryScopes?: TaskResult["effectiveMemoryScopes"];
136
+ /** #327 — the leg's effective reasoning resolution, echoed on `TaskResult.effectiveReasoning`.
137
+ * Pure pass-through on every terminal (same law as the read-posture seats): the SAME object the
138
+ * `reasoning.resolved` trace frame carried (runtask resolves once per leg — two faces, one mint);
139
+ * absent when thinking was off/unset for the leg, so the key is omitted. */
140
+ effectiveReasoning?: TaskResult["effectiveReasoning"];
136
141
  /** ruled 2026-08-04 — the usage-governance wait hint carried by the platform terminal the run adopted
137
142
  * (`undefined` for every other cause: an expiring environment has no return time to give, and a store
138
143
  * failure is not a window). Passed as data rather than read back off `threw` so the seat has a typed
@@ -165,5 +165,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
165
165
  void _internalCompaction;
166
166
  if (flags.unpricedSpend)
167
167
  delete publicStats.costMicroUsd;
168
- return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, ...(checkpointId !== undefined ? { checkpointId } : {}), checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), stats: publicStats };
168
+ return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, ...(checkpointId !== undefined ? { checkpointId } : {}), checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
169
169
  }
@@ -144,6 +144,12 @@ export interface PrepareConfigDoorsResult {
144
144
  * material; the in-force arm never lands here — it throws at the door). */
145
145
  discardedEnvRaw: string | undefined;
146
146
  };
147
+ /** owned — the NORMALIZED per-leg delegation-evidence standard (design/324): the deps seat read
148
+ * ONCE, in this synchronous pre-await stretch, screened, and absent folded to the "static-face"
149
+ * default. The delegation wrap consumes THIS value, never the live deps object — a deps seat
150
+ * mutated (or getter-backed) after the door must not present an unscreened value to the mark
151
+ * branch (the #245 5.33 read-once posture). */
152
+ memoryDelegationEvidence: "static-face" | "attested-only";
147
153
  /** owned — validated deployment governance windows (undefined = ungoverned). */
148
154
  usageWindows: readonly UsageWindow[] | undefined;
149
155
  /** owned, out-param cell — created EMPTY here; the brain-call wiring later installs into
@@ -132,6 +132,22 @@ export function prepareConfigDoors(input) {
132
132
  }
133
133
  assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
134
134
  assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
135
+ const memoryDelegationEvidenceRaw = deps.memoryDelegationEvidence;
136
+ if (memoryDelegationEvidenceRaw !== undefined && memoryDelegationEvidenceRaw !== "static-face" && memoryDelegationEvidenceRaw !== "attested-only") {
137
+ const evidence = memoryDelegationEvidenceRaw;
138
+ const got = typeof evidence === "string"
139
+ ? JSON.stringify(evidence.length > 64 ? `${evidence.slice(0, 64)}…` : evidence)
140
+ : evidence === null
141
+ ? "null"
142
+ : Array.isArray(evidence)
143
+ ? "an array"
144
+ : typeof evidence;
145
+ const e = new Error(`RunnerDeps.memoryDelegationEvidence must be "static-face" or "attested-only" when present (got ${got}) — ` +
146
+ `an unevaluable evidence standard is refused loudly, never folded to either standard.`);
147
+ e.code = "config.memory_delegation_evidence";
148
+ throw e;
149
+ }
150
+ const memoryDelegationEvidence = memoryDelegationEvidenceRaw === "attested-only" ? "attested-only" : "static-face";
135
151
  if (spec.resumeAtMode !== undefined) {
136
152
  if (spec.resumeAt === undefined) {
137
153
  const e = new Error(`resumeAtMode "${spec.resumeAtMode}" requires resumeAt (there is no branch target to position against)`);
@@ -294,6 +310,7 @@ export function prepareConfigDoors(input) {
294
310
  compModel,
295
311
  fableMitigations,
296
312
  modelGate,
313
+ memoryDelegationEvidence,
297
314
  usageWindows,
298
315
  brainCallGuardrailRef,
299
316
  brainCallGuardrailMs,
@@ -1092,7 +1092,9 @@ export interface RunInternals {
1092
1092
  * it); `contentSafety` is the chain's FROZEN classification snapshot — the child may narrow it
1093
1093
  * with its own config, never widen (design/180 A-2). Trusted internals chain only, same posture as
1094
1094
  * {@link inheritedGate}. Absent ⇒ the child records nothing (its deliveries then read `unknown`,
1095
- * and every judgment falls back to the static floor — fail-closed by construction).
1095
+ * and every judgment falls back to the static floor — fail-closed by construction; whether the
1096
+ * floor's verdict MARKS the judging session follows that run's deployment evidence standard,
1097
+ * {@link RunnerDeps.memoryDelegationEvidence}).
1096
1098
  */
1097
1099
  delegationProvenance?: {
1098
1100
  ref: {
@@ -333,7 +333,7 @@ function cwdConflictsRestoreError(requestedCwd) {
333
333
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
334
334
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
335
335
  spec = doors.spec;
336
- const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
336
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
337
337
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
338
338
  const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
339
339
  let shellGatedBash = false;
@@ -1855,6 +1855,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1855
1855
  const delegationProvenanceChannel = internals?.delegationProvenance;
1856
1856
  if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
1857
1857
  const pollution = memoryEngineSession?.pollution;
1858
+ const delegationEvidenceAttestedOnly = memoryDelegationEvidence === "attested-only";
1859
+ let staticMarkWaiverAnnounced = false;
1858
1860
  const effectiveSafety = narrowContentSafety(delegationProvenanceChannel?.contentSafety, memoryEngineSession?.contentSafety);
1859
1861
  const trustedTools = effectiveSafety.trustedTools;
1860
1862
  const execIsExternalContent = effectiveSafety.execIsExternalContent;
@@ -1934,8 +1936,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1934
1936
  }
1935
1937
  else if (att !== "clean") {
1936
1938
  if (external) {
1937
- mark(`tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
1938
- (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)"));
1939
+ const staticFaceReason = `tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
1940
+ (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)");
1941
+ if (!delegationEvidenceAttestedOnly) {
1942
+ mark(staticFaceReason);
1943
+ }
1944
+ else if (pollution !== undefined && !staticMarkWaiverAnnounced) {
1945
+ staticMarkWaiverAnnounced = true;
1946
+ const waivedReason = inlineUntrusted(staticFaceReason, 200);
1947
+ deliverEngineNotice(deps.onNotice, {
1948
+ code: "memory.delegation_static_mark_waived",
1949
+ message: `Delegation static-face pollution mark waived: ${waivedReason}. The deployment configured ` +
1950
+ `memoryDelegationEvidence="attested-only", so a static tool-face verdict alone does not mark this ` +
1951
+ `session's memory polluted; a delivered "external" attestation and a direct polluting-class ` +
1952
+ `invocation still mark, and on a provenance-armed chain this delegation still records as ` +
1953
+ `incomplete. At most one notice per prepared task leg.`,
1954
+ detail: {
1955
+ reason: waivedReason,
1956
+ ...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
1957
+ sessionId,
1958
+ },
1959
+ });
1960
+ }
1939
1961
  }
1940
1962
  recordIncomplete();
1941
1963
  }
@@ -1661,6 +1661,7 @@ export class Runner {
1661
1661
  errorCode: code,
1662
1662
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1663
1663
  ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
1664
+ ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
1664
1665
  ...(() => {
1665
1666
  const hinted = err.retryAfterMs;
1666
1667
  return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
@@ -2468,23 +2469,24 @@ export class Runner {
2468
2469
  ts: Date.now(),
2469
2470
  }));
2470
2471
  }
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
- });
2472
+ const reasoningResolution = prepared.thinking && prepared.thinking !== "off" ? resolveReasoning(prepared.thinking, prepared.model) : undefined;
2473
+ if (taskIdRef && reasoningResolution !== undefined)
2474
+ taskIdRef.effectiveReasoning = reasoningResolution;
2475
+ if (reasoningResolution !== undefined) {
2476
+ emitTrace(rs.telemetry.tracer, () => ({
2477
+ kind: "reasoning.resolved",
2478
+ version: 1,
2479
+ taskId: rs.telemetry.taskId,
2480
+ model: prepared.model.id,
2481
+ requested: reasoningResolution.requested,
2482
+ effective: reasoningResolution.effective,
2483
+ graded: reasoningResolution.graded,
2484
+ clamped: reasoningResolution.clamped,
2485
+ format: reasoningResolution.format,
2486
+ endpoint: reasoningResolution.endpoint,
2487
+ ...(reasoningResolution.dropped === true ? { dropped: true } : {}),
2488
+ ts: Date.now(),
2489
+ }));
2488
2490
  }
2489
2491
  const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
2490
2492
  const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
@@ -3458,6 +3460,7 @@ export class Runner {
3458
3460
  effectiveReadFace: prepared.effectiveReadFace,
3459
3461
  effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
3460
3462
  effectiveMemoryScopes: prepared.effectiveMemoryScopes,
3463
+ effectiveReasoning: reasoningResolution,
3461
3464
  retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
3462
3465
  abortedForTimeout: timeout.fired,
3463
3466
  abortedForTurns: rs.limits.turnsExceeded,
@@ -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;
@@ -846,7 +846,9 @@ export interface ToolExecuteContext {
846
846
  * content-safety snapshot — the tool then mints the child's recorder ref and threads both into
847
847
  * the child's trusted `RunInternals.delegationProvenance`. Undefined return / absent field ⇒ the
848
848
  * child spawns without a recorder (its deliveries read `unknown` and every judgment stays on the
849
- * static floor — v1 behavior byte-identical). Same trust posture as
849
+ * static floor — whose MARK action follows the judging run's deployment evidence standard,
850
+ * {@link RunnerDeps.memoryDelegationEvidence}; under the `"static-face"` default this is v1
851
+ * behavior byte-identical). Same trust posture as
850
852
  * {@link inheritedGateForChildren}: never a model/tool argument, never a TaskSpec field.
851
853
  */
852
854
  delegationProvenanceForChildren?: () => import("./memory-engine/delegation-provenance.js").DelegationContentSafety | undefined;
@@ -2839,6 +2841,29 @@ export interface TaskResult {
2839
2841
  * `TaskSpec.model` ref (which may be a role / name / `@mention`). Lets a UI echo "served by X" instead of the
2840
2842
  * requested ref. A mid-run degradation is observed separately (see the degraded-model fields). */
2841
2843
  model?: string;
2844
+ /**
2845
+ * #327 — the leg's effective REASONING resolution: the result-face twin of {@link model} for the thinking
2846
+ * knob. How the requested tier resolved against the serving model's real capability
2847
+ * (`requested`/`effective`/`graded`/`clamped`/`format`/`endpoint`, plus `dropped:true` when a
2848
+ * non-reasoning model dropped the request entirely — field semantics on
2849
+ * {@link import("../brain/reasoning.js").ResolvedReasoning}). It is the SAME resolver output the
2850
+ * `reasoning.resolved` trace frame carries, computed once per leg — the two faces cannot tell different
2851
+ * stories; this seat serves consumers without a tracer (the trace frame is the deployment-observability
2852
+ * face, this is the caller face).
2853
+ *
2854
+ * **In-presence condition** — mirrors the trace frame exactly: present on every terminal of a leg that ran
2855
+ * with a REQUESTED thinking tier other than off/unset (the `spec > role > model.defaultThinking` chain);
2856
+ * absent when thinking was off/unset for the leg, and on prepare failures (the resolution is minted after
2857
+ * prepare). On a resumed task each leg re-resolves against the leg's own serving model.
2858
+ *
2859
+ * **Degradation law** — same as {@link model}, whose resolution this is: the seat describes the leg's
2860
+ * PRIMARY serving model at leg entry. A mid-run degradation (reactive fallback / near-budget switch)
2861
+ * changes the serving model WITHOUT re-minting this seat or its trace twin — read {@link degraded} to see
2862
+ * the switch; the fallback's own reasoning capability is deliberately NOT re-reported here (re-resolving
2863
+ * one face would desync it from the task-start `reasoning.resolved` frame). A consumer needing the
2864
+ * fallback's reasoning posture resolves `degraded.to` itself (`resolveReasoning` is exported).
2865
+ */
2866
+ effectiveReasoning?: import("../brain/reasoning.js").ResolvedReasoning;
2842
2867
  /** Final assistant text. */
2843
2868
  result: string;
2844
2869
  /**
@@ -4710,6 +4735,22 @@ export interface EngineNotice {
4710
4735
  * time — the withheld count stays true either way). Registered gap: the derived index (`MEMORY.md`) is
4711
4736
  * contained on a path that mints no rejection row, so an index-ONLY containment produces no
4712
4737
  * notice and is disclosed by the harvest report's warnings alone.
4738
+ * - `"memory.delegation_static_mark_waived"` (design/324, #324 ruling ①) — the deployment set
4739
+ * {@link RunnerDeps.memoryDelegationEvidence} to `"attested-only"` and a delegation call whose
4740
+ * STATIC tool-face verdict would have marked this session's memory polluted (attestation
4741
+ * missing/unknown + face can reach external content) was not marked: the waiver is announced —
4742
+ * the explicit dual of `"memory.session_polluted"` for this arm, so the arm has a voice where a
4743
+ * mark used to land. MINT-side guarantee only: delivery rides the shared guarded form
4744
+ * ({@link deliverEngineNotice} — a wired sink that throws owns that loss, exactly as at every
4745
+ * de-duplicating station), and the leg latch is consumed at mint. Minted only when a pollution
4746
+ * face is mounted (a
4747
+ * recorder-only child's mark is a no-op — nothing is waived there), at most ONCE PER PREPARED
4748
+ * TASK LEG (a resume leg may announce again — a new leg's audit stream is a new fact;
4749
+ * deliberately no durable once-per-session state), never on the `"static-face"` default, and
4750
+ * never for a delivered `"external"` attestation (that mark still lands);
4751
+ * `detail: { reason, subagentType?, sessionId? }` — `reason` is the same sentence the waived
4752
+ * mark would have carried, neutralized/length-bounded (tool and agent-type names are
4753
+ * host/model-controlled inputs).
4713
4754
  *
4714
4755
  * Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
4715
4756
  * transient network failure being retried). Those are per-attempt liveness frames with their own
@@ -4858,6 +4899,36 @@ export interface RunnerDeps {
4858
4899
  * bypasses the gate as always — the human already adjudicated it).
4859
4900
  */
4860
4901
  writeProtectedPaths?: readonly import("./write-protect.js").WriteProtectedEntry[];
4902
+ /**
4903
+ * design/324 (#324 ruling ① containment) — the EVIDENCE STANDARD the delegation arm of the
4904
+ * content-origin wrap applies when deciding whether a delegation call marks THIS session's memory
4905
+ * polluted (design/178 §3 / design/180 half A):
4906
+ * - `"static-face"` (absent ≡ this; the default) — today's behavior, byte-identical: a delegation
4907
+ * whose delivered attestation is missing/unknown and whose static tool face can reach external
4908
+ * content marks the session (the capability over-approximation: possibility counts as exposure).
4909
+ * - `"attested-only"` — exactly that ONE static-face mark is waived, and each prepared leg
4910
+ * announces the first waiver (`"memory.delegation_static_mark_waived"`). Everything else is
4911
+ * unchanged: a delivered `"external"` attestation still marks, the chain's `incomplete`
4912
+ * recording still happens, a non-delegation polluting-class tool still marks, and a delegation
4913
+ * tool that is ITSELF classified polluting still marks pre-call.
4914
+ * ACCEPTED COST (the deployment's to own, stated as mechanism, not as absence of risk): under
4915
+ * `"attested-only"` a BACKGROUND child's real external contact does not mark this session — its
4916
+ * content re-enters through the TaskOutput result, the task-notification injection, or the
4917
+ * AgentTranscript step summaries, none of which carries an attestation — and a foreground child
4918
+ * that ended abnormally (crash/salvage) is likewise not marked on its face alone (the chain still
4919
+ * records `incomplete`). Already-marked sessions are never retroactively cleaned; the key only
4920
+ * governs NEW marks.
4921
+ * DEPLOYMENT seat ONLY (same posture as {@link readDenyBuiltinTiers}): deliberately no TaskSpec
4922
+ * twin and not in the governed workflow whitelist — a task author or governed script gets no
4923
+ * channel to loosen the evidence standard below its deployment. Not frozen into checkpoints: a
4924
+ * resumed leg follows the CURRENT deployment configuration. Each run reads the deps of the Runner
4925
+ * that PREPARES it — a multi-runner assembly should configure every runner with the same value
4926
+ * (drift is the deployment's own configuration hazard; recorder/attestation semantics are
4927
+ * value-independent, so the chain's evidence quality never varies with this key). Any other value
4928
+ * refuses loudly at prepare (`config.memory_delegation_evidence`, #123 — exact spellings only,
4929
+ * never truthiness).
4930
+ */
4931
+ memoryDelegationEvidence?: "static-face" | "attested-only";
4861
4932
  /**
4862
4933
  * design/199 件A — the DEPLOYMENT's read-face declaration
4863
4934
  * ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.44.0",
3
+ "version": "5.45.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",