agent_sdk_core/records/event.rs
1//! Canonical event records for live observation and telemetry projection. Use these
2//! records to describe what happened without requiring raw content capture.
3//! Constructors are data-only; delivery belongs to event buses and sinks.
4//!
5use std::num::NonZeroUsize;
6
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10use crate::{
11 domain::{
12 AgentError, AgentId, ArchiveCursorId, ContextItemId, CorrelationEntry, DestinationKind,
13 DestinationRef, EntityKind, EntityRef, EventId, JournalCursor, MessageId, PolicyRef,
14 PrivacyClass, RunId, SessionId, SourceKind, SourceRef, SpanId, TraceId, TurnId,
15 },
16 ids::AttemptId,
17};
18
19macro_rules! typed_string {
20 ($name:ident, $debug:literal) => {
21 #[doc = concat!(
22 "Typed event-string wrapper for `",
23 stringify!($name),
24 "`. Use it for stable event filter, cursor, or fingerprint fields; ",
25 "constructing it is data-only and performs no side effects."
26 )]
27 #[derive(Clone, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
28 #[serde(transparent)]
29 pub struct $name(String);
30
31 impl $name {
32 /// Creates a new records::event value with explicit
33 /// caller-provided inputs. This constructor is data-only
34 /// and performs no I/O or external side effects.
35 pub fn new(value: impl Into<String>) -> Self {
36 Self(value.into())
37 }
38
39 /// Returns this value as str. The accessor is side-effect
40 /// free and keeps ownership with the caller.
41 pub fn as_str(&self) -> &str {
42 &self.0
43 }
44 }
45
46 impl core::fmt::Debug for $name {
47 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 formatter.write_str(concat!($debug, "(redacted)"))
49 }
50 }
51
52 impl core::fmt::Display for $name {
53 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 formatter.write_str(concat!($debug, "(redacted)"))
55 }
56 }
57 };
58}
59
60/// Constant value for the records::event contract. Use it to keep SDK
61/// records and tests aligned on the same stable value.
62pub const EVENT_SCHEMA_VERSION: u16 = 1;
63
64#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
65#[serde(rename_all = "snake_case")]
66/// Enumerates the finite event family cases.
67/// Serialized names are part of the SDK contract; update fixtures when variants change.
68pub enum EventFamily {
69 /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
70 Run,
71 /// Use this variant when the contract needs to represent turn; selecting it has no side effect by itself.
72 Turn,
73 /// Use this variant when the contract needs to represent message; selecting it has no side effect by itself.
74 Message,
75 /// Use this variant when the contract needs to represent model; selecting it has no side effect by itself.
76 Model,
77 /// Use this variant when the contract needs to represent tool; selecting it has no side effect by itself.
78 Tool,
79 /// Use this variant when the contract needs to represent approval; selecting it has no side effect by itself.
80 Approval,
81 /// Use this variant when the contract needs to represent hook; selecting it has no side effect by itself.
82 Hook,
83 /// Use this variant when the contract needs to represent context; selecting it has no side effect by itself.
84 Context,
85 /// Use this variant when the contract needs to represent stream rule; selecting it has no side effect by itself.
86 StreamRule,
87 /// Use this variant when the contract needs to represent realtime; selecting it has no side effect by itself.
88 Realtime,
89 /// Use this variant when the contract needs to represent isolation; selecting it has no side effect by itself.
90 Isolation,
91 /// Use this variant when the contract needs to represent child lifecycle; selecting it has no side effect by itself.
92 ChildLifecycle,
93 /// Use this variant when the contract needs to represent agent pool; selecting it has no side effect by itself.
94 AgentPool,
95 /// Use this variant when the contract needs to represent subagent; selecting it has no side effect by itself.
96 Subagent,
97 /// Use this variant when the contract needs to represent extension; selecting it has no side effect by itself.
98 Extension,
99 /// Use this variant when the contract needs to represent structured output; selecting it has no side effect by itself.
100 StructuredOutput,
101 /// Use this variant when the contract needs to represent output; selecting it has no side effect by itself.
102 Output,
103 /// Use this variant when the contract needs to represent output delivery; selecting it has no side effect by itself.
104 OutputDelivery,
105 /// Use this variant when the contract needs to represent telemetry; selecting it has no side effect by itself.
106 Telemetry,
107 /// Use this variant when the contract needs to represent recovery; selecting it has no side effect by itself.
108 Recovery,
109}
110
111#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
112#[serde(rename_all = "snake_case")]
113/// Enumerates the finite event kind cases.
114/// Serialized names are part of the SDK contract; update fixtures when variants change.
115pub enum EventKind {
116 /// Use this variant when the contract needs to represent run started; selecting it has no side effect by itself.
117 RunStarted,
118 /// Use this variant when the contract needs to represent run completed; selecting it has no side effect by itself.
119 RunCompleted,
120 /// Use this variant when the contract needs to represent run failed; selecting it has no side effect by itself.
121 RunFailed,
122 /// Use this variant when the contract needs to represent run cancelled; selecting it has no side effect by itself.
123 RunCancelled,
124 /// Use this variant when the contract needs to represent run checkpointed; selecting it has no side effect by itself.
125 RunCheckpointed,
126 /// Use this variant when the contract needs to represent run cancel requested; selecting it has no side effect by itself.
127 RunCancelRequested,
128 /// Use this variant when the contract needs to represent run resume requested; selecting it has no side effect by itself.
129 RunResumeRequested,
130 /// Use this variant when the contract needs to represent run resume failed; selecting it has no side effect by itself.
131 RunResumeFailed,
132 /// Use this variant when the contract needs to represent turn started; selecting it has no side effect by itself.
133 TurnStarted,
134 /// Use this variant when the contract needs to represent turn completed; selecting it has no side effect by itself.
135 TurnCompleted,
136 /// Use this variant when the contract needs to represent turn failed; selecting it has no side effect by itself.
137 TurnFailed,
138 /// Use this variant when the contract needs to represent message accepted; selecting it has no side effect by itself.
139 MessageAccepted,
140 /// Use this variant when the contract needs to represent message committed; selecting it has no side effect by itself.
141 MessageCommitted,
142 /// Use this variant when the contract needs to represent provider request projected; selecting it has no side effect by itself.
143 ProviderRequestProjected,
144 /// Use this variant when the contract needs to represent model attempt started; selecting it has no side effect by itself.
145 ModelAttemptStarted,
146 /// Use this variant when the contract needs to represent model stream delta; selecting it has no side effect by itself.
147 ModelStreamDelta,
148 /// Use this variant when the contract needs to represent model message completed; selecting it has no side effect by itself.
149 ModelMessageCompleted,
150 /// Use this variant when the contract needs to represent model attempt failed; selecting it has no side effect by itself.
151 ModelAttemptFailed,
152 /// Use this variant when the contract needs to represent tool requested; selecting it has no side effect by itself.
153 ToolRequested,
154 /// Use this variant when the contract needs to represent tool started; selecting it has no side effect by itself.
155 ToolStarted,
156 /// Use this variant when the contract needs to represent tool completed; selecting it has no side effect by itself.
157 ToolCompleted,
158 /// Use this variant when the contract needs to represent tool failed; selecting it has no side effect by itself.
159 ToolFailed,
160 /// Use this variant when the contract needs to represent tool denied; selecting it has no side effect by itself.
161 ToolDenied,
162 /// Use this variant when the contract needs to represent tool recovery required; selecting it has no side effect by itself.
163 ToolRecoveryRequired,
164 /// Use this variant when the contract needs to represent approval requested; selecting it has no side effect by itself.
165 ApprovalRequested,
166 /// Use this variant when the contract needs to represent approval dispatched; selecting it has no side effect by itself.
167 ApprovalDispatched,
168 /// Use this variant when the contract needs to represent approval dispatch unavailable; selecting it has no side effect by itself.
169 ApprovalDispatchUnavailable,
170 /// Use this variant when the contract needs to represent approval responded; selecting it has no side effect by itself.
171 ApprovalResponded,
172 /// Use this variant when the contract needs to represent approval denied; selecting it has no side effect by itself.
173 ApprovalDenied,
174 /// Use this variant when the contract needs to represent approval timed out; selecting it has no side effect by itself.
175 ApprovalTimedOut,
176 /// Use this variant when the contract needs to represent approval cancelled; selecting it has no side effect by itself.
177 ApprovalCancelled,
178 /// Use this variant when the contract needs to represent hook registered; selecting it has no side effect by itself.
179 HookRegistered,
180 /// Use this variant when the contract needs to represent hook invoked; selecting it has no side effect by itself.
181 HookInvoked,
182 /// Use this variant when the contract needs to represent hook completed; selecting it has no side effect by itself.
183 HookCompleted,
184 /// Use this variant when the contract needs to represent hook failed; selecting it has no side effect by itself.
185 HookFailed,
186 /// Use this variant when the contract needs to represent hook timed out; selecting it has no side effect by itself.
187 HookTimedOut,
188 /// Use this variant when the contract needs to represent hook cancelled; selecting it has no side effect by itself.
189 HookCancelled,
190 /// Use this variant when the contract needs to represent hook response applied; selecting it has no side effect by itself.
191 HookResponseApplied,
192 /// Use this variant when the contract needs to represent hook response rejected; selecting it has no side effect by itself.
193 HookResponseRejected,
194 /// Use this variant when the contract needs to represent context assembled; selecting it has no side effect by itself.
195 ContextAssembled,
196 /// Use this variant when the contract needs to represent stream rule registered; selecting it has no side effect by itself.
197 StreamRuleRegistered,
198 /// Use this variant when the contract needs to represent stream rule compile failed; selecting it has no side effect by itself.
199 StreamRuleCompileFailed,
200 /// Use this variant when the contract needs to represent stream rule matched; selecting it has no side effect by itself.
201 StreamRuleMatched,
202 /// Use this variant when the contract needs to represent stream intervention requested; selecting it has no side effect by itself.
203 StreamInterventionRequested,
204 /// Use this variant when the contract needs to represent stream intervention applied; selecting it has no side effect by itself.
205 StreamInterventionApplied,
206 /// Use this variant when the contract needs to represent stream rule repeat state recorded; selecting it has no side effect by itself.
207 StreamRuleRepeatStateRecorded,
208 /// Use this variant when the contract needs to represent realtime connect requested; selecting it has no side effect by itself.
209 RealtimeConnectRequested,
210 /// Use this variant when the contract needs to represent realtime connected; selecting it has no side effect by itself.
211 RealtimeConnected,
212 /// Use this variant when the contract needs to represent realtime input send requested; selecting it has no side effect by itself.
213 RealtimeInputSendRequested,
214 /// Use this variant when the contract needs to represent realtime input sent; selecting it has no side effect by itself.
215 RealtimeInputSent,
216 /// Use this variant when the contract needs to represent realtime output receive requested; selecting it has no side effect by itself.
217 RealtimeOutputReceiveRequested,
218 /// Use this variant when the contract needs to represent realtime output received; selecting it has no side effect by itself.
219 RealtimeOutputReceived,
220 /// Use this variant when the contract needs to represent realtime interrupted; selecting it has no side effect by itself.
221 RealtimeInterrupted,
222 /// Use this variant when the contract needs to represent realtime restart requested; selecting it has no side effect by itself.
223 RealtimeRestartRequested,
224 /// Use this variant when the contract needs to represent realtime restart started; selecting it has no side effect by itself.
225 RealtimeRestartStarted,
226 /// Use this variant when the contract needs to represent realtime restart completed; selecting it has no side effect by itself.
227 RealtimeRestartCompleted,
228 /// Use this variant when the contract needs to represent realtime restart failed; selecting it has no side effect by itself.
229 RealtimeRestartFailed,
230 /// Use this variant when the contract needs to represent realtime close requested; selecting it has no side effect by itself.
231 RealtimeCloseRequested,
232 /// Use this variant when the contract needs to represent realtime closed; selecting it has no side effect by itself.
233 RealtimeClosed,
234 /// Use this variant when the contract needs to represent realtime backpressure applied; selecting it has no side effect by itself.
235 RealtimeBackpressureApplied,
236 /// Use this variant when the contract needs to represent isolation requested; selecting it has no side effect by itself.
237 IsolationRequested,
238 /// Use this variant when the contract needs to represent isolation adapter health checked; selecting it has no side effect by itself.
239 IsolationAdapterHealthChecked,
240 /// Use this variant when the contract needs to represent isolation capability matched; selecting it has no side effect by itself.
241 IsolationCapabilityMatched,
242 /// Use this variant when the contract needs to represent isolation downgrade denied; selecting it has no side effect by itself.
243 IsolationDowngradeDenied,
244 /// Use this variant when the contract needs to represent isolation downgrade approved; selecting it has no side effect by itself.
245 IsolationDowngradeApproved,
246 /// Use this variant when the contract needs to represent isolation environment prepared; selecting it has no side effect by itself.
247 IsolationEnvironmentPrepared,
248 /// Use this variant when the contract needs to represent isolation process started; selecting it has no side effect by itself.
249 IsolationProcessStarted,
250 /// Use this variant when the contract needs to represent isolation process io captured; selecting it has no side effect by itself.
251 IsolationProcessIoCaptured,
252 /// Use this variant when the contract needs to represent isolation process stats recorded; selecting it has no side effect by itself.
253 IsolationProcessStatsRecorded,
254 /// Use this variant when the contract needs to represent isolation cleanup started; selecting it has no side effect by itself.
255 IsolationCleanupStarted,
256 /// Use this variant when the contract needs to represent isolation cleanup completed; selecting it has no side effect by itself.
257 IsolationCleanupCompleted,
258 /// Use this variant when the contract needs to represent isolation cleanup failed; selecting it has no side effect by itself.
259 IsolationCleanupFailed,
260 /// Use this variant when the contract needs to represent isolation failed; selecting it has no side effect by itself.
261 IsolationFailed,
262 /// Use this variant when the contract needs to represent child lifecycle requested; selecting it has no side effect by itself.
263 ChildLifecycleRequested,
264 /// Use this variant when the contract needs to represent child lifecycle completed; selecting it has no side effect by itself.
265 ChildLifecycleCompleted,
266 /// Use this variant when the contract needs to represent child lifecycle detached; selecting it has no side effect by itself.
267 ChildLifecycleDetached,
268 /// Use this variant when the contract needs to represent subagent started; selecting it has no side effect by itself.
269 SubagentStarted,
270 /// Use this variant when the contract needs to represent subagent handoff; selecting it has no side effect by itself.
271 SubagentHandoff,
272 /// Use this variant when the contract needs to represent subagent event wrapped; selecting it has no side effect by itself.
273 SubagentEventWrapped,
274 /// Use this variant when the contract needs to represent subagent usage rolled up; selecting it has no side effect by itself.
275 SubagentUsageRolledUp,
276 /// Use this variant when the contract needs to represent subagent completed; selecting it has no side effect by itself.
277 SubagentCompleted,
278 /// Use this variant when the contract needs to represent extension action submitted; selecting it has no side effect by itself.
279 ExtensionActionSubmitted,
280 /// Use this variant when the contract needs to represent extension action started; selecting it has no side effect by itself.
281 ExtensionActionStarted,
282 /// Use this variant when the contract needs to represent extension action completed; selecting it has no side effect by itself.
283 ExtensionActionCompleted,
284 /// Use this variant when the contract needs to represent extension action failed; selecting it has no side effect by itself.
285 ExtensionActionFailed,
286 /// Use this variant when the contract needs to represent extension action denied; selecting it has no side effect by itself.
287 ExtensionActionDenied,
288 /// Use this variant when the contract needs to represent output dispatch requested; selecting it has no side effect by itself.
289 OutputDispatchRequested,
290 /// Use this variant when the contract needs to represent output dispatch completed; selecting it has no side effect by itself.
291 OutputDispatchCompleted,
292 /// Use this variant when the contract needs to represent output dispatch failed; selecting it has no side effect by itself.
293 OutputDispatchFailed,
294 /// Use this variant when the contract needs to represent output dispatch deduped; selecting it has no side effect by itself.
295 OutputDispatchDeduped,
296 /// Use this variant when the contract needs to represent structured output requested; selecting it has no side effect by itself.
297 StructuredOutputRequested,
298 /// Use this variant when the contract needs to represent structured output validation started; selecting it has no side effect by itself.
299 StructuredOutputValidationStarted,
300 /// Use this variant when the contract needs to represent structured output validation failed; selecting it has no side effect by itself.
301 StructuredOutputValidationFailed,
302 /// Use this variant when the contract needs to represent structured output repair requested; selecting it has no side effect by itself.
303 StructuredOutputRepairRequested,
304 /// Use this variant when the contract needs to represent structured output validated; selecting it has no side effect by itself.
305 StructuredOutputValidated,
306 /// Use this variant when the contract needs to represent structured output failed; selecting it has no side effect by itself.
307 StructuredOutputFailed,
308 /// Use this variant when the contract needs to represent telemetry sink failed; selecting it has no side effect by itself.
309 TelemetrySinkFailed,
310 /// Use this variant when the contract needs to represent telemetry sink recovered; selecting it has no side effect by itself.
311 TelemetrySinkRecovered,
312 /// Use this variant when the contract needs to represent usage recorded; selecting it has no side effect by itself.
313 UsageRecorded,
314 /// Use this variant when the contract needs to represent cost estimated; selecting it has no side effect by itself.
315 CostEstimated,
316 /// Use this variant when the contract needs to represent cost corrected; selecting it has no side effect by itself.
317 CostCorrected,
318 /// Use this variant when the contract needs to represent replay started; selecting it has no side effect by itself.
319 ReplayStarted,
320 /// Use this variant when the contract needs to represent replay completed; selecting it has no side effect by itself.
321 ReplayCompleted,
322 /// Use this variant when the contract needs to represent replay failed; selecting it has no side effect by itself.
323 ReplayFailed,
324 /// Use this variant when the contract needs to represent agent pool created; selecting it has no side effect by itself.
325 AgentPoolCreated,
326 /// Use this variant when the contract needs to represent agent pool run joined; selecting it has no side effect by itself.
327 AgentPoolRunJoined,
328 /// Use this variant when the contract needs to represent agent pool run left; selecting it has no side effect by itself.
329 AgentPoolRunLeft,
330 /// Use this variant when the contract needs to represent run message accepted; selecting it has no side effect by itself.
331 RunMessageAccepted,
332 /// Use this variant when the contract needs to represent run message delivered; selecting it has no side effect by itself.
333 RunMessageDelivered,
334 /// Use this variant when the contract needs to represent run message responded; selecting it has no side effect by itself.
335 RunMessageResponded,
336 /// Use this variant when the contract needs to represent run message failed; selecting it has no side effect by itself.
337 RunMessageFailed,
338 /// Use this variant when the contract needs to represent run message timed out; selecting it has no side effect by itself.
339 RunMessageTimedOut,
340 /// Use this variant when the contract needs to represent run message expired; selecting it has no side effect by itself.
341 RunMessageExpired,
342 /// Use this variant when the contract needs to represent run message cancelled; selecting it has no side effect by itself.
343 RunMessageCancelled,
344 /// Use this variant when the contract needs to represent wake condition registered; selecting it has no side effect by itself.
345 WakeConditionRegistered,
346 /// Use this variant when the contract needs to represent wake condition triggered; selecting it has no side effect by itself.
347 WakeConditionTriggered,
348 /// Use this variant when the contract needs to represent wake condition timed out; selecting it has no side effect by itself.
349 WakeConditionTimedOut,
350 /// Use this variant when the contract needs to represent wake condition cancelled; selecting it has no side effect by itself.
351 WakeConditionCancelled,
352 /// Use this variant when the contract needs to represent wake condition failed; selecting it has no side effect by itself.
353 WakeConditionFailed,
354}
355
356impl EventKind {
357 /// Reports whether this value is terminal. The check is pure and
358 /// does not mutate SDK or host state.
359 pub fn is_terminal(&self) -> bool {
360 matches!(
361 self,
362 Self::RunCompleted
363 | Self::RunFailed
364 | Self::RunCancelled
365 | Self::TurnCompleted
366 | Self::TurnFailed
367 | Self::ModelMessageCompleted
368 | Self::ModelAttemptFailed
369 | Self::ToolCompleted
370 | Self::ToolFailed
371 | Self::ToolDenied
372 | Self::ToolRecoveryRequired
373 | Self::ApprovalResponded
374 | Self::OutputDispatchCompleted
375 | Self::OutputDispatchFailed
376 | Self::OutputDispatchDeduped
377 | Self::ApprovalDenied
378 | Self::ApprovalTimedOut
379 | Self::ApprovalCancelled
380 | Self::ApprovalDispatchUnavailable
381 | Self::HookCompleted
382 | Self::HookFailed
383 | Self::HookTimedOut
384 | Self::HookCancelled
385 | Self::HookResponseApplied
386 | Self::HookResponseRejected
387 | Self::RealtimeRestartFailed
388 | Self::RealtimeClosed
389 | Self::IsolationCleanupCompleted
390 | Self::IsolationCleanupFailed
391 | Self::IsolationFailed
392 | Self::ChildLifecycleCompleted
393 | Self::ChildLifecycleDetached
394 | Self::SubagentCompleted
395 | Self::ExtensionActionCompleted
396 | Self::ExtensionActionFailed
397 | Self::ExtensionActionDenied
398 | Self::StructuredOutputValidated
399 | Self::StructuredOutputFailed
400 | Self::ReplayCompleted
401 | Self::ReplayFailed
402 | Self::RunMessageResponded
403 | Self::RunMessageFailed
404 | Self::RunMessageTimedOut
405 | Self::RunMessageExpired
406 | Self::RunMessageCancelled
407 | Self::WakeConditionTriggered
408 | Self::WakeConditionTimedOut
409 | Self::WakeConditionCancelled
410 | Self::WakeConditionFailed
411 )
412 }
413}
414
415#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
416/// Carries the agent event record payload for journal, event, or fixture surfaces.
417/// 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.
418pub struct AgentEvent {
419 /// Envelope used by this record or request.
420 pub envelope: EventEnvelope,
421 /// Payload carried by this record.
422 /// Use the surrounding policy and redaction fields to decide whether it can be exposed.
423 pub payload: EventPayload,
424}
425
426impl AgentEvent {
427 /// Builds the envelope only value.
428 /// This is data construction and performs no I/O, journal append, event publication, or
429 /// process work.
430 pub fn envelope_only(envelope: EventEnvelope) -> Self {
431 Self {
432 envelope,
433 payload: EventPayload::EnvelopeOnly,
434 }
435 }
436
437 /// Returns this value with its redacted summary setting replaced.
438 /// The method follows builder-style data construction and does not
439 /// execute external work.
440 pub fn with_redacted_summary(
441 envelope: EventEnvelope,
442 redacted_summary: impl Into<String>,
443 ) -> Self {
444 Self {
445 envelope,
446 payload: EventPayload::RedactedSummary {
447 redacted_summary: redacted_summary.into(),
448 payload_refs: Vec::new(),
449 },
450 }
451 }
452
453 /// Reads the stored redacted summary without registry or runtime work.
454 /// This is data-only and does not perform I/O, call host ports, append journals, publish
455 /// events, or start processes.
456 pub fn redacted_summary(&self) -> Option<&str> {
457 match &self.payload {
458 EventPayload::EnvelopeOnly => None,
459 EventPayload::RedactedSummary {
460 redacted_summary, ..
461 } => Some(redacted_summary.as_str()),
462 }
463 }
464}
465
466#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
467/// Carries the event envelope record payload for journal, event, or fixture surfaces.
468/// 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.
469pub struct EventEnvelope {
470 /// Wire schema version used for compatibility checks.
471 pub schema_version: u16,
472 /// Event identifier used to correlate live events with journal or replay
473 /// evidence.
474 pub event_id: EventId,
475 /// Event seq used by this record or request.
476 pub event_seq: u64,
477 /// Event family used by this record or request.
478 pub event_family: EventFamily,
479 /// Kind discriminator for event kind.
480 /// Use it to route finite match arms without parsing display text.
481 pub event_kind: EventKind,
482 /// Wire schema version for this record shape.
483 /// Use it for compatibility checks before deserializing or replaying stored data.
484 pub payload_schema_version: u16,
485 /// Timestamp in milliseconds associated with this record.
486 /// Use it for ordering and diagnostics; durable causality still comes from ids and cursors.
487 pub timestamp: String,
488 /// Recorded at used by this record or request.
489 pub recorded_at: String,
490 /// Run identifier used for lineage, filtering, replay, and dedupe.
491 pub run_id: RunId,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 /// Optional host-provided session identifier for grouping related turns.
494 pub session_id: Option<SessionId>,
495 /// Agent identifier used for lineage, filtering, and ownership checks.
496 pub agent_id: AgentId,
497 #[serde(skip_serializing_if = "Option::is_none")]
498 /// Turn identifier for one loop turn within a run.
499 pub turn_id: Option<TurnId>,
500 #[serde(skip_serializing_if = "Option::is_none")]
501 /// Attempt identifier for retry, repair, provider, or tool execution
502 /// evidence.
503 pub attempt_id: Option<AttemptId>,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 /// Message identifier for transcript, projection, or provider-response
506 /// lineage.
507 pub message_id: Option<MessageId>,
508 #[serde(skip_serializing_if = "Option::is_none")]
509 /// Stable context item id used for typed lineage, lookup, or dedupe.
510 pub context_item_id: Option<ContextItemId>,
511 /// Stable trace id used for typed lineage, lookup, or dedupe.
512 pub trace_id: TraceId,
513 /// Stable span id used for typed lineage, lookup, or dedupe.
514 pub span_id: SpanId,
515 #[serde(skip_serializing_if = "Option::is_none")]
516 /// Stable parent event id used for typed lineage, lookup, or dedupe.
517 pub parent_event_id: Option<EventId>,
518 #[serde(skip_serializing_if = "Option::is_none")]
519 /// Optional caused by value.
520 /// When absent, callers should use the documented default or skip that optional behavior.
521 pub caused_by: Option<CausalRef>,
522 /// Typed subject ref reference. Resolving or executing it is a separate
523 /// policy-gated step.
524 pub subject_ref: EntityRef,
525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
526 /// Typed related refs references. Resolving them is separate from
527 /// constructing this record.
528 pub related_refs: Vec<EntityRef>,
529 #[serde(default, skip_serializing_if = "Vec::is_empty")]
530 /// Typed causal refs references. Resolving them is separate from
531 /// constructing this record.
532 pub causal_refs: Vec<CausalRef>,
533 /// Correlation used by this record or request.
534 pub correlation: EventCorrelation,
535 #[serde(default, skip_serializing_if = "Vec::is_empty")]
536 /// Tag selector for event filtering.
537 /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
538 pub tags: Vec<EventTag>,
539 /// Source label or ref for this item; it is metadata and does not fetch
540 /// content by itself.
541 pub source: SourceRef,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 /// Destination label or ref for this item; it is metadata and does not
544 /// deliver content by itself.
545 pub destination: Option<DestinationRef>,
546 #[serde(default, skip_serializing_if = "Vec::is_empty")]
547 /// Policy references that govern admission, projection, execution, or
548 /// delivery.
549 pub policy_refs: Vec<PolicyRef>,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 /// Cursor identifying a replay, export, or subscription position.
552 /// Use it to resume without widening the original scope.
553 pub journal_cursor: Option<JournalCursor>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 /// Optional state before value.
556 /// When absent, callers should use the documented default or skip that optional behavior.
557 pub state_before: Option<String>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 /// Optional state after value.
560 /// When absent, callers should use the documented default or skip that optional behavior.
561 pub state_after: Option<String>,
562 /// Delivery-semantic selector for event filtering.
563 /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
564 /// semantics.
565 pub delivery_semantics: EventDeliverySemantics,
566 /// Privacy class used for projection, telemetry, and raw-content access
567 /// decisions.
568 pub privacy: PrivacyClass,
569 /// Content capture used by this record or request.
570 pub content_capture: ContentCaptureMode,
571 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
572 pub redaction_policy_id: String,
573 /// Fingerprint of the runtime package snapshot in force when this value was produced.
574 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
575 /// not execute package behavior.
576 pub runtime_package_fingerprint: String,
577}
578
579impl EventEnvelope {
580 /// Builds the cursor value.
581 /// This is data construction and performs no I/O, journal append, event publication, or
582 /// process work.
583 pub fn cursor(&self, scope: EventStreamScope) -> EventCursor {
584 EventCursor {
585 scope,
586 event_seq: self.event_seq,
587 event_id: self.event_id.clone(),
588 journal_cursor: self.journal_cursor.clone(),
589 }
590 }
591
592 /// Builds the redacted summary value.
593 /// This is data construction and performs no I/O, journal append, event publication, or
594 /// process work.
595 pub fn redacted_summary(&self) -> String {
596 format!("{:?}/{:?}", self.event_family, self.event_kind)
597 }
598}
599
600#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
601#[serde(tag = "mode", rename_all = "snake_case")]
602/// Enumerates the finite event payload cases.
603/// Serialized names are part of the SDK contract; update fixtures when variants change.
604pub enum EventPayload {
605 /// Use this variant when the contract needs to represent envelope only; selecting it has no side effect by itself.
606 EnvelopeOnly,
607 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
608 RedactedSummary {
609 /// Redacted human-readable summary safe for events, telemetry, and
610 /// logs.
611 redacted_summary: String,
612 #[serde(default, skip_serializing_if = "Vec::is_empty")]
613 /// Typed payload refs references. Resolving them is separate from
614 /// constructing this record.
615 payload_refs: Vec<EntityRef>,
616 },
617}
618
619#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
620/// Carries the causal ref record payload for journal, event, or fixture surfaces.
621/// 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.
622pub struct CausalRef {
623 #[serde(skip_serializing_if = "Option::is_none")]
624 /// Event identifier used to correlate live events with journal or replay
625 /// evidence.
626 pub event_id: Option<EventId>,
627 /// Typed subject ref reference. Resolving or executing it is a separate
628 /// policy-gated step.
629 pub subject_ref: EntityRef,
630 #[serde(skip_serializing_if = "Option::is_none")]
631 /// Redacted explanation for a denial, failure, status, or package delta.
632 pub reason: Option<String>,
633}
634
635#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
636/// Carries the event correlation record payload for journal, event, or fixture surfaces.
637/// 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.
638pub struct EventCorrelation {
639 #[serde(default, skip_serializing_if = "Vec::is_empty")]
640 /// Bounded entries included in this record. Limits and truncation are
641 /// represented by companion metadata when applicable.
642 pub entries: Vec<CorrelationEntry>,
643}
644
645#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
646#[serde(transparent)]
647/// Carries the event tag record payload for journal, event, or fixture surfaces.
648/// 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.
649pub struct EventTag(String);
650
651impl EventTag {
652 /// Creates a new records::event value with explicit caller-provided
653 /// inputs. This constructor is data-only and performs no I/O or
654 /// external side effects.
655 pub fn new(value: impl Into<String>) -> Self {
656 Self(value.into())
657 }
658
659 /// Returns this value as str. The accessor is side-effect free and
660 /// keeps ownership with the caller.
661 pub fn as_str(&self) -> &str {
662 &self.0
663 }
664}
665
666#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
667/// Carries the event frame record payload for journal, event, or fixture surfaces.
668/// 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.
669pub struct EventFrame {
670 /// Event used by this record or request.
671 pub event: AgentEvent,
672 /// Cursor identifying a replay, export, or subscription position.
673 /// Use it to resume without widening the original scope.
674 pub cursor: EventCursor,
675 #[serde(skip_serializing_if = "Option::is_none")]
676 /// Cursor identifying a replay, export, or subscription position.
677 /// Use it to resume without widening the original scope.
678 pub archive_cursor: Option<ArchiveCursor>,
679 #[serde(skip_serializing_if = "Option::is_none")]
680 /// Overflow policy applied when a subscriber queue reaches capacity.
681 /// It decides whether to drop, summarize, backpressure, or fail the subscriber.
682 pub overflow: Option<EventOverflowNotice>,
683}
684
685#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
686/// Carries the event cursor record payload for journal, event, or fixture surfaces.
687/// 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.
688pub struct EventCursor {
689 /// Scope used by this record or request.
690 pub scope: EventStreamScope,
691 /// Event seq used by this record or request.
692 pub event_seq: u64,
693 /// Event identifier used to correlate live events with journal or replay
694 /// evidence.
695 pub event_id: EventId,
696 #[serde(skip_serializing_if = "Option::is_none")]
697 /// Cursor identifying a replay, export, or subscription position.
698 /// Use it to resume without widening the original scope.
699 pub journal_cursor: Option<JournalCursor>,
700}
701
702#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
703#[serde(rename_all = "snake_case")]
704/// Enumerates the finite event stream scope cases.
705/// Serialized names are part of the SDK contract; update fixtures when variants change.
706pub enum EventStreamScope {
707 /// Use this variant when the contract needs to represent all; selecting it has no side effect by itself.
708 All,
709 /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
710 Run(RunId),
711 /// Use this variant when the contract needs to represent agent; selecting it has no side effect by itself.
712 Agent(AgentId),
713 /// Use this variant when the contract needs to represent filter; selecting it has no side effect by itself.
714 Filter {
715 /// Stable filter id used for typed lineage, lookup, or dedupe.
716 filter_id: EventFilterId,
717 /// Deterministic filter fingerprint used for stale checks, package
718 /// evidence, or replay comparisons.
719 filter_fingerprint: EventFilterFingerprint,
720 },
721}
722
723#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
724/// Carries the archive cursor record payload for journal, event, or fixture surfaces.
725/// 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.
726pub struct ArchiveCursor {
727 /// Stable archive id used for typed lineage, lookup, or dedupe.
728 pub archive_id: ArchiveCursorId,
729 /// Position used by this record or request.
730 pub position: String,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 /// Event identifier used to correlate live events with journal or replay
733 /// evidence.
734 pub event_id: Option<EventId>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 /// Optional watermark value.
737 /// When absent, callers should use the documented default or skip that optional behavior.
738 pub watermark: Option<String>,
739}
740
741#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
742/// Carries the event overflow notice record payload for journal, event, or fixture surfaces.
743/// 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.
744pub struct EventOverflowNotice {
745 /// Policy used by this record or request.
746 pub policy: SubscriberOverflowPolicy,
747 /// Count of dropped items observed or included in this record.
748 pub dropped_count: u64,
749 #[serde(skip_serializing_if = "Option::is_none")]
750 /// Optional gap start value.
751 /// When absent, callers should use the documented default or skip that optional behavior.
752 pub gap_start: Option<EventCursor>,
753 /// Gap end used by this record or request.
754 pub gap_end: EventCursor,
755 #[serde(skip_serializing_if = "Option::is_none")]
756 /// Repair policy used after structured output validation fails.
757 /// It controls whether repair is attempted and which policy gates must approve it.
758 pub repair_from: Option<JournalCursor>,
759 /// Whether terminal preserved is enabled.
760 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
761 pub terminal_preserved: bool,
762 /// Redacted explanation for a denial, failure, status, or package delta.
763 pub reason: EventOverflowReason,
764}
765
766#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
767#[serde(rename_all = "snake_case")]
768/// Enumerates the finite event overflow reason cases.
769/// Serialized names are part of the SDK contract; update fixtures when variants change.
770pub enum EventOverflowReason {
771 /// Use this variant when the contract needs to represent subscriber queue full; selecting it has no side effect by itself.
772 SubscriberQueueFull,
773 /// Use this variant when the contract needs to represent subscriber lagged; selecting it has no side effect by itself.
774 SubscriberLagged,
775 /// Use this variant when the contract needs to represent live buffer expired; selecting it has no side effect by itself.
776 LiveBufferExpired,
777 /// Use this variant when the contract needs to represent policy dropped progress; selecting it has no side effect by itself.
778 PolicyDroppedProgress,
779 /// Use this variant when the contract needs to represent policy dropped non terminal; selecting it has no side effect by itself.
780 PolicyDroppedNonTerminal,
781}
782
783#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
784#[serde(rename_all = "snake_case")]
785/// Enumerates the finite event delivery semantics cases.
786/// Serialized names are part of the SDK contract; update fixtures when variants change.
787pub enum EventDeliverySemantics {
788 /// Use this variant when the contract needs to represent best effort live; selecting it has no side effect by itself.
789 BestEffortLive,
790 /// Use this variant when the contract needs to represent journal backed; selecting it has no side effect by itself.
791 JournalBacked,
792 /// Use this variant when the contract needs to represent derived replay; selecting it has no side effect by itself.
793 DerivedReplay,
794 /// Use this variant when the contract needs to represent diagnostic only; selecting it has no side effect by itself.
795 DiagnosticOnly,
796}
797
798#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
799#[serde(rename_all = "snake_case")]
800/// Enumerates the finite content capture mode cases.
801/// Serialized names are part of the SDK contract; update fixtures when variants change.
802pub enum ContentCaptureMode {
803 /// Use this variant when the contract needs to represent off; selecting it has no side effect by itself.
804 Off,
805 /// Use this variant when the contract needs to represent metadata only; selecting it has no side effect by itself.
806 MetadataOnly,
807 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
808 RedactedSummary,
809 /// Use this variant when the contract needs to represent payload refs; selecting it has no side effect by itself.
810 PayloadRefs,
811 /// Use this variant when the contract needs to represent raw content; selecting it has no side effect by itself.
812 RawContent,
813}
814
815#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
816#[serde(rename_all = "snake_case")]
817/// Enumerates the finite payload access mode cases.
818/// Serialized names are part of the SDK contract; update fixtures when variants change.
819pub enum PayloadAccessMode {
820 /// Use this variant when the contract needs to represent envelope only; selecting it has no side effect by itself.
821 EnvelopeOnly,
822 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
823 RedactedSummary,
824 /// Use this variant when the contract needs to represent payload refs; selecting it has no side effect by itself.
825 PayloadRefs,
826 /// Use this variant when the contract needs to represent full payload if policy allows; selecting it has no side effect by itself.
827 FullPayloadIfPolicyAllows,
828}
829
830#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
831#[serde(rename_all = "snake_case")]
832/// Enumerates the finite subscriber overflow policy cases.
833/// Serialized names are part of the SDK contract; update fixtures when variants change.
834pub enum SubscriberOverflowPolicy {
835 /// Use this variant when the contract needs to represent drop non terminal; selecting it has no side effect by itself.
836 DropNonTerminal,
837 /// Use this variant when the contract needs to represent drop progress; selecting it has no side effect by itself.
838 DropProgress,
839 /// Use this variant when the contract needs to represent summarize and continue; selecting it has no side effect by itself.
840 SummarizeAndContinue,
841 /// Use this variant when the contract needs to represent backpressure caller; selecting it has no side effect by itself.
842 BackpressureCaller,
843 /// Use this variant when the contract needs to represent fail subscriber; selecting it has no side effect by itself.
844 FailSubscriber,
845}
846
847#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
848/// Carries the subscriber queue config record payload for journal, event, or fixture surfaces.
849/// 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.
850pub struct SubscriberQueueConfig {
851 /// Total subscriber queue capacity.
852 /// This bounds buffered event frames for a live subscriber.
853 pub capacity: NonZeroUsize,
854 /// Queue slots reserved for terminal frames.
855 /// This keeps important terminal events available even when non-terminal frames overflow.
856 pub terminal_reserve: NonZeroUsize,
857 /// Overflow policy applied when a subscriber queue reaches capacity.
858 /// It decides whether to drop, summarize, backpressure, or fail the subscriber.
859 pub overflow: SubscriberOverflowPolicy,
860}
861
862impl Default for SubscriberQueueConfig {
863 fn default() -> Self {
864 Self {
865 capacity: NonZeroUsize::new(64).expect("nonzero default capacity"),
866 terminal_reserve: NonZeroUsize::new(1).expect("nonzero terminal reserve"),
867 overflow: SubscriberOverflowPolicy::DropNonTerminal,
868 }
869 }
870}
871
872#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
873#[serde(rename_all = "snake_case")]
874/// Enumerates the finite event filter set cases.
875/// Serialized names are part of the SDK contract; update fixtures when variants change.
876pub enum EventFilterSet<T> {
877 #[default]
878 /// Use this variant when the contract needs to represent any; selecting it has no side effect by itself.
879 Any,
880 /// Use this variant when the contract needs to represent include; selecting it has no side effect by itself.
881 Include(Vec<T>),
882}
883
884impl<T: PartialEq> EventFilterSet<T> {
885 /// Returns matches for the current value.
886 /// This is a read-only or data-construction helper unless the method body explicitly calls
887 /// a port or store.
888 pub fn matches(&self, candidate: &T) -> bool {
889 match self {
890 Self::Any => true,
891 Self::Include(values) => values.contains(candidate),
892 }
893 }
894
895 /// Reports whether this value is any. The check is pure and does
896 /// not mutate SDK or host state.
897 pub fn is_any(&self) -> bool {
898 matches!(self, Self::Any)
899 }
900}
901
902#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
903/// Carries the event filter record payload for journal, event, or fixture surfaces.
904/// 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.
905pub struct EventFilter {
906 /// Run-id selector for event filtering.
907 /// `Any` leaves run ids unconstrained; `Include` restricts matches to the listed runs.
908 pub run_ids: EventFilterSet<RunId>,
909 /// Session-id selector for event filtering.
910 /// `Any` leaves session ids unconstrained; `Include` restricts matches to listed sessions.
911 pub session_ids: EventFilterSet<SessionId>,
912 /// Agent-id selector for event filtering.
913 /// `Any` leaves agent ids unconstrained; `Include` restricts matches to the listed agents.
914 pub agent_ids: EventFilterSet<AgentId>,
915 /// Turn-id selector for event filtering.
916 /// `Any` leaves turn ids unconstrained; `Include` restricts matches to the listed turns.
917 pub turn_ids: EventFilterSet<TurnId>,
918 /// Event-family selector for event filtering.
919 /// `Any` leaves event families unconstrained; `Include` restricts matches to listed
920 /// families.
921 pub families: EventFilterSet<EventFamily>,
922 /// Event-kind selector for event filtering.
923 /// `Any` leaves event kinds unconstrained; `Include` restricts matches to listed event
924 /// kinds.
925 pub kinds: EventFilterSet<EventKind>,
926 /// Source-kind selector for event filtering.
927 /// `Any` leaves source kinds unconstrained; `Include` restricts matches to listed source
928 /// kinds.
929 pub source_kinds: EventFilterSet<SourceKind>,
930 /// Destination-kind selector for event filtering.
931 /// `Any` leaves destination kinds unconstrained; `Include` restricts matches to listed
932 /// destination kinds.
933 pub destination_kinds: EventFilterSet<DestinationKind>,
934 /// Subject entity-kind selector for event filtering.
935 /// `Any` leaves subject kinds unconstrained; `Include` restricts matches to listed entity
936 /// kinds.
937 pub subject_kinds: EventFilterSet<EntityKind>,
938 /// Related-entity kind selector for event filtering.
939 /// `Any` leaves related kinds unconstrained; `Include` restricts matches to listed entity
940 /// kinds.
941 pub related_entity_kinds: EventFilterSet<EntityKind>,
942 /// Correlation-key selector for event filtering.
943 /// `Any` leaves correlation keys unconstrained; `Include` restricts matches to listed keys.
944 pub correlation_keys: EventFilterSet<crate::domain::CorrelationKey>,
945 /// Tag selector for event filtering.
946 /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
947 pub tags: EventFilterSet<EventTag>,
948 /// Privacy-class selector for event filtering.
949 /// `Any` leaves privacy classes unconstrained; `Include` restricts matches to listed
950 /// classes.
951 pub privacy_classes: EventFilterSet<PrivacyClass>,
952 /// Delivery-semantic selector for event filtering.
953 /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
954 /// semantics.
955 pub delivery_semantics: EventFilterSet<EventDeliverySemantics>,
956 /// Whether the filter should match only terminal event frames.
957 /// When true, non-terminal frames are excluded even if all other selectors match.
958 pub terminal_only: bool,
959 /// Payload access mode allowed for matching event frames.
960 /// Use it to keep subscriptions envelope-only unless payload access is explicitly
961 /// requested.
962 pub payload_access: PayloadAccessMode,
963 /// Subscriber queue settings used for streams created with this filter.
964 /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
965 pub queue: SubscriberQueueConfig,
966}
967
968impl Default for EventFilter {
969 fn default() -> Self {
970 Self {
971 run_ids: EventFilterSet::Any,
972 session_ids: EventFilterSet::Any,
973 agent_ids: EventFilterSet::Any,
974 turn_ids: EventFilterSet::Any,
975 families: EventFilterSet::Any,
976 kinds: EventFilterSet::Any,
977 source_kinds: EventFilterSet::Any,
978 destination_kinds: EventFilterSet::Any,
979 subject_kinds: EventFilterSet::Any,
980 related_entity_kinds: EventFilterSet::Any,
981 correlation_keys: EventFilterSet::Any,
982 tags: EventFilterSet::Any,
983 privacy_classes: EventFilterSet::Any,
984 delivery_semantics: EventFilterSet::Any,
985 terminal_only: false,
986 payload_access: PayloadAccessMode::EnvelopeOnly,
987 queue: SubscriberQueueConfig::default(),
988 }
989 }
990}
991
992impl EventFilter {
993 /// Builds the terminal run events value.
994 /// This is data construction and performs no I/O, journal append, event publication, or
995 /// process work.
996 pub fn terminal_run_events() -> Self {
997 Self {
998 families: EventFilterSet::Include(vec![EventFamily::Run]),
999 terminal_only: true,
1000 ..Self::default()
1001 }
1002 }
1003
1004 /// Builds the run value with the documented defaults.
1005 /// This is data-only and does not perform I/O, call host ports, append journals, publish
1006 /// events, or start processes.
1007 pub fn run(run_id: RunId) -> Self {
1008 Self {
1009 run_ids: EventFilterSet::Include(vec![run_id]),
1010 ..Self::default()
1011 }
1012 }
1013
1014 /// Returns agent for the current value.
1015 /// This is a read-only or data-construction helper unless the method body explicitly calls
1016 /// a port or store.
1017 pub fn agent(agent_id: AgentId) -> Self {
1018 Self {
1019 agent_ids: EventFilterSet::Include(vec![agent_id]),
1020 ..Self::default()
1021 }
1022 }
1023
1024 /// Returns compile for the current value.
1025 /// This is a read-only or data-construction helper unless the method body explicitly calls
1026 /// a port or store.
1027 pub fn compile(self) -> Result<CompiledEventFilter, AgentError> {
1028 CompiledEventFilter::new(self)
1029 }
1030
1031 fn indexed_fields(&self) -> Vec<EventIndexField> {
1032 let mut fields = Vec::new();
1033 if !self.run_ids.is_any() {
1034 fields.push(EventIndexField::RunId);
1035 }
1036 if !self.session_ids.is_any() {
1037 fields.push(EventIndexField::SessionId);
1038 }
1039 if !self.agent_ids.is_any() {
1040 fields.push(EventIndexField::AgentId);
1041 }
1042 if !self.turn_ids.is_any() {
1043 fields.push(EventIndexField::TurnId);
1044 }
1045 if !self.families.is_any() {
1046 fields.push(EventIndexField::EventFamily);
1047 }
1048 if !self.kinds.is_any() {
1049 fields.push(EventIndexField::EventKind);
1050 }
1051 if !self.source_kinds.is_any() {
1052 fields.push(EventIndexField::Source);
1053 }
1054 if !self.destination_kinds.is_any() {
1055 fields.push(EventIndexField::Destination);
1056 }
1057 if !self.subject_kinds.is_any() {
1058 fields.push(EventIndexField::SubjectKind);
1059 }
1060 if !self.related_entity_kinds.is_any() {
1061 fields.push(EventIndexField::RelatedEntityKind);
1062 }
1063 if !self.correlation_keys.is_any() {
1064 fields.push(EventIndexField::CorrelationKey);
1065 }
1066 if !self.tags.is_any() {
1067 fields.push(EventIndexField::Tag);
1068 }
1069 if !self.privacy_classes.is_any() {
1070 fields.push(EventIndexField::Privacy);
1071 }
1072 if !self.delivery_semantics.is_any() {
1073 fields.push(EventIndexField::DeliverySemantics);
1074 }
1075 fields
1076 }
1077
1078 fn matches_envelope(&self, envelope: &EventEnvelope) -> bool {
1079 self.run_ids.matches(&envelope.run_id)
1080 && option_matches(&self.session_ids, envelope.session_id.as_ref())
1081 && self.agent_ids.matches(&envelope.agent_id)
1082 && option_matches(&self.turn_ids, envelope.turn_id.as_ref())
1083 && self.families.matches(&envelope.event_family)
1084 && self.kinds.matches(&envelope.event_kind)
1085 && self.source_kinds.matches(&envelope.source.kind)
1086 && option_matches(
1087 &self.destination_kinds,
1088 envelope
1089 .destination
1090 .as_ref()
1091 .map(|destination| &destination.kind),
1092 )
1093 && self.subject_kinds.matches(&envelope.subject_ref.kind)
1094 && any_matches(
1095 &self.related_entity_kinds,
1096 envelope.related_refs.iter().map(|entity| &entity.kind),
1097 )
1098 && any_matches(
1099 &self.correlation_keys,
1100 envelope.correlation.entries.iter().map(|entry| &entry.key),
1101 )
1102 && any_matches(&self.tags, envelope.tags.iter())
1103 && self.privacy_classes.matches(&envelope.privacy)
1104 && self
1105 .delivery_semantics
1106 .matches(&envelope.delivery_semantics)
1107 && (!self.terminal_only || envelope.event_kind.is_terminal())
1108 }
1109}
1110
1111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1112/// Carries the compiled event filter record payload for journal, event, or fixture surfaces.
1113/// 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.
1114pub struct CompiledEventFilter {
1115 /// Stable filter id used for typed lineage, lookup, or dedupe.
1116 pub filter_id: EventFilterId,
1117 /// Deterministic filter fingerprint used for stale checks, package
1118 /// evidence, or replay comparisons.
1119 pub filter_fingerprint: EventFilterFingerprint,
1120 /// Collection of indexed fields values.
1121 /// Ordering and membership should be treated as part of the serialized contract when
1122 /// relevant.
1123 pub indexed_fields: Vec<EventIndexField>,
1124 /// Payload access mode allowed for matching event frames.
1125 /// Use it to keep subscriptions envelope-only unless payload access is explicitly
1126 /// requested.
1127 pub payload_access: PayloadAccessMode,
1128 /// Subscriber queue settings used for streams created with this filter.
1129 /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
1130 pub queue: SubscriberQueueConfig,
1131 criteria: EventFilter,
1132}
1133
1134impl CompiledEventFilter {
1135 /// Creates a new records::event value with explicit caller-provided
1136 /// inputs. This constructor is data-only and performs no I/O or
1137 /// external side effects.
1138 pub fn new(criteria: EventFilter) -> Result<Self, AgentError> {
1139 let encoded = serde_json::to_vec(&criteria)
1140 .map_err(|error| AgentError::contract_violation(error.to_string()))?;
1141 let fingerprint = format!("sha256:{:x}", Sha256::digest(encoded));
1142 let filter_id = EventFilterId::new(format!("filter.{:x}", Sha256::digest(&fingerprint)));
1143 let indexed_fields = criteria.indexed_fields();
1144
1145 Ok(Self {
1146 filter_id,
1147 filter_fingerprint: EventFilterFingerprint::new(fingerprint),
1148 indexed_fields,
1149 payload_access: criteria.payload_access.clone(),
1150 queue: criteria.queue.clone(),
1151 criteria,
1152 })
1153 }
1154
1155 /// Returns matches envelope for the current value.
1156 /// This is a read-only or data-construction helper unless the method body explicitly calls
1157 /// a port or store.
1158 pub fn matches_envelope(&self, envelope: &EventEnvelope) -> bool {
1159 self.criteria.matches_envelope(envelope)
1160 }
1161
1162 /// Returns cursor scope derived from the supplied state.
1163 /// This is data-only and does not perform I/O, call host ports, append journals, publish
1164 /// events, or start processes.
1165 pub fn cursor_scope(&self) -> EventStreamScope {
1166 EventStreamScope::Filter {
1167 filter_id: self.filter_id.clone(),
1168 filter_fingerprint: self.filter_fingerprint.clone(),
1169 }
1170 }
1171
1172 /// Returns the criteria currently held by this value.
1173 /// This is data-only and does not perform I/O, call host ports, append journals, publish
1174 /// events, or start processes.
1175 pub fn criteria(&self) -> &EventFilter {
1176 &self.criteria
1177 }
1178}
1179
1180fn option_matches<T: PartialEq>(filter: &EventFilterSet<T>, candidate: Option<&T>) -> bool {
1181 match filter {
1182 EventFilterSet::Any => true,
1183 EventFilterSet::Include(_) => candidate.is_some_and(|candidate| filter.matches(candidate)),
1184 }
1185}
1186
1187fn any_matches<'a, T: PartialEq + 'a>(
1188 filter: &EventFilterSet<T>,
1189 candidates: impl Iterator<Item = &'a T>,
1190) -> bool {
1191 match filter {
1192 EventFilterSet::Any => true,
1193 EventFilterSet::Include(_) => candidates
1194 .into_iter()
1195 .any(|candidate| filter.matches(candidate)),
1196 }
1197}
1198
1199#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1200#[serde(rename_all = "snake_case")]
1201/// Enumerates the finite event index field cases.
1202/// Serialized names are part of the SDK contract; update fixtures when variants change.
1203pub enum EventIndexField {
1204 /// Use this variant when the contract needs to represent run id; selecting it has no side effect by itself.
1205 RunId,
1206 /// Use this variant when the contract needs to represent session id; selecting it has no side effect by itself.
1207 SessionId,
1208 /// Use this variant when the contract needs to represent agent id; selecting it has no side effect by itself.
1209 AgentId,
1210 /// Use this variant when the contract needs to represent turn id; selecting it has no side effect by itself.
1211 TurnId,
1212 /// Use this variant when the contract needs to represent event family; selecting it has no side effect by itself.
1213 EventFamily,
1214 /// Use this variant when the contract needs to represent event kind; selecting it has no side effect by itself.
1215 EventKind,
1216 /// Use this variant when the contract needs to represent source; selecting it has no side effect by itself.
1217 Source,
1218 /// Use this variant when the contract needs to represent destination; selecting it has no side effect by itself.
1219 Destination,
1220 /// Use this variant when the contract needs to represent subject kind; selecting it has no side effect by itself.
1221 SubjectKind,
1222 /// Use this variant when the contract needs to represent related entity kind; selecting it has no side effect by itself.
1223 RelatedEntityKind,
1224 /// Use this variant when the contract needs to represent correlation key; selecting it has no side effect by itself.
1225 CorrelationKey,
1226 /// Use this variant when the contract needs to represent tag; selecting it has no side effect by itself.
1227 Tag,
1228 /// Use this variant when the contract needs to represent privacy; selecting it has no side effect by itself.
1229 Privacy,
1230 /// Use this variant when the contract needs to represent delivery semantics; selecting it has no side effect by itself.
1231 DeliverySemantics,
1232}
1233
1234typed_string!(EventFilterId, "EventFilterId");
1235typed_string!(EventFilterFingerprint, "EventFilterFingerprint");
1236
1237#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1238/// Carries the subscription options record payload for journal, event, or fixture surfaces.
1239/// 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.
1240pub struct SubscriptionOptions {
1241 /// Subscriber queue settings used for streams created with this filter.
1242 /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
1243 pub queue: SubscriberQueueConfig,
1244 /// Payload access mode allowed for matching event frames.
1245 /// Use it to keep subscriptions envelope-only unless payload access is explicitly
1246 /// requested.
1247 pub payload_access: PayloadAccessMode,
1248}
1249
1250impl Default for SubscriptionOptions {
1251 fn default() -> Self {
1252 Self {
1253 queue: SubscriberQueueConfig::default(),
1254 payload_access: PayloadAccessMode::EnvelopeOnly,
1255 }
1256 }
1257}
1258
1259/// Returns cursor compatible derived from the supplied state.
1260/// This is data-only and does not perform I/O, call host ports, append journals, publish
1261/// events, or start processes.
1262pub fn cursor_compatible(
1263 requested_scope: &EventStreamScope,
1264 cursor: Option<&EventCursor>,
1265) -> Result<(), AgentError> {
1266 let Some(cursor) = cursor else {
1267 return Ok(());
1268 };
1269
1270 let compatible = match (requested_scope, &cursor.scope) {
1271 (EventStreamScope::All, EventStreamScope::All) => true,
1272 (EventStreamScope::Run(requested), EventStreamScope::Run(cursor_run)) => {
1273 requested == cursor_run
1274 }
1275 (EventStreamScope::Agent(requested), EventStreamScope::Agent(cursor_agent)) => {
1276 requested == cursor_agent
1277 }
1278 (
1279 EventStreamScope::Filter {
1280 filter_fingerprint: requested,
1281 ..
1282 },
1283 EventStreamScope::Filter {
1284 filter_fingerprint: cursor,
1285 ..
1286 },
1287 ) => requested == cursor,
1288 _ => false,
1289 };
1290
1291 if compatible {
1292 Ok(())
1293 } else {
1294 Err(AgentError::contract_violation(
1295 "cursor scope mismatch for requested event stream",
1296 ))
1297 }
1298}