harnery 0.34.0 → 0.35.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 (212) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +2 -0
  3. package/dist/commands/agents.d.ts +15 -4
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +91 -162
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +26 -2
  8. package/dist/commands/decision.d.ts.map +1 -1
  9. package/dist/commands/decision.js +2 -2
  10. package/dist/commands/events.d.ts.map +1 -1
  11. package/dist/commands/events.js +18 -1
  12. package/dist/commands/presence.d.ts.map +1 -1
  13. package/dist/commands/presence.js +2 -2
  14. package/dist/commands/semantic.d.ts +4 -0
  15. package/dist/commands/semantic.d.ts.map +1 -0
  16. package/dist/commands/semantic.js +147 -0
  17. package/dist/commands/web.d.ts.map +1 -1
  18. package/dist/commands/web.js +17 -0
  19. package/dist/core/agents/canonical-emit.d.ts +3 -0
  20. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  21. package/dist/core/agents/canonical-emit.js +12 -0
  22. package/dist/core/agents/claim-path.d.ts +11 -0
  23. package/dist/core/agents/claim-path.d.ts.map +1 -0
  24. package/dist/core/agents/claim-path.js +22 -0
  25. package/dist/core/agents/coord-client.d.ts +29 -3
  26. package/dist/core/agents/coord-client.d.ts.map +1 -1
  27. package/dist/core/agents/coord-client.js +88 -9
  28. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  29. package/dist/core/agents/live-authority-v3.js +1 -3
  30. package/dist/core/agents/live-lifecycle-v3.js +3 -3
  31. package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
  32. package/dist/core/agents/live-observation-v3.js +2 -2
  33. package/dist/core/agents/render/prompt-context.js +12 -10
  34. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  35. package/dist/core/agents/rules/stop-hook.js +23 -11
  36. package/dist/core/agents/session-name-display.d.ts +2 -1
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +10 -3
  39. package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
  40. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
  41. package/dist/core/agents/state/heartbeat-reader.js +25 -0
  42. package/dist/core/agents/state/heartbeat-writer.d.ts +3 -48
  43. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  44. package/dist/core/agents/state/heartbeat-writer.js +16 -34
  45. package/dist/core/agents/state/live-coordination-view.d.ts +1 -1
  46. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  47. package/dist/core/agents/state/live-coordination-view.js +25 -1
  48. package/dist/core/agents/state/names.d.ts +9 -1
  49. package/dist/core/agents/state/names.d.ts.map +1 -1
  50. package/dist/core/agents/state/names.js +190 -182
  51. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  52. package/dist/core/agents/state/stale-sweep.js +23 -5
  53. package/dist/core/artifacts/constants.d.ts +5 -0
  54. package/dist/core/artifacts/constants.d.ts.map +1 -0
  55. package/dist/core/artifacts/constants.js +4 -0
  56. package/dist/core/artifacts/index.d.ts +3 -3
  57. package/dist/core/artifacts/index.d.ts.map +1 -1
  58. package/dist/core/artifacts/index.js +50 -7
  59. package/dist/core/events/v3/base-contract.d.ts +795 -0
  60. package/dist/core/events/v3/base-contract.d.ts.map +1 -1
  61. package/dist/core/events/v3/base-contract.js +33 -0
  62. package/dist/core/events/v3/capabilities.d.ts +11 -0
  63. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  64. package/dist/core/events/v3/capabilities.js +23 -0
  65. package/dist/core/events/v3/generated.d.ts +1 -1
  66. package/dist/core/events/v3/generated.js +1 -1
  67. package/dist/core/events/v3/index.d.ts +4 -3
  68. package/dist/core/events/v3/index.d.ts.map +1 -1
  69. package/dist/core/events/v3/index.js +4 -3
  70. package/dist/core/events/v3/latency.d.ts +31 -1
  71. package/dist/core/events/v3/latency.d.ts.map +1 -1
  72. package/dist/core/events/v3/latency.js +127 -3
  73. package/dist/core/events/v3/live-routing.d.ts +30 -0
  74. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  75. package/dist/core/events/v3/live-routing.js +109 -1
  76. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  77. package/dist/core/events/v3/producers/command-recorder.js +10 -1
  78. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  79. package/dist/core/events/v3/producers/coordination-recorder.js +14 -3
  80. package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
  81. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  82. package/dist/core/events/v3/producers/hook-base.js +82 -1
  83. package/dist/core/events/v3/producers/intake.d.ts +6 -0
  84. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  85. package/dist/core/events/v3/producers/intake.js +11 -0
  86. package/dist/core/events/v3/producers/recorder.d.ts +62 -0
  87. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  88. package/dist/core/events/v3/producers/recorder.js +654 -18
  89. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +51 -0
  90. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
  91. package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
  92. package/dist/core/events/v3/turn-telemetry.d.ts +9 -0
  93. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  94. package/dist/core/hooks/adapter/parse.d.ts +3 -0
  95. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  96. package/dist/core/hooks/adapter/parse.js +14 -0
  97. package/dist/core/hooks/adapter/runtime-telemetry.d.ts +119 -0
  98. package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
  99. package/dist/core/hooks/adapter/runtime-telemetry.js +568 -0
  100. package/dist/core/hooks/cli.js +114 -38
  101. package/dist/core/hooks/remediation-cap.d.ts +34 -0
  102. package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
  103. package/dist/core/hooks/remediation-cap.js +71 -0
  104. package/dist/core/hooks/resolve/transcript.d.ts +25 -9
  105. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  106. package/dist/core/hooks/resolve/transcript.js +35 -17
  107. package/dist/core/semantic/adapters.d.ts +34 -0
  108. package/dist/core/semantic/adapters.d.ts.map +1 -0
  109. package/dist/core/semantic/adapters.js +222 -0
  110. package/dist/core/semantic/contract.d.ts +588 -0
  111. package/dist/core/semantic/contract.d.ts.map +1 -0
  112. package/dist/core/semantic/contract.js +230 -0
  113. package/dist/core/semantic/evidence.d.ts +11 -0
  114. package/dist/core/semantic/evidence.d.ts.map +1 -0
  115. package/dist/core/semantic/evidence.js +262 -0
  116. package/dist/core/semantic/index.d.ts +10 -0
  117. package/dist/core/semantic/index.d.ts.map +1 -0
  118. package/dist/core/semantic/index.js +9 -0
  119. package/dist/core/semantic/once.d.ts +33 -0
  120. package/dist/core/semantic/once.d.ts.map +1 -0
  121. package/dist/core/semantic/once.js +293 -0
  122. package/dist/core/semantic/prompt.d.ts +12 -0
  123. package/dist/core/semantic/prompt.d.ts.map +1 -0
  124. package/dist/core/semantic/prompt.js +70 -0
  125. package/dist/core/semantic/scheduler.d.ts +16 -0
  126. package/dist/core/semantic/scheduler.d.ts.map +1 -0
  127. package/dist/core/semantic/scheduler.js +67 -0
  128. package/dist/core/semantic/service-status.d.ts +31 -0
  129. package/dist/core/semantic/service-status.d.ts.map +1 -0
  130. package/dist/core/semantic/service-status.js +84 -0
  131. package/dist/core/semantic/service.d.ts +51 -0
  132. package/dist/core/semantic/service.d.ts.map +1 -0
  133. package/dist/core/semantic/service.js +470 -0
  134. package/dist/core/semantic/storage.d.ts +71 -0
  135. package/dist/core/semantic/storage.d.ts.map +1 -0
  136. package/dist/core/semantic/storage.js +179 -0
  137. package/dist/core/semantic/validate.d.ts +16 -0
  138. package/dist/core/semantic/validate.d.ts.map +1 -0
  139. package/dist/core/semantic/validate.js +174 -0
  140. package/dist/lib/browser/client.d.ts +11 -0
  141. package/dist/lib/browser/client.d.ts.map +1 -1
  142. package/dist/lib/browser/client.js +13 -0
  143. package/dist/lib/browser/index.d.ts +1 -0
  144. package/dist/lib/browser/index.d.ts.map +1 -1
  145. package/dist/lib/browser/standalone-html.d.ts +25 -0
  146. package/dist/lib/browser/standalone-html.d.ts.map +1 -0
  147. package/dist/lib/browser/standalone-html.js +284 -0
  148. package/dist/lib/instructions/apply.d.ts.map +1 -1
  149. package/dist/lib/instructions/apply.js +1 -0
  150. package/dist/lib/instructions/templates.d.ts +2 -0
  151. package/dist/lib/instructions/templates.d.ts.map +1 -1
  152. package/dist/lib/instructions/templates.js +144 -4
  153. package/package.json +6 -1
  154. package/schemas/event-v3.schema.json +2758 -478
  155. package/src/commander.ts +2 -0
  156. package/src/commands/agents.ts +104 -152
  157. package/src/commands/browse.ts +25 -2
  158. package/src/commands/decision.ts +8 -2
  159. package/src/commands/events.ts +21 -1
  160. package/src/commands/presence.ts +7 -2
  161. package/src/commands/semantic.ts +179 -0
  162. package/src/commands/web.ts +15 -0
  163. package/src/core/agents/canonical-emit.ts +15 -0
  164. package/src/core/agents/claim-path.ts +23 -0
  165. package/src/core/agents/coord-client.ts +92 -8
  166. package/src/core/agents/live-authority-v3.ts +1 -4
  167. package/src/core/agents/live-lifecycle-v3.ts +8 -3
  168. package/src/core/agents/live-observation-v3.ts +7 -2
  169. package/src/core/agents/render/prompt-context.ts +13 -10
  170. package/src/core/agents/rules/stop-hook.ts +24 -11
  171. package/src/core/agents/session-name-display.ts +11 -3
  172. package/src/core/agents/state/heartbeat-reader.ts +69 -0
  173. package/src/core/agents/state/heartbeat-writer.ts +20 -84
  174. package/src/core/agents/state/live-coordination-view.ts +26 -1
  175. package/src/core/agents/state/names.ts +190 -182
  176. package/src/core/agents/state/stale-sweep.ts +25 -5
  177. package/src/core/artifacts/constants.ts +4 -0
  178. package/src/core/artifacts/index.ts +49 -7
  179. package/src/core/events/v3/base-contract.ts +39 -0
  180. package/src/core/events/v3/capabilities.ts +34 -0
  181. package/src/core/events/v3/generated.ts +1 -1
  182. package/src/core/events/v3/index.ts +21 -0
  183. package/src/core/events/v3/latency.ts +213 -2
  184. package/src/core/events/v3/live-routing.ts +120 -1
  185. package/src/core/events/v3/producers/command-recorder.ts +10 -1
  186. package/src/core/events/v3/producers/coordination-recorder.ts +17 -5
  187. package/src/core/events/v3/producers/hook-base.ts +115 -1
  188. package/src/core/events/v3/producers/intake.ts +18 -0
  189. package/src/core/events/v3/producers/recorder.ts +856 -21
  190. package/src/core/events/v3/runtime-telemetry-capabilities.ts +137 -0
  191. package/src/core/events/v3/turn-telemetry.ts +10 -0
  192. package/src/core/hooks/adapter/parse.ts +16 -0
  193. package/src/core/hooks/adapter/runtime-telemetry.ts +805 -0
  194. package/src/core/hooks/cli.ts +126 -41
  195. package/src/core/hooks/remediation-cap.ts +77 -0
  196. package/src/core/hooks/resolve/transcript.ts +49 -17
  197. package/src/core/semantic/adapters.ts +293 -0
  198. package/src/core/semantic/contract.ts +298 -0
  199. package/src/core/semantic/evidence.ts +336 -0
  200. package/src/core/semantic/index.ts +127 -0
  201. package/src/core/semantic/once.ts +410 -0
  202. package/src/core/semantic/prompt.ts +84 -0
  203. package/src/core/semantic/scheduler.ts +107 -0
  204. package/src/core/semantic/service-status.ts +118 -0
  205. package/src/core/semantic/service.ts +580 -0
  206. package/src/core/semantic/storage.ts +286 -0
  207. package/src/core/semantic/validate.ts +212 -0
  208. package/src/lib/browser/client.ts +21 -0
  209. package/src/lib/browser/index.ts +4 -0
  210. package/src/lib/browser/standalone-html.ts +312 -0
  211. package/src/lib/instructions/apply.ts +1 -0
  212. package/src/lib/instructions/templates.ts +149 -4
