1use crate::ids::*;
12use crate::memory::MemoryScope;
13use crate::mode::OperatingMode;
14use crate::state::{AgentStateVector, BudgetState, StatePatch};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum ActorType {
22 User,
23 Agent,
24 System,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29pub struct EventActor {
30 #[serde(rename = "type")]
31 pub actor_type: ActorType,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub component: Option<String>,
34}
35
36impl Default for EventActor {
37 fn default() -> Self {
38 Self {
39 actor_type: ActorType::System,
40 component: Some("arcan-daemon".to_owned()),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
47pub struct EventSchema {
48 pub name: String,
49 pub version: String,
50}
51
52impl Default for EventSchema {
53 fn default() -> Self {
54 Self {
55 name: "aios-protocol".to_owned(),
56 version: "0.1.0".to_owned(),
57 }
58 }
59}
60
61fn default_agent_id() -> AgentId {
62 AgentId::default()
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct EventEnvelope {
71 pub event_id: EventId,
72 pub session_id: SessionId,
73 #[serde(default = "default_agent_id")]
74 pub agent_id: AgentId,
75 pub branch_id: BranchId,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub run_id: Option<RunId>,
78 pub seq: SeqNo,
79 #[serde(rename = "ts_ms", alias = "timestamp")]
81 pub timestamp: u64,
82 #[serde(default)]
83 pub actor: EventActor,
84 #[serde(default)]
85 pub schema: EventSchema,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 #[serde(rename = "parent_event_id", alias = "parent_id")]
88 pub parent_id: Option<EventId>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub trace_id: Option<String>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub span_id: Option<String>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub digest: Option<String>,
95 pub kind: EventKind,
96 #[serde(default)]
97 pub metadata: HashMap<String, String>,
98 #[serde(default = "default_schema_version")]
99 pub schema_version: u8,
100}
101
102fn default_schema_version() -> u8 {
103 1
104}
105
106impl EventEnvelope {
107 pub fn now_micros() -> u64 {
109 std::time::SystemTime::now()
110 .duration_since(std::time::UNIX_EPOCH)
111 .unwrap_or_default()
112 .as_micros() as u64
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct EventRecord {
122 pub event_id: EventId,
123 pub session_id: SessionId,
124 #[serde(default = "default_agent_id")]
125 pub agent_id: AgentId,
126 pub branch_id: BranchId,
127 pub sequence: SeqNo,
128 pub timestamp: chrono::DateTime<chrono::Utc>,
129 #[serde(default)]
130 pub actor: EventActor,
131 #[serde(default)]
132 pub schema: EventSchema,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub causation_id: Option<EventId>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub correlation_id: Option<String>,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub trace_id: Option<String>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub span_id: Option<String>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub digest: Option<String>,
143 pub kind: EventKind,
144}
145
146impl EventRecord {
147 pub fn new(
149 session_id: SessionId,
150 branch_id: BranchId,
151 sequence: SeqNo,
152 kind: EventKind,
153 ) -> Self {
154 Self {
155 event_id: EventId::default(),
156 session_id,
157 agent_id: AgentId::default(),
158 branch_id,
159 sequence,
160 timestamp: chrono::Utc::now(),
161 actor: EventActor::default(),
162 schema: EventSchema::default(),
163 causation_id: None,
164 correlation_id: None,
165 trace_id: None,
166 span_id: None,
167 digest: None,
168 kind,
169 }
170 }
171
172 pub fn to_envelope(&self) -> EventEnvelope {
174 EventEnvelope {
175 event_id: self.event_id.clone(),
176 session_id: self.session_id.clone(),
177 agent_id: self.agent_id.clone(),
178 branch_id: self.branch_id.clone(),
179 run_id: None,
180 seq: self.sequence,
181 timestamp: self.timestamp.timestamp_micros() as u64,
182 actor: self.actor.clone(),
183 schema: self.schema.clone(),
184 parent_id: self.causation_id.clone(),
185 trace_id: self.trace_id.clone(),
186 span_id: self.span_id.clone(),
187 digest: self.digest.clone(),
188 kind: self.kind.clone(),
189 metadata: HashMap::new(),
190 schema_version: 1,
191 }
192 }
193}
194
195#[derive(Debug, Clone, Serialize)]
204#[non_exhaustive]
205#[serde(tag = "type")]
206pub enum EventKind {
207 UserMessage {
209 content: String,
210 },
211 ExternalSignal {
212 signal_type: String,
213 data: serde_json::Value,
214 },
215
216 SessionCreated {
218 name: String,
219 config: serde_json::Value,
220 },
221 SessionResumed {
222 #[serde(skip_serializing_if = "Option::is_none")]
223 from_snapshot: Option<SnapshotId>,
224 },
225 SessionClosed {
226 reason: String,
227 },
228
229 BranchCreated {
231 new_branch_id: BranchId,
232 fork_point_seq: SeqNo,
233 name: String,
234 },
235 BranchMerged {
236 source_branch_id: BranchId,
237 merge_seq: SeqNo,
238 },
239
240 PhaseEntered {
242 phase: LoopPhase,
243 },
244 DeliberationProposed {
245 summary: String,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 proposed_tool: Option<String>,
248 },
249
250 RunStarted {
252 provider: String,
253 max_iterations: u32,
254 },
255 RunFinished {
256 reason: String,
257 total_iterations: u32,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 final_answer: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 usage: Option<TokenUsage>,
262 },
263 RunErrored {
264 error: String,
265 },
266
267 StepStarted {
269 index: u32,
270 },
271 StepFinished {
272 index: u32,
273 stop_reason: String,
274 directive_count: usize,
275 },
276
277 AssistantTextDelta {
279 delta: String,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 index: Option<u32>,
282 },
283 AssistantMessageCommitted {
284 role: String,
285 content: String,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 model: Option<String>,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 token_usage: Option<TokenUsage>,
290 },
291 TextDelta {
292 delta: String,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 index: Option<u32>,
295 },
296 Message {
297 role: String,
298 content: String,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 model: Option<String>,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 token_usage: Option<TokenUsage>,
303 },
304
305 ToolCallRequested {
307 call_id: String,
308 tool_name: String,
309 arguments: serde_json::Value,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 category: Option<String>,
312 },
313 ToolCallStarted {
314 tool_run_id: ToolRunId,
315 tool_name: String,
316 },
317 ToolCallCompleted {
318 tool_run_id: ToolRunId,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 call_id: Option<String>,
321 tool_name: String,
322 result: serde_json::Value,
323 duration_ms: u64,
324 status: SpanStatus,
325 },
326 ToolCallFailed {
327 call_id: String,
328 tool_name: String,
329 error: String,
330 },
331
332 FileWrite {
334 path: String,
335 blob_hash: BlobHash,
336 size_bytes: u64,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 content_type: Option<String>,
339 },
340 FileDelete {
341 path: String,
342 },
343 FileRename {
344 old_path: String,
345 new_path: String,
346 },
347 FileMutated {
348 path: String,
349 content_hash: String,
350 },
351
352 StatePatchCommitted {
354 new_version: u64,
355 patch: StatePatch,
356 },
357 StatePatched {
358 #[serde(skip_serializing_if = "Option::is_none")]
359 index: Option<u32>,
360 patch: serde_json::Value,
361 revision: u64,
362 },
363 ContextCompacted {
364 dropped_count: usize,
365 tokens_before: usize,
366 tokens_after: usize,
367 },
368
369 PolicyEvaluated {
371 tool_name: String,
372 decision: PolicyDecisionKind,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 rule_id: Option<String>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 explanation: Option<String>,
377 },
378
379 ApprovalRequested {
381 approval_id: ApprovalId,
382 call_id: String,
383 tool_name: String,
384 arguments: serde_json::Value,
385 risk: RiskLevel,
386 },
387 ApprovalResolved {
388 approval_id: ApprovalId,
389 decision: ApprovalDecision,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 reason: Option<String>,
392 },
393
394 SnapshotCreated {
396 snapshot_id: SnapshotId,
397 snapshot_type: SnapshotType,
398 covers_through_seq: SeqNo,
399 data_hash: BlobHash,
400 },
401
402 SandboxCreated {
404 sandbox_id: String,
405 tier: String,
406 config: serde_json::Value,
407 },
408 SandboxExecuted {
409 sandbox_id: String,
410 command: String,
411 exit_code: i32,
412 duration_ms: u64,
413 },
414 SandboxViolation {
415 sandbox_id: String,
416 violation_type: String,
417 details: String,
418 },
419 SandboxDestroyed {
420 sandbox_id: String,
421 },
422
423 ObservationAppended {
425 scope: MemoryScope,
426 observation_ref: BlobHash,
427 #[serde(skip_serializing_if = "Option::is_none")]
428 source_run_id: Option<String>,
429 },
430 ReflectionCompacted {
431 scope: MemoryScope,
432 summary_ref: BlobHash,
433 covers_through_seq: SeqNo,
434 },
435 MemoryProposed {
436 scope: MemoryScope,
437 proposal_id: MemoryId,
438 entries_ref: BlobHash,
439 #[serde(skip_serializing_if = "Option::is_none")]
440 source_run_id: Option<String>,
441 },
442 MemoryCommitted {
443 scope: MemoryScope,
444 memory_id: MemoryId,
445 committed_ref: BlobHash,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 supersedes: Option<MemoryId>,
448 },
449 MemoryTombstoned {
450 scope: MemoryScope,
451 memory_id: MemoryId,
452 reason: String,
453 },
454
455 Heartbeat {
457 summary: String,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 checkpoint_id: Option<CheckpointId>,
460 },
461 StateEstimated {
462 state: AgentStateVector,
463 mode: OperatingMode,
464 },
465 BudgetUpdated {
466 budget: BudgetState,
467 reason: String,
468 },
469 ModeChanged {
470 from: OperatingMode,
471 to: OperatingMode,
472 reason: String,
473 },
474 GatesUpdated {
475 gates: serde_json::Value,
476 reason: String,
477 },
478 CircuitBreakerTripped {
479 reason: String,
480 error_streak: u32,
481 },
482
483 CheckpointCreated {
485 checkpoint_id: CheckpointId,
486 event_sequence: u64,
487 state_hash: String,
488 },
489 CheckpointRestored {
490 checkpoint_id: CheckpointId,
491 restored_to_seq: u64,
492 },
493
494 VoiceSessionStarted {
496 voice_session_id: String,
497 adapter: String,
498 model: String,
499 sample_rate_hz: u32,
500 channels: u8,
501 },
502 VoiceInputChunk {
503 voice_session_id: String,
504 chunk_index: u64,
505 bytes: usize,
506 format: String,
507 },
508 VoiceOutputChunk {
509 voice_session_id: String,
510 chunk_index: u64,
511 bytes: usize,
512 format: String,
513 },
514 VoiceSessionStopped {
515 voice_session_id: String,
516 reason: String,
517 },
518 VoiceAdapterError {
519 voice_session_id: String,
520 message: String,
521 },
522
523 WorldModelObserved {
525 state_ref: BlobHash,
526 meta: serde_json::Value,
527 },
528 WorldModelRollout {
529 trajectory_ref: BlobHash,
530 #[serde(skip_serializing_if = "Option::is_none")]
531 score: Option<f32>,
532 },
533
534 IntentProposed {
536 intent_id: String,
537 kind: String,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 risk: Option<RiskLevel>,
540 },
541 IntentEvaluated {
542 intent_id: String,
543 allowed: bool,
544 requires_approval: bool,
545 #[serde(default)]
546 reasons: Vec<String>,
547 },
548 IntentApproved {
549 intent_id: String,
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 actor: Option<String>,
552 },
553 IntentRejected {
554 intent_id: String,
555 #[serde(default)]
556 reasons: Vec<String>,
557 },
558
559 HiveTaskCreated {
561 hive_task_id: HiveTaskId,
562 objective: String,
563 agent_count: u32,
564 },
565 HiveArtifactShared {
566 hive_task_id: HiveTaskId,
567 source_session_id: SessionId,
568 score: f32,
569 mutation_summary: String,
570 },
571 HiveSelectionMade {
572 hive_task_id: HiveTaskId,
573 winning_session_id: SessionId,
574 winning_score: f32,
575 generation: u32,
576 },
577 HiveGenerationCompleted {
578 hive_task_id: HiveTaskId,
579 generation: u32,
580 best_score: f32,
581 agent_results: serde_json::Value,
582 },
583 HiveTaskCompleted {
584 hive_task_id: HiveTaskId,
585 total_generations: u32,
586 total_trials: u32,
587 final_score: f32,
588 },
589
590 ErrorRaised {
592 message: String,
593 },
594
595 Custom {
597 event_type: String,
598 data: serde_json::Value,
599 },
600}
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
606#[serde(rename_all = "snake_case")]
607pub enum LoopPhase {
608 Perceive,
609 Deliberate,
610 Gate,
611 Execute,
612 Commit,
613 Reflect,
614 Sleep,
615}
616
617#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
619pub struct TokenUsage {
620 #[serde(default)]
621 pub prompt_tokens: u32,
622 #[serde(default)]
623 pub completion_tokens: u32,
624 #[serde(default)]
625 pub total_tokens: u32,
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
630#[serde(rename_all = "snake_case")]
631pub enum SpanStatus {
632 Ok,
633 Error,
634 Timeout,
635 Cancelled,
636}
637
638#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
640#[serde(rename_all = "snake_case")]
641pub enum RiskLevel {
642 Low,
643 Medium,
644 High,
645 Critical,
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
650#[serde(rename_all = "snake_case")]
651pub enum ApprovalDecision {
652 Approved,
653 Denied,
654 Timeout,
655}
656
657#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
659#[serde(rename_all = "snake_case")]
660pub enum SnapshotType {
661 Full,
662 Incremental,
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
667#[serde(rename_all = "snake_case")]
668pub enum PolicyDecisionKind {
669 Allow,
670 Deny,
671 RequireApproval,
672}
673
674#[derive(Deserialize)]
679#[serde(tag = "type")]
680enum EventKindKnown {
681 UserMessage {
682 content: String,
683 },
684 ExternalSignal {
685 signal_type: String,
686 data: serde_json::Value,
687 },
688 SessionCreated {
689 name: String,
690 config: serde_json::Value,
691 },
692 SessionResumed {
693 #[serde(default)]
694 from_snapshot: Option<SnapshotId>,
695 },
696 SessionClosed {
697 reason: String,
698 },
699 BranchCreated {
700 new_branch_id: BranchId,
701 fork_point_seq: SeqNo,
702 name: String,
703 },
704 BranchMerged {
705 source_branch_id: BranchId,
706 merge_seq: SeqNo,
707 },
708 PhaseEntered {
709 phase: LoopPhase,
710 },
711 DeliberationProposed {
712 summary: String,
713 #[serde(default)]
714 proposed_tool: Option<String>,
715 },
716 RunStarted {
717 provider: String,
718 max_iterations: u32,
719 },
720 RunFinished {
721 reason: String,
722 total_iterations: u32,
723 #[serde(default)]
724 final_answer: Option<String>,
725 #[serde(default)]
726 usage: Option<TokenUsage>,
727 },
728 RunErrored {
729 error: String,
730 },
731 StepStarted {
732 index: u32,
733 },
734 StepFinished {
735 index: u32,
736 stop_reason: String,
737 directive_count: usize,
738 },
739 AssistantTextDelta {
740 delta: String,
741 #[serde(default)]
742 index: Option<u32>,
743 },
744 AssistantMessageCommitted {
745 role: String,
746 content: String,
747 #[serde(default)]
748 model: Option<String>,
749 #[serde(default)]
750 token_usage: Option<TokenUsage>,
751 },
752 TextDelta {
753 delta: String,
754 #[serde(default)]
755 index: Option<u32>,
756 },
757 Message {
758 role: String,
759 content: String,
760 #[serde(default)]
761 model: Option<String>,
762 #[serde(default)]
763 token_usage: Option<TokenUsage>,
764 },
765 ToolCallRequested {
766 call_id: String,
767 tool_name: String,
768 arguments: serde_json::Value,
769 #[serde(default)]
770 category: Option<String>,
771 },
772 ToolCallStarted {
773 tool_run_id: ToolRunId,
774 tool_name: String,
775 },
776 ToolCallCompleted {
777 tool_run_id: ToolRunId,
778 #[serde(default)]
779 call_id: Option<String>,
780 tool_name: String,
781 result: serde_json::Value,
782 duration_ms: u64,
783 status: SpanStatus,
784 },
785 ToolCallFailed {
786 call_id: String,
787 tool_name: String,
788 error: String,
789 },
790 FileWrite {
791 path: String,
792 blob_hash: BlobHash,
793 size_bytes: u64,
794 #[serde(default)]
795 content_type: Option<String>,
796 },
797 FileDelete {
798 path: String,
799 },
800 FileRename {
801 old_path: String,
802 new_path: String,
803 },
804 FileMutated {
805 path: String,
806 content_hash: String,
807 },
808 StatePatched {
809 #[serde(default)]
810 index: Option<u32>,
811 patch: serde_json::Value,
812 revision: u64,
813 },
814 StatePatchCommitted {
815 new_version: u64,
816 patch: StatePatch,
817 },
818 ContextCompacted {
819 dropped_count: usize,
820 tokens_before: usize,
821 tokens_after: usize,
822 },
823 PolicyEvaluated {
824 tool_name: String,
825 decision: PolicyDecisionKind,
826 #[serde(default)]
827 rule_id: Option<String>,
828 #[serde(default)]
829 explanation: Option<String>,
830 },
831 ApprovalRequested {
832 approval_id: ApprovalId,
833 call_id: String,
834 tool_name: String,
835 arguments: serde_json::Value,
836 risk: RiskLevel,
837 },
838 ApprovalResolved {
839 approval_id: ApprovalId,
840 decision: ApprovalDecision,
841 #[serde(default)]
842 reason: Option<String>,
843 },
844 SnapshotCreated {
845 snapshot_id: SnapshotId,
846 snapshot_type: SnapshotType,
847 covers_through_seq: SeqNo,
848 data_hash: BlobHash,
849 },
850 SandboxCreated {
851 sandbox_id: String,
852 tier: String,
853 config: serde_json::Value,
854 },
855 SandboxExecuted {
856 sandbox_id: String,
857 command: String,
858 exit_code: i32,
859 duration_ms: u64,
860 },
861 SandboxViolation {
862 sandbox_id: String,
863 violation_type: String,
864 details: String,
865 },
866 SandboxDestroyed {
867 sandbox_id: String,
868 },
869 ObservationAppended {
870 scope: MemoryScope,
871 observation_ref: BlobHash,
872 #[serde(default)]
873 source_run_id: Option<String>,
874 },
875 ReflectionCompacted {
876 scope: MemoryScope,
877 summary_ref: BlobHash,
878 covers_through_seq: SeqNo,
879 },
880 MemoryProposed {
881 scope: MemoryScope,
882 proposal_id: MemoryId,
883 entries_ref: BlobHash,
884 #[serde(default)]
885 source_run_id: Option<String>,
886 },
887 MemoryCommitted {
888 scope: MemoryScope,
889 memory_id: MemoryId,
890 committed_ref: BlobHash,
891 #[serde(default)]
892 supersedes: Option<MemoryId>,
893 },
894 MemoryTombstoned {
895 scope: MemoryScope,
896 memory_id: MemoryId,
897 reason: String,
898 },
899 Heartbeat {
900 summary: String,
901 #[serde(default)]
902 checkpoint_id: Option<CheckpointId>,
903 },
904 StateEstimated {
905 state: AgentStateVector,
906 mode: OperatingMode,
907 },
908 BudgetUpdated {
909 budget: BudgetState,
910 reason: String,
911 },
912 ModeChanged {
913 from: OperatingMode,
914 to: OperatingMode,
915 reason: String,
916 },
917 GatesUpdated {
918 gates: serde_json::Value,
919 reason: String,
920 },
921 CircuitBreakerTripped {
922 reason: String,
923 error_streak: u32,
924 },
925 CheckpointCreated {
926 checkpoint_id: CheckpointId,
927 event_sequence: u64,
928 state_hash: String,
929 },
930 CheckpointRestored {
931 checkpoint_id: CheckpointId,
932 restored_to_seq: u64,
933 },
934 VoiceSessionStarted {
935 voice_session_id: String,
936 adapter: String,
937 model: String,
938 sample_rate_hz: u32,
939 channels: u8,
940 },
941 VoiceInputChunk {
942 voice_session_id: String,
943 chunk_index: u64,
944 bytes: usize,
945 format: String,
946 },
947 VoiceOutputChunk {
948 voice_session_id: String,
949 chunk_index: u64,
950 bytes: usize,
951 format: String,
952 },
953 VoiceSessionStopped {
954 voice_session_id: String,
955 reason: String,
956 },
957 VoiceAdapterError {
958 voice_session_id: String,
959 message: String,
960 },
961 WorldModelObserved {
962 state_ref: BlobHash,
963 meta: serde_json::Value,
964 },
965 WorldModelRollout {
966 trajectory_ref: BlobHash,
967 #[serde(default)]
968 score: Option<f32>,
969 },
970 IntentProposed {
971 intent_id: String,
972 kind: String,
973 #[serde(default)]
974 risk: Option<RiskLevel>,
975 },
976 IntentEvaluated {
977 intent_id: String,
978 allowed: bool,
979 requires_approval: bool,
980 #[serde(default)]
981 reasons: Vec<String>,
982 },
983 IntentApproved {
984 intent_id: String,
985 #[serde(default)]
986 actor: Option<String>,
987 },
988 IntentRejected {
989 intent_id: String,
990 #[serde(default)]
991 reasons: Vec<String>,
992 },
993 HiveTaskCreated {
994 hive_task_id: HiveTaskId,
995 objective: String,
996 agent_count: u32,
997 },
998 HiveArtifactShared {
999 hive_task_id: HiveTaskId,
1000 source_session_id: SessionId,
1001 score: f32,
1002 mutation_summary: String,
1003 },
1004 HiveSelectionMade {
1005 hive_task_id: HiveTaskId,
1006 winning_session_id: SessionId,
1007 winning_score: f32,
1008 generation: u32,
1009 },
1010 HiveGenerationCompleted {
1011 hive_task_id: HiveTaskId,
1012 generation: u32,
1013 best_score: f32,
1014 agent_results: serde_json::Value,
1015 },
1016 HiveTaskCompleted {
1017 hive_task_id: HiveTaskId,
1018 total_generations: u32,
1019 total_trials: u32,
1020 final_score: f32,
1021 },
1022 ErrorRaised {
1023 message: String,
1024 },
1025 Custom {
1026 event_type: String,
1027 data: serde_json::Value,
1028 },
1029}
1030
1031impl<'de> Deserialize<'de> for EventKind {
1033 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1034 where
1035 D: serde::Deserializer<'de>,
1036 {
1037 let raw = serde_json::Value::deserialize(deserializer)?;
1038 match serde_json::from_value::<EventKindKnown>(raw.clone()) {
1039 Ok(known) => Ok(known.into()),
1040 Err(_) => {
1041 let event_type = raw
1042 .get("type")
1043 .and_then(|v| v.as_str())
1044 .unwrap_or("Unknown")
1045 .to_string();
1046 let mut data = raw;
1047 if let Some(obj) = data.as_object_mut() {
1048 obj.remove("type");
1049 }
1050 Ok(EventKind::Custom { event_type, data })
1051 }
1052 }
1053 }
1054}
1055
1056impl From<EventKindKnown> for EventKind {
1059 #[allow(clippy::too_many_lines)]
1060 fn from(k: EventKindKnown) -> Self {
1061 match k {
1062 EventKindKnown::UserMessage { content } => Self::UserMessage { content },
1063 EventKindKnown::ExternalSignal { signal_type, data } => {
1064 Self::ExternalSignal { signal_type, data }
1065 }
1066 EventKindKnown::SessionCreated { name, config } => {
1067 Self::SessionCreated { name, config }
1068 }
1069 EventKindKnown::SessionResumed { from_snapshot } => {
1070 Self::SessionResumed { from_snapshot }
1071 }
1072 EventKindKnown::SessionClosed { reason } => Self::SessionClosed { reason },
1073 EventKindKnown::BranchCreated {
1074 new_branch_id,
1075 fork_point_seq,
1076 name,
1077 } => Self::BranchCreated {
1078 new_branch_id,
1079 fork_point_seq,
1080 name,
1081 },
1082 EventKindKnown::BranchMerged {
1083 source_branch_id,
1084 merge_seq,
1085 } => Self::BranchMerged {
1086 source_branch_id,
1087 merge_seq,
1088 },
1089 EventKindKnown::PhaseEntered { phase } => Self::PhaseEntered { phase },
1090 EventKindKnown::DeliberationProposed {
1091 summary,
1092 proposed_tool,
1093 } => Self::DeliberationProposed {
1094 summary,
1095 proposed_tool,
1096 },
1097 EventKindKnown::RunStarted {
1098 provider,
1099 max_iterations,
1100 } => Self::RunStarted {
1101 provider,
1102 max_iterations,
1103 },
1104 EventKindKnown::RunFinished {
1105 reason,
1106 total_iterations,
1107 final_answer,
1108 usage,
1109 } => Self::RunFinished {
1110 reason,
1111 total_iterations,
1112 final_answer,
1113 usage,
1114 },
1115 EventKindKnown::RunErrored { error } => Self::RunErrored { error },
1116 EventKindKnown::StepStarted { index } => Self::StepStarted { index },
1117 EventKindKnown::StepFinished {
1118 index,
1119 stop_reason,
1120 directive_count,
1121 } => Self::StepFinished {
1122 index,
1123 stop_reason,
1124 directive_count,
1125 },
1126 EventKindKnown::AssistantTextDelta { delta, index } => {
1127 Self::AssistantTextDelta { delta, index }
1128 }
1129 EventKindKnown::AssistantMessageCommitted {
1130 role,
1131 content,
1132 model,
1133 token_usage,
1134 } => Self::AssistantMessageCommitted {
1135 role,
1136 content,
1137 model,
1138 token_usage,
1139 },
1140 EventKindKnown::TextDelta { delta, index } => Self::TextDelta { delta, index },
1141 EventKindKnown::Message {
1142 role,
1143 content,
1144 model,
1145 token_usage,
1146 } => Self::Message {
1147 role,
1148 content,
1149 model,
1150 token_usage,
1151 },
1152 EventKindKnown::ToolCallRequested {
1153 call_id,
1154 tool_name,
1155 arguments,
1156 category,
1157 } => Self::ToolCallRequested {
1158 call_id,
1159 tool_name,
1160 arguments,
1161 category,
1162 },
1163 EventKindKnown::ToolCallStarted {
1164 tool_run_id,
1165 tool_name,
1166 } => Self::ToolCallStarted {
1167 tool_run_id,
1168 tool_name,
1169 },
1170 EventKindKnown::ToolCallCompleted {
1171 tool_run_id,
1172 call_id,
1173 tool_name,
1174 result,
1175 duration_ms,
1176 status,
1177 } => Self::ToolCallCompleted {
1178 tool_run_id,
1179 call_id,
1180 tool_name,
1181 result,
1182 duration_ms,
1183 status,
1184 },
1185 EventKindKnown::ToolCallFailed {
1186 call_id,
1187 tool_name,
1188 error,
1189 } => Self::ToolCallFailed {
1190 call_id,
1191 tool_name,
1192 error,
1193 },
1194 EventKindKnown::FileWrite {
1195 path,
1196 blob_hash,
1197 size_bytes,
1198 content_type,
1199 } => Self::FileWrite {
1200 path,
1201 blob_hash,
1202 size_bytes,
1203 content_type,
1204 },
1205 EventKindKnown::FileDelete { path } => Self::FileDelete { path },
1206 EventKindKnown::FileRename { old_path, new_path } => {
1207 Self::FileRename { old_path, new_path }
1208 }
1209 EventKindKnown::FileMutated { path, content_hash } => {
1210 Self::FileMutated { path, content_hash }
1211 }
1212 EventKindKnown::StatePatched {
1213 index,
1214 patch,
1215 revision,
1216 } => Self::StatePatched {
1217 index,
1218 patch,
1219 revision,
1220 },
1221 EventKindKnown::StatePatchCommitted { new_version, patch } => {
1222 Self::StatePatchCommitted { new_version, patch }
1223 }
1224 EventKindKnown::ContextCompacted {
1225 dropped_count,
1226 tokens_before,
1227 tokens_after,
1228 } => Self::ContextCompacted {
1229 dropped_count,
1230 tokens_before,
1231 tokens_after,
1232 },
1233 EventKindKnown::PolicyEvaluated {
1234 tool_name,
1235 decision,
1236 rule_id,
1237 explanation,
1238 } => Self::PolicyEvaluated {
1239 tool_name,
1240 decision,
1241 rule_id,
1242 explanation,
1243 },
1244 EventKindKnown::ApprovalRequested {
1245 approval_id,
1246 call_id,
1247 tool_name,
1248 arguments,
1249 risk,
1250 } => Self::ApprovalRequested {
1251 approval_id,
1252 call_id,
1253 tool_name,
1254 arguments,
1255 risk,
1256 },
1257 EventKindKnown::ApprovalResolved {
1258 approval_id,
1259 decision,
1260 reason,
1261 } => Self::ApprovalResolved {
1262 approval_id,
1263 decision,
1264 reason,
1265 },
1266 EventKindKnown::SnapshotCreated {
1267 snapshot_id,
1268 snapshot_type,
1269 covers_through_seq,
1270 data_hash,
1271 } => Self::SnapshotCreated {
1272 snapshot_id,
1273 snapshot_type,
1274 covers_through_seq,
1275 data_hash,
1276 },
1277 EventKindKnown::SandboxCreated {
1278 sandbox_id,
1279 tier,
1280 config,
1281 } => Self::SandboxCreated {
1282 sandbox_id,
1283 tier,
1284 config,
1285 },
1286 EventKindKnown::SandboxExecuted {
1287 sandbox_id,
1288 command,
1289 exit_code,
1290 duration_ms,
1291 } => Self::SandboxExecuted {
1292 sandbox_id,
1293 command,
1294 exit_code,
1295 duration_ms,
1296 },
1297 EventKindKnown::SandboxViolation {
1298 sandbox_id,
1299 violation_type,
1300 details,
1301 } => Self::SandboxViolation {
1302 sandbox_id,
1303 violation_type,
1304 details,
1305 },
1306 EventKindKnown::SandboxDestroyed { sandbox_id } => {
1307 Self::SandboxDestroyed { sandbox_id }
1308 }
1309 EventKindKnown::ObservationAppended {
1310 scope,
1311 observation_ref,
1312 source_run_id,
1313 } => Self::ObservationAppended {
1314 scope,
1315 observation_ref,
1316 source_run_id,
1317 },
1318 EventKindKnown::ReflectionCompacted {
1319 scope,
1320 summary_ref,
1321 covers_through_seq,
1322 } => Self::ReflectionCompacted {
1323 scope,
1324 summary_ref,
1325 covers_through_seq,
1326 },
1327 EventKindKnown::MemoryProposed {
1328 scope,
1329 proposal_id,
1330 entries_ref,
1331 source_run_id,
1332 } => Self::MemoryProposed {
1333 scope,
1334 proposal_id,
1335 entries_ref,
1336 source_run_id,
1337 },
1338 EventKindKnown::MemoryCommitted {
1339 scope,
1340 memory_id,
1341 committed_ref,
1342 supersedes,
1343 } => Self::MemoryCommitted {
1344 scope,
1345 memory_id,
1346 committed_ref,
1347 supersedes,
1348 },
1349 EventKindKnown::MemoryTombstoned {
1350 scope,
1351 memory_id,
1352 reason,
1353 } => Self::MemoryTombstoned {
1354 scope,
1355 memory_id,
1356 reason,
1357 },
1358 EventKindKnown::Heartbeat {
1359 summary,
1360 checkpoint_id,
1361 } => Self::Heartbeat {
1362 summary,
1363 checkpoint_id,
1364 },
1365 EventKindKnown::StateEstimated { state, mode } => Self::StateEstimated { state, mode },
1366 EventKindKnown::BudgetUpdated { budget, reason } => {
1367 Self::BudgetUpdated { budget, reason }
1368 }
1369 EventKindKnown::ModeChanged { from, to, reason } => {
1370 Self::ModeChanged { from, to, reason }
1371 }
1372 EventKindKnown::GatesUpdated { gates, reason } => Self::GatesUpdated { gates, reason },
1373 EventKindKnown::CircuitBreakerTripped {
1374 reason,
1375 error_streak,
1376 } => Self::CircuitBreakerTripped {
1377 reason,
1378 error_streak,
1379 },
1380 EventKindKnown::CheckpointCreated {
1381 checkpoint_id,
1382 event_sequence,
1383 state_hash,
1384 } => Self::CheckpointCreated {
1385 checkpoint_id,
1386 event_sequence,
1387 state_hash,
1388 },
1389 EventKindKnown::CheckpointRestored {
1390 checkpoint_id,
1391 restored_to_seq,
1392 } => Self::CheckpointRestored {
1393 checkpoint_id,
1394 restored_to_seq,
1395 },
1396 EventKindKnown::VoiceSessionStarted {
1397 voice_session_id,
1398 adapter,
1399 model,
1400 sample_rate_hz,
1401 channels,
1402 } => Self::VoiceSessionStarted {
1403 voice_session_id,
1404 adapter,
1405 model,
1406 sample_rate_hz,
1407 channels,
1408 },
1409 EventKindKnown::VoiceInputChunk {
1410 voice_session_id,
1411 chunk_index,
1412 bytes,
1413 format,
1414 } => Self::VoiceInputChunk {
1415 voice_session_id,
1416 chunk_index,
1417 bytes,
1418 format,
1419 },
1420 EventKindKnown::VoiceOutputChunk {
1421 voice_session_id,
1422 chunk_index,
1423 bytes,
1424 format,
1425 } => Self::VoiceOutputChunk {
1426 voice_session_id,
1427 chunk_index,
1428 bytes,
1429 format,
1430 },
1431 EventKindKnown::VoiceSessionStopped {
1432 voice_session_id,
1433 reason,
1434 } => Self::VoiceSessionStopped {
1435 voice_session_id,
1436 reason,
1437 },
1438 EventKindKnown::VoiceAdapterError {
1439 voice_session_id,
1440 message,
1441 } => Self::VoiceAdapterError {
1442 voice_session_id,
1443 message,
1444 },
1445 EventKindKnown::WorldModelObserved { state_ref, meta } => {
1446 Self::WorldModelObserved { state_ref, meta }
1447 }
1448 EventKindKnown::WorldModelRollout {
1449 trajectory_ref,
1450 score,
1451 } => Self::WorldModelRollout {
1452 trajectory_ref,
1453 score,
1454 },
1455 EventKindKnown::IntentProposed {
1456 intent_id,
1457 kind,
1458 risk,
1459 } => Self::IntentProposed {
1460 intent_id,
1461 kind,
1462 risk,
1463 },
1464 EventKindKnown::IntentEvaluated {
1465 intent_id,
1466 allowed,
1467 requires_approval,
1468 reasons,
1469 } => Self::IntentEvaluated {
1470 intent_id,
1471 allowed,
1472 requires_approval,
1473 reasons,
1474 },
1475 EventKindKnown::IntentApproved { intent_id, actor } => {
1476 Self::IntentApproved { intent_id, actor }
1477 }
1478 EventKindKnown::IntentRejected { intent_id, reasons } => {
1479 Self::IntentRejected { intent_id, reasons }
1480 }
1481 EventKindKnown::HiveTaskCreated {
1482 hive_task_id,
1483 objective,
1484 agent_count,
1485 } => Self::HiveTaskCreated {
1486 hive_task_id,
1487 objective,
1488 agent_count,
1489 },
1490 EventKindKnown::HiveArtifactShared {
1491 hive_task_id,
1492 source_session_id,
1493 score,
1494 mutation_summary,
1495 } => Self::HiveArtifactShared {
1496 hive_task_id,
1497 source_session_id,
1498 score,
1499 mutation_summary,
1500 },
1501 EventKindKnown::HiveSelectionMade {
1502 hive_task_id,
1503 winning_session_id,
1504 winning_score,
1505 generation,
1506 } => Self::HiveSelectionMade {
1507 hive_task_id,
1508 winning_session_id,
1509 winning_score,
1510 generation,
1511 },
1512 EventKindKnown::HiveGenerationCompleted {
1513 hive_task_id,
1514 generation,
1515 best_score,
1516 agent_results,
1517 } => Self::HiveGenerationCompleted {
1518 hive_task_id,
1519 generation,
1520 best_score,
1521 agent_results,
1522 },
1523 EventKindKnown::HiveTaskCompleted {
1524 hive_task_id,
1525 total_generations,
1526 total_trials,
1527 final_score,
1528 } => Self::HiveTaskCompleted {
1529 hive_task_id,
1530 total_generations,
1531 total_trials,
1532 final_score,
1533 },
1534 EventKindKnown::ErrorRaised { message } => Self::ErrorRaised { message },
1535 EventKindKnown::Custom { event_type, data } => Self::Custom { event_type, data },
1536 }
1537 }
1538}
1539
1540#[cfg(test)]
1541mod tests {
1542 use super::*;
1543
1544 fn make_envelope(kind: EventKind) -> EventEnvelope {
1545 EventEnvelope {
1546 event_id: EventId::from_string("EVT001"),
1547 session_id: SessionId::from_string("SESS001"),
1548 agent_id: AgentId::from_string("AGENT001"),
1549 branch_id: BranchId::from_string("main"),
1550 run_id: None,
1551 seq: 1,
1552 timestamp: 1_700_000_000_000_000,
1553 actor: EventActor::default(),
1554 schema: EventSchema::default(),
1555 parent_id: None,
1556 trace_id: None,
1557 span_id: None,
1558 digest: None,
1559 kind,
1560 metadata: HashMap::new(),
1561 schema_version: 1,
1562 }
1563 }
1564
1565 #[test]
1566 fn error_raised_roundtrip() {
1567 let kind = EventKind::ErrorRaised {
1568 message: "boom".into(),
1569 };
1570 let json = serde_json::to_string(&kind).unwrap();
1571 assert!(json.contains("\"type\":\"ErrorRaised\""));
1572 let back: EventKind = serde_json::from_str(&json).unwrap();
1573 assert!(matches!(back, EventKind::ErrorRaised { message } if message == "boom"));
1574 }
1575
1576 #[test]
1577 fn heartbeat_roundtrip() {
1578 let kind = EventKind::Heartbeat {
1579 summary: "alive".into(),
1580 checkpoint_id: None,
1581 };
1582 let json = serde_json::to_string(&kind).unwrap();
1583 let back: EventKind = serde_json::from_str(&json).unwrap();
1584 assert!(matches!(back, EventKind::Heartbeat { .. }));
1585 }
1586
1587 #[test]
1588 fn state_estimated_roundtrip() {
1589 let kind = EventKind::StateEstimated {
1590 state: AgentStateVector::default(),
1591 mode: OperatingMode::Execute,
1592 };
1593 let json = serde_json::to_string(&kind).unwrap();
1594 let back: EventKind = serde_json::from_str(&json).unwrap();
1595 assert!(matches!(back, EventKind::StateEstimated { .. }));
1596 }
1597
1598 #[test]
1599 fn unknown_variant_becomes_custom() {
1600 let json = r#"{"type":"FutureFeature","key":"value","num":42}"#;
1601 let kind: EventKind = serde_json::from_str(json).unwrap();
1602 if let EventKind::Custom { event_type, data } = kind {
1603 assert_eq!(event_type, "FutureFeature");
1604 assert_eq!(data["key"], "value");
1605 assert_eq!(data["num"], 42);
1606 } else {
1607 panic!("should be Custom");
1608 }
1609 }
1610
1611 #[test]
1612 fn full_envelope_roundtrip() {
1613 let envelope = make_envelope(EventKind::RunStarted {
1614 provider: "anthropic".into(),
1615 max_iterations: 10,
1616 });
1617 let json = serde_json::to_string(&envelope).unwrap();
1618 let back: EventEnvelope = serde_json::from_str(&json).unwrap();
1619 assert_eq!(back.seq, 1);
1620 assert_eq!(back.schema_version, 1);
1621 assert!(matches!(back.kind, EventKind::RunStarted { .. }));
1622 }
1623
1624 #[test]
1625 fn tool_call_lifecycle_roundtrip() {
1626 let requested = EventKind::ToolCallRequested {
1627 call_id: "c1".into(),
1628 tool_name: "read_file".into(),
1629 arguments: serde_json::json!({"path": "/etc/hosts"}),
1630 category: Some("fs".into()),
1631 };
1632 let json = serde_json::to_string(&requested).unwrap();
1633 let back: EventKind = serde_json::from_str(&json).unwrap();
1634 assert!(matches!(back, EventKind::ToolCallRequested { .. }));
1635 }
1636
1637 #[test]
1638 fn memory_events_roundtrip() {
1639 let proposed = EventKind::MemoryProposed {
1640 scope: MemoryScope::Agent,
1641 proposal_id: MemoryId::from_string("PROP001"),
1642 entries_ref: BlobHash::from_hex("abc"),
1643 source_run_id: None,
1644 };
1645 let json = serde_json::to_string(&proposed).unwrap();
1646 let back: EventKind = serde_json::from_str(&json).unwrap();
1647 assert!(matches!(back, EventKind::MemoryProposed { .. }));
1648 }
1649
1650 #[test]
1651 fn mode_changed_roundtrip() {
1652 let kind = EventKind::ModeChanged {
1653 from: OperatingMode::Execute,
1654 to: OperatingMode::Recover,
1655 reason: "error streak".into(),
1656 };
1657 let json = serde_json::to_string(&kind).unwrap();
1658 let back: EventKind = serde_json::from_str(&json).unwrap();
1659 assert!(matches!(back, EventKind::ModeChanged { .. }));
1660 }
1661
1662 #[test]
1663 fn schema_version_defaults_to_1() {
1664 let json = r#"{"event_id":"E1","session_id":"S1","branch_id":"main","seq":0,"timestamp":100,"kind":{"type":"ErrorRaised","message":"x"},"metadata":{}}"#;
1665 let envelope: EventEnvelope = serde_json::from_str(json).unwrap();
1666 assert_eq!(envelope.schema_version, 1);
1667 }
1668
1669 #[test]
1670 fn hive_task_created_roundtrip() {
1671 let kind = EventKind::HiveTaskCreated {
1672 hive_task_id: HiveTaskId::from_string("HIVE001"),
1673 objective: "optimize scoring".into(),
1674 agent_count: 3,
1675 };
1676 let json = serde_json::to_string(&kind).unwrap();
1677 assert!(json.contains("\"type\":\"HiveTaskCreated\""));
1678 let back: EventKind = serde_json::from_str(&json).unwrap();
1679 assert!(matches!(
1680 back,
1681 EventKind::HiveTaskCreated { agent_count: 3, .. }
1682 ));
1683 }
1684
1685 #[test]
1686 fn hive_artifact_shared_roundtrip() {
1687 let kind = EventKind::HiveArtifactShared {
1688 hive_task_id: HiveTaskId::from_string("HIVE001"),
1689 source_session_id: SessionId::from_string("SESS-A"),
1690 score: 0.87,
1691 mutation_summary: "rewrote parser".into(),
1692 };
1693 let json = serde_json::to_string(&kind).unwrap();
1694 let back: EventKind = serde_json::from_str(&json).unwrap();
1695 assert!(matches!(back, EventKind::HiveArtifactShared { .. }));
1696 }
1697
1698 #[test]
1699 fn hive_selection_made_roundtrip() {
1700 let kind = EventKind::HiveSelectionMade {
1701 hive_task_id: HiveTaskId::from_string("HIVE001"),
1702 winning_session_id: SessionId::from_string("SESS-B"),
1703 winning_score: 0.92,
1704 generation: 2,
1705 };
1706 let json = serde_json::to_string(&kind).unwrap();
1707 let back: EventKind = serde_json::from_str(&json).unwrap();
1708 assert!(matches!(
1709 back,
1710 EventKind::HiveSelectionMade { generation: 2, .. }
1711 ));
1712 }
1713
1714 #[test]
1715 fn hive_generation_completed_roundtrip() {
1716 let kind = EventKind::HiveGenerationCompleted {
1717 hive_task_id: HiveTaskId::from_string("HIVE001"),
1718 generation: 3,
1719 best_score: 0.95,
1720 agent_results: serde_json::json!({"agents": 3, "improved": true}),
1721 };
1722 let json = serde_json::to_string(&kind).unwrap();
1723 let back: EventKind = serde_json::from_str(&json).unwrap();
1724 assert!(matches!(
1725 back,
1726 EventKind::HiveGenerationCompleted { generation: 3, .. }
1727 ));
1728 }
1729
1730 #[test]
1731 fn hive_task_completed_roundtrip() {
1732 let kind = EventKind::HiveTaskCompleted {
1733 hive_task_id: HiveTaskId::from_string("HIVE001"),
1734 total_generations: 5,
1735 total_trials: 15,
1736 final_score: 0.98,
1737 };
1738 let json = serde_json::to_string(&kind).unwrap();
1739 let back: EventKind = serde_json::from_str(&json).unwrap();
1740 assert!(matches!(
1741 back,
1742 EventKind::HiveTaskCompleted {
1743 total_generations: 5,
1744 ..
1745 }
1746 ));
1747 }
1748
1749 #[test]
1750 fn hive_full_envelope_roundtrip() {
1751 let envelope = make_envelope(EventKind::HiveTaskCreated {
1752 hive_task_id: HiveTaskId::from_string("HIVE-ENV"),
1753 objective: "test envelope".into(),
1754 agent_count: 5,
1755 });
1756 let json = serde_json::to_string(&envelope).unwrap();
1757 let back: EventEnvelope = serde_json::from_str(&json).unwrap();
1758 assert!(matches!(
1759 back.kind,
1760 EventKind::HiveTaskCreated { agent_count: 5, .. }
1761 ));
1762 }
1763
1764 #[test]
1765 fn voice_events_roundtrip() {
1766 let kind = EventKind::VoiceSessionStarted {
1767 voice_session_id: "vs1".into(),
1768 adapter: "openai-realtime".into(),
1769 model: "gpt-4o-realtime".into(),
1770 sample_rate_hz: 24000,
1771 channels: 1,
1772 };
1773 let json = serde_json::to_string(&kind).unwrap();
1774 let back: EventKind = serde_json::from_str(&json).unwrap();
1775 assert!(matches!(back, EventKind::VoiceSessionStarted { .. }));
1776 }
1777}