@ian-pascoe/pi-minimal-subagents 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,219 +1,1569 @@
1
+ import type { JsonValue } from "@earendil-works/pi-ai";
2
+ import { Value } from "typebox/value";
3
+ import { COORDINATOR_TOOL_NAMES } from "./minimal-subagents-capabilities.js";
4
+ import {
5
+ createDeliveryLedger,
6
+ deliveryLedgerSnapshot,
7
+ isDeliveryLedgerTurnClaimed,
8
+ pruneDeliveryLedgerAgents,
9
+ settleCoordinationDelivery,
10
+ settleTerminalDelivery,
11
+ upsertCoordinationDelivery,
12
+ upsertTerminalDelivery,
13
+ WAIT_TERMINAL_RETENTION_LIMIT,
14
+ claimDeliveryLedgerTurn,
15
+ releaseDeliveryLedgerTurn,
16
+ releaseEmptyDeliveryLedgerTurn,
17
+ type DeliveryLedger,
18
+ } from "./minimal-subagents-delivery-ledger.js";
19
+ import {
20
+ RegistryAgentCreatedEventWireSchema,
21
+ RegistryAgentDeletedEventWireSchema,
22
+ RegistryCheckpointEventWireSchema,
23
+ RegistryCoordinationPendingEventWireSchema,
24
+ RegistryCoordinationSettledEventWireSchema,
25
+ RegistryDeliveryPendingEventWireSchema,
26
+ RegistryDeliveryPrunedEventWireSchema,
27
+ RegistryDeliverySettledEventWireSchema,
28
+ RegistryDeliveryTurnEventWireSchema,
29
+ RegistryEnvelopeWireSchema,
30
+ RegistryEventDiscriminantWireSchema,
31
+ RegistryJsonValueWireSchema,
32
+ RegistryLooseEnvelopeWireSchema,
33
+ RegistryMessageRecordedEventWireSchema,
34
+ RegistryRootProbeWireSchema,
35
+ RegistryTurnSettledEventWireSchema,
36
+ RegistryTurnStartedEventWireSchema,
37
+ type RegistryAgentWire,
38
+ type RegistryCoordinationDeliveryWire,
39
+ type RegistryCoordinatorMessageWire,
40
+ type RegistryLaunchContractWire,
41
+ type RegistryRecentMessageWire,
42
+ type RegistrySnapshotWire,
43
+ type RegistryTerminalDeliveryWire,
44
+ type RegistryTurnResultWire,
45
+ } from "./minimal-subagents-registry-wire.js";
1
46
  import type {
47
+ CoordinatorMessage,
48
+ LaunchContract,
2
49
  PersistedAgent,
50
+ PersistedCoordinationDelivery,
3
51
  PersistedDelivery,
52
+ RecentAgentMessage,
4
53
  RegistrySnapshot,
5
54
  TurnResult,
6
55
  } from "./minimal-subagents-types.js";
7
56
 
8
- /** Names append-only root conversation entries that own the persistent agent registry. */
57
+ /** Names append-only root conversation entries that own the persistent agent Registry. */
9
58
  export const REGISTRY_ENTRY_TYPE = "minimal-subagents.registry";
10
59
  /** Names the first custom entry that promotes a child JSONL session to persistent identity. */
11
60
  export const CHILD_IDENTITY_ENTRY_TYPE = "minimal-subagents.identity";
61
+ /** Names destination-root ownership records appended to forked child sessions. */
62
+ export const FORK_OWNERSHIP_ENTRY_TYPE = "minimal-subagents.fork-ownership";
63
+ /** Names source-session provenance records appended while cloning child sessions. */
64
+ export const FORK_CLONE_ENTRY_TYPE = "minimal-subagents.fork-clone";
12
65
 
