agent_sdk_core/records/journal.rs
1//! Durable run-journal records. Use these records as replayable truth for messages,
2//! effects, checkpoints, output, and recovery. Record constructors are data-only;
3//! append side effects happen through journal ports.
4//!
5pub use crate::domain::JournalCursor;
6
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 approval_records::ApprovalRecord,
11 domain::{
12 AgentError, AgentId, AgentPoolId, AttemptId, ContentRef, ContextProjectionId,
13 CorrelationEntry, DedupeKey, DestinationRef, EntityRef, IdempotencyKey, MessageId,
14 PolicyRef, PrivacyClass, RunId, SourceRef, TopicId, TurnId, WakeConditionId,
15 },
16 effect::{EffectIntent, EffectResult},
17 event::{EventCorrelation, EventFilterFingerprint},
18 extension_records::ExtensionActionRecord,
19 hook_records::HookRecord,
20 output_delivery::OutputDeliveryRecord,
21 provider::{ProviderStopReason, ProviderUsage},
22 realtime_records::RealtimeSessionRecord,
23 records_isolation::IsolationRecord,
24 stream_records::StreamRuleRecord,
25 structured_output::{
26 RepairExhaustionRecord, RepairRecord, StructuredOutputLifecycleRecord, ValidationRecord,
27 },
28 subagent_records::{ChildLifecycleRecord, SubagentRecord},
29 tool_records::ToolCallRecord,
30 validated_output::{TypedResultPublicationRecord, ValidatedOutput, ValidationReportRecord},
31};
32
33/// Constant value for the records::journal contract. Use it to keep SDK
34/// records and tests aligned on the same stable value.
35pub const JOURNAL_SCHEMA_VERSION: u16 = 1;
36
37#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
38#[serde(rename_all = "snake_case")]
39/// Enumerates the finite journal record kind cases.
40/// Serialized names are part of the SDK contract; update fixtures when variants change.
41pub enum JournalRecordKind {
42 /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
43 Run,
44 /// Use this variant when the contract needs to represent turn; selecting it has no side effect by itself.
45 Turn,
46 /// Use this variant when the contract needs to represent context; selecting it has no side effect by itself.
47 Context,
48 /// Use this variant when the contract needs to represent message; selecting it has no side effect by itself.
49 Message,
50 /// Use this variant when the contract needs to represent model attempt; selecting it has no side effect by itself.
51 ModelAttempt,
52 /// Use this variant when the contract needs to represent structured output; selecting it has no side effect by itself.
53 StructuredOutput,
54 /// Use this variant when the contract needs to represent stream rule; selecting it has no side effect by itself.
55 StreamRule,
56 /// Use this variant when the contract needs to represent realtime session; selecting it has no side effect by itself.
57 RealtimeSession,
58 /// Use this variant when the contract needs to represent hook; selecting it has no side effect by itself.
59 Hook,
60 /// Use this variant when the contract needs to represent approval; selecting it has no side effect by itself.
61 Approval,
62 /// Use this variant when the contract needs to represent tool; selecting it has no side effect by itself.
63 Tool,
64 /// Use this variant when the contract needs to represent isolation; selecting it has no side effect by itself.
65 Isolation,
66 /// Use this variant when the contract needs to represent child lifecycle; selecting it has no side effect by itself.
67 ChildLifecycle,
68 /// Use this variant when the contract needs to represent agent pool; selecting it has no side effect by itself.
69 AgentPool,
70 /// Use this variant when the contract needs to represent run message; selecting it has no side effect by itself.
71 RunMessage,
72 /// Use this variant when the contract needs to represent wake; selecting it has no side effect by itself.
73 Wake,
74 /// Use this variant when the contract needs to represent output dispatch; selecting it has no side effect by itself.
75 OutputDispatch,
76 /// Use this variant when the contract needs to represent subagent; selecting it has no side effect by itself.
77 Subagent,
78 /// Use this variant when the contract needs to represent extension action; selecting it has no side effect by itself.
79 ExtensionAction,
80 /// Use this variant when the contract needs to represent telemetry; selecting it has no side effect by itself.
81 Telemetry,
82 /// Use this variant when the contract needs to represent recovery; selecting it has no side effect by itself.
83 Recovery,
84 /// Use this variant when the contract needs to represent checkpoint; selecting it has no side effect by itself.
85 Checkpoint,
86 /// Use this variant when the contract needs to represent effect intent; selecting it has no side effect by itself.
87 EffectIntent,
88 /// Use this variant when the contract needs to represent effect result; selecting it has no side effect by itself.
89 EffectResult,
90}
91
92#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93/// Carries the event index projection record payload for journal, event, or fixture surfaces.
94/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
95pub struct EventIndexProjection {
96 /// Run identifier used for lineage, filtering, replay, and dedupe.
97 pub run_id: RunId,
98 /// Agent identifier used for lineage, filtering, and ownership checks.
99 pub agent_id: AgentId,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 /// Turn identifier for one loop turn within a run.
102 pub turn_id: Option<TurnId>,
103 /// Event family used by this record or request.
104 pub event_family: String,
105 /// Kind discriminator for event kind.
106 /// Use it to route finite match arms without parsing display text.
107 pub event_kind: String,
108 /// Source label or ref for this item; it is metadata and does not fetch
109 /// content by itself.
110 pub source: SourceRef,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 /// Destination label or ref for this item; it is metadata and does not
113 /// deliver content by itself.
114 pub destination: Option<DestinationRef>,
115 /// Typed subject ref reference. Resolving or executing it is a separate
116 /// policy-gated step.
117 pub subject_ref: EntityRef,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
119 /// Typed related refs references. Resolving them is separate from
120 /// constructing this record.
121 pub related_refs: Vec<EntityRef>,
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
123 /// Correlation-key selector for event filtering.
124 /// `Any` leaves correlation keys unconstrained; `Include` restricts matches to listed keys.
125 pub correlation_keys: Vec<CorrelationEntry>,
126 #[serde(default, skip_serializing_if = "Vec::is_empty")]
127 /// Tag selector for event filtering.
128 /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
129 pub tags: Vec<String>,
130 /// Privacy class used for projection, telemetry, and raw-content access
131 /// decisions.
132 pub privacy_class: PrivacyClass,
133 /// Delivery-semantic selector for event filtering.
134 /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
135 /// semantics.
136 pub delivery_semantics: String,
137}
138
139#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
140/// Carries the journal record record payload for journal, event, or fixture surfaces.
141/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
142pub struct JournalRecord {
143 /// Wire schema version for this record shape.
144 /// Use it for compatibility checks before deserializing or replaying stored data.
145 pub journal_schema_version: u16,
146 /// Journal seq used by this record or request.
147 pub journal_seq: u64,
148 /// Stable record id used for typed lineage, lookup, or dedupe.
149 pub record_id: String,
150 /// Kind discriminator for record kind.
151 /// Use it to route finite match arms without parsing display text.
152 pub record_kind: JournalRecordKind,
153 /// Run identifier used for lineage, filtering, replay, and dedupe.
154 pub run_id: RunId,
155 /// Agent identifier used for lineage, filtering, and ownership checks.
156 pub agent_id: AgentId,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 /// Turn identifier for one loop turn within a run.
159 pub turn_id: Option<TurnId>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 /// Attempt identifier for retry, repair, provider, or tool execution
162 /// evidence.
163 pub attempt_id: Option<AttemptId>,
164 /// Typed subject ref reference. Resolving or executing it is a separate
165 /// policy-gated step.
166 pub subject_ref: EntityRef,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 /// Typed related refs references. Resolving them is separate from
169 /// constructing this record.
170 pub related_refs: Vec<EntityRef>,
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 /// Typed causal refs references. Resolving them is separate from
173 /// constructing this record.
174 pub causal_refs: Vec<String>,
175 /// Source label or ref for this item; it is metadata and does not fetch
176 /// content by itself.
177 pub source: SourceRef,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 /// Destination label or ref for this item; it is metadata and does not
180 /// deliver content by itself.
181 pub destination: Option<DestinationRef>,
182 #[serde(default, skip_serializing_if = "Vec::is_empty")]
183 /// Correlation-key selector for event filtering.
184 /// `Any` leaves correlation keys unconstrained; `Include` restricts matches to listed keys.
185 pub correlation_keys: Vec<CorrelationEntry>,
186 #[serde(default, skip_serializing_if = "Vec::is_empty")]
187 /// Tag selector for event filtering.
188 /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
189 pub tags: Vec<String>,
190 /// Delivery-semantic selector for event filtering.
191 /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
192 /// semantics.
193 pub delivery_semantics: String,
194 /// Event index used by this record or request.
195 pub event_index: EventIndexProjection,
196 /// Timestamp in milliseconds associated with this record.
197 /// Use it for ordering and diagnostics; durable causality still comes from ids and cursors.
198 pub timestamp_millis: u64,
199 /// Fingerprint of the runtime package snapshot in force when this value was produced.
200 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
201 /// not execute package behavior.
202 pub runtime_package_fingerprint: String,
203 /// Privacy class used for projection, telemetry, and raw-content access
204 /// decisions.
205 pub privacy: PrivacyClass,
206 #[serde(default, skip_serializing_if = "Vec::is_empty")]
207 /// Content references associated with this record; resolving them is a
208 /// separate policy-gated step.
209 pub content_refs: Vec<ContentRef>,
210 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
211 pub redaction_policy_id: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 /// Idempotency setting or key for deduping retries.
214 /// Use it to prevent duplicate side effects during replay or repair.
215 pub idempotency_key: Option<IdempotencyKey>,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 /// Dedupe policy or key for a side-effecting operation.
218 /// Replay and repair use it to avoid sending or executing the same effect twice.
219 pub dedupe_key: Option<DedupeKey>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 /// Typed checkpoint ref reference. Resolving or executing it is a
222 /// separate policy-gated step.
223 pub checkpoint_ref: Option<String>,
224 /// Payload carried by this record.
225 /// Use the surrounding policy and redaction fields to decide whether it can be exposed.
226 pub payload: JournalRecordPayload,
227}
228
229impl JournalRecord {
230 /// Returns an updated records::journal value with effect intent applied.
231 /// This is data construction only and does not execute the configured
232 /// behavior.
233 pub fn effect_intent(base: JournalRecordBase, intent: EffectIntent) -> Self {
234 let mut event_index = base.event_index("effect", "intent");
235 let effect_ref =
236 EntityRef::new(crate::domain::EntityKind::Effect, intent.effect_id.clone());
237 event_index.subject_ref = intent.subject_ref.clone();
238 event_index.related_refs = vec![effect_ref.clone()];
239 Self {
240 journal_schema_version: JOURNAL_SCHEMA_VERSION,
241 journal_seq: base.journal_seq,
242 record_id: base.record_id,
243 record_kind: JournalRecordKind::EffectIntent,
244 run_id: base.run_id,
245 agent_id: base.agent_id,
246 turn_id: base.turn_id,
247 attempt_id: base.attempt_id,
248 subject_ref: intent.subject_ref.clone(),
249 related_refs: vec![effect_ref],
250 causal_refs: base.causal_refs,
251 source: intent.source.clone(),
252 destination: intent.destination.clone(),
253 correlation_keys: Vec::new(),
254 tags: base.tags,
255 delivery_semantics: "journal_backed".to_string(),
256 event_index,
257 timestamp_millis: base.timestamp_millis,
258 runtime_package_fingerprint: base.runtime_package_fingerprint,
259 privacy: base.privacy,
260 content_refs: intent.content_refs.clone(),
261 redaction_policy_id: base.redaction_policy_id,
262 idempotency_key: intent.idempotency_key.clone(),
263 dedupe_key: intent.dedupe_key.clone(),
264 checkpoint_ref: base.checkpoint_ref,
265 payload: JournalRecordPayload::EffectIntent(intent),
266 }
267 }
268
269 /// Builds the effect result record or result value.
270 /// This is data-only and does not perform I/O, call host ports, append journals, publish
271 /// events, or start processes.
272 pub fn effect_result(base: JournalRecordBase, result: EffectResult) -> Self {
273 let effect_ref =
274 EntityRef::new(crate::domain::EntityKind::Effect, result.effect_id.clone());
275 let mut event_index = base.event_index("effect", "result");
276 event_index.subject_ref = effect_ref.clone();
277 event_index.related_refs = vec![effect_ref.clone()];
278 Self {
279 journal_schema_version: JOURNAL_SCHEMA_VERSION,
280 journal_seq: base.journal_seq,
281 record_id: base.record_id,
282 record_kind: JournalRecordKind::EffectResult,
283 run_id: base.run_id,
284 agent_id: base.agent_id,
285 turn_id: base.turn_id,
286 attempt_id: base.attempt_id,
287 subject_ref: effect_ref.clone(),
288 related_refs: vec![effect_ref],
289 causal_refs: base.causal_refs,
290 source: base.source,
291 destination: base.destination,
292 correlation_keys: Vec::new(),
293 tags: base.tags,
294 delivery_semantics: "journal_backed".to_string(),
295 event_index,
296 timestamp_millis: base.timestamp_millis,
297 runtime_package_fingerprint: base.runtime_package_fingerprint,
298 privacy: base.privacy,
299 content_refs: result.content_refs.clone(),
300 redaction_policy_id: base.redaction_policy_id,
301 idempotency_key: None,
302 dedupe_key: None,
303 checkpoint_ref: base.checkpoint_ref,
304 payload: JournalRecordPayload::EffectResult(result),
305 }
306 }
307
308 /// Builds the checkpoint record or result value.
309 /// This is data-only and does not perform I/O, call host ports, append journals, publish
310 /// events, or start processes.
311 pub fn checkpoint(base: JournalRecordBase, checkpoint: RunCheckpoint) -> Self {
312 let event_index = base.event_index("checkpoint", "saved");
313 Self {
314 journal_schema_version: JOURNAL_SCHEMA_VERSION,
315 journal_seq: base.journal_seq,
316 record_id: base.record_id,
317 record_kind: JournalRecordKind::Checkpoint,
318 run_id: base.run_id.clone(),
319 agent_id: base.agent_id,
320 turn_id: base.turn_id,
321 attempt_id: base.attempt_id,
322 subject_ref: EntityRef::run(base.run_id),
323 related_refs: Vec::new(),
324 causal_refs: base.causal_refs,
325 source: base.source,
326 destination: base.destination,
327 correlation_keys: Vec::new(),
328 tags: base.tags,
329 delivery_semantics: "journal_backed".to_string(),
330 event_index,
331 timestamp_millis: base.timestamp_millis,
332 runtime_package_fingerprint: base.runtime_package_fingerprint,
333 privacy: base.privacy,
334 content_refs: checkpoint.content_ref_manifest.clone(),
335 redaction_policy_id: base.redaction_policy_id,
336 idempotency_key: None,
337 dedupe_key: None,
338 checkpoint_ref: Some(checkpoint.checkpoint_id.clone()),
339 payload: JournalRecordPayload::Checkpoint(checkpoint),
340 }
341 }
342
343 /// Recovery.
344 /// This is data-only and does not perform I/O, call host ports, append journals, publish
345 /// events, or start processes.
346 pub fn recovery(base: JournalRecordBase, recovery: RecoveryMarker) -> Self {
347 let event_index = base.event_index("recovery", "marker");
348 Self {
349 journal_schema_version: JOURNAL_SCHEMA_VERSION,
350 journal_seq: base.journal_seq,
351 record_id: base.record_id,
352 record_kind: JournalRecordKind::Recovery,
353 run_id: base.run_id.clone(),
354 agent_id: base.agent_id,
355 turn_id: base.turn_id,
356 attempt_id: base.attempt_id,
357 subject_ref: EntityRef::run(base.run_id),
358 related_refs: Vec::new(),
359 causal_refs: base.causal_refs,
360 source: base.source,
361 destination: base.destination,
362 correlation_keys: Vec::new(),
363 tags: base.tags,
364 delivery_semantics: "journal_backed".to_string(),
365 event_index,
366 timestamp_millis: base.timestamp_millis,
367 runtime_package_fingerprint: base.runtime_package_fingerprint,
368 privacy: base.privacy,
369 content_refs: Vec::new(),
370 redaction_policy_id: base.redaction_policy_id,
371 idempotency_key: None,
372 dedupe_key: None,
373 checkpoint_ref: base.checkpoint_ref,
374 payload: JournalRecordPayload::Recovery(recovery),
375 }
376 }
377
378 /// Builds the feature record record or result value.
379 /// This is data-only and does not perform I/O, call host ports, append journals, publish
380 /// events, or start processes.
381 #[expect(
382 clippy::too_many_arguments,
383 reason = "feature records intentionally expose journal/event lineage fields; replacing this with a builder needs a separate public API review"
384 )]
385 pub fn feature_record(
386 base: JournalRecordBase,
387 record_kind: JournalRecordKind,
388 event_family: impl Into<String>,
389 event_kind: impl Into<String>,
390 subject_ref: EntityRef,
391 related_refs: Vec<EntityRef>,
392 content_refs: Vec<ContentRef>,
393 payload: JournalRecordPayload,
394 ) -> Self {
395 let mut event_index = base.event_index(event_family, event_kind);
396 event_index.subject_ref = subject_ref.clone();
397 event_index.related_refs = related_refs.clone();
398 Self {
399 journal_schema_version: JOURNAL_SCHEMA_VERSION,
400 journal_seq: base.journal_seq,
401 record_id: base.record_id,
402 record_kind,
403 run_id: base.run_id,
404 agent_id: base.agent_id,
405 turn_id: base.turn_id,
406 attempt_id: base.attempt_id,
407 subject_ref,
408 related_refs,
409 causal_refs: base.causal_refs,
410 source: base.source,
411 destination: base.destination,
412 correlation_keys: Vec::new(),
413 tags: base.tags,
414 delivery_semantics: "journal_backed".to_string(),
415 event_index,
416 timestamp_millis: base.timestamp_millis,
417 runtime_package_fingerprint: base.runtime_package_fingerprint,
418 privacy: base.privacy,
419 content_refs,
420 redaction_policy_id: base.redaction_policy_id,
421 idempotency_key: None,
422 dedupe_key: None,
423 checkpoint_ref: base.checkpoint_ref,
424 payload,
425 }
426 }
427}
428
429#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
430/// Carries the journal record base record payload for journal, event, or fixture surfaces.
431/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
432pub struct JournalRecordBase {
433 /// Journal seq used by this record or request.
434 pub journal_seq: u64,
435 /// Stable record id used for typed lineage, lookup, or dedupe.
436 pub record_id: String,
437 /// Run identifier used for lineage, filtering, replay, and dedupe.
438 pub run_id: RunId,
439 /// Agent identifier used for lineage, filtering, and ownership checks.
440 pub agent_id: AgentId,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 /// Turn identifier for one loop turn within a run.
443 pub turn_id: Option<TurnId>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 /// Attempt identifier for retry, repair, provider, or tool execution
446 /// evidence.
447 pub attempt_id: Option<AttemptId>,
448 /// Source label or ref for this item; it is metadata and does not fetch
449 /// content by itself.
450 pub source: SourceRef,
451 #[serde(skip_serializing_if = "Option::is_none")]
452 /// Destination label or ref for this item; it is metadata and does not
453 /// deliver content by itself.
454 pub destination: Option<DestinationRef>,
455 #[serde(default, skip_serializing_if = "Vec::is_empty")]
456 /// Typed causal refs references. Resolving them is separate from
457 /// constructing this record.
458 pub causal_refs: Vec<String>,
459 #[serde(default, skip_serializing_if = "Vec::is_empty")]
460 /// Tag selector for event filtering.
461 /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
462 pub tags: Vec<String>,
463 /// Timestamp in milliseconds associated with this record.
464 /// Use it for ordering and diagnostics; durable causality still comes from ids and cursors.
465 pub timestamp_millis: u64,
466 /// Fingerprint of the runtime package snapshot in force when this value was produced.
467 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
468 /// not execute package behavior.
469 pub runtime_package_fingerprint: String,
470 /// Privacy class used for projection, telemetry, and raw-content access
471 /// decisions.
472 pub privacy: PrivacyClass,
473 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
474 pub redaction_policy_id: String,
475 #[serde(skip_serializing_if = "Option::is_none")]
476 /// Typed checkpoint ref reference. Resolving or executing it is a
477 /// separate policy-gated step.
478 pub checkpoint_ref: Option<String>,
479}
480
481impl JournalRecordBase {
482 /// Creates a new records::journal value with explicit
483 /// caller-provided inputs. This constructor is data-only and
484 /// performs no I/O or external side effects.
485 pub fn new(
486 journal_seq: u64,
487 record_id: impl Into<String>,
488 run_id: RunId,
489 agent_id: AgentId,
490 source: SourceRef,
491 ) -> Self {
492 Self {
493 journal_seq,
494 record_id: record_id.into(),
495 run_id,
496 agent_id,
497 turn_id: None,
498 attempt_id: None,
499 source,
500 destination: None,
501 causal_refs: Vec::new(),
502 tags: Vec::new(),
503 timestamp_millis: 0,
504 runtime_package_fingerprint: "runtime.package.fingerprint.test".to_string(),
505 privacy: PrivacyClass::ContentRefsOnly,
506 redaction_policy_id: "redaction.default".to_string(),
507 checkpoint_ref: None,
508 }
509 }
510
511 fn event_index(
512 &self,
513 event_family: impl Into<String>,
514 event_kind: impl Into<String>,
515 ) -> EventIndexProjection {
516 EventIndexProjection {
517 run_id: self.run_id.clone(),
518 agent_id: self.agent_id.clone(),
519 turn_id: self.turn_id.clone(),
520 event_family: event_family.into(),
521 event_kind: event_kind.into(),
522 source: self.source.clone(),
523 destination: self.destination.clone(),
524 subject_ref: EntityRef::run(self.run_id.clone()),
525 related_refs: Vec::new(),
526 correlation_keys: Vec::new(),
527 tags: self.tags.clone(),
528 privacy_class: self.privacy,
529 delivery_semantics: "journal_backed".to_string(),
530 }
531 }
532}
533
534#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
535#[serde(tag = "type", rename_all = "snake_case")]
536/// Enumerates the finite journal record payload cases.
537/// Serialized names are part of the SDK contract; update fixtures when variants change.
538#[expect(
539 clippy::large_enum_variant,
540 reason = "journal payloads are durable serde records; direct variant payloads are intentionally preserved until a fixture-reviewed envelope redesign"
541)]
542pub enum JournalRecordPayload {
543 /// Use this variant when the contract needs to represent run lifecycle; selecting it has no side effect by itself.
544 RunLifecycle(RunLifecycleRecord),
545 /// Use this variant when the contract needs to represent context projection; selecting it has no side effect by itself.
546 ContextProjection(ContextProjectionRecord),
547 /// Use this variant when the contract needs to represent model attempt; selecting it has no side effect by itself.
548 ModelAttempt(ModelAttemptRecord),
549 /// Use this variant when the contract needs to represent message; selecting it has no side effect by itself.
550 Message(MessageRecord),
551 /// Use this variant when the contract needs to represent structured output; selecting it has no side effect by itself.
552 StructuredOutput(StructuredOutputRecord),
553 /// Use this variant when the contract needs to represent approval; selecting it has no side effect by itself.
554 Approval(ApprovalRecord),
555 /// Use this variant when the contract needs to represent tool; selecting it has no side effect by itself.
556 Tool(ToolCallRecord),
557 /// Use this variant when the contract needs to represent output delivery; selecting it has no side effect by itself.
558 OutputDelivery(OutputDeliveryRecord),
559 /// Use this variant when the contract needs to represent hook; selecting it has no side effect by itself.
560 Hook(HookRecord),
561 /// Use this variant when the contract needs to represent stream rule; selecting it has no side effect by itself.
562 StreamRule(StreamRuleRecord),
563 /// Use this variant when the contract needs to represent realtime session; selecting it has no side effect by itself.
564 RealtimeSession(RealtimeSessionRecord),
565 /// Use this variant when the contract needs to represent isolation; selecting it has no side effect by itself.
566 Isolation(IsolationRecord),
567 /// Use this variant when the contract needs to represent child lifecycle; selecting it has no side effect by itself.
568 ChildLifecycle(ChildLifecycleRecord),
569 /// Use this variant when the contract needs to represent agent pool; selecting it has no side effect by itself.
570 AgentPool(AgentPoolRecord),
571 /// Use this variant when the contract needs to represent run message; selecting it has no side effect by itself.
572 RunMessage(RunMessageRecord),
573 /// Use this variant when the contract needs to represent wake; selecting it has no side effect by itself.
574 Wake(WakeRecord),
575 /// Use this variant when the contract needs to represent subagent; selecting it has no side effect by itself.
576 Subagent(SubagentRecord),
577 /// Use this variant when the contract needs to represent extension action; selecting it has no side effect by itself.
578 ExtensionAction(ExtensionActionRecord),
579 /// Use this variant when the contract needs to represent effect intent; selecting it has no side effect by itself.
580 EffectIntent(EffectIntent),
581 /// Use this variant when the contract needs to represent effect result; selecting it has no side effect by itself.
582 EffectResult(EffectResult),
583 /// Use this variant when the contract needs to represent checkpoint; selecting it has no side effect by itself.
584 Checkpoint(RunCheckpoint),
585 /// Use this variant when the contract needs to represent recovery; selecting it has no side effect by itself.
586 Recovery(RecoveryMarker),
587 /// Use this variant when the contract needs to represent terminal result; selecting it has no side effect by itself.
588 TerminalResult(TerminalResultMarker),
589}
590
591#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
592#[serde(tag = "record_type", content = "record", rename_all = "snake_case")]
593/// Enumerates the finite structured output record cases.
594/// Serialized names are part of the SDK contract; update fixtures when variants change.
595#[expect(
596 clippy::large_enum_variant,
597 reason = "structured-output payloads preserve serde and pattern-match ergonomics; boxing variants should be a dedicated contract migration"
598)]
599pub enum StructuredOutputRecord {
600 /// Use this variant when the contract needs to represent lifecycle; selecting it has no side effect by itself.
601 Lifecycle(StructuredOutputLifecycleRecord),
602 /// Use this variant when the contract needs to represent validation; selecting it has no side effect by itself.
603 Validation(ValidationRecord),
604 /// Use this variant when the contract needs to represent repair; selecting it has no side effect by itself.
605 Repair(RepairRecord),
606 /// Use this variant when the contract needs to represent repair exhaustion; selecting it has no side effect by itself.
607 RepairExhaustion(RepairExhaustionRecord),
608 /// Use this variant when the contract needs to represent validation report; selecting it has no side effect by itself.
609 ValidationReport(ValidationReportRecord),
610 /// Use this variant when the contract needs to represent validated output; selecting it has no side effect by itself.
611 ValidatedOutput(ValidatedOutput),
612 /// Use this variant when the contract needs to represent typed result publication; selecting it has no side effect by itself.
613 TypedResultPublication(TypedResultPublicationRecord),
614}
615
616#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
617/// Carries the run lifecycle record record payload for journal, event, or fixture surfaces.
618/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
619pub struct RunLifecycleRecord {
620 /// Finite status for this record or lifecycle stage.
621 pub status: String,
622 /// Redacted explanation for a denial, failure, status, or package delta.
623 pub reason: String,
624}
625
626#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
627/// Carries the context projection record record payload for journal, event, or fixture surfaces.
628/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
629pub struct ContextProjectionRecord {
630 /// Stable projection id used for typed lineage, lookup, or dedupe.
631 pub projection_id: ContextProjectionId,
632 /// Count of selected item items observed or included in this record.
633 pub selected_item_count: u32,
634 /// Provider destination used by this record or request.
635 pub provider_destination: DestinationRef,
636}
637
638#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
639/// Carries the model attempt record record payload for journal, event, or fixture surfaces.
640/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
641pub struct ModelAttemptRecord {
642 /// Stable provider route id used for typed lineage, lookup, or dedupe.
643 pub provider_route_id: String,
644 /// Stable provider model id used for typed lineage, lookup, or dedupe.
645 pub provider_model_id: String,
646 /// Count of request message items observed or included in this record.
647 pub request_message_count: u32,
648 #[serde(skip_serializing_if = "Option::is_none")]
649 /// Optional stop reason value.
650 /// When absent, callers should use the documented default or skip that optional behavior.
651 pub stop_reason: Option<ProviderStopReason>,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 /// Optional usage value.
654 /// When absent, callers should use the documented default or skip that optional behavior.
655 pub usage: Option<ProviderUsage>,
656}
657
658#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
659/// Carries the message record record payload for journal, event, or fixture surfaces.
660/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
661pub struct MessageRecord {
662 /// Message identifier for transcript, projection, or provider-response
663 /// lineage.
664 pub message_id: MessageId,
665 /// Role used by this record or request.
666 pub role: String,
667 /// Redacted human-readable summary safe for events, telemetry, and logs.
668 pub redacted_summary: String,
669}
670
671#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
672/// Carries the agent pool record record payload for journal, event, or fixture surfaces.
673/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
674pub struct AgentPoolRecord {
675 /// Stable pool id used for typed lineage, lookup, or dedupe.
676 pub pool_id: AgentPoolId,
677 #[serde(default, skip_serializing_if = "Vec::is_empty")]
678 /// Identifiers used to select or correlate member run values.
679 /// Use them for typed lookup, filtering, or lineage instead of stringly typed matching.
680 pub member_run_ids: Vec<RunId>,
681 #[serde(default, skip_serializing_if = "Vec::is_empty")]
682 /// Collection of topics values.
683 /// Ordering and membership should be treated as part of the serialized contract when
684 /// relevant.
685 pub topics: Vec<TopicId>,
686 #[serde(default, skip_serializing_if = "Vec::is_empty")]
687 /// Policy references that govern admission, projection, execution, or
688 /// delivery.
689 pub policy_refs: Vec<PolicyRef>,
690 /// Lifecycle status used by this record or request.
691 pub lifecycle_status: AgentPoolLifecycleStatus,
692}
693
694#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
695#[serde(rename_all = "snake_case")]
696/// Enumerates the finite agent pool lifecycle status cases.
697/// Serialized names are part of the SDK contract; update fixtures when variants change.
698pub enum AgentPoolLifecycleStatus {
699 /// Use this variant when the contract needs to represent created; selecting it has no side effect by itself.
700 Created,
701 /// Use this variant when the contract needs to represent run joined; selecting it has no side effect by itself.
702 RunJoined,
703 /// Use this variant when the contract needs to represent run left; selecting it has no side effect by itself.
704 RunLeft,
705}
706
707#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
708/// Carries the run message record record payload for journal, event, or fixture surfaces.
709/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
710pub struct RunMessageRecord {
711 /// Message identifier for transcript, projection, or provider-response
712 /// lineage.
713 pub message_id: MessageId,
714 /// Stable source run id used for typed lineage, lookup, or dedupe.
715 pub source_run_id: RunId,
716 /// Address target used by this record or request.
717 pub address_target: RunMessageAddressTargetRecord,
718 /// Content reference where payload bytes or structured tool output are
719 /// stored.
720 pub content_ref: ContentRef,
721 /// Correlation used by this record or request.
722 pub correlation: EventCorrelation,
723 #[serde(skip_serializing_if = "Option::is_none")]
724 /// Optional reply to value.
725 /// When absent, callers should use the documented default or skip that optional behavior.
726 pub reply_to: Option<MessageId>,
727 /// Output delivery setting or policy.
728 /// Delivery coordinators use it to decide sink mode, dedupe, and required evidence.
729 pub delivery_status: RunMessageDeliveryStatus,
730 #[serde(default, skip_serializing_if = "Vec::is_empty")]
731 /// Collection of delivered to values.
732 /// Ordering and membership should be treated as part of the serialized contract when
733 /// relevant.
734 pub delivered_to: Vec<RunId>,
735 #[serde(default, skip_serializing_if = "Vec::is_empty")]
736 /// Policy references that govern admission, projection, execution, or
737 /// delivery.
738 pub policy_refs: Vec<PolicyRef>,
739 /// Idempotency setting or key for deduping retries.
740 /// Use it to prevent duplicate side effects during replay or repair.
741 pub idempotency_key: IdempotencyKey,
742 #[serde(skip_serializing_if = "Option::is_none")]
743 /// Optional effect intent value.
744 /// When absent, callers should use the documented default or skip that optional behavior.
745 pub effect_intent: Option<EffectIntent>,
746 #[serde(skip_serializing_if = "Option::is_none")]
747 /// Optional effect result value.
748 /// When absent, callers should use the documented default or skip that optional behavior.
749 pub effect_result: Option<EffectResult>,
750}
751
752#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
753#[serde(tag = "type", rename_all = "snake_case")]
754/// Enumerates the finite run message address target record cases.
755/// Serialized names are part of the SDK contract; update fixtures when variants change.
756pub enum RunMessageAddressTargetRecord {
757 /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
758 Run {
759 /// Run identifier used for lineage, filtering, replay, and dedupe.
760 run_id: RunId,
761 },
762 /// Use this variant when the contract needs to represent agent; selecting it has no side effect by itself.
763 Agent {
764 /// Agent identifier used for lineage, filtering, and ownership
765 /// checks.
766 agent_id: AgentId,
767 },
768 /// Use this variant when the contract needs to represent topic; selecting it has no side effect by itself.
769 Topic {
770 /// Stable topic id used for typed lineage, lookup, or dedupe.
771 topic_id: TopicId,
772 },
773 /// Use this variant when the contract needs to represent pool; selecting it has no side effect by itself.
774 Pool {
775 /// Stable pool id used for typed lineage, lookup, or dedupe.
776 pool_id: AgentPoolId,
777 },
778}
779
780#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
781#[serde(rename_all = "snake_case")]
782/// Enumerates the finite run message delivery status cases.
783/// Serialized names are part of the SDK contract; update fixtures when variants change.
784pub enum RunMessageDeliveryStatus {
785 /// Use this variant when the contract needs to represent accepted; selecting it has no side effect by itself.
786 Accepted,
787 /// Use this variant when the contract needs to represent delivered; selecting it has no side effect by itself.
788 Delivered,
789 /// Use this variant when the contract needs to represent responded; selecting it has no side effect by itself.
790 Responded,
791 /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
792 Failed,
793 /// Use this variant when the contract needs to represent timed out; selecting it has no side effect by itself.
794 TimedOut,
795 /// Use this variant when the contract needs to represent expired; selecting it has no side effect by itself.
796 Expired,
797 /// Use this variant when the contract needs to represent cancelled; selecting it has no side effect by itself.
798 Cancelled,
799}
800
801#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
802/// Carries the wake record record payload for journal, event, or fixture surfaces.
803/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
804pub struct WakeRecord {
805 /// Stable condition id used for typed lineage, lookup, or dedupe.
806 pub condition_id: WakeConditionId,
807 /// Run identifier used for lineage, filtering, replay, and dedupe.
808 pub run_id: RunId,
809 /// Deterministic event filter fingerprint used for stale checks, package
810 /// evidence, or replay comparisons.
811 pub event_filter_fingerprint: EventFilterFingerprint,
812 #[serde(skip_serializing_if = "Option::is_none")]
813 /// Time value in milliseconds for timeout millis.
814 /// Use it for timeout, ordering, or diagnostic calculations.
815 pub timeout_millis: Option<u64>,
816 /// Resume policy used by this record or request.
817 pub resume_policy: WakeResumeInputPolicyRecord,
818 /// Trigger status used by this record or request.
819 pub trigger_status: WakeTriggerStatus,
820 #[serde(default, skip_serializing_if = "Vec::is_empty")]
821 /// Policy references that govern admission, projection, execution, or
822 /// delivery.
823 pub policy_refs: Vec<PolicyRef>,
824 /// Idempotency setting or key for deduping retries.
825 /// Use it to prevent duplicate side effects during replay or repair.
826 pub idempotency_key: IdempotencyKey,
827 #[serde(skip_serializing_if = "Option::is_none")]
828 /// Stable matched event id used for typed lineage, lookup, or dedupe.
829 pub matched_event_id: Option<crate::domain::EventId>,
830}
831
832#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
833#[serde(rename_all = "snake_case")]
834/// Enumerates the finite wake resume input policy record cases.
835/// Serialized names are part of the SDK contract; update fixtures when variants change.
836pub enum WakeResumeInputPolicyRecord {
837 /// Use this variant when the contract needs to represent matching event refs; selecting it has no side effect by itself.
838 MatchingEventRefs,
839 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
840 RedactedSummary,
841 /// Use this variant when the contract needs to represent none; selecting it has no side effect by itself.
842 None,
843}
844
845#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
846#[serde(rename_all = "snake_case")]
847/// Enumerates the finite wake trigger status cases.
848/// Serialized names are part of the SDK contract; update fixtures when variants change.
849pub enum WakeTriggerStatus {
850 /// Use this variant when the contract needs to represent registered; selecting it has no side effect by itself.
851 Registered,
852 /// Use this variant when the contract needs to represent triggered; selecting it has no side effect by itself.
853 Triggered,
854 /// Use this variant when the contract needs to represent timed out; selecting it has no side effect by itself.
855 TimedOut,
856 /// Use this variant when the contract needs to represent cancelled; selecting it has no side effect by itself.
857 Cancelled,
858 /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
859 Failed,
860}
861
862#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
863/// Carries the run checkpoint record payload for journal, event, or fixture surfaces.
864/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
865pub struct RunCheckpoint {
866 /// Stable checkpoint id used for typed lineage, lookup, or dedupe.
867 pub checkpoint_id: String,
868 /// Run identifier used for lineage, filtering, replay, and dedupe.
869 pub run_id: RunId,
870 /// Checkpoint seq used by this record or request.
871 pub checkpoint_seq: u64,
872 /// Covers journal seq used by this record or request.
873 pub covers_journal_seq: u64,
874 /// Loop state used by this record or request.
875 pub loop_state: String,
876 #[serde(skip_serializing_if = "Option::is_none")]
877 /// Turn identifier for one loop turn within a run.
878 pub turn_id: Option<TurnId>,
879 #[serde(skip_serializing_if = "Option::is_none")]
880 /// Attempt identifier for retry, repair, provider, or tool execution
881 /// evidence.
882 pub attempt_id: Option<AttemptId>,
883 /// Fingerprint of the runtime package snapshot in force when this value was produced.
884 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
885 /// not execute package behavior.
886 pub runtime_package_fingerprint: String,
887 #[serde(default, skip_serializing_if = "Vec::is_empty")]
888 /// Collection of pending side effects values.
889 /// Ordering and membership should be treated as part of the serialized contract when
890 /// relevant.
891 pub pending_side_effects: Vec<PendingSideEffect>,
892 #[serde(default, skip_serializing_if = "Vec::is_empty")]
893 /// Collection of pending approvals values.
894 /// Ordering and membership should be treated as part of the serialized contract when
895 /// relevant.
896 pub pending_approvals: Vec<String>,
897 #[serde(default, skip_serializing_if = "Vec::is_empty")]
898 /// Content reference associated with this value.
899 /// Resolve it through policy-gated content stores instead of embedding raw content.
900 pub content_ref_manifest: Vec<ContentRef>,
901 /// Deterministic state hash used for stale checks, package evidence, or
902 /// replay comparisons.
903 pub state_hash: String,
904 /// Time value in milliseconds for created at millis.
905 /// Use it for timeout, ordering, or diagnostic calculations.
906 pub created_at_millis: u64,
907 /// Stable writer id used for typed lineage, lookup, or dedupe.
908 pub writer_id: String,
909}
910
911impl RunCheckpoint {
912 /// Validates the records::journal invariants and returns a typed
913 /// error on failure. Validation is pure and does not perform I/O,
914 /// dispatch, journal appends, or adapter calls.
915 pub fn validate_against_latest_seq(&self, latest_journal_seq: u64) -> Result<(), AgentError> {
916 if self.covers_journal_seq > latest_journal_seq {
917 return Err(AgentError::contract_violation(
918 "checkpoint covers_journal_seq cannot point past latest committed journal record",
919 ));
920 }
921 Ok(())
922 }
923}
924
925#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
926/// Carries the pending side effect record payload for journal, event, or fixture surfaces.
927/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
928pub struct PendingSideEffect {
929 /// Stable effect id used for typed lineage, lookup, or dedupe.
930 pub effect_id: crate::domain::EffectId,
931 /// Stable intent record id used for typed lineage, lookup, or dedupe.
932 pub intent_record_id: String,
933 #[serde(skip_serializing_if = "Option::is_none")]
934 /// Idempotency setting or key for deduping retries.
935 /// Use it to prevent duplicate side effects during replay or repair.
936 pub idempotency_key: Option<IdempotencyKey>,
937 #[serde(skip_serializing_if = "Option::is_none")]
938 /// Dedupe policy or key for a side-effecting operation.
939 /// Replay and repair use it to avoid sending or executing the same effect twice.
940 pub dedupe_key: Option<DedupeKey>,
941 /// Reason a pending side effect is unsafe to retry automatically.
942 /// Recovery uses it to require repair or reconciliation before continuing.
943 pub unsafe_pending_reason: String,
944}
945
946#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
947/// Carries the terminal result marker record payload for journal, event, or fixture surfaces.
948/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
949pub struct TerminalResultMarker {
950 /// Stable effect id used for typed lineage, lookup, or dedupe.
951 pub effect_id: crate::domain::EffectId,
952 /// Stable result record id used for typed lineage, lookup, or dedupe.
953 pub result_record_id: String,
954 /// Terminal status used by this record or request.
955 pub terminal_status: String,
956}
957
958#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
959/// Carries the recovery marker record payload for journal, event, or fixture surfaces.
960/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
961pub struct RecoveryMarker {
962 /// Collection of unsafe pending values.
963 /// Ordering and membership should be treated as part of the serialized contract when
964 /// relevant.
965 pub unsafe_pending: Vec<PendingSideEffect>,
966 /// Recovery reason used by this record or request.
967 pub recovery_reason: String,
968 #[serde(default, skip_serializing_if = "Vec::is_empty")]
969 /// Policy references that govern admission, projection, execution, or
970 /// delivery.
971 pub policy_refs: Vec<PolicyRef>,
972}