@@ -17,10 +17,18 @@ import { hostname } from "node:os";
17
17
  import { basename, join, resolve } from "node:path";
18
18
  import type { Adapter } from "../../../adapter.ts";
19
19
  import { extractBashCommand, type ParsedPayload } from "../../../hooks/adapter/parse.ts";
20
+ import {
21
+ discoverCodexSessionTranscript,
22
+ type RuntimeContextTelemetry,
23
+ type RuntimeTelemetryOptions,
24
+ readRuntimeContextTelemetry,
25
+ readRuntimeTuning,
26
+ } from "../../../hooks/adapter/runtime-telemetry.ts";
20
27
  import { fsyncParentDirectory } from "../../../workflow/durable-record.ts";
21
28
  import { acquireNoClobberLease } from "../../../workflow/workspaces/leases.ts";
29
+ import type { RuntimeAttestationV3Base } from "../base-contract.ts";
22
30
  import { buildEventV3 } from "../builder.ts";
23
- import { fingerprintV3, normalizeNativeIdV3 } from "../canonical.ts";
31
+ import { canonicalJsonV3, fingerprintV3, normalizeNativeIdV3 } from "../canonical.ts";
24
32
  import {
25
33
  type AdapterSignalV3,
26
34
  adapterCapabilityProfileDigestV3,
@@ -33,8 +41,19 @@ import { markObservedClockRegressionV3 } from "../clock-order.ts";
33
41
  import type { EventV3 } from "../contract.ts";
34
42
  import { type EventV3WriteMode, readEventV3ControlState } from "../control.ts";
35
43
  import { fingerprintContextV3 } from "../fingerprint-keys.ts";
36
- import { attestationIdV3, clockIdV3, delegationIdV3, generationIdV3, spanIdV3 } from "../ids.ts";
44
+ import {
45
+ attestationIdV3,
46
+ clockIdV3,
47
+ delegationIdV3,
48
+ eventIdV3,
49
+ generationIdV3,
50
+ spanIdV3,
51
+ } from "../ids.ts";
37
52
  import { readLedgerV3 } from "../reader.ts";
53
+ import {
54
+ effectiveRuntimeTelemetryCapabilitiesV3,
55
+ type RuntimeTelemetryCapabilitiesV3,
56
+ } from "../runtime-telemetry-capabilities.ts";
38
57
  import {
39
58
  closeSpanStateV3,
40
59
  type OpenSpanStateV3,
@@ -44,8 +63,10 @@ import {
44
63
  } from "../span-state.ts";
45
64
  import {
46
65
  type ContextMeasurementV3,
66
+ type ContextTelemetryProvenanceV3,
47
67
  extractTurnTelemetryV3,
48
68
  type TelemetryObservationV3,
69
+ type TurnTelemetryV3,
49
70
  } from "../turn-telemetry.ts";
50
71
  import { assertEventV3, validateEventV3 } from "../validate.ts";
51
72
  import {
@@ -74,6 +95,10 @@ const CLOSED_SPAN_TURN_RETENTION = 2;
74
95
  const CLOSED_SPAN_MEMORY_CAP = 512;
75
96
  const CLOSED_TURN_MEMORY_CAP = 16;
76
97
  const SPAN_SOFT_WATERMARK = 128;
98
+ const PENDING_RUNTIME_CONTEXT_CAP = 4;
99
+ const RUNTIME_CONTEXT_RETRY_LIMIT = 2;
100
+ /** Finalization is off the interactive hook path; briefly cover Codex's delayed transcript flush. */
101
+ const APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS = [0, 250, 250] as const;
77
102
  /**
78
103
  * Adapters whose turn-boundary recovery and mid-flight onboarding are enabled.
79
104
  * Kept in code, outside the digested capability profiles, so tuning recovery
@@ -117,6 +142,23 @@ interface PendingEventV3 {
117
142
  event: EventV3;
118
143
  }
119
144
 
145
+ interface TurnContextTargetV3 {
146
+ terminal_event_id: `evt_${string}`;
147
+ terminal_observed_at: string;
148
+ turn_id: `tid_${string}`;
149
+ run_id?: `run_${string}`;
150
+ workflow_id?: `wf_${string}`;
151
+ workflow_agent_id?: string;
152
+ }
153
+
154
+ interface PendingRuntimeContextV3 extends TurnContextTargetV3 {
155
+ native_session_id: string;
156
+ native_turn_id: string;
157
+ transcript_path?: string;
158
+ runtime_version?: string;
159
+ attempts: number;
160
+ }
161
+
120
162
  interface DelegationStateV3 extends OpenSpanStateV3 {
121
163
  source_id: `hid_${string}`;
122
164
  delegation_id: `del_${string}`;
@@ -139,6 +181,8 @@ export interface HookProducerStateV3 {
139
181
  clock_id: `clk_${string}`;
140
182
  next_sequence: number;
141
183
  current_turn_id?: `tid_${string}`;
184
+ /** Native turn identity retained owner-only for transcript attribution. */
185
+ current_native_turn_id?: string;
142
186
  tool_call_count: number;
143
187
  tool_call_count_turn_id?: `tid_${string}`;
144
188
  last_event_id?: `evt_${string}`;
@@ -156,6 +200,25 @@ export interface HookProducerStateV3 {
156
200
  turn_harness: TurnHarnessTimingV3;
157
201
  turn_ordinal: number;
158
202
  pending?: PendingEventV3;
203
+ pending_runtime_contexts?: PendingRuntimeContextV3[];
204
+ /** Verified adapter-native transcript path retained only in owner-only state. */
205
+ runtime_transcript_path?: string;
206
+ /** Tuning carried by the current attestation; {} means attested-none.
207
+ * Absent on pre-upgrade state files, which seed silently on first sight. */
208
+ last_attested_tuning?: { effort?: string; speed?: string };
209
+ /** Model observation carried forward into a refreshed attestation when the
210
+ * change-time transcript read does not pair a model with the new tuning. */
211
+ last_attested_model?: { provider: string; id: string };
212
+ /** Telemetry evidence carried forward verbatim: a tuning change does not
213
+ * alter what telemetry the runtime proved it can deliver. */
214
+ last_attested_telemetry?: RuntimeTelemetryCapabilitiesV3;
215
+ /** Full observations preserve missing and unsupported states when a
216
+ * different attestation channel changes. */
217
+ last_attested_model_observation?: RuntimeAttestationV3Base["model"];
218
+ last_attested_tuning_observation?: RuntimeAttestationV3Base["tuning"];
219
+ /** Turn already probed for Codex tuning while effort is unknown; bounds the
220
+ * rollout forward-scan to one attempt per turn. */
221
+ tuning_probe_turn_id?: `tid_${string}`;
159
222
  }
160
223
 
161
224
  export interface RecordHookSignalV3Input {
@@ -181,6 +244,14 @@ export interface RecordHookSignalV3Input {
181
244
  stop_remediation?: boolean;
182
245
  turn_ritual?: TurnRitualEvidenceV3;
183
246
  session_start_derivation?: "approved_lifecycle_reopen";
247
+ delegated_child?: {
248
+ generation_id: `gen_${string}`;
249
+ parent_generation_id: `gen_${string}`;
250
+ delegation_id: `del_${string}`;
251
+ caused_by_event_id: `evt_${string}`;
252
+ };
253
+ /** Optional roots and read budgets for runtime telemetry adapters. */
254
+ runtimeTelemetryOptions?: RuntimeTelemetryOptions;
184
255
  writerOptions?: WriteEventV3Options;
185
256
  }
186
257
 
@@ -310,11 +381,21 @@ function hookSignalGate(
310
381
  }
311
382
 
312
383
  function sessionHashForSignal(
313
- input: Pick<RecordHookSignalV3Input, "coordRoot" | "payload" | "adapter" | "instance_id">,
384
+ input: Pick<
385
+ RecordHookSignalV3Input,
386
+ "coordRoot" | "payload" | "adapter" | "instance_id" | "signal"
387
+ >,
314
388
  context: ReturnType<typeof fingerprintContextV3>,
315
389
  ): `hid_${string}` {
316
- const nativeSession =
317
- input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id;
390
+ const nativeChild = input.payload.subagent_id ?? input.payload.agent_id;
391
+ const childOwnsSignal =
392
+ input.signal !== "sub-agent-start" &&
393
+ input.signal !== "sub-agent-stop" &&
394
+ nativeChild !== undefined &&
395
+ input.instance_id === asLiveInstanceId(nativeChild);
396
+ const nativeSession = childOwnsSignal
397
+ ? nativeChild
398
+ : (input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id);
318
399
  if (nativeSession !== undefined) {
319
400
  return normalizeNativeIdV3(context, `${input.adapter}.session`, nativeSession);
320
401
  }
@@ -360,6 +441,7 @@ function intakeRecord(input: RecordHookSignalV3Input): HookIntakeRecordV3 {
360
441
  : {}),
361
442
  ...(input.stop_remediation !== undefined ? { stop_remediation: input.stop_remediation } : {}),
362
443
  ...(input.turn_ritual ? { turn_ritual: input.turn_ritual } : {}),
444
+ ...(input.delegated_child ? { delegated_child: input.delegated_child } : {}),
363
445
  };
364
446
  }
365
447
 
@@ -386,6 +468,7 @@ function inputForIntakeRecord(
386
468
  ...(record.hook_duration_ms !== undefined ? { hook_duration_ms: record.hook_duration_ms } : {}),
387
469
  ...(record.stop_remediation !== undefined ? { stop_remediation: record.stop_remediation } : {}),
388
470
  ...(record.turn_ritual ? { turn_ritual: record.turn_ritual } : {}),
471
+ ...(record.delegated_child ? { delegated_child: record.delegated_child } : {}),
389
472
  };
390
473
  }
391
474
 
@@ -669,6 +752,9 @@ function processHookSignalLocked(
669
752
  (nativeTid !== undefined && state.current_turn_id === nativeTid));
670
753
 
671
754
  recordTurnHarnessTiming(state, input, !duplicateOpenTurnStart);
755
+ reconcilePendingRuntimeContexts(input, state, path, rootId, fingerprintContext, {
756
+ finalAttempt: input.signal === "session-end",
757
+ });
672
758
 
673
759
  closeResolvedWaits(input, state, path, rootId, fingerprintContext, nativeTid);
674
760
  if (input.signal === "permission-request") {
@@ -736,6 +822,7 @@ function processHookSignalLocked(
736
822
  boot_id: state.boot_id,
737
823
  clock: eventClock,
738
824
  });
825
+ state.current_native_turn_id = input.payload.turn_id;
739
826
  }
740
827
 
741
828
  let sourceId = sourceIdForSignal(input, rootFingerprintContext);
@@ -933,7 +1020,7 @@ function processHookSignalLocked(
933
1020
 
934
1021
  const turnTelemetry =
935
1022
  input.signal === "stop" || input.signal === "stop-failure"
936
- ? extractTurnTelemetryV3(input.adapter, input.payload.raw, eventClock.observed_at)
1023
+ ? turnTelemetryForTerminal(input, state, eventClock.observed_at)
937
1024
  : undefined;
938
1025
  const cursorToolChannelSupport =
939
1026
  input.adapter === "cursor"
@@ -950,6 +1037,9 @@ function processHookSignalLocked(
950
1037
  const toolCallCountScopeMismatch =
951
1038
  (input.signal === "stop" || input.signal === "stop-failure") &&
952
1039
  (!state.current_turn_id || state.tool_call_count_turn_id !== state.current_turn_id);
1040
+ // Before the signal's own event: the tool call or turn that carries the
1041
+ // new effort already ran under it, so it must ride the new attestation.
1042
+ maybeAttestTuningChange(input, state, path, rootId);
953
1043
  const event = normalizeHookEventV3(input.signal, eventInput.payload, {
954
1044
  coordRoot: input.coordRoot,
955
1045
  adapter: input.adapter,
@@ -973,6 +1063,7 @@ function processHookSignalLocked(
973
1063
  platform: input.platform,
974
1064
  bridge: input.bridge,
975
1065
  capability_profile: state.capability_profile,
1066
+ cursor_mode: state.cursor_mode,
976
1067
  fingerprintContext,
977
1068
  turn_id: state.current_turn_id,
978
1069
  span_id: openingSpan?.span_id ?? closingSpan?.span_id,
@@ -1010,6 +1101,48 @@ function processHookSignalLocked(
1010
1101
  turn_ritual: input.turn_ritual,
1011
1102
  });
1012
1103
  if (!event) return { state: "ignored" };
1104
+ if (
1105
+ input.signal === "session-start" &&
1106
+ input.delegated_child &&
1107
+ event.event_type === "session.started"
1108
+ ) {
1109
+ event.links = {
1110
+ caused_by: [input.delegated_child.caused_by_event_id],
1111
+ parent_generation_id: input.delegated_child.parent_generation_id,
1112
+ delegation_id: input.delegated_child.delegation_id,
1113
+ };
1114
+ event.provenance = {
1115
+ ...event.provenance,
1116
+ source_event: `${input.adapter}.subagent-start.child-generation`,
1117
+ attestation: "derived",
1118
+ confidence: "high",
1119
+ attribution: {
1120
+ method: "native_payload",
1121
+ state: "verified",
1122
+ subject_instance_id: state.instance_id,
1123
+ },
1124
+ };
1125
+ assertEventV3(event);
1126
+ }
1127
+ if (
1128
+ input.signal === "user-prompt-submit" &&
1129
+ input.delegated_child &&
1130
+ event.event_type === "turn.started"
1131
+ ) {
1132
+ event.links = { caused_by: [input.delegated_child.caused_by_event_id] };
1133
+ event.provenance = {
1134
+ ...event.provenance,
1135
+ source_event: `${input.adapter}.subagent-start.child-turn`,
1136
+ attestation: "derived",
1137
+ confidence: "high",
1138
+ attribution: {
1139
+ method: "native_payload",
1140
+ state: "verified",
1141
+ subject_instance_id: state.instance_id,
1142
+ },
1143
+ };
1144
+ assertEventV3(event);
1145
+ }
1013
1146
  if (
1014
1147
  input.signal === "session-start" &&
1015
1148
  input.session_start_derivation === "approved_lifecycle_reopen" &&
@@ -1051,7 +1184,18 @@ function processHookSignalLocked(
1051
1184
  commitProgressObservation(input, state, path, rootId, event, progressKind);
1052
1185
  }
1053
1186
  if (turnTelemetry) {
1054
- commitTurnContextObservation(input, state, path, rootId, event, turnTelemetry.context);
1187
+ const contextTarget = turnContextTarget(event);
1188
+ queuePendingRuntimeContext(input, state, contextTarget, turnTelemetry.context);
1189
+ commitTurnContextObservation(
1190
+ input,
1191
+ state,
1192
+ path,
1193
+ rootId,
1194
+ fingerprintContext,
1195
+ contextTarget,
1196
+ turnTelemetry.context,
1197
+ turnTelemetry.context_provenance,
1198
+ );
1055
1199
  }
1056
1200
  if (event.event_type === "session.ended") {
1057
1201
  commitCapabilityDriftEvents(input, state, path, rootId);
@@ -1501,10 +1645,13 @@ function commitTurnContextObservation(
1501
1645
  state: HookProducerStateV3,
1502
1646
  path: string,
1503
1647
  rootId: `root_${string}`,
1504
- turnTerminal: EventV3,
1648
+ fingerprintContext: ReturnType<typeof fingerprintContextV3>,
1649
+ target: TurnContextTargetV3 | undefined,
1505
1650
  measurement: TelemetryObservationV3<ContextMeasurementV3>,
1651
+ contextProvenance?: ContextTelemetryProvenanceV3,
1652
+ observedAt?: string,
1506
1653
  ): void {
1507
- if (turnTerminal.event_type !== "turn.completed") return;
1654
+ if (!target) return;
1508
1655
  const event = buildEventV3("context.observed", {
1509
1656
  producer: {
1510
1657
  producer_id: input.producer_id,
@@ -1520,36 +1667,631 @@ function commitTurnContextObservation(
1520
1667
  instance_id: state.instance_id,
1521
1668
  session_id: state.session_id,
1522
1669
  generation_id: state.generation_id,
1523
- turn_id: (turnTerminal.scope as { turn_id: `tid_${string}` }).turn_id,
1524
- ...(input.run_id ? { run_id: input.run_id } : {}),
1525
- ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
1526
- ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
1670
+ turn_id: target.turn_id,
1671
+ ...(target.run_id ? { run_id: target.run_id } : {}),
1672
+ ...(target.workflow_id ? { workflow_id: target.workflow_id } : {}),
1673
+ ...(target.workflow_agent_id ? { workflow_agent_id: target.workflow_agent_id } : {}),
1527
1674
  },
1528
1675
  attestation_id: state.attestation_id,
1529
- links: { caused_by: [turnTerminal.event_id] },
1676
+ links: { caused_by: [target.terminal_event_id] },
1530
1677
  provenance: {
1531
- source_event: `${input.adapter}.turn-context`,
1678
+ source_event: safeRole(
1679
+ [
1680
+ contextProvenance?.source_event ?? `${input.adapter}.turn-context`,
1681
+ contextProvenance?.runtime_version,
1682
+ ]
1683
+ .filter(Boolean)
1684
+ .join("."),
1685
+ ),
1686
+ attestation: contextProvenance?.attestation ?? "native",
1687
+ confidence: contextProvenance?.confidence ?? "exact",
1688
+ ...(contextProvenance?.source_witness
1689
+ ? {
1690
+ source_record_id: normalizeNativeIdV3(
1691
+ fingerprintContext,
1692
+ `${input.adapter}.runtime-context`,
1693
+ contextProvenance.source_witness,
1694
+ ),
1695
+ }
1696
+ : {}),
1697
+ attribution: {
1698
+ method: "native_payload",
1699
+ state: "verified",
1700
+ subject_instance_id: state.instance_id,
1701
+ },
1702
+ },
1703
+ // The source sample may precede native task completion. The event itself
1704
+ // is committed after turn.completed, while measurement.measured_at keeps
1705
+ // the sample clock without creating a causal wall-clock regression.
1706
+ observed_at: observedAt ?? target.terminal_observed_at,
1707
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1708
+ clock_id: state.clock_id,
1709
+ payload: {
1710
+ measurement,
1711
+ },
1712
+ }) as EventV3;
1713
+ commitEventLocked(input, state, path, event);
1714
+ maybeAttestContextCapabilityChange(input, state, path, rootId, measurement, contextProvenance);
1715
+ }
1716
+
1717
+ function maybeAttestContextCapabilityChange(
1718
+ input: RecordHookSignalV3Input,
1719
+ state: HookProducerStateV3,
1720
+ path: string,
1721
+ rootId: `root_${string}`,
1722
+ measurement: TelemetryObservationV3<ContextMeasurementV3>,
1723
+ provenance?: ContextTelemetryProvenanceV3,
1724
+ ): void {
1725
+ const context =
1726
+ measurement.state === "observed"
1727
+ ? {
1728
+ state: "observed" as const,
1729
+ source: provenance?.source_event ?? `${input.adapter}.turn_context`,
1730
+ attestation: provenance?.attestation ?? measurement.attestation,
1731
+ confidence: provenance?.confidence ?? measurement.confidence,
1732
+ }
1733
+ : measurement.state === "expected_but_missing"
1734
+ ? { state: "partial" as const, reason: measurement.reason }
1735
+ : { state: "unsupported" as const };
1736
+ const telemetry = effectiveRuntimeTelemetryCapabilitiesV3({
1737
+ adapter: input.adapter,
1738
+ ...(state.cursor_mode ? { cursor_mode: state.cursor_mode } : {}),
1739
+ context,
1740
+ canonical_turn_boundaries: true,
1741
+ });
1742
+ if (!state.last_attested_telemetry) return;
1743
+ if (canonicalJsonV3(telemetry) === canonicalJsonV3(state.last_attested_telemetry)) return;
1744
+ if (!state.last_attested_model_observation || !state.last_attested_tuning_observation) return;
1745
+
1746
+ commitRuntimeAttestationChange(input, state, path, rootId, {
1747
+ model: state.last_attested_model_observation,
1748
+ tuning: state.last_attested_tuning_observation,
1749
+ telemetry,
1750
+ reason: "runtime_telemetry_changed",
1751
+ source_event: provenance?.source_event ?? `${input.adapter}.turn_context`,
1752
+ attestation: provenance?.attestation ?? "derived",
1753
+ confidence: provenance?.confidence ?? (measurement.state === "observed" ? "exact" : "high"),
1754
+ });
1755
+ }
1756
+
1757
+ function turnContextTarget(turnTerminal: EventV3): TurnContextTargetV3 | undefined {
1758
+ if (turnTerminal.event_type !== "turn.completed") return undefined;
1759
+ const scope = turnTerminal.scope as {
1760
+ turn_id: `tid_${string}`;
1761
+ run_id?: `run_${string}`;
1762
+ workflow_id?: `wf_${string}`;
1763
+ workflow_agent_id?: string;
1764
+ };
1765
+ return {
1766
+ terminal_event_id: turnTerminal.event_id as `evt_${string}`,
1767
+ terminal_observed_at: turnTerminal.time.observed_at,
1768
+ turn_id: scope.turn_id,
1769
+ ...(scope.run_id ? { run_id: scope.run_id } : {}),
1770
+ ...(scope.workflow_id ? { workflow_id: scope.workflow_id } : {}),
1771
+ ...(scope.workflow_agent_id ? { workflow_agent_id: scope.workflow_agent_id } : {}),
1772
+ };
1773
+ }
1774
+
1775
+ function queuePendingRuntimeContext(
1776
+ input: RecordHookSignalV3Input,
1777
+ state: HookProducerStateV3,
1778
+ target: TurnContextTargetV3 | undefined,
1779
+ measurement: TelemetryObservationV3<ContextMeasurementV3>,
1780
+ ): void {
1781
+ if (
1782
+ input.adapter !== "codex" ||
1783
+ !target ||
1784
+ measurement.state !== "expected_but_missing" ||
1785
+ !retryableRuntimeContextReason(measurement.reason)
1786
+ ) {
1787
+ return;
1788
+ }
1789
+ const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
1790
+ const nativeTurnId = input.payload.turn_id;
1791
+ if (!nativeSessionId || !nativeTurnId) return;
1792
+ const pending: PendingRuntimeContextV3 = {
1793
+ ...target,
1794
+ native_session_id: nativeSessionId,
1795
+ native_turn_id: nativeTurnId,
1796
+ ...((input.payload.transcript_path ?? state.runtime_transcript_path)
1797
+ ? { transcript_path: input.payload.transcript_path ?? state.runtime_transcript_path }
1798
+ : {}),
1799
+ ...(input.adapterVersion ? { runtime_version: input.adapterVersion } : {}),
1800
+ attempts: 0,
1801
+ };
1802
+ state.pending_runtime_contexts = [
1803
+ ...(state.pending_runtime_contexts ?? []).filter(
1804
+ (candidate) => candidate.turn_id !== target.turn_id,
1805
+ ),
1806
+ pending,
1807
+ ].slice(-PENDING_RUNTIME_CONTEXT_CAP);
1808
+ }
1809
+
1810
+ function reconcilePendingRuntimeContexts(
1811
+ input: RecordHookSignalV3Input,
1812
+ state: HookProducerStateV3,
1813
+ path: string,
1814
+ rootId: `root_${string}`,
1815
+ fingerprintContext: ReturnType<typeof fingerprintContextV3>,
1816
+ options: { deferRetryLimit?: boolean; finalAttempt?: boolean } = {},
1817
+ ): void {
1818
+ if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length) return;
1819
+ for (const pending of [...state.pending_runtime_contexts]) {
1820
+ const runtime = readRuntimeContextTelemetry(
1821
+ {
1822
+ adapter: "codex",
1823
+ session_id: pending.native_session_id,
1824
+ turn_id: pending.native_turn_id,
1825
+ transcript_path: pending.transcript_path,
1826
+ mode: "turn",
1827
+ },
1828
+ input.runtimeTelemetryOptions,
1829
+ );
1830
+ if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1831
+ const queue = state.pending_runtime_contexts;
1832
+ if (!queue) break;
1833
+ const index = queue.findIndex((candidate) => candidate.turn_id === pending.turn_id);
1834
+ if (index < 0) continue;
1835
+ if (runtime.state === "observed") {
1836
+ queue.splice(index, 1);
1837
+ if (queue.length === 0) {
1838
+ state.pending_runtime_contexts = undefined;
1839
+ }
1840
+ commitTurnContextObservation(
1841
+ input,
1842
+ state,
1843
+ path,
1844
+ rootId,
1845
+ fingerprintContext,
1846
+ pending,
1847
+ runtimeContextObservation(runtime),
1848
+ {
1849
+ source_event: runtime.source_event,
1850
+ source_witness: runtime.source_witness,
1851
+ ...(pending.runtime_version ? { runtime_version: pending.runtime_version } : {}),
1852
+ attestation: "derived",
1853
+ confidence: "exact",
1854
+ },
1855
+ signalClock(input).observed_at,
1856
+ );
1857
+ continue;
1858
+ }
1859
+ const attempts = pending.attempts + 1;
1860
+ if (
1861
+ options.finalAttempt ||
1862
+ runtime.state === "unsupported" ||
1863
+ !retryableRuntimeContextReason(runtime.reason) ||
1864
+ (!options.deferRetryLimit && attempts >= RUNTIME_CONTEXT_RETRY_LIMIT)
1865
+ ) {
1866
+ queue.splice(index, 1);
1867
+ } else {
1868
+ queue[index] = { ...pending, attempts };
1869
+ }
1870
+ }
1871
+ if (state.pending_runtime_contexts?.length === 0) {
1872
+ state.pending_runtime_contexts = undefined;
1873
+ }
1874
+ publishProducerState(path, state);
1875
+ }
1876
+
1877
+ function reconcilePendingRuntimeContextsBeforeApprovedEnd(
1878
+ input: RecordApprovedSessionEndV3Input,
1879
+ state: HookProducerStateV3,
1880
+ path: string,
1881
+ rootId: `root_${string}`,
1882
+ fingerprintContext: ReturnType<typeof fingerprintContextV3>,
1883
+ ): void {
1884
+ if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length) return;
1885
+ for (const [index, delayMs] of APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS.entries()) {
1886
+ if (delayMs > 0) sleepSync(delayMs);
1887
+ reconcilePendingRuntimeContexts(
1888
+ {
1889
+ coordRoot: input.coordRoot,
1890
+ mode: input.mode,
1891
+ signal: "session-end",
1892
+ payload: { raw: {} },
1893
+ adapter: state.adapter,
1894
+ instance_id: state.instance_id,
1895
+ producer_id: "prd_agent-finalizer",
1896
+ build_id: input.build_id,
1897
+ platform: input.platform,
1898
+ observed_at: new Date().toISOString(),
1899
+ writerOptions: input.writerOptions,
1900
+ },
1901
+ state,
1902
+ path,
1903
+ rootId,
1904
+ fingerprintContext,
1905
+ {
1906
+ deferRetryLimit: true,
1907
+ finalAttempt: index === APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS.length - 1,
1908
+ },
1909
+ );
1910
+ if (!state.pending_runtime_contexts?.length) return;
1911
+ }
1912
+ }
1913
+
1914
+ function retryableRuntimeContextReason(reason: string | undefined): boolean {
1915
+ return (
1916
+ reason === "codex_transcript_turn_not_found" ||
1917
+ reason === "codex_transcript_turn_not_terminal" ||
1918
+ reason === "codex_transcript_token_count_missing"
1919
+ );
1920
+ }
1921
+
1922
+ function turnTelemetryForTerminal(
1923
+ input: RecordHookSignalV3Input,
1924
+ state: HookProducerStateV3,
1925
+ observedAt: string,
1926
+ ): TurnTelemetryV3 {
1927
+ const native = extractTurnTelemetryV3(input.adapter, input.payload.raw, observedAt);
1928
+ if (native.context.state === "observed" || input.adapter === "cursor") return native;
1929
+
1930
+ const runtime = readRuntimeContextTelemetry(
1931
+ {
1932
+ adapter: input.adapter,
1933
+ session_id: input.payload.session_id ?? input.payload.conversation_id,
1934
+ turn_id: input.payload.turn_id ?? state.current_native_turn_id,
1935
+ transcript_path: runtimeTranscriptPath(input, state),
1936
+ mode: "turn",
1937
+ },
1938
+ input.runtimeTelemetryOptions,
1939
+ );
1940
+ if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1941
+ if (runtime.state === "unsupported") return native;
1942
+ return {
1943
+ ...native,
1944
+ context: runtimeContextObservation(runtime),
1945
+ context_provenance: {
1946
+ source_event:
1947
+ runtime.state === "observed" ? runtime.source_event : `${input.adapter}.runtime_context`,
1948
+ ...(runtime.state === "observed" ? { source_witness: runtime.source_witness } : {}),
1949
+ ...(input.adapterVersion ? { runtime_version: input.adapterVersion } : {}),
1950
+ attestation: "derived",
1951
+ confidence: runtime.state === "observed" ? "exact" : "high",
1952
+ },
1953
+ };
1954
+ }
1955
+
1956
+ /**
1957
+ * Resolve Codex's rollout once, retain it in owner-only producer state, and
1958
+ * verify the cached path against the native session before every reuse. Public
1959
+ * events receive only numeric telemetry and an opaque source witness.
1960
+ */
1961
+ function runtimeTranscriptPath(
1962
+ input: RecordHookSignalV3Input,
1963
+ state: HookProducerStateV3,
1964
+ ): string | undefined {
1965
+ if (input.adapter !== "codex") return input.payload.transcript_path;
1966
+ const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
1967
+ if (!nativeSessionId) return input.payload.transcript_path;
1968
+
1969
+ const supplied = input.payload.transcript_path;
1970
+ if (supplied) {
1971
+ const verified = discoverCodexSessionTranscript(
1972
+ nativeSessionId,
1973
+ supplied,
1974
+ input.runtimeTelemetryOptions,
1975
+ );
1976
+ if (verified) state.runtime_transcript_path = verified;
1977
+ // Preserve a supplied mismatch so the typed reader reports it honestly.
1978
+ return verified ?? supplied;
1979
+ }
1980
+
1981
+ if (state.runtime_transcript_path) {
1982
+ const verified = discoverCodexSessionTranscript(
1983
+ nativeSessionId,
1984
+ state.runtime_transcript_path,
1985
+ input.runtimeTelemetryOptions,
1986
+ );
1987
+ if (verified) return verified;
1988
+ state.runtime_transcript_path = undefined;
1989
+ }
1990
+
1991
+ const discovered = discoverCodexSessionTranscript(
1992
+ nativeSessionId,
1993
+ undefined,
1994
+ input.runtimeTelemetryOptions,
1995
+ );
1996
+ if (discovered) state.runtime_transcript_path = discovered;
1997
+ return discovered;
1998
+ }
1999
+
2000
+ function runtimeContextObservation(
2001
+ runtime: RuntimeContextTelemetry,
2002
+ ): TelemetryObservationV3<ContextMeasurementV3> {
2003
+ if (runtime.state === "observed") {
2004
+ return {
2005
+ state: "observed",
2006
+ value: {
2007
+ used_tokens: runtime.used_tokens,
2008
+ limit_tokens: runtime.limit_tokens,
2009
+ remaining_tokens: Math.max(0, runtime.limit_tokens - runtime.used_tokens),
2010
+ measured_at: runtime.measured_at,
2011
+ method: runtime.method,
2012
+ },
2013
+ attestation: "derived",
2014
+ confidence: "exact",
2015
+ };
2016
+ }
2017
+ return {
2018
+ state: "expected_but_missing",
2019
+ capability: "context_usage",
2020
+ reason: runtime.reason,
2021
+ };
2022
+ }
2023
+
2024
+ /** Seed the change-detection baseline from a committed session.started. */
2025
+ function seedAttestedTuning(state: HookProducerStateV3, event: { payload: unknown }): void {
2026
+ const attestation = (event.payload as { runtime_attestation?: RuntimeAttestationV3Base })
2027
+ .runtime_attestation;
2028
+ if (!attestation) return;
2029
+ const tuning = attestation.tuning;
2030
+ state.last_attested_tuning =
2031
+ tuning?.state === "observed"
2032
+ ? {
2033
+ ...(tuning.value?.effort ? { effort: tuning.value.effort } : {}),
2034
+ ...(tuning.value?.speed ? { speed: tuning.value.speed } : {}),
2035
+ }
2036
+ : {};
2037
+ const model = attestation.model;
2038
+ if (model.state === "observed") {
2039
+ state.last_attested_model = { provider: model.value.provider, id: model.value.id };
2040
+ }
2041
+ state.last_attested_model_observation = model;
2042
+ state.last_attested_tuning_observation = tuning;
2043
+ state.last_attested_telemetry = attestation.telemetry as RuntimeTelemetryCapabilitiesV3;
2044
+ }
2045
+
2046
+ /**
2047
+ * Emit `session.attestation_changed` when the observed tuning moves.
2048
+ *
2049
+ * Detection channels are deliberately cheap: CC stamps `effort.level` on
2050
+ * every tool hook and Stop (zero I/O), and Codex tuning is read from the
2051
+ * rollout only at turn terminals, where the context enrichment already pays
2052
+ * for a bounded tail read. A pre-upgrade state file (no baseline) seeds
2053
+ * silently so existing sessions do not fire a spurious change on their next
2054
+ * tool event; a fresh session's baseline comes from its session.started.
2055
+ */
2056
+ function maybeAttestTuningChange(
2057
+ input: RecordHookSignalV3Input,
2058
+ state: HookProducerStateV3,
2059
+ path: string,
2060
+ rootId: `root_${string}`,
2061
+ ): void {
2062
+ if (
2063
+ input.signal !== "pre-tool-use" &&
2064
+ input.signal !== "post-tool-use" &&
2065
+ input.signal !== "stop"
2066
+ ) {
2067
+ return;
2068
+ }
2069
+ if (state.terminal) return;
2070
+
2071
+ let candidate: { effort?: string; speed?: string; model?: string } | undefined;
2072
+ let attestationSource: "native" | "derived" = "native";
2073
+ let sourceEvent = `${input.adapter}.effort-payload`;
2074
+ if (input.adapter === "claude-code" || input.adapter === "cursor") {
2075
+ if (!input.payload.effort) return;
2076
+ candidate = { effort: input.payload.effort };
2077
+ } else if (
2078
+ input.adapter === "codex" &&
2079
+ (input.signal === "stop" ||
2080
+ // Early probe: without it, a Codex card shows no effort until the first
2081
+ // turn terminal. One rollout read per turn while effort is unknown.
2082
+ (input.signal === "post-tool-use" &&
2083
+ !state.last_attested_tuning?.effort &&
2084
+ state.current_turn_id !== undefined &&
2085
+ state.tuning_probe_turn_id !== state.current_turn_id))
2086
+ ) {
2087
+ if (input.signal === "post-tool-use") state.tuning_probe_turn_id = state.current_turn_id;
2088
+ const runtime = readRuntimeTuning(
2089
+ {
2090
+ adapter: "codex",
2091
+ session_id: input.payload.session_id ?? input.payload.conversation_id,
2092
+ transcript_path: runtimeTranscriptPath(input, state),
2093
+ },
2094
+ input.runtimeTelemetryOptions,
2095
+ );
2096
+ if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
2097
+ if (runtime.state !== "observed") return;
2098
+ candidate = {
2099
+ ...(runtime.effort ? { effort: runtime.effort } : {}),
2100
+ ...(runtime.speed ? { speed: runtime.speed } : {}),
2101
+ ...(runtime.model ? { model: runtime.model } : {}),
2102
+ };
2103
+ attestationSource = "derived";
2104
+ sourceEvent = runtime.source_event;
2105
+ } else {
2106
+ return;
2107
+ }
2108
+
2109
+ const baseline = state.last_attested_tuning;
2110
+ const next = { effort: candidate.effort, speed: candidate.speed ?? baseline?.speed };
2111
+ if (baseline === undefined || state.last_attested_telemetry === undefined) {
2112
+ // Pre-upgrade state: adopt without emitting; there is no attested prior.
2113
+ state.last_attested_tuning = { ...(next.effort ? { effort: next.effort } : {}) };
2114
+ if (next.speed) state.last_attested_tuning.speed = next.speed;
2115
+ return;
2116
+ }
2117
+ if (baseline.effort === next.effort && (candidate.speed ?? baseline.speed) === baseline.speed) {
2118
+ return;
2119
+ }
2120
+
2121
+ // CC change detection rides the payload; the paired transcript row also
2122
+ // carries speed and the (possibly swapped) model, so refresh from it.
2123
+ if (input.adapter === "claude-code") {
2124
+ const runtime = readRuntimeTuning({
2125
+ adapter: "claude-code",
2126
+ transcript_path: input.payload.transcript_path,
2127
+ });
2128
+ if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
2129
+ if (runtime.state === "observed" && runtime.effort === candidate.effort) {
2130
+ if (runtime.speed) candidate.speed = runtime.speed;
2131
+ if (runtime.model) candidate.model = runtime.model;
2132
+ }
2133
+ }
2134
+
2135
+ const model: RuntimeAttestationV3Base["model"] = candidate.model
2136
+ ? {
2137
+ state: "observed",
2138
+ value: { provider: modelProviderFor(input.adapter), id: safeRole(candidate.model) },
2139
+ attestation: "derived",
2140
+ confidence: "high",
2141
+ }
2142
+ : state.last_attested_model
2143
+ ? {
2144
+ state: "observed",
2145
+ value: state.last_attested_model,
2146
+ attestation: "derived",
2147
+ confidence: "high",
2148
+ }
2149
+ : { state: "expected_but_missing", capability: "model_identity", reason: "not_reported" };
2150
+ const tuning: RuntimeAttestationV3Base["tuning"] =
2151
+ candidate.effort || candidate.speed
2152
+ ? {
2153
+ state: "observed",
2154
+ value: {
2155
+ ...(candidate.effort ? { effort: candidate.effort } : {}),
2156
+ ...(candidate.speed ? { speed: candidate.speed } : {}),
2157
+ },
2158
+ attestation: attestationSource,
2159
+ confidence: "exact",
2160
+ }
2161
+ : { state: "unsupported", capability: "effort_selection" };
2162
+ commitRuntimeAttestationChange(input, state, path, rootId, {
2163
+ model,
2164
+ tuning,
2165
+ telemetry: state.last_attested_telemetry,
2166
+ reason: "runtime_tuning_changed",
2167
+ source_event: sourceEvent,
2168
+ attestation: attestationSource,
2169
+ confidence: attestationSource === "native" ? "exact" : "high",
2170
+ });
2171
+ }
2172
+
2173
+ interface RuntimeAttestationChangeV3 {
2174
+ model: RuntimeAttestationV3Base["model"];
2175
+ tuning: RuntimeAttestationV3Base["tuning"];
2176
+ telemetry: RuntimeTelemetryCapabilitiesV3;
2177
+ reason: string;
2178
+ source_event: string;
2179
+ attestation: "native" | "derived";
2180
+ confidence: "exact" | "high";
2181
+ }
2182
+
2183
+ function commitRuntimeAttestationChange(
2184
+ input: RecordHookSignalV3Input,
2185
+ state: HookProducerStateV3,
2186
+ path: string,
2187
+ rootId: `root_${string}`,
2188
+ change: RuntimeAttestationChangeV3,
2189
+ ): void {
2190
+ const priorAttestationId = state.attestation_id;
2191
+ const nextAttestationId = attestationIdV3();
2192
+ const eventId = eventIdV3();
2193
+ const runtimeAttestation: RuntimeAttestationV3Base = {
2194
+ attestation_id: nextAttestationId,
2195
+ generation_id: state.generation_id,
2196
+ adapter: {
2197
+ state: "observed",
2198
+ value: {
2199
+ id: input.adapter,
2200
+ ...(input.adapterVersion ? { version: input.adapterVersion } : {}),
2201
+ },
2202
+ attestation: "native",
2203
+ confidence: "exact",
2204
+ },
2205
+ harness: {
2206
+ state: "observed",
2207
+ value: {
2208
+ id: input.adapter,
2209
+ ...(input.harnessVersion ? { version: input.harnessVersion } : {}),
2210
+ },
1532
2211
  attestation: "native",
1533
2212
  confidence: "exact",
2213
+ },
2214
+ model: change.model,
2215
+ tuning: change.tuning,
2216
+ telemetry: change.telemetry,
2217
+ capability_profile: state.capability_profile,
2218
+ declared_by_event_id: eventId,
2219
+ };
2220
+ const event = buildEventV3("session.attestation_changed", {
2221
+ event_id: eventId,
2222
+ producer: {
2223
+ producer_id: input.producer_id,
2224
+ boot_id: state.boot_id,
2225
+ sequence: state.next_sequence,
2226
+ component: "agent-hook",
2227
+ build_id: input.build_id,
2228
+ platform: input.platform,
2229
+ ...(input.bridge ? { bridge: input.bridge } : {}),
2230
+ },
2231
+ scope: {
2232
+ root_id: rootId,
2233
+ instance_id: state.instance_id,
2234
+ session_id: state.session_id,
2235
+ generation_id: state.generation_id,
2236
+ ...(input.run_id ? { run_id: input.run_id } : {}),
2237
+ ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
2238
+ ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
2239
+ },
2240
+ attestation_id: nextAttestationId,
2241
+ links: { caused_by: state.last_event_id ? [state.last_event_id] : [] },
2242
+ provenance: {
2243
+ source_event: safeRole(change.source_event),
2244
+ attestation: change.attestation,
2245
+ confidence: change.confidence,
1534
2246
  attribution: {
1535
2247
  method: "native_payload",
1536
2248
  state: "verified",
1537
2249
  subject_instance_id: state.instance_id,
1538
2250
  },
1539
2251
  },
1540
- observed_at:
1541
- measurement.state === "observed"
1542
- ? measurement.value.measured_at
1543
- : turnTerminal.time.observed_at,
2252
+ observed_at: input.observed_at ?? new Date().toISOString(),
1544
2253
  monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1545
2254
  clock_id: state.clock_id,
1546
2255
  payload: {
1547
- measurement,
2256
+ prior_attestation_id: priorAttestationId,
2257
+ runtime_attestation: runtimeAttestation,
2258
+ reason: change.reason,
1548
2259
  },
1549
2260
  }) as EventV3;
2261
+ // Mutate BEFORE the commit: commitEventLocked publishes the state file, so
2262
+ // post-commit mutations would be dropped by the next signal's fresh read.
2263
+ // A crash between publishes replays the pending event against a state that
2264
+ // already carries the new attestation id, which is the id the event declares.
2265
+ state.attestation_id = nextAttestationId;
2266
+ state.last_attested_model_observation = change.model;
2267
+ state.last_attested_tuning_observation = change.tuning;
2268
+ state.last_attested_telemetry = change.telemetry;
2269
+ state.last_attested_model =
2270
+ change.model.state === "observed" ? { ...change.model.value } : undefined;
2271
+ state.last_attested_tuning = change.tuning.state === "observed" ? { ...change.tuning.value } : {};
1550
2272
  commitEventLocked(input, state, path, event);
1551
2273
  }
1552
2274
 
2275
+ function modelProviderFor(adapter: Adapter): string {
2276
+ if (adapter === "claude-code") return "anthropic";
2277
+ if (adapter === "codex") return "openai";
2278
+ return "cursor";
2279
+ }
2280
+
2281
+ function recordRuntimeTelemetryTiming(state: HookProducerStateV3, durationMs: number): void {
2282
+ const duration = Math.max(0, Math.ceil(durationMs));
2283
+ if (!Number.isSafeInteger(duration)) return;
2284
+ state.turn_harness = {
2285
+ hook_time_ms: state.turn_harness.hook_time_ms + duration,
2286
+ hook_count: state.turn_harness.hook_count + 1,
2287
+ slowest_hook:
2288
+ duration >= state.turn_harness.slowest_hook_ms
2289
+ ? "runtime-context"
2290
+ : state.turn_harness.slowest_hook,
2291
+ slowest_hook_ms: Math.max(duration, state.turn_harness.slowest_hook_ms),
2292
+ };
2293
+ }
2294
+
1553
2295
  function commitCapabilityDriftEvents(
1554
2296
  input: RecordHookSignalV3Input,
1555
2297
  state: HookProducerStateV3,
@@ -1854,6 +2596,7 @@ export function recordApprovedSessionEndV3(
1854
2596
  state.generation_id,
1855
2597
  state.privacy_epoch_id,
1856
2598
  );
2599
+ reconcilePendingRuntimeContextsBeforeApprovedEnd(input, state, record.path, rootId, context);
1857
2600
  const expected = [
1858
2601
  "session_started",
1859
2602
  "turn_closed",
@@ -2181,6 +2924,26 @@ export function readHookProducerStateByInstanceV3(
2181
2924
  return matches.length === 1 ? matches[0] : undefined;
2182
2925
  }
2183
2926
 
2927
+ /**
2928
+ * Join a coordination actor to its hook producer.
2929
+ *
2930
+ * Native subagent commands can retain the parent's session id even after the
2931
+ * child has its own canonical generation. Prefer the native session join, but
2932
+ * when that points at a different instance accept only one exact live
2933
+ * instance match for the same adapter.
2934
+ */
2935
+ export function readJoinableHookProducerStateV3(
2936
+ coordRoot: string,
2937
+ adapter: Adapter,
2938
+ nativeSessionId: string,
2939
+ instanceId: `inst_${string}`,
2940
+ ): HookProducerStateV3 | undefined {
2941
+ const direct = readHookProducerStateV3(coordRoot, adapter, nativeSessionId);
2942
+ if (direct?.instance_id === instanceId && !direct.terminal) return direct;
2943
+ const byInstance = readHookProducerStateByInstanceV3(coordRoot, instanceId);
2944
+ return byInstance?.adapter === adapter && !byInstance.terminal ? byInstance : undefined;
2945
+ }
2946
+
2184
2947
  function newProducerState(
2185
2948
  input: RecordHookSignalV3Input,
2186
2949
  sessionId: `sid_${string}`,
@@ -2194,7 +2957,7 @@ function newProducerState(
2194
2957
  adapter: input.adapter,
2195
2958
  instance_id: input.instance_id,
2196
2959
  session_id: sessionId,
2197
- generation_id: generationIdV3(),
2960
+ generation_id: input.delegated_child?.generation_id ?? generationIdV3(),
2198
2961
  attestation_id: attestationIdV3(),
2199
2962
  capability_profile: adapterCapabilityProfileDigestV3(input.adapter),
2200
2963
  cursor_mode: input.adapter === "cursor" ? (input.payload.cursor_mode ?? "unknown") : undefined,
@@ -2234,6 +2997,7 @@ function applyCommittedEvent(state: HookProducerStateV3, event: EventV3): void {
2234
2997
  if (event.event_type === "session.started") {
2235
2998
  state.started_event_id = event.event_id as `evt_${string}`;
2236
2999
  state.session_span.open_event_id = event.event_id as `evt_${string}`;
3000
+ seedAttestedTuning(state, event);
2237
3001
  }
2238
3002
  if (event.event_type === "turn.started") {
2239
3003
  const nextTurnId = (event.scope as { turn_id: `tid_${string}` }).turn_id;
@@ -2321,6 +3085,7 @@ function applyCommittedEvent(state: HookProducerStateV3, event: EventV3): void {
2321
3085
  }
2322
3086
  }
2323
3087
  state.current_turn_id = undefined;
3088
+ state.current_native_turn_id = undefined;
2324
3089
  state.current_turn_span = undefined;
2325
3090
  state.tool_call_count = 0;
2326
3091
  state.tool_call_count_turn_id = undefined;
@@ -2511,6 +3276,14 @@ function safeRole(value: unknown): string {
2511
3276
  return /^[a-zA-Z0-9]/.test(normalized) ? normalized : "agent";
2512
3277
  }
2513
3278
 
3279
+ function asLiveInstanceId(nativeInstanceId: string): `inst_${string}` {
3280
+ if (/^inst_[a-zA-Z0-9._-]{1,128}$/.test(nativeInstanceId)) {
3281
+ return nativeInstanceId as `inst_${string}`;
3282
+ }
3283
+ if (/^[a-zA-Z0-9._-]{1,128}$/.test(nativeInstanceId)) return `inst_${nativeInstanceId}`;
3284
+ return `inst_${createHash("sha256").update(nativeInstanceId.normalize("NFC")).digest("hex")}`;
3285
+ }
3286
+
2514
3287
  function safeTokenOrUndefined(value: unknown): string | undefined {
2515
3288
  if (typeof value !== "string") return undefined;
2516
3289
  const normalized = value.normalize("NFC");
@@ -2597,6 +3370,12 @@ function readProducerState(path: string): HookProducerStateV3 {
2597
3370
  state.waits ??= [];
2598
3371
  state.closed_turn_ids ??= [];
2599
3372
  state.turn_harness ??= emptyTurnHarnessTiming();
3373
+ if (
3374
+ Array.isArray(state.pending_runtime_contexts) &&
3375
+ state.pending_runtime_contexts.length === 0
3376
+ ) {
3377
+ state.pending_runtime_contexts = undefined;
3378
+ }
2600
3379
  if (state.adapter === "cursor") state.cursor_mode ??= "unknown";
2601
3380
  const allowedKeys = new Set([
2602
3381
  "adapter",
@@ -2607,6 +3386,7 @@ function readProducerState(path: string): HookProducerStateV3 {
2607
3386
  "closed_spans",
2608
3387
  "closed_turn_ids",
2609
3388
  "current_turn_id",
3389
+ "current_native_turn_id",
2610
3390
  "current_turn_span",
2611
3391
  "cursor_mode",
2612
3392
  "delegations",
@@ -2614,11 +3394,19 @@ function readProducerState(path: string): HookProducerStateV3 {
2614
3394
  "format_version",
2615
3395
  "generation_id",
2616
3396
  "instance_id",
3397
+ "last_attested_model",
3398
+ "last_attested_model_observation",
3399
+ "last_attested_telemetry",
3400
+ "last_attested_tuning",
3401
+ "last_attested_tuning_observation",
3402
+ "tuning_probe_turn_id",
2617
3403
  "last_event_id",
2618
3404
  "last_monotonic_ns",
2619
3405
  "last_observed_at",
2620
3406
  "next_sequence",
2621
3407
  "pending",
3408
+ "pending_runtime_contexts",
3409
+ "runtime_transcript_path",
2622
3410
  "privacy_epoch_id",
2623
3411
  "session_id",
2624
3412
  "session_span",
@@ -2710,11 +3498,25 @@ function readProducerState(path: string): HookProducerStateV3 {
2710
3498
  !Number.isSafeInteger(state.turn_ordinal) ||
2711
3499
  state.turn_ordinal < 0 ||
2712
3500
  (state.current_turn_id !== undefined && !/^tid_[a-f0-9]{64}$/.test(state.current_turn_id)) ||
3501
+ (state.current_native_turn_id !== undefined &&
3502
+ (typeof state.current_native_turn_id !== "string" ||
3503
+ state.current_native_turn_id.length === 0 ||
3504
+ state.current_native_turn_id.length > 512)) ||
2713
3505
  (state.last_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.last_event_id)) ||
2714
3506
  (state.last_monotonic_ns !== undefined && !/^\d+$/.test(state.last_monotonic_ns)) ||
2715
3507
  (state.last_observed_at !== undefined &&
2716
3508
  !Number.isFinite(Date.parse(state.last_observed_at))) ||
2717
3509
  (state.started_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.started_event_id)) ||
3510
+ (state.pending_runtime_contexts !== undefined &&
3511
+ (!Array.isArray(state.pending_runtime_contexts) ||
3512
+ state.pending_runtime_contexts.length === 0 ||
3513
+ state.pending_runtime_contexts.length > PENDING_RUNTIME_CONTEXT_CAP ||
3514
+ state.pending_runtime_contexts.some((pending) => !validPendingRuntimeContext(pending)))) ||
3515
+ (state.runtime_transcript_path !== undefined &&
3516
+ (typeof state.runtime_transcript_path !== "string" ||
3517
+ state.runtime_transcript_path.length === 0 ||
3518
+ state.runtime_transcript_path.length > 4096 ||
3519
+ state.runtime_transcript_path.includes("\0"))) ||
2718
3520
  (state.pending?.source_id !== undefined &&
2719
3521
  !/^hid_[a-f0-9]{64}$/.test(state.pending.source_id)) ||
2720
3522
  (state.pending && !validateEventV3(state.pending.event).ok)
@@ -2794,6 +3596,39 @@ function validTurnHarnessTiming(value: TurnHarnessTimingV3): boolean {
2794
3596
  );
2795
3597
  }
2796
3598
 
3599
+ function validPendingRuntimeContext(value: unknown): value is PendingRuntimeContextV3 {
3600
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3601
+ const pending = value as PendingRuntimeContextV3;
3602
+ return (
3603
+ typeof pending.terminal_event_id === "string" &&
3604
+ /^evt_[0-9a-f-]{36}$/.test(pending.terminal_event_id) &&
3605
+ typeof pending.terminal_observed_at === "string" &&
3606
+ Number.isFinite(Date.parse(pending.terminal_observed_at)) &&
3607
+ typeof pending.turn_id === "string" &&
3608
+ /^tid_[a-f0-9]{64}$/.test(pending.turn_id) &&
3609
+ (pending.run_id === undefined || /^run_[0-9a-f-]{36}$/.test(pending.run_id)) &&
3610
+ (pending.workflow_id === undefined || /^wf_[0-9a-f-]{36}$/.test(pending.workflow_id)) &&
3611
+ (pending.workflow_agent_id === undefined ||
3612
+ /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.workflow_agent_id)) &&
3613
+ typeof pending.native_session_id === "string" &&
3614
+ pending.native_session_id.length > 0 &&
3615
+ pending.native_session_id.length <= 512 &&
3616
+ typeof pending.native_turn_id === "string" &&
3617
+ pending.native_turn_id.length > 0 &&
3618
+ pending.native_turn_id.length <= 512 &&
3619
+ (pending.transcript_path === undefined ||
3620
+ (typeof pending.transcript_path === "string" &&
3621
+ pending.transcript_path.length > 0 &&
3622
+ pending.transcript_path.length <= 4096)) &&
3623
+ (pending.runtime_version === undefined ||
3624
+ (typeof pending.runtime_version === "string" &&
3625
+ /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.runtime_version))) &&
3626
+ Number.isSafeInteger(pending.attempts) &&
3627
+ pending.attempts >= 0 &&
3628
+ pending.attempts < RUNTIME_CONTEXT_RETRY_LIMIT
3629
+ );
3630
+ }
3631
+
2797
3632
  function pidIsAlive(pid: number): boolean {
2798
3633
  if (!Number.isSafeInteger(pid) || pid < 1) return false;
2799
3634
  try {