13
- interface RegistryEventBaseV1 {
14
- version: 1;
66
+ interface RegistryEventBaseV2 {
67
+ version: 2;
15
68
  root_session_id: string;
16
69
  timestamp: string;
17
70
  }
18
71
 
19
- /** Stores one complete versioned root registry checkpoint. */
20
- export interface RegistryCheckpointV1 extends RegistryEventBaseV1 {
21
- event: "checkpoint";
22
- snapshot: RegistrySnapshot;
72
+ /** Defines unversioned Registry V2 payloads accepted by the write-time event factory. */
73
+ export type RegistryEventData =
74
+ | { event: "checkpoint"; snapshot: RegistrySnapshot }
75
+ | { event: "agent-created"; agent: PersistedAgent }
76
+ | { event: "turn-started"; agent_id: string; turn_id: string; started_at: string }
77
+ | {
78
+ event: "turn-settled";
79
+ result: TurnResult;
80
+ settled_at?: string;
81
+ session_leaf_id?: string;
82
+ }
83
+ | { event: "delivery-pending"; delivery: PersistedDelivery }
84
+ | { event: "delivery-settled"; source_agent_id: string; source_turn_id: string; error?: string }
85
+ | {
86
+ event: "delivery-pruned";
87
+ source_agent_id: string;
88
+ source_turn_id: string;
89
+ reason: "retention-limit";
90
+ }
91
+ | { event: "coordination-delivery-pending"; delivery: PersistedCoordinationDelivery }
92
+ | { event: "coordination-delivery-settled"; delivery_id: string; error?: string }
93
+ | { event: "delivery-turn-claimed"; source_agent_id: string; source_turn_id: string }
94
+ | { event: "delivery-turn-released"; source_agent_id: string; source_turn_id: string }
95
+ | {
96
+ event: "agent-message-recorded";
97
+ agent_id: string;
98
+ message: RecentAgentMessage;
99
+ recorded_at: string;
100
+ }
101
+ | { event: "agent-deleted"; agent_ids: string[] };
102
+
103
+ /** Unites parsed and migrated append-only Registry V2 events. */
104
+ export type RegistryEventV2 = RegistryEventBaseV2 & RegistryEventData;
105
+
106
+ /** Lists stable semantic reasons for rejecting one owned Registry record. */
107
+ export type RegistryReplayDiagnosticCode =
108
+ | "invalid-envelope"
109
+ | "unsupported-version"
110
+ | "invalid-event-fields"
111
+ | "invalid-checkpoint"
112
+ | "invalid-agent-hierarchy"
113
+ | "invalid-event-reference"
114
+ | "invalid-delivery-identity"
115
+ | "invalid-delivery-sequence"
116
+ | "invalid-delivery-adjacency";
117
+
118
+ /** Identifies one malformed or semantically invalid active-branch Registry record. */
119
+ export interface RegistryReplayDiagnostic {
120
+ entry_index: number;
121
+ code: RegistryReplayDiagnosticCode;
122
+ message: string;
23
123
  }
24
124
 
25
- /** Records one persistent child identity before runtime initialization. */
26
- export interface AgentCreatedV1 extends RegistryEventBaseV1 {
27
- event: "agent-created";
28
- agent: PersistedAgent;
125
+ type RegistryEventFactoryArguments = {
126
+ [TEvent in RegistryEventData["event"]]: [
127
+ event: TEvent,
128
+ data: Omit<Extract<RegistryEventData, { event: TEvent }>, "event">,
129
+ timestamp?: string,
130
+ ];
131
+ }[RegistryEventData["event"]];
132
+
133
+ /** Add an explicit Registry V2 root-ownership envelope to one append-only event. */
134
+ export function createRegistryEvent<TArguments extends RegistryEventFactoryArguments>(
135
+ rootSessionId: string,
136
+ ...args: TArguments
137
+ ): RegistryEventBaseV2 & Extract<RegistryEventData, { event: TArguments[0] }>;
138
+ export function createRegistryEvent(
139
+ rootSessionId: string,
140
+ ...[event, data, suppliedTimestamp]: RegistryEventFactoryArguments
141
+ ): RegistryEventV2 {
142
+ const timestamp = suppliedTimestamp ?? new Date().toISOString();
143
+ const envelope = { version: 2 as const, root_session_id: rootSessionId, timestamp };
144
+ switch (event) {
145
+ case "checkpoint": {
146
+ const ledger = deliveryLedgerSnapshot(
147
+ createDeliveryLedger({
148
+ deliveries: data.snapshot.deliveries,
149
+ coordination_deliveries: data.snapshot.coordination_deliveries,
150
+ wait_claimed_turns: data.snapshot.wait_claimed_turns,
151
+ next_delivery_sequence: data.snapshot.next_delivery_sequence,
152
+ }),
153
+ );
154
+ return {
155
+ ...envelope,
156
+ event,
157
+ snapshot: { ...structuredClone(data.snapshot), ...ledger },
158
+ };
159
+ }
160
+ case "agent-created":
161
+ return { ...envelope, event, agent: data.agent };
162
+ case "turn-started":
163
+ return {
164
+ ...envelope,
165
+ event,
166
+ agent_id: data.agent_id,
167
+ turn_id: data.turn_id,
168
+ started_at: data.started_at,
169
+ };
170
+ case "turn-settled": {
171
+ const result: RegistryEventV2 & { event: "turn-settled" } = {
172
+ ...envelope,
173
+ event,
174
+ result: data.result,
175
+ };
176
+ if (data.settled_at !== undefined) result.settled_at = data.settled_at;
177
+ if (data.session_leaf_id !== undefined) result.session_leaf_id = data.session_leaf_id;
178
+ return result;
179
+ }
180
+ case "delivery-pending":
181
+ return { ...envelope, event, delivery: data.delivery };
182
+ case "delivery-settled": {
183
+ const result: RegistryEventV2 & { event: "delivery-settled" } = {
184
+ ...envelope,
185
+ event,
186
+ source_agent_id: data.source_agent_id,
187
+ source_turn_id: data.source_turn_id,
188
+ };
189
+ if (data.error !== undefined) result.error = data.error;
190
+ return result;
191
+ }
192
+ case "delivery-pruned":
193
+ return {
194
+ ...envelope,
195
+ event,
196
+ source_agent_id: data.source_agent_id,
197
+ source_turn_id: data.source_turn_id,
198
+ reason: data.reason,
199
+ };
200
+ case "coordination-delivery-pending":
201
+ return { ...envelope, event, delivery: data.delivery };
202
+ case "coordination-delivery-settled": {
203
+ const result: RegistryEventV2 & { event: "coordination-delivery-settled" } = {
204
+ ...envelope,
205
+ event,
206
+ delivery_id: data.delivery_id,
207
+ };
208
+ if (data.error !== undefined) result.error = data.error;
209
+ return result;
210
+ }
211
+ case "delivery-turn-claimed":
212
+ return {
213
+ ...envelope,
214
+ event,
215
+ source_agent_id: data.source_agent_id,
216
+ source_turn_id: data.source_turn_id,
217
+ };
218
+ case "delivery-turn-released":
219
+ return {
220
+ ...envelope,
221
+ event,
222
+ source_agent_id: data.source_agent_id,
223
+ source_turn_id: data.source_turn_id,
224
+ };
225
+ case "agent-message-recorded":
226
+ return {
227
+ ...envelope,
228
+ event,
229
+ agent_id: data.agent_id,
230
+ message: data.message,
231
+ recorded_at: data.recorded_at,
232
+ };
233
+ case "agent-deleted":
234
+ return { ...envelope, event, agent_ids: data.agent_ids };
235
+ }
29
236
  }
30
237
 
31
- /** Records one collision-resistant active turn identity and start time. */
32
- export interface TurnStartedV1 extends RegistryEventBaseV1 {
33
- event: "turn-started";
34
- agent_id: string;
35
- turn_id: string;
36
- started_at: string;
238
+ interface RegistryEntryLike {
239
+ type?: string;
240
+ customType?: string;
241
+ data?: unknown;
37
242
  }
38
243
 
39
- /** Records one terminal turn result for waits, status, and delivery recovery. */
40
- export interface TurnSettledV1 extends RegistryEventBaseV1 {
41
- event: "turn-settled";
42
- result: TurnResult;
244
+ type ParsedRegistryEvent =
245
+ | { kind: "event"; event: RegistryEventV2 }
246
+ | { kind: "foreign-root" }
247
+ | { kind: "invalid"; code: RegistryReplayDiagnosticCode; message: string };
248
+
249
+ const FRIENDLY_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
250
+ const CANONICAL_AGENT_ID_PATTERN =
251
+ /^(?:root\.)?[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?:\.[A-Za-z0-9][A-Za-z0-9_-]{0,63})*$/;
252
+ const COORDINATOR_TOOL_NAME_SET = new Set<string>(COORDINATOR_TOOL_NAMES);
253
+
254
+ function isCanonicalModelId(value: string): boolean {
255
+ const separatorIndex = value.indexOf("/");
256
+ return separatorIndex > 0 && separatorIndex < value.length - 1;
43
257
  }
44
258
 
45
- /** Records successful output awaiting keyed destination evidence. */
46
- export interface DeliveryPendingV1 extends RegistryEventBaseV1 {
47
- event: "delivery-pending";
48
- delivery: PersistedDelivery;
259
+ function isOwnedTurnId(sourceAgentId: string, turnId: string): boolean {
260
+ return turnId.startsWith(`${sourceAgentId}:`) && !turnId.includes("\u0000");
49
261
  }
50
262
 
51
- /** Records keyed destination evidence or the latest delivery error. */
52
- export interface DeliverySettledV1 extends RegistryEventBaseV1 {
53
- event: "delivery-settled";
54
- source_agent_id: string;
55
- source_turn_id: string;
56
- error?: string;
263
+ function isIsoTimestamp(value: string): boolean {
264
+ const timestamp = Date.parse(value);
265
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
57
266
  }
58
267
 
59
- /** Records durable deletion tombstones for canonical agent identities. */
60
- export interface AgentDeletedV1 extends RegistryEventBaseV1 {
61
- event: "agent-deleted";
62
- agent_ids: string[];
268
+ function isUniqueNonEmptyStringArray(value: readonly string[]): boolean {
269
+ return value.every((item) => item.length > 0) && new Set(value).size === value.length;
63
270
  }
64
271
 
65
- /** Unites all version-one append-only registry event envelopes. */
66
- export type RegistryEventV1 =
67
- | RegistryCheckpointV1
68
- | AgentCreatedV1
69
- | TurnStartedV1
70
- | TurnSettledV1
71
- | DeliveryPendingV1
72
- | DeliverySettledV1
73
- | AgentDeletedV1;
272
+ function isSafeSequence(value: number): boolean {
273
+ return Number.isSafeInteger(value) && value >= 1 && value < Number.MAX_SAFE_INTEGER;
274
+ }
74
275
 
75
- /** Defines unversioned registry payloads accepted by the event factory. */
76
- export type RegistryEventData =
77
- | { event: "checkpoint"; snapshot: RegistrySnapshot }
78
- | { event: "agent-created"; agent: PersistedAgent }
79
- | { event: "turn-started"; agent_id: string; turn_id: string; started_at: string }
80
- | { event: "turn-settled"; result: TurnResult }
81
- | { event: "delivery-pending"; delivery: PersistedDelivery }
82
- | { event: "delivery-settled"; source_agent_id: string; source_turn_id: string; error?: string }
83
- | { event: "agent-deleted"; agent_ids: string[] };
276
+ function cloneRegistryTurnResult(value: RegistryTurnResultWire): TurnResult {
277
+ const result: TurnResult = {
278
+ agent_id: value.agent_id,
279
+ turn_id: value.turn_id,
280
+ status: value.status,
281
+ output: value.output,
282
+ };
283
+ if (value.error !== undefined) result.error = value.error;
284
+ if (value.usage !== undefined) result.usage = structuredClone(value.usage);
285
+ if (value.elapsed_ms !== undefined) result.elapsed_ms = value.elapsed_ms;
286
+ return result;
287
+ }
84
288
 
85
- /** Add the versioned root ownership envelope to one append-only registry event. */
86
- export function createRegistryEvent<TEvent extends RegistryEventData["event"]>(
87
- rootSessionId: string,
88
- event: TEvent,
89
- data: Omit<Extract<RegistryEventData, { event: TEvent }>, "event">,
90
- timestamp = new Date().toISOString(),
91
- ): RegistryEventV1 {
289
+ function cloneRegistryRecentMessage(value: RegistryRecentMessageWire): RecentAgentMessage {
92
290
  return {
93
- version: 1,
94
- root_session_id: rootSessionId,
95
- timestamp,
96
- event,
97
- ...data,
98
- } as unknown as RegistryEventV1;
291
+ source_agent_id: value.source_agent_id,
292
+ turn_id: value.turn_id,
293
+ content: value.content,
294
+ };
99
295
  }
100
296
 
101
- interface RegistryEntryLike {
102
- type?: string;
103
- customType?: string;
104
- data?: unknown;
297
+ function excludesCoordinatorToolNames(toolNames: readonly string[]): boolean {
298
+ return toolNames.every((toolName) => !COORDINATOR_TOOL_NAME_SET.has(toolName));
299
+ }
300
+
301
+ function parseRegistryLaunchContract(
302
+ value: RegistryLaunchContractWire,
303
+ ): LaunchContract | undefined {
304
+ if (
305
+ !isCanonicalModelId(value.model) ||
306
+ !isUniqueNonEmptyStringArray(value.ordinary_tools) ||
307
+ !excludesCoordinatorToolNames(value.ordinary_tools) ||
308
+ (Array.isArray(value.tools) &&
309
+ (!isUniqueNonEmptyStringArray(value.tools) || !excludesCoordinatorToolNames(value.tools)))
310
+ ) {
311
+ return undefined;
312
+ }
313
+ const launchContract: LaunchContract = {
314
+ session_context: value.session_context,
315
+ project_context: value.project_context,
316
+ model: value.model,
317
+ thinking_level: value.thinking_level,
318
+ tools: value.tools === undefined ? undefined : structuredClone(value.tools),
319
+ ordinary_tools: [...value.ordinary_tools],
320
+ };
321
+ if (value.delegation !== undefined) launchContract.delegation = value.delegation;
322
+ return launchContract;
323
+ }
324
+
325
+ function parseRegistryAgent(value: RegistryAgentWire, version: 1 | 2): PersistedAgent | undefined {
326
+ const launchContract = parseRegistryLaunchContract(value.launch_contract);
327
+ if (
328
+ value.agent_id === "root" ||
329
+ !CANONICAL_AGENT_ID_PATTERN.test(value.agent_id) ||
330
+ value.friendly_id === "root" ||
331
+ value.friendly_id === "parent" ||
332
+ !FRIENDLY_AGENT_ID_PATTERN.test(value.friendly_id) ||
333
+ !isIsoTimestamp(value.created_at) ||
334
+ launchContract === undefined ||
335
+ !isUniqueNonEmptyStringArray(value.capability_ceiling) ||
336
+ !isUniqueNonEmptyStringArray(value.missing_dependencies) ||
337
+ (value.latest_activity_at !== undefined && !isIsoTimestamp(value.latest_activity_at)) ||
338
+ (value.active_turn_started_at !== undefined && !isIsoTimestamp(value.active_turn_started_at))
339
+ ) {
340
+ return undefined;
341
+ }
342
+ if ((value.active_turn_id === undefined) !== (value.active_turn_started_at === undefined)) {
343
+ return undefined;
344
+ }
345
+ if ((value.session_file === undefined) !== (value.session_id === undefined)) return undefined;
346
+ if (version === 2) {
347
+ if (value.session_leaf_id !== undefined && value.session_id === undefined) return undefined;
348
+ if (value.session_id !== undefined && value.session_leaf_id === undefined) return undefined;
349
+ }
350
+ if (value.clone_error !== undefined && value.availability !== "unavailable") return undefined;
351
+ if (!excludesCoordinatorToolNames(value.capability_ceiling)) return undefined;
352
+ const capabilityCeiling = new Set(value.capability_ceiling);
353
+ if (
354
+ launchContract.ordinary_tools.length !== capabilityCeiling.size ||
355
+ launchContract.ordinary_tools.some((toolName) => !capabilityCeiling.has(toolName))
356
+ ) {
357
+ return undefined;
358
+ }
359
+ if (value.active_turn_id !== undefined && !isOwnedTurnId(value.agent_id, value.active_turn_id)) {
360
+ return undefined;
361
+ }
362
+ const expectedAgentId =
363
+ value.parent_id === "root"
364
+ ? [value.friendly_id, `root.${value.friendly_id}`]
365
+ : [`${value.parent_id}.${value.friendly_id}`];
366
+ if (!expectedAgentId.includes(value.agent_id)) return undefined;
367
+ if (
368
+ value.latest_result !== undefined &&
369
+ (value.latest_result.agent_id !== value.agent_id ||
370
+ !isOwnedTurnId(value.agent_id, value.latest_result.turn_id) ||
371
+ value.latest_result.turn_id === value.active_turn_id)
372
+ ) {
373
+ return undefined;
374
+ }
375
+
376
+ const agent: PersistedAgent = {
377
+ agent_id: value.agent_id,
378
+ friendly_id: value.friendly_id,
379
+ parent_id: value.parent_id,
380
+ created_at: value.created_at,
381
+ spawn_entry_id: value.spawn_entry_id,
382
+ launch_contract: launchContract,
383
+ capability_ceiling: [...value.capability_ceiling],
384
+ availability: value.availability,
385
+ missing_dependencies: [...value.missing_dependencies],
386
+ recent_messages: value.recent_messages.map(cloneRegistryRecentMessage),
387
+ };
388
+ if (value.task !== undefined) agent.task = value.task;
389
+ if (value.latest_activity_at !== undefined) agent.latest_activity_at = value.latest_activity_at;
390
+ if (value.session_file !== undefined) agent.session_file = value.session_file;
391
+ if (value.session_id !== undefined) agent.session_id = value.session_id;
392
+ if (value.session_leaf_id !== undefined) agent.session_leaf_id = value.session_leaf_id;
393
+ if (value.clone_error !== undefined) agent.clone_error = value.clone_error;
394
+ if (value.active_turn_id !== undefined) agent.active_turn_id = value.active_turn_id;
395
+ if (value.active_turn_started_at !== undefined) {
396
+ agent.active_turn_started_at = value.active_turn_started_at;
397
+ }
398
+ if (value.latest_result !== undefined) {
399
+ agent.latest_result = cloneRegistryTurnResult(value.latest_result);
400
+ }
401
+ if (value.unavailable_reason !== undefined) agent.unavailable_reason = value.unavailable_reason;
402
+ if (value.deleted !== undefined) agent.deleted = value.deleted;
403
+ return agent;
105
404
  }
106
405
 
107
- function isRegistryEvent(value: unknown, rootSessionId: string): value is RegistryEventV1 {
108
- if (!value || typeof value !== "object") return false;
109
- const candidate = value as Partial<RegistryEventV1>;
406
+ function parseRegistryTerminalDelivery(
407
+ value: RegistryTerminalDeliveryWire,
408
+ version: 1 | 2,
409
+ ): PersistedDelivery | undefined {
410
+ if (
411
+ (version === 2 &&
412
+ (value.sequence === undefined || value.result === undefined || value.settled !== false)) ||
413
+ (value.sequence !== undefined && !isSafeSequence(value.sequence))
414
+ ) {
415
+ return undefined;
416
+ }
417
+ const delivery: PersistedDelivery = {
418
+ source_agent_id: value.source_agent_id,
419
+ source_turn_id: value.source_turn_id,
420
+ destination_agent_id: value.destination_agent_id,
421
+ path: value.path,
422
+ settled: value.settled,
423
+ };
424
+ if (value.sequence !== undefined) delivery.sequence = value.sequence;
425
+ if (value.result !== undefined) delivery.result = cloneRegistryTurnResult(value.result);
426
+ if (value.error !== undefined) delivery.error = value.error;
427
+ return delivery;
428
+ }
429
+
430
+ function parseRegistryCoordinatorMessage(
431
+ value: RegistryCoordinatorMessageWire,
432
+ ): CoordinatorMessage | undefined {
433
+ if (
434
+ value.customType !== "minimal-subagents.message" ||
435
+ value.details.destination_agent_id === undefined ||
436
+ value.details.delivery_id === undefined ||
437
+ value.details.status !== undefined ||
438
+ value.details.elapsed_ms !== undefined ||
439
+ value.details.usage !== undefined
440
+ ) {
441
+ return undefined;
442
+ }
443
+ return {
444
+ customType: "minimal-subagents.message",
445
+ content: value.content,
446
+ details: {
447
+ source_agent_id: value.details.source_agent_id,
448
+ destination_agent_id: value.details.destination_agent_id,
449
+ source_turn_id: value.details.source_turn_id,
450
+ message_id: value.details.message_id,
451
+ delivery_id: value.details.delivery_id,
452
+ },
453
+ };
454
+ }
455
+
456
+ function parseRegistryCoordinationDelivery(
457
+ value: RegistryCoordinationDeliveryWire,
458
+ version: 1 | 2,
459
+ ): PersistedCoordinationDelivery | undefined {
460
+ const message = parseRegistryCoordinatorMessage(value.message);
461
+ if (
462
+ !isSafeSequence(value.sequence) ||
463
+ message === undefined ||
464
+ (version === 2 && value.settled)
465
+ ) {
466
+ return undefined;
467
+ }
468
+ const delivery: PersistedCoordinationDelivery = {
469
+ delivery_id: value.delivery_id,
470
+ sequence: value.sequence,
471
+ destination_agent_id: value.destination_agent_id,
472
+ path: value.path,
473
+ settled: value.settled,
474
+ message,
475
+ };
476
+ if (value.error !== undefined) delivery.error = value.error;
477
+ return delivery;
478
+ }
479
+
480
+ function hasValidAgentHierarchy(agents: readonly PersistedAgent[]): boolean {
481
+ const byId = new Map<string, PersistedAgent>();
482
+ for (const agent of agents) {
483
+ if (byId.has(agent.agent_id)) return false;
484
+ byId.set(agent.agent_id, agent);
485
+ }
486
+ for (const agent of agents) {
487
+ const visited = new Set<string>([agent.agent_id]);
488
+ let parentId = agent.parent_id;
489
+ while (parentId !== "root") {
490
+ if (visited.has(parentId)) return false;
491
+ visited.add(parentId);
492
+ const parent = byId.get(parentId);
493
+ if (!parent) return false;
494
+ parentId = parent.parent_id;
495
+ }
496
+ }
497
+ return true;
498
+ }
499
+
500
+ function areAdjacentAgents(
501
+ sourceAgentId: string,
502
+ destinationAgentId: string,
503
+ agentsById: ReadonlyMap<string, PersistedAgent>,
504
+ ): boolean {
505
+ if (sourceAgentId === destinationAgentId) return false;
506
+ const source = sourceAgentId === "root" ? undefined : agentsById.get(sourceAgentId);
507
+ const destination =
508
+ destinationAgentId === "root" ? undefined : agentsById.get(destinationAgentId);
509
+ if (sourceAgentId === "root") return destination?.parent_id === "root";
510
+ if (destinationAgentId === "root") return source?.parent_id === "root";
511
+ if (!source || !destination) return false;
110
512
  return (
111
- candidate.version === 1 &&
112
- candidate.root_session_id === rootSessionId &&
113
- typeof candidate.timestamp === "string" &&
114
- typeof candidate.event === "string"
513
+ source.parent_id === destinationAgentId ||
514
+ destination.parent_id === sourceAgentId ||
515
+ source.parent_id === destination.parent_id
115
516
  );
116
517
  }
117
518
 
118
- function cloneSnapshot(snapshot: RegistrySnapshot): RegistrySnapshot {
119
- return structuredClone(snapshot);
519
+ function validateDeliveryIdentity(delivery: PersistedDelivery, version: 1 | 2): string | undefined {
520
+ if (!isOwnedTurnId(delivery.source_agent_id, delivery.source_turn_id))
521
+ return "terminal source_turn_id must belong to source_agent_id";
522
+ if (delivery.result) {
523
+ if (delivery.result.agent_id !== delivery.source_agent_id)
524
+ return "terminal result agent_id must equal delivery source_agent_id";
525
+ if (delivery.result.turn_id !== delivery.source_turn_id)
526
+ return "terminal result turn_id must equal delivery source_turn_id";
527
+ if (delivery.result.status !== "completed")
528
+ return "pending terminal delivery result must be completed";
529
+ } else if (version === 2) {
530
+ return "Registry V2 terminal delivery must retain its result";
531
+ }
532
+ return undefined;
533
+ }
534
+
535
+ function validateCoordinationIdentity(delivery: PersistedCoordinationDelivery): string | undefined {
536
+ const details = delivery.message.details;
537
+ if (!isOwnedTurnId(details.source_agent_id, details.source_turn_id))
538
+ return "Coordination Message source_turn_id must belong to source_agent_id";
539
+ if (delivery.delivery_id !== `message:${details.message_id}`)
540
+ return "Coordination delivery_id must equal message:<message_id>";
541
+ if (details.delivery_id !== delivery.delivery_id)
542
+ return "Coordination message delivery_id must equal delivery delivery_id";
543
+ if (details.destination_agent_id !== delivery.destination_agent_id)
544
+ return "Coordination message destination_agent_id must equal delivery destination_agent_id";
545
+ return undefined;
120
546
  }
121
547
 
122
- function deliveryKey(sourceAgentId: string, sourceTurnId: string): string {
123
- return `${sourceAgentId}\u0000${sourceTurnId}`;
548
+ function isObservableSourceTurn(
549
+ agent: PersistedAgent,
550
+ sourceTurnId: string,
551
+ deliveries: readonly PersistedDelivery[],
552
+ coordinationDeliveries: readonly PersistedCoordinationDelivery[],
553
+ ): boolean {
554
+ return (
555
+ agent.active_turn_id === sourceTurnId ||
556
+ agent.latest_result?.turn_id === sourceTurnId ||
557
+ deliveries.some(
558
+ (delivery) =>
559
+ !delivery.settled &&
560
+ delivery.source_agent_id === agent.agent_id &&
561
+ delivery.source_turn_id === sourceTurnId,
562
+ ) ||
563
+ coordinationDeliveries.some(
564
+ (delivery) =>
565
+ !delivery.settled &&
566
+ delivery.message.details.source_agent_id === agent.agent_id &&
567
+ delivery.message.details.source_turn_id === sourceTurnId,
568
+ )
569
+ );
124
570
  }
125
571
 
126
- /** Replay root-session-wide registry entries, starting from the latest complete checkpoint. */
572
+ type RegistrySnapshotValidationResult =
573
+ | { kind: "valid"; snapshot: RegistrySnapshot }
574
+ | {
575
+ kind: "invalid";
576
+ code: RegistryReplayDiagnosticCode;
577
+ message: string;
578
+ };
579
+
580
+ function invalidRegistrySnapshot(
581
+ code: RegistryReplayDiagnosticCode,
582
+ message: string,
583
+ ): RegistrySnapshotValidationResult {
584
+ return { kind: "invalid", code, message };
585
+ }
586
+
587
+ function validateRegistrySnapshot(
588
+ value: RegistrySnapshotWire,
589
+ version: 1 | 2,
590
+ ): RegistrySnapshotValidationResult {
591
+ const agents: PersistedAgent[] = [];
592
+ for (const wireAgent of value.agents) {
593
+ const agent = parseRegistryAgent(wireAgent, version);
594
+ if (agent === undefined) {
595
+ return invalidRegistrySnapshot(
596
+ "invalid-checkpoint",
597
+ "snapshot agents are incomplete or invalid",
598
+ );
599
+ }
600
+ agents.push(agent);
601
+ }
602
+ if (!hasValidAgentHierarchy(agents)) {
603
+ return invalidRegistrySnapshot(
604
+ "invalid-agent-hierarchy",
605
+ "snapshot agent hierarchy is invalid",
606
+ );
607
+ }
608
+ if (!isUniqueNonEmptyStringArray(value.tombstones)) {
609
+ return invalidRegistrySnapshot(
610
+ "invalid-checkpoint",
611
+ "snapshot tombstones must be unique agent IDs",
612
+ );
613
+ }
614
+ const tombstones = [...value.tombstones];
615
+ if (
616
+ tombstones.some((agentId) => agentId === "root" || !CANONICAL_AGENT_ID_PATTERN.test(agentId))
617
+ ) {
618
+ return invalidRegistrySnapshot(
619
+ "invalid-agent-hierarchy",
620
+ "tombstone agent ID is not canonical",
621
+ );
622
+ }
623
+ if (agents.some((agent) => agent.deleted === true)) {
624
+ return invalidRegistrySnapshot(
625
+ "invalid-agent-hierarchy",
626
+ "live snapshot agents cannot be deleted",
627
+ );
628
+ }
629
+ const agentIds = new Set(agents.map((agent) => agent.agent_id));
630
+ if (tombstones.some((agentId) => agentIds.has(agentId))) {
631
+ return invalidRegistrySnapshot(
632
+ "invalid-agent-hierarchy",
633
+ "live agents cannot also be tombstoned",
634
+ );
635
+ }
636
+
637
+ const deliveries: PersistedDelivery[] = [];
638
+ for (const wireDelivery of value.deliveries) {
639
+ const delivery = parseRegistryTerminalDelivery(wireDelivery, version);
640
+ if (delivery === undefined) {
641
+ return invalidRegistrySnapshot(
642
+ "invalid-checkpoint",
643
+ "snapshot terminal deliveries are invalid",
644
+ );
645
+ }
646
+ deliveries.push(delivery);
647
+ }
648
+ if (version === 2 && value.coordination_deliveries === undefined) {
649
+ return invalidRegistrySnapshot(
650
+ "invalid-checkpoint",
651
+ "Registry V2 checkpoint must include coordination_deliveries",
652
+ );
653
+ }
654
+ const coordinationDeliveries: PersistedCoordinationDelivery[] = [];
655
+ for (const wireDelivery of value.coordination_deliveries ?? []) {
656
+ const delivery = parseRegistryCoordinationDelivery(wireDelivery, version);
657
+ if (delivery === undefined) {
658
+ return invalidRegistrySnapshot(
659
+ "invalid-checkpoint",
660
+ "snapshot Coordination Messages are invalid",
661
+ );
662
+ }
663
+ coordinationDeliveries.push(delivery);
664
+ }
665
+ if (version === 2 && value.wait_claimed_turns === undefined) {
666
+ return invalidRegistrySnapshot(
667
+ "invalid-checkpoint",
668
+ "Registry V2 checkpoint must include wait_claimed_turns",
669
+ );
670
+ }
671
+ const waitClaimedTurns = value.wait_claimed_turns ?? [];
672
+ if (
673
+ !isUniqueNonEmptyStringArray(waitClaimedTurns) ||
674
+ waitClaimedTurns.some(
675
+ (key) => key.indexOf("\u0000") <= 0 || key.indexOf("\u0000") !== key.lastIndexOf("\u0000"),
676
+ )
677
+ ) {
678
+ return invalidRegistrySnapshot("invalid-checkpoint", "snapshot wait claims are invalid");
679
+ }
680
+ if (version === 2 && value.next_delivery_sequence === undefined) {
681
+ return invalidRegistrySnapshot(
682
+ "invalid-delivery-sequence",
683
+ "Registry V2 checkpoint must include next_delivery_sequence",
684
+ );
685
+ }
686
+ if (value.next_delivery_sequence !== undefined && !isSafeSequence(value.next_delivery_sequence)) {
687
+ return invalidRegistrySnapshot(
688
+ "invalid-delivery-sequence",
689
+ "next_delivery_sequence must be a positive safe integer",
690
+ );
691
+ }
692
+
693
+ const agentsById = new Map(agents.map((agent) => [agent.agent_id, agent]));
694
+ for (const agent of agents) {
695
+ if (version === 2 && agent.recent_messages.length > 20) {
696
+ return invalidRegistrySnapshot(
697
+ "invalid-checkpoint",
698
+ "agent recent_messages exceeds the 20-item projection limit",
699
+ );
700
+ }
701
+ for (const message of agent.recent_messages) {
702
+ if (
703
+ !isOwnedTurnId(message.source_agent_id, message.turn_id) ||
704
+ !areAdjacentAgents(message.source_agent_id, agent.agent_id, agentsById)
705
+ ) {
706
+ return invalidRegistrySnapshot(
707
+ "invalid-delivery-adjacency",
708
+ "recent message endpoints and source turn must identify adjacent agents",
709
+ );
710
+ }
711
+ }
712
+ }
713
+ const terminalDeliveryKeys = deliveries.map(
714
+ (delivery) => `${delivery.source_agent_id}\u0000${delivery.source_turn_id}`,
715
+ );
716
+ if (new Set(terminalDeliveryKeys).size !== terminalDeliveryKeys.length) {
717
+ return invalidRegistrySnapshot(
718
+ "invalid-delivery-identity",
719
+ "terminal delivery source-turn identities must be unique",
720
+ );
721
+ }
722
+ const coordinationDeliveryIds = coordinationDeliveries.map((delivery) => delivery.delivery_id);
723
+ if (new Set(coordinationDeliveryIds).size !== coordinationDeliveryIds.length) {
724
+ return invalidRegistrySnapshot(
725
+ "invalid-delivery-identity",
726
+ "Coordination Message delivery IDs must be unique",
727
+ );
728
+ }
729
+ const waitTerminalCounts = new Map<string, number>();
730
+ for (const delivery of deliveries) {
731
+ if (delivery.path === "wait") {
732
+ const count = (waitTerminalCounts.get(delivery.source_agent_id) ?? 0) + 1;
733
+ waitTerminalCounts.set(delivery.source_agent_id, count);
734
+ if (version === 2 && count > WAIT_TERMINAL_RETENTION_LIMIT) {
735
+ return invalidRegistrySnapshot(
736
+ "invalid-checkpoint",
737
+ `wait-only terminal retention exceeds ${WAIT_TERMINAL_RETENTION_LIMIT} items for one source agent`,
738
+ );
739
+ }
740
+ }
741
+ const identityError = validateDeliveryIdentity(delivery, version);
742
+ if (identityError) return invalidRegistrySnapshot("invalid-delivery-identity", identityError);
743
+ const source = agentsById.get(delivery.source_agent_id);
744
+ if (!source) {
745
+ return invalidRegistrySnapshot(
746
+ "invalid-event-reference",
747
+ "terminal delivery source is not live",
748
+ );
749
+ }
750
+ if (source.parent_id !== delivery.destination_agent_id) {
751
+ return invalidRegistrySnapshot(
752
+ "invalid-delivery-adjacency",
753
+ "terminal delivery destination must be the source agent direct parent",
754
+ );
755
+ }
756
+ }
757
+ for (const delivery of coordinationDeliveries) {
758
+ const identityError = validateCoordinationIdentity(delivery);
759
+ if (identityError) return invalidRegistrySnapshot("invalid-delivery-identity", identityError);
760
+ if (
761
+ !areAdjacentAgents(
762
+ delivery.message.details.source_agent_id,
763
+ delivery.destination_agent_id,
764
+ agentsById,
765
+ )
766
+ ) {
767
+ return invalidRegistrySnapshot(
768
+ "invalid-delivery-adjacency",
769
+ "Coordination Message endpoints must be adjacent live agents",
770
+ );
771
+ }
772
+ }
773
+ const sequences = [
774
+ ...deliveries.flatMap((delivery) =>
775
+ delivery.sequence === undefined ? [] : [delivery.sequence],
776
+ ),
777
+ ...coordinationDeliveries.map((delivery) => delivery.sequence),
778
+ ];
779
+ if (new Set(sequences).size !== sequences.length) {
780
+ return invalidRegistrySnapshot(
781
+ "invalid-delivery-sequence",
782
+ "delivery sequences must be unique",
783
+ );
784
+ }
785
+ if (
786
+ value.next_delivery_sequence !== undefined &&
787
+ sequences.some((sequence) => sequence >= Number(value.next_delivery_sequence))
788
+ ) {
789
+ return invalidRegistrySnapshot(
790
+ "invalid-delivery-sequence",
791
+ "next_delivery_sequence must be greater than every retained sequence",
792
+ );
793
+ }
794
+ for (const key of waitClaimedTurns) {
795
+ const separatorIndex = key.indexOf("\u0000");
796
+ const sourceAgentId = key.slice(0, separatorIndex);
797
+ const sourceTurnId = key.slice(separatorIndex + 1);
798
+ const sourceAgent = agentsById.get(sourceAgentId);
799
+ if (
800
+ !sourceAgent ||
801
+ !isOwnedTurnId(sourceAgentId, sourceTurnId) ||
802
+ !isObservableSourceTurn(sourceAgent, sourceTurnId, deliveries, coordinationDeliveries)
803
+ ) {
804
+ return invalidRegistrySnapshot(
805
+ "invalid-event-reference",
806
+ "wait claim must reference an active, latest, or retained source turn",
807
+ );
808
+ }
809
+ }
810
+
811
+ if (version === 1) {
812
+ for (const agent of agents) {
813
+ if (!agent.session_id) agent.session_leaf_id = undefined;
814
+ agent.recent_messages = agent.recent_messages.slice(-20);
815
+ }
816
+ }
817
+ const snapshot: RegistrySnapshot = { agents, tombstones, deliveries };
818
+ if (coordinationDeliveries.length > 0 || value.coordination_deliveries !== undefined) {
819
+ snapshot.coordination_deliveries = coordinationDeliveries;
820
+ }
821
+ if (waitClaimedTurns.length > 0 || value.wait_claimed_turns !== undefined) {
822
+ snapshot.wait_claimed_turns = [...waitClaimedTurns];
823
+ }
824
+ if (value.next_delivery_sequence !== undefined) {
825
+ snapshot.next_delivery_sequence = value.next_delivery_sequence;
826
+ }
827
+ const normalizedLedger = deliveryLedgerSnapshot(
828
+ createDeliveryLedger({
829
+ deliveries: snapshot.deliveries,
830
+ coordination_deliveries: snapshot.coordination_deliveries,
831
+ wait_claimed_turns: snapshot.wait_claimed_turns,
832
+ next_delivery_sequence: snapshot.next_delivery_sequence,
833
+ }),
834
+ );
835
+ snapshot.deliveries = normalizedLedger.deliveries;
836
+ snapshot.coordination_deliveries = normalizedLedger.coordination_deliveries;
837
+ snapshot.wait_claimed_turns = normalizedLedger.wait_claimed_turns;
838
+ snapshot.next_delivery_sequence = normalizedLedger.next_delivery_sequence;
839
+ return { kind: "valid", snapshot };
840
+ }
841
+
842
+ function invalidParsedEvent(
843
+ code: RegistryReplayDiagnosticCode,
844
+ message: string,
845
+ ): ParsedRegistryEvent {
846
+ return { kind: "invalid", code, message };
847
+ }
848
+
849
+ function validParsedEvent(event: RegistryEventV2): ParsedRegistryEvent {
850
+ return { kind: "event", event };
851
+ }
852
+
853
+ type RegistryParseInput = JsonValue | RegistryEventV2;
854
+
855
+ function parseRegistryEventRecord(
856
+ value: RegistryParseInput,
857
+ rootSessionId: string,
858
+ ): ParsedRegistryEvent {
859
+ if (Value.Check(RegistryRootProbeWireSchema, value) && value.root_session_id !== rootSessionId) {
860
+ return { kind: "foreign-root" };
861
+ }
862
+ if (!Value.Check(RegistryLooseEnvelopeWireSchema, value) || !isIsoTimestamp(value.timestamp)) {
863
+ return invalidParsedEvent(
864
+ "invalid-envelope",
865
+ "root_session_id and timestamp must be valid strings",
866
+ );
867
+ }
868
+ if (value.version !== 1 && value.version !== 2) {
869
+ return invalidParsedEvent("unsupported-version", "Registry version must be 1 or 2");
870
+ }
871
+ if (!Value.Check(RegistryEventDiscriminantWireSchema, value)) {
872
+ return invalidParsedEvent("invalid-event-fields", "event discriminant must be a string");
873
+ }
874
+ if (!Value.Check(RegistryEnvelopeWireSchema, value)) {
875
+ return invalidParsedEvent("invalid-envelope", "Registry envelope is invalid");
876
+ }
877
+ const version = value.version;
878
+ const timestamp = value.timestamp;
879
+ switch (value.event) {
880
+ case "checkpoint": {
881
+ if (!Value.Check(RegistryCheckpointEventWireSchema, value)) {
882
+ return invalidParsedEvent("invalid-checkpoint", "snapshot must be an object");
883
+ }
884
+ const parsed = validateRegistrySnapshot(value.snapshot, version);
885
+ if (parsed.kind === "invalid") return invalidParsedEvent(parsed.code, parsed.message);
886
+ return validParsedEvent(
887
+ createRegistryEvent(rootSessionId, "checkpoint", { snapshot: parsed.snapshot }, timestamp),
888
+ );
889
+ }
890
+ case "agent-created": {
891
+ if (!Value.Check(RegistryAgentCreatedEventWireSchema, value)) {
892
+ return invalidParsedEvent("invalid-event-fields", "agent-created agent is incomplete");
893
+ }
894
+ const agent = parseRegistryAgent(value.agent, version);
895
+ if (agent === undefined || (version === 2 && agent.recent_messages.length > 20)) {
896
+ return invalidParsedEvent("invalid-event-fields", "agent-created agent is incomplete");
897
+ }
898
+ if (version === 1) {
899
+ if (!agent.session_id) agent.session_leaf_id = undefined;
900
+ agent.recent_messages = agent.recent_messages.slice(-20);
901
+ }
902
+ return validParsedEvent(
903
+ createRegistryEvent(rootSessionId, "agent-created", { agent }, timestamp),
904
+ );
905
+ }
906
+ case "turn-started":
907
+ if (
908
+ !Value.Check(RegistryTurnStartedEventWireSchema, value) ||
909
+ !isOwnedTurnId(value.agent_id, value.turn_id) ||
910
+ !isIsoTimestamp(value.started_at)
911
+ ) {
912
+ return invalidParsedEvent("invalid-event-fields", "turn-started fields are invalid");
913
+ }
914
+ return validParsedEvent(
915
+ createRegistryEvent(
916
+ rootSessionId,
917
+ "turn-started",
918
+ {
919
+ agent_id: value.agent_id,
920
+ turn_id: value.turn_id,
921
+ started_at: value.started_at,
922
+ },
923
+ timestamp,
924
+ ),
925
+ );
926
+ case "turn-settled": {
927
+ if (
928
+ !Value.Check(RegistryTurnSettledEventWireSchema, value) ||
929
+ !isOwnedTurnId(value.result.agent_id, value.result.turn_id) ||
930
+ (value.settled_at !== undefined && !isIsoTimestamp(value.settled_at))
931
+ ) {
932
+ return invalidParsedEvent("invalid-event-fields", "turn-settled fields are invalid");
933
+ }
934
+ const data: Omit<Extract<RegistryEventData, { event: "turn-settled" }>, "event"> = {
935
+ result: cloneRegistryTurnResult(value.result),
936
+ };
937
+ if (value.settled_at !== undefined) data.settled_at = value.settled_at;
938
+ if (value.session_leaf_id !== undefined) data.session_leaf_id = value.session_leaf_id;
939
+ return validParsedEvent(createRegistryEvent(rootSessionId, "turn-settled", data, timestamp));
940
+ }
941
+ case "delivery-pending": {
942
+ if (!Value.Check(RegistryDeliveryPendingEventWireSchema, value)) {
943
+ return invalidParsedEvent("invalid-event-fields", "terminal delivery fields are invalid");
944
+ }
945
+ const delivery = parseRegistryTerminalDelivery(value.delivery, version);
946
+ if (delivery === undefined) {
947
+ return invalidParsedEvent("invalid-event-fields", "terminal delivery fields are invalid");
948
+ }
949
+ const identityError = validateDeliveryIdentity(delivery, version);
950
+ if (identityError !== undefined) {
951
+ return invalidParsedEvent("invalid-delivery-identity", identityError);
952
+ }
953
+ return validParsedEvent(
954
+ createRegistryEvent(rootSessionId, "delivery-pending", { delivery }, timestamp),
955
+ );
956
+ }
957
+ case "delivery-settled": {
958
+ if (
959
+ !Value.Check(RegistryDeliverySettledEventWireSchema, value) ||
960
+ !isOwnedTurnId(value.source_agent_id, value.source_turn_id)
961
+ ) {
962
+ return invalidParsedEvent("invalid-event-fields", "delivery-settled fields are invalid");
963
+ }
964
+ const data: Omit<Extract<RegistryEventData, { event: "delivery-settled" }>, "event"> = {
965
+ source_agent_id: value.source_agent_id,
966
+ source_turn_id: value.source_turn_id,
967
+ };
968
+ if (value.error !== undefined) data.error = value.error;
969
+ return validParsedEvent(
970
+ createRegistryEvent(rootSessionId, "delivery-settled", data, timestamp),
971
+ );
972
+ }
973
+ case "delivery-pruned":
974
+ if (
975
+ version !== 2 ||
976
+ !Value.Check(RegistryDeliveryPrunedEventWireSchema, value) ||
977
+ !isOwnedTurnId(value.source_agent_id, value.source_turn_id)
978
+ ) {
979
+ return invalidParsedEvent("invalid-event-fields", "delivery-pruned fields are invalid");
980
+ }
981
+ return validParsedEvent(
982
+ createRegistryEvent(
983
+ rootSessionId,
984
+ "delivery-pruned",
985
+ {
986
+ source_agent_id: value.source_agent_id,
987
+ source_turn_id: value.source_turn_id,
988
+ reason: "retention-limit",
989
+ },
990
+ timestamp,
991
+ ),
992
+ );
993
+ case "coordination-delivery-pending": {
994
+ if (!Value.Check(RegistryCoordinationPendingEventWireSchema, value)) {
995
+ return invalidParsedEvent(
996
+ "invalid-event-fields",
997
+ "Coordination Message delivery fields are invalid",
998
+ );
999
+ }
1000
+ const delivery = parseRegistryCoordinationDelivery(value.delivery, version);
1001
+ if (delivery === undefined) {
1002
+ return invalidParsedEvent(
1003
+ "invalid-event-fields",
1004
+ "Coordination Message delivery fields are invalid",
1005
+ );
1006
+ }
1007
+ const identityError = validateCoordinationIdentity(delivery);
1008
+ if (identityError !== undefined) {
1009
+ return invalidParsedEvent("invalid-delivery-identity", identityError);
1010
+ }
1011
+ return validParsedEvent(
1012
+ createRegistryEvent(
1013
+ rootSessionId,
1014
+ "coordination-delivery-pending",
1015
+ { delivery },
1016
+ timestamp,
1017
+ ),
1018
+ );
1019
+ }
1020
+ case "coordination-delivery-settled": {
1021
+ if (!Value.Check(RegistryCoordinationSettledEventWireSchema, value)) {
1022
+ return invalidParsedEvent(
1023
+ "invalid-event-fields",
1024
+ "coordination-delivery-settled fields are invalid",
1025
+ );
1026
+ }
1027
+ const data: Omit<
1028
+ Extract<RegistryEventData, { event: "coordination-delivery-settled" }>,
1029
+ "event"
1030
+ > = { delivery_id: value.delivery_id };
1031
+ if (value.error !== undefined) data.error = value.error;
1032
+ return validParsedEvent(
1033
+ createRegistryEvent(rootSessionId, "coordination-delivery-settled", data, timestamp),
1034
+ );
1035
+ }
1036
+ case "delivery-turn-claimed":
1037
+ case "delivery-turn-released":
1038
+ if (
1039
+ !Value.Check(RegistryDeliveryTurnEventWireSchema, value) ||
1040
+ !isOwnedTurnId(value.source_agent_id, value.source_turn_id)
1041
+ ) {
1042
+ return invalidParsedEvent("invalid-event-fields", `${value.event} fields are invalid`);
1043
+ }
1044
+ if (value.event === "delivery-turn-claimed") {
1045
+ return validParsedEvent(
1046
+ createRegistryEvent(
1047
+ rootSessionId,
1048
+ "delivery-turn-claimed",
1049
+ {
1050
+ source_agent_id: value.source_agent_id,
1051
+ source_turn_id: value.source_turn_id,
1052
+ },
1053
+ timestamp,
1054
+ ),
1055
+ );
1056
+ }
1057
+ return validParsedEvent(
1058
+ createRegistryEvent(
1059
+ rootSessionId,
1060
+ "delivery-turn-released",
1061
+ {
1062
+ source_agent_id: value.source_agent_id,
1063
+ source_turn_id: value.source_turn_id,
1064
+ },
1065
+ timestamp,
1066
+ ),
1067
+ );
1068
+ case "agent-message-recorded":
1069
+ if (
1070
+ !Value.Check(RegistryMessageRecordedEventWireSchema, value) ||
1071
+ !isOwnedTurnId(value.message.source_agent_id, value.message.turn_id) ||
1072
+ (version === 2 && (value.recorded_at === undefined || !isIsoTimestamp(value.recorded_at)))
1073
+ ) {
1074
+ return invalidParsedEvent("invalid-event-fields", "agent activity fields are invalid");
1075
+ }
1076
+ const recordedAt = version === 1 ? timestamp : value.recorded_at;
1077
+ if (recordedAt === undefined) {
1078
+ return invalidParsedEvent("invalid-event-fields", "agent activity fields are invalid");
1079
+ }
1080
+ return validParsedEvent(
1081
+ createRegistryEvent(
1082
+ rootSessionId,
1083
+ "agent-message-recorded",
1084
+ {
1085
+ agent_id: value.agent_id,
1086
+ message: cloneRegistryRecentMessage(value.message),
1087
+ recorded_at: recordedAt,
1088
+ },
1089
+ timestamp,
1090
+ ),
1091
+ );
1092
+ case "agent-deleted":
1093
+ if (
1094
+ !Value.Check(RegistryAgentDeletedEventWireSchema, value) ||
1095
+ !isUniqueNonEmptyStringArray(value.agent_ids) ||
1096
+ value.agent_ids.length === 0 ||
1097
+ value.agent_ids.some(
1098
+ (agentId) => agentId === "root" || !CANONICAL_AGENT_ID_PATTERN.test(agentId),
1099
+ )
1100
+ ) {
1101
+ return invalidParsedEvent(
1102
+ "invalid-event-fields",
1103
+ "agent-deleted IDs must be unique canonical non-root agent IDs",
1104
+ );
1105
+ }
1106
+ return validParsedEvent(
1107
+ createRegistryEvent(
1108
+ rootSessionId,
1109
+ "agent-deleted",
1110
+ { agent_ids: [...value.agent_ids] },
1111
+ timestamp,
1112
+ ),
1113
+ );
1114
+ default:
1115
+ return invalidParsedEvent("invalid-event-fields", `unknown Registry event: ${value.event}`);
1116
+ }
1117
+ }
1118
+
1119
+ /** Parse one V1 or V2 Registry payload into the current V2 representation without throwing. */
1120
+ export function parseRegistryEvent(
1121
+ value: RegistryParseInput,
1122
+ rootSessionId: string,
1123
+ ): RegistryEventV2 | undefined {
1124
+ const parsed = parseRegistryEventRecord(value, rootSessionId);
1125
+ return parsed.kind === "event" ? parsed.event : undefined;
1126
+ }
1127
+
1128
+ function reportDiagnostic(
1129
+ diagnostics: RegistryReplayDiagnostic[],
1130
+ entryIndex: number,
1131
+ code: RegistryReplayDiagnosticCode,
1132
+ message: string,
1133
+ ): void {
1134
+ diagnostics.push({ entry_index: entryIndex, code, message });
1135
+ }
1136
+
1137
+ interface ReplayDeliverySequenceState {
1138
+ readonly terminalSequences: Map<string, number>;
1139
+ readonly coordinationSequences: Map<string, number>;
1140
+ highWaterMark: number;
1141
+ }
1142
+
1143
+ function terminalDeliveryIdentity(delivery: PersistedDelivery): string {
1144
+ return `${delivery.source_agent_id}\u0000${delivery.source_turn_id}`;
1145
+ }
1146
+
1147
+ function observeTerminalEventSequence(
1148
+ state: ReplayDeliverySequenceState,
1149
+ delivery: PersistedDelivery,
1150
+ ): PersistedDelivery | undefined {
1151
+ const identity = terminalDeliveryIdentity(delivery);
1152
+ const existingSequence = state.terminalSequences.get(identity);
1153
+ if (existingSequence !== undefined) {
1154
+ if (delivery.sequence !== undefined && delivery.sequence !== existingSequence) return undefined;
1155
+ return { ...delivery, sequence: existingSequence };
1156
+ }
1157
+ const sequence = delivery.sequence ?? state.highWaterMark + 1;
1158
+ if (
1159
+ !isSafeSequence(sequence) ||
1160
+ sequence >= Number.MAX_SAFE_INTEGER ||
1161
+ sequence <= state.highWaterMark
1162
+ )
1163
+ return undefined;
1164
+ state.terminalSequences.set(identity, sequence);
1165
+ state.highWaterMark = sequence;
1166
+ return { ...delivery, sequence };
1167
+ }
1168
+
1169
+ function observeCoordinationEventSequence(
1170
+ state: ReplayDeliverySequenceState,
1171
+ delivery: PersistedCoordinationDelivery,
1172
+ ): PersistedCoordinationDelivery | undefined {
1173
+ const existingSequence = state.coordinationSequences.get(delivery.delivery_id);
1174
+ if (existingSequence !== undefined) {
1175
+ return delivery.sequence === existingSequence ? delivery : undefined;
1176
+ }
1177
+ if (delivery.sequence >= Number.MAX_SAFE_INTEGER || delivery.sequence <= state.highWaterMark)
1178
+ return undefined;
1179
+ state.coordinationSequences.set(delivery.delivery_id, delivery.sequence);
1180
+ state.highWaterMark = delivery.sequence;
1181
+ return delivery;
1182
+ }
1183
+
1184
+ function validateEventAgentReference(
1185
+ agentId: string,
1186
+ agents: ReadonlyMap<string, PersistedAgent>,
1187
+ ): boolean {
1188
+ return agentId === "root" || agents.has(agentId);
1189
+ }
1190
+
1191
+ /** Replay active-branch Registry V1/V2 entries and report precise semantic diagnostics. */
127
1192
  export function replayRegistryEntries(
128
1193
  entries: readonly RegistryEntryLike[],
129
1194
  rootSessionId: string,
1195
+ reportInvalidRecords?: (diagnostics: RegistryReplayDiagnostic[]) => void,
130
1196
  ): RegistrySnapshot {
131
- const events = entries
132
- .filter((entry) => entry.type === "custom" && entry.customType === REGISTRY_ENTRY_TYPE)
133
- .map((entry) => entry.data)
134
- .filter((data): data is RegistryEventV1 => isRegistryEvent(data, rootSessionId));
135
- let checkpointIndex = -1;
136
- for (let index = events.length - 1; index >= 0; index--) {
137
- if (events[index]?.event === "checkpoint") {
138
- checkpointIndex = index;
139
- break;
140
- }
141
- }
142
-
143
- let snapshot: RegistrySnapshot = { agents: [], tombstones: [], deliveries: [] };
144
- if (checkpointIndex >= 0) {
145
- snapshot = cloneSnapshot((events[checkpointIndex] as RegistryCheckpointV1).snapshot);
146
- }
147
- const agents = new Map(snapshot.agents.map((agent) => [agent.agent_id, agent]));
148
- const tombstones = new Set(snapshot.tombstones);
149
- const deliveries = new Map(
150
- snapshot.deliveries.map((delivery) => [
151
- deliveryKey(delivery.source_agent_id, delivery.source_turn_id),
152
- delivery,
153
- ]),
1197
+ const parsedEvents: Array<{ entryIndex: number; event: RegistryEventV2 }> = [];
1198
+ const diagnostics: RegistryReplayDiagnostic[] = [];
1199
+ entries.forEach((entry, entryIndex) => {
1200
+ if (entry.type !== "custom" || entry.customType !== REGISTRY_ENTRY_TYPE) return;
1201
+ if (!Value.Check(RegistryJsonValueWireSchema, entry.data)) {
1202
+ reportDiagnostic(diagnostics, entryIndex, "invalid-envelope", "record must be JSON");
1203
+ return;
1204
+ }
1205
+ const parsed = parseRegistryEventRecord(entry.data, rootSessionId);
1206
+ if (parsed.kind === "foreign-root") return;
1207
+ if (parsed.kind === "event") {
1208
+ parsedEvents.push({ entryIndex, event: parsed.event });
1209
+ } else {
1210
+ reportDiagnostic(diagnostics, entryIndex, parsed.code, parsed.message);
1211
+ }
1212
+ });
1213
+
1214
+ const checkpointIndex = parsedEvents.findLastIndex(({ event }) => event.event === "checkpoint");
1215
+ const checkpointEvent = checkpointIndex >= 0 ? parsedEvents[checkpointIndex]?.event : undefined;
1216
+ const checkpoint = checkpointEvent?.event === "checkpoint" ? checkpointEvent.snapshot : undefined;
1217
+ const agents = new Map(
1218
+ (checkpoint?.agents ?? []).map((agent) => [agent.agent_id, structuredClone(agent)]),
154
1219
  );
1220
+ const tombstones = new Set(checkpoint?.tombstones ?? []);
1221
+ let ledger: DeliveryLedger = createDeliveryLedger({
1222
+ deliveries: checkpoint?.deliveries,
1223
+ coordination_deliveries: checkpoint?.coordination_deliveries,
1224
+ wait_claimed_turns: checkpoint?.wait_claimed_turns,
1225
+ next_delivery_sequence: checkpoint?.next_delivery_sequence,
1226
+ });
1227
+ const sequenceState: ReplayDeliverySequenceState = {
1228
+ terminalSequences: new Map(
1229
+ ledger.terminalDeliveries.map((delivery) => [
1230
+ terminalDeliveryIdentity(delivery),
1231
+ delivery.sequence ?? 0,
1232
+ ]),
1233
+ ),
1234
+ coordinationSequences: new Map(
1235
+ ledger.coordinationDeliveries.map((delivery) => [delivery.delivery_id, delivery.sequence]),
1236
+ ),
1237
+ highWaterMark: ledger.nextSequence - 1,
1238
+ };
155
1239
 
156
- for (const event of events.slice(checkpointIndex + 1)) {
1240
+ for (const { entryIndex, event } of parsedEvents.slice(checkpointIndex + 1)) {
157
1241
  switch (event.event) {
158
- case "checkpoint": {
159
- snapshot = cloneSnapshot(event.snapshot);
160
- agents.clear();
161
- for (const agent of snapshot.agents) agents.set(agent.agent_id, agent);
162
- tombstones.clear();
163
- for (const agentId of snapshot.tombstones) tombstones.add(agentId);
164
- deliveries.clear();
165
- for (const delivery of snapshot.deliveries) {
166
- deliveries.set(deliveryKey(delivery.source_agent_id, delivery.source_turn_id), delivery);
167
- }
1242
+ case "checkpoint":
168
1243
  break;
169
- }
170
1244
  case "agent-created":
171
- if (!tombstones.has(event.agent.agent_id))
1245
+ if (
1246
+ event.agent.active_turn_id ||
1247
+ event.agent.latest_result ||
1248
+ event.agent.recent_messages.length > 0 ||
1249
+ event.agent.deleted
1250
+ ) {
1251
+ reportDiagnostic(
1252
+ diagnostics,
1253
+ entryIndex,
1254
+ "invalid-event-fields",
1255
+ `agent-created contains post-creation state for ${event.agent.agent_id}`,
1256
+ );
1257
+ } else if (tombstones.has(event.agent.agent_id) || agents.has(event.agent.agent_id)) {
1258
+ reportDiagnostic(
1259
+ diagnostics,
1260
+ entryIndex,
1261
+ "invalid-event-reference",
1262
+ `agent-created duplicates or resurrects ${event.agent.agent_id}`,
1263
+ );
1264
+ } else if (event.agent.parent_id !== "root" && !agents.has(event.agent.parent_id)) {
1265
+ reportDiagnostic(
1266
+ diagnostics,
1267
+ entryIndex,
1268
+ "invalid-agent-hierarchy",
1269
+ `agent-created parent is missing for ${event.agent.agent_id}`,
1270
+ );
1271
+ } else {
172
1272
  agents.set(event.agent.agent_id, structuredClone(event.agent));
1273
+ }
173
1274
  break;
174
1275
  case "turn-started": {
175
1276
  const agent = agents.get(event.agent_id);
176
- if (agent) {
177
- agent.active_turn_id = event.turn_id;
178
- agent.active_turn_started_at = event.started_at;
1277
+ if (!agent || agent.active_turn_id) {
1278
+ reportDiagnostic(
1279
+ diagnostics,
1280
+ entryIndex,
1281
+ "invalid-event-reference",
1282
+ `turn-started cannot target ${event.agent_id}`,
1283
+ );
1284
+ break;
179
1285
  }
1286
+ agent.active_turn_id = event.turn_id;
1287
+ agent.active_turn_started_at = event.started_at;
1288
+ agent.latest_activity_at = event.started_at;
180
1289
  break;
181
1290
  }
182
1291
  case "turn-settled": {
183
1292
  const agent = agents.get(event.result.agent_id);
184
- if (agent) {
185
- agent.active_turn_id = undefined;
186
- agent.active_turn_started_at = undefined;
187
- agent.latest_result = structuredClone(event.result);
1293
+ if (!agent || agent.active_turn_id !== event.result.turn_id) {
1294
+ reportDiagnostic(
1295
+ diagnostics,
1296
+ entryIndex,
1297
+ "invalid-event-reference",
1298
+ `turn-settled does not match ${event.result.agent_id} active turn`,
1299
+ );
1300
+ break;
188
1301
  }
1302
+ agent.active_turn_id = undefined;
1303
+ agent.active_turn_started_at = undefined;
1304
+ agent.latest_result = structuredClone(event.result);
1305
+ agent.latest_activity_at = event.settled_at ?? event.timestamp;
1306
+ if (event.session_leaf_id) agent.session_leaf_id = event.session_leaf_id;
189
1307
  break;
190
1308
  }
191
- case "delivery-pending":
192
- deliveries.set(
193
- deliveryKey(event.delivery.source_agent_id, event.delivery.source_turn_id),
194
- structuredClone(event.delivery),
195
- );
1309
+ case "delivery-pending": {
1310
+ const delivery = observeTerminalEventSequence(sequenceState, event.delivery);
1311
+ if (!delivery) {
1312
+ reportDiagnostic(
1313
+ diagnostics,
1314
+ entryIndex,
1315
+ "invalid-delivery-sequence",
1316
+ "new terminal delivery sequences must strictly increase and updates must remain stable",
1317
+ );
1318
+ break;
1319
+ }
1320
+ const source = agents.get(delivery.source_agent_id);
1321
+ const identityError = validateDeliveryIdentity(delivery, delivery.result ? 2 : 1);
1322
+ if (identityError) {
1323
+ reportDiagnostic(diagnostics, entryIndex, "invalid-delivery-identity", identityError);
1324
+ } else if (!source) {
1325
+ reportDiagnostic(
1326
+ diagnostics,
1327
+ entryIndex,
1328
+ "invalid-event-reference",
1329
+ "terminal delivery source is not live",
1330
+ );
1331
+ } else if (source.parent_id !== delivery.destination_agent_id) {
1332
+ reportDiagnostic(
1333
+ diagnostics,
1334
+ entryIndex,
1335
+ "invalid-delivery-adjacency",
1336
+ "terminal delivery destination must be the direct parent",
1337
+ );
1338
+ } else {
1339
+ ledger = upsertTerminalDelivery(ledger, delivery).ledger;
1340
+ }
196
1341
  break;
1342
+ }
197
1343
  case "delivery-settled": {
198
- const delivery = deliveries.get(deliveryKey(event.source_agent_id, event.source_turn_id));
199
- if (delivery) {
200
- delivery.settled = event.error === undefined;
201
- delivery.error = event.error;
1344
+ const delivery = ledger.terminalDeliveries.find(
1345
+ (candidate) =>
1346
+ candidate.source_agent_id === event.source_agent_id &&
1347
+ candidate.source_turn_id === event.source_turn_id,
1348
+ );
1349
+ if (!delivery) {
1350
+ reportDiagnostic(
1351
+ diagnostics,
1352
+ entryIndex,
1353
+ "invalid-event-reference",
1354
+ "delivery-settled has no pending terminal item",
1355
+ );
1356
+ } else if (event.error !== undefined) {
1357
+ ledger = upsertTerminalDelivery(ledger, { ...delivery, error: event.error }).ledger;
1358
+ } else {
1359
+ ledger = settleTerminalDelivery(
1360
+ ledger,
1361
+ event.source_agent_id,
1362
+ event.source_turn_id,
1363
+ ).ledger;
1364
+ }
1365
+ break;
1366
+ }
1367
+ case "delivery-pruned": {
1368
+ const delivery = ledger.terminalDeliveries.find(
1369
+ (candidate) =>
1370
+ candidate.source_agent_id === event.source_agent_id &&
1371
+ candidate.source_turn_id === event.source_turn_id,
1372
+ );
1373
+ if (!delivery || delivery.path !== "wait") {
1374
+ reportDiagnostic(
1375
+ diagnostics,
1376
+ entryIndex,
1377
+ "invalid-event-reference",
1378
+ "delivery-pruned must target one pending wait-only terminal item",
1379
+ );
1380
+ break;
202
1381
  }
1382
+ ledger = settleTerminalDelivery(ledger, event.source_agent_id, event.source_turn_id).ledger;
1383
+ ledger = releaseEmptyDeliveryLedgerTurn(
1384
+ ledger,
1385
+ event.source_agent_id,
1386
+ event.source_turn_id,
1387
+ agents.get(event.source_agent_id)?.active_turn_id === event.source_turn_id,
1388
+ ).ledger;
203
1389
  break;
204
1390
  }
205
- case "agent-deleted":
1391
+ case "coordination-delivery-pending": {
1392
+ const delivery = observeCoordinationEventSequence(sequenceState, event.delivery);
1393
+ if (!delivery) {
1394
+ reportDiagnostic(
1395
+ diagnostics,
1396
+ entryIndex,
1397
+ "invalid-delivery-sequence",
1398
+ "new Coordination Message sequences must strictly increase and updates must remain stable",
1399
+ );
1400
+ break;
1401
+ }
1402
+ const identityError = validateCoordinationIdentity(delivery);
1403
+ if (identityError) {
1404
+ reportDiagnostic(diagnostics, entryIndex, "invalid-delivery-identity", identityError);
1405
+ } else if (
1406
+ !areAdjacentAgents(
1407
+ delivery.message.details.source_agent_id,
1408
+ delivery.destination_agent_id,
1409
+ agents,
1410
+ )
1411
+ ) {
1412
+ reportDiagnostic(
1413
+ diagnostics,
1414
+ entryIndex,
1415
+ "invalid-delivery-adjacency",
1416
+ "Coordination Message endpoints must be adjacent live agents",
1417
+ );
1418
+ } else {
1419
+ ledger = upsertCoordinationDelivery(ledger, delivery).ledger;
1420
+ }
1421
+ break;
1422
+ }
1423
+ case "coordination-delivery-settled": {
1424
+ const delivery = ledger.coordinationDeliveries.find(
1425
+ (candidate) => candidate.delivery_id === event.delivery_id,
1426
+ );
1427
+ if (!delivery) {
1428
+ reportDiagnostic(
1429
+ diagnostics,
1430
+ entryIndex,
1431
+ "invalid-event-reference",
1432
+ "coordination-delivery-settled has no pending item",
1433
+ );
1434
+ } else if (event.error !== undefined) {
1435
+ ledger = upsertCoordinationDelivery(ledger, { ...delivery, error: event.error }).ledger;
1436
+ } else {
1437
+ ledger = settleCoordinationDelivery(ledger, event.delivery_id).ledger;
1438
+ }
1439
+ break;
1440
+ }
1441
+ case "delivery-turn-claimed": {
1442
+ const source = agents.get(event.source_agent_id);
1443
+ if (
1444
+ !source ||
1445
+ !isObservableSourceTurn(
1446
+ source,
1447
+ event.source_turn_id,
1448
+ ledger.terminalDeliveries,
1449
+ ledger.coordinationDeliveries,
1450
+ ) ||
1451
+ isDeliveryLedgerTurnClaimed(ledger, event.source_agent_id, event.source_turn_id)
1452
+ ) {
1453
+ reportDiagnostic(
1454
+ diagnostics,
1455
+ entryIndex,
1456
+ "invalid-event-reference",
1457
+ "delivery-turn-claimed must reference one observable unclaimed live turn",
1458
+ );
1459
+ } else {
1460
+ ledger = claimDeliveryLedgerTurn(
1461
+ ledger,
1462
+ event.source_agent_id,
1463
+ event.source_turn_id,
1464
+ ).ledger;
1465
+ }
1466
+ break;
1467
+ }
1468
+ case "delivery-turn-released":
1469
+ if (!isDeliveryLedgerTurnClaimed(ledger, event.source_agent_id, event.source_turn_id)) {
1470
+ reportDiagnostic(
1471
+ diagnostics,
1472
+ entryIndex,
1473
+ "invalid-event-reference",
1474
+ "delivery-turn-released must reference one existing wait claim",
1475
+ );
1476
+ } else {
1477
+ ledger = releaseDeliveryLedgerTurn(
1478
+ ledger,
1479
+ event.source_agent_id,
1480
+ event.source_turn_id,
1481
+ ).ledger;
1482
+ }
1483
+ break;
1484
+ case "agent-message-recorded": {
1485
+ const agent = agents.get(event.agent_id);
1486
+ if (!agent) {
1487
+ reportDiagnostic(
1488
+ diagnostics,
1489
+ entryIndex,
1490
+ "invalid-event-reference",
1491
+ `agent-message-recorded target is not live: ${event.agent_id}`,
1492
+ );
1493
+ } else if (
1494
+ !validateEventAgentReference(event.message.source_agent_id, agents) ||
1495
+ !areAdjacentAgents(event.message.source_agent_id, event.agent_id, agents)
1496
+ ) {
1497
+ reportDiagnostic(
1498
+ diagnostics,
1499
+ entryIndex,
1500
+ "invalid-delivery-adjacency",
1501
+ "recorded message endpoints must be adjacent live agents",
1502
+ );
1503
+ } else {
1504
+ agent.recent_messages.push(structuredClone(event.message));
1505
+ agent.recent_messages = agent.recent_messages.slice(-20);
1506
+ agent.latest_activity_at = event.recorded_at;
1507
+ }
1508
+ break;
1509
+ }
1510
+ case "agent-deleted": {
1511
+ const deleting = new Set(event.agent_ids);
1512
+ if (event.agent_ids.some((agentId) => !agents.has(agentId))) {
1513
+ reportDiagnostic(
1514
+ diagnostics,
1515
+ entryIndex,
1516
+ "invalid-event-reference",
1517
+ "agent-deleted must target only currently live agents",
1518
+ );
1519
+ break;
1520
+ }
1521
+ const leavesOrphan = [...agents.values()].some(
1522
+ (agent) => deleting.has(agent.parent_id) && !deleting.has(agent.agent_id),
1523
+ );
1524
+ if (leavesOrphan) {
1525
+ reportDiagnostic(
1526
+ diagnostics,
1527
+ entryIndex,
1528
+ "invalid-agent-hierarchy",
1529
+ "agent-deleted must contain every live descendant in each deleted subtree",
1530
+ );
1531
+ break;
1532
+ }
1533
+ const belongsToDeletedSubtree = (agentId: string) =>
1534
+ event.agent_ids.some(
1535
+ (deletedId) => agentId === deletedId || agentId.startsWith(`${deletedId}.`),
1536
+ );
206
1537
  for (const agentId of event.agent_ids) {
207
1538
  agents.delete(agentId);
208
1539
  tombstones.add(agentId);
209
1540
  }
1541
+ for (const agent of agents.values()) {
1542
+ agent.recent_messages = agent.recent_messages.filter(
1543
+ (message) => !belongsToDeletedSubtree(message.source_agent_id),
1544
+ );
1545
+ }
1546
+ ledger = pruneDeliveryLedgerAgents(ledger, event.agent_ids).ledger;
210
1547
  break;
1548
+ }
211
1549
  }
212
1550
  }
213
1551
 
214
- return {
1552
+ if (diagnostics.length > 0) reportInvalidRecords?.(diagnostics);
1553
+ const ledgerSnapshot = deliveryLedgerSnapshot(ledger);
1554
+ const result: RegistrySnapshot = {
215
1555
  agents: [...agents.values()],
216
1556
  tombstones: [...tombstones],
217
- deliveries: [...deliveries.values()],
1557
+ deliveries: ledgerSnapshot.deliveries,
218
1558
  };
1559
+ if (ledgerSnapshot.coordination_deliveries.length > 0)
1560
+ result.coordination_deliveries = ledgerSnapshot.coordination_deliveries;
1561
+ if (ledgerSnapshot.wait_claimed_turns.length > 0)
1562
+ result.wait_claimed_turns = ledgerSnapshot.wait_claimed_turns;
1563
+ const nextDeliverySequence = Math.max(
1564
+ ledgerSnapshot.next_delivery_sequence,
1565
+ sequenceState.highWaterMark + 1,
1566
+ );
1567
+ if (nextDeliverySequence > 1) result.next_delivery_sequence = nextDeliverySequence;
1568
+ return result;
219
1569
  }