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 Queued {
592 queue_id: String,
593 mode: SteeringMode,
594 message: String,
595 },
596 Steered {
597 queue_id: String,
598 preempted_at: String,
600 },
601 QueueDrained {
602 queue_id: String,
603 processed: usize,
604 },
605
606 ErrorRaised {
608 message: String,
609 },
610
611 Custom {
613 event_type: String,
614 data: serde_json::Value,
615 },
616}
617
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
622#[serde(rename_all = "snake_case")]
623pub enum LoopPhase {
624 Perceive,
625 Deliberate,
626 Gate,
627 Execute,
628 Commit,
629 Reflect,
630 Sleep,
631}
632
633#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
635pub struct TokenUsage {
636 #[serde(default)]
637 pub prompt_tokens: u32,
638 #[serde(default)]
639 pub completion_tokens: u32,
640 #[serde(default)]
641 pub total_tokens: u32,
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
646#[serde(rename_all = "snake_case")]
647pub enum SpanStatus {
648 Ok,
649 Error,
650 Timeout,
651 Cancelled,
652}
653
654#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
656#[serde(rename_all = "snake_case")]
657pub enum RiskLevel {
658 Low,
659 Medium,
660 High,
661 Critical,
662}
663
664#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
666#[serde(rename_all = "snake_case")]
667pub enum ApprovalDecision {
668 Approved,
669 Denied,
670 Timeout,
671}
672
673#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
675#[serde(rename_all = "snake_case")]
676pub enum SnapshotType {
677 Full,
678 Incremental,
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
683#[serde(rename_all = "snake_case")]
684pub enum PolicyDecisionKind {
685 Allow,
686 Deny,
687 RequireApproval,
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
694#[serde(rename_all = "snake_case")]
695pub enum SteeringMode {
696 Collect,
698 Steer,
700 Followup,
702 Interrupt,
704}
705
706#[derive(Deserialize)]
711#[serde(tag = "type")]
712enum EventKindKnown {
713 UserMessage {
714 content: String,
715 },
716 ExternalSignal {
717 signal_type: String,
718 data: serde_json::Value,
719 },
720 SessionCreated {
721 name: String,
722 config: serde_json::Value,
723 },
724 SessionResumed {
725 #[serde(default)]
726 from_snapshot: Option<SnapshotId>,
727 },
728 SessionClosed {
729 reason: String,
730 },
731 BranchCreated {
732 new_branch_id: BranchId,
733 fork_point_seq: SeqNo,
734 name: String,
735 },
736 BranchMerged {
737 source_branch_id: BranchId,
738 merge_seq: SeqNo,
739 },
740 PhaseEntered {
741 phase: LoopPhase,
742 },
743 DeliberationProposed {
744 summary: String,
745 #[serde(default)]
746 proposed_tool: Option<String>,
747 },
748 RunStarted {
749 provider: String,
750 max_iterations: u32,
751 },
752 RunFinished {
753 reason: String,
754 total_iterations: u32,
755 #[serde(default)]
756 final_answer: Option<String>,
757 #[serde(default)]
758 usage: Option<TokenUsage>,
759 },
760 RunErrored {
761 error: String,
762 },
763 StepStarted {
764 index: u32,
765 },
766 StepFinished {
767 index: u32,
768 stop_reason: String,
769 directive_count: usize,
770 },
771 AssistantTextDelta {
772 delta: String,
773 #[serde(default)]
774 index: Option<u32>,
775 },
776 AssistantMessageCommitted {
777 role: String,
778 content: String,
779 #[serde(default)]
780 model: Option<String>,
781 #[serde(default)]
782 token_usage: Option<TokenUsage>,
783 },
784 TextDelta {
785 delta: String,
786 #[serde(default)]
787 index: Option<u32>,
788 },
789 Message {
790 role: String,
791 content: String,
792 #[serde(default)]
793 model: Option<String>,
794 #[serde(default)]
795 token_usage: Option<TokenUsage>,
796 },
797 ToolCallRequested {
798 call_id: String,
799 tool_name: String,
800 arguments: serde_json::Value,
801 #[serde(default)]
802 category: Option<String>,
803 },
804 ToolCallStarted {
805 tool_run_id: ToolRunId,
806 tool_name: String,
807 },
808 ToolCallCompleted {
809 tool_run_id: ToolRunId,
810 #[serde(default)]
811 call_id: Option<String>,
812 tool_name: String,
813 result: serde_json::Value,
814 duration_ms: u64,
815 status: SpanStatus,
816 },
817 ToolCallFailed {
818 call_id: String,
819 tool_name: String,
820 error: String,
821 },
822 FileWrite {
823 path: String,
824 blob_hash: BlobHash,
825 size_bytes: u64,
826 #[serde(default)]
827 content_type: Option<String>,
828 },
829 FileDelete {
830 path: String,
831 },
832 FileRename {
833 old_path: String,
834 new_path: String,
835 },
836 FileMutated {
837 path: String,
838 content_hash: String,
839 },
840 StatePatched {
841 #[serde(default)]
842 index: Option<u32>,
843 patch: serde_json::Value,
844 revision: u64,
845 },
846 StatePatchCommitted {
847 new_version: u64,
848 patch: StatePatch,
849 },
850 ContextCompacted {
851 dropped_count: usize,
852 tokens_before: usize,
853 tokens_after: usize,
854 },
855 PolicyEvaluated {
856 tool_name: String,
857 decision: PolicyDecisionKind,
858 #[serde(default)]
859 rule_id: Option<String>,
860 #[serde(default)]
861 explanation: Option<String>,
862 },
863 ApprovalRequested {
864 approval_id: ApprovalId,
865 call_id: String,
866 tool_name: String,
867 arguments: serde_json::Value,
868 risk: RiskLevel,
869 },
870 ApprovalResolved {
871 approval_id: ApprovalId,
872 decision: ApprovalDecision,
873 #[serde(default)]
874 reason: Option<String>,
875 },
876 SnapshotCreated {
877 snapshot_id: SnapshotId,
878 snapshot_type: SnapshotType,
879 covers_through_seq: SeqNo,
880 data_hash: BlobHash,
881 },
882 SandboxCreated {
883 sandbox_id: String,
884 tier: String,
885 config: serde_json::Value,
886 },
887 SandboxExecuted {
888 sandbox_id: String,
889 command: String,
890 exit_code: i32,
891 duration_ms: u64,
892 },
893 SandboxViolation {
894 sandbox_id: String,
895 violation_type: String,
896 details: String,
897 },
898 SandboxDestroyed {
899 sandbox_id: String,
900 },
901 ObservationAppended {
902 scope: MemoryScope,
903 observation_ref: BlobHash,
904 #[serde(default)]
905 source_run_id: Option<String>,
906 },
907 ReflectionCompacted {
908 scope: MemoryScope,
909 summary_ref: BlobHash,
910 covers_through_seq: SeqNo,
911 },
912 MemoryProposed {
913 scope: MemoryScope,
914 proposal_id: MemoryId,
915 entries_ref: BlobHash,
916 #[serde(default)]
917 source_run_id: Option<String>,
918 },
919 MemoryCommitted {
920 scope: MemoryScope,
921 memory_id: MemoryId,
922 committed_ref: BlobHash,
923 #[serde(default)]
924 supersedes: Option<MemoryId>,
925 },
926 MemoryTombstoned {
927 scope: MemoryScope,
928 memory_id: MemoryId,
929 reason: String,
930 },
931 Heartbeat {
932 summary: String,
933 #[serde(default)]
934 checkpoint_id: Option<CheckpointId>,
935 },
936 StateEstimated {
937 state: AgentStateVector,
938 mode: OperatingMode,
939 },
940 BudgetUpdated {
941 budget: BudgetState,
942 reason: String,
943 },
944 ModeChanged {
945 from: OperatingMode,
946 to: OperatingMode,
947 reason: String,
948 },
949 GatesUpdated {
950 gates: serde_json::Value,
951 reason: String,
952 },
953 CircuitBreakerTripped {
954 reason: String,
955 error_streak: u32,
956 },
957 CheckpointCreated {
958 checkpoint_id: CheckpointId,
959 event_sequence: u64,
960 state_hash: String,
961 },
962 CheckpointRestored {
963 checkpoint_id: CheckpointId,
964 restored_to_seq: u64,
965 },
966 VoiceSessionStarted {
967 voice_session_id: String,
968 adapter: String,
969 model: String,
970 sample_rate_hz: u32,
971 channels: u8,
972 },
973 VoiceInputChunk {
974 voice_session_id: String,
975 chunk_index: u64,
976 bytes: usize,
977 format: String,
978 },
979 VoiceOutputChunk {
980 voice_session_id: String,
981 chunk_index: u64,
982 bytes: usize,
983 format: String,
984 },
985 VoiceSessionStopped {
986 voice_session_id: String,
987 reason: String,
988 },
989 VoiceAdapterError {
990 voice_session_id: String,
991 message: String,
992 },
993 WorldModelObserved {
994 state_ref: BlobHash,
995 meta: serde_json::Value,
996 },
997 WorldModelRollout {
998 trajectory_ref: BlobHash,
999 #[serde(default)]
1000 score: Option<f32>,
1001 },
1002 IntentProposed {
1003 intent_id: String,
1004 kind: String,
1005 #[serde(default)]
1006 risk: Option<RiskLevel>,
1007 },
1008 IntentEvaluated {
1009 intent_id: String,
1010 allowed: bool,
1011 requires_approval: bool,
1012 #[serde(default)]
1013 reasons: Vec<String>,
1014 },
1015 IntentApproved {
1016 intent_id: String,
1017 #[serde(default)]
1018 actor: Option<String>,
1019 },
1020 IntentRejected {
1021 intent_id: String,
1022 #[serde(default)]
1023 reasons: Vec<String>,
1024 },
1025 HiveTaskCreated {
1026 hive_task_id: HiveTaskId,
1027 objective: String,
1028 agent_count: u32,
1029 },
1030 HiveArtifactShared {
1031 hive_task_id: HiveTaskId,
1032 source_session_id: SessionId,
1033 score: f32,
1034 mutation_summary: String,
1035 },
1036 HiveSelectionMade {
1037 hive_task_id: HiveTaskId,
1038 winning_session_id: SessionId,
1039 winning_score: f32,
1040 generation: u32,
1041 },
1042 HiveGenerationCompleted {
1043 hive_task_id: HiveTaskId,
1044 generation: u32,
1045 best_score: f32,
1046 agent_results: serde_json::Value,
1047 },
1048 HiveTaskCompleted {
1049 hive_task_id: HiveTaskId,
1050 total_generations: u32,
1051 total_trials: u32,
1052 final_score: f32,
1053 },
1054 Queued {
1055 queue_id: String,
1056 mode: SteeringMode,
1057 message: String,
1058 },
1059 Steered {
1060 queue_id: String,
1061 preempted_at: String,
1062 },
1063 QueueDrained {
1064 queue_id: String,
1065 processed: usize,
1066 },
1067 ErrorRaised {
1068 message: String,
1069 },
1070 Custom {
1071 event_type: String,
1072 data: serde_json::Value,
1073 },
1074}
1075
1076impl<'de> Deserialize<'de> for EventKind {
1078 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1079 where
1080 D: serde::Deserializer<'de>,
1081 {
1082 let raw = serde_json::Value::deserialize(deserializer)?;
1083 match serde_json::from_value::<EventKindKnown>(raw.clone()) {
1084 Ok(known) => Ok(known.into()),
1085 Err(_) => {
1086 let event_type = raw
1087 .get("type")
1088 .and_then(|v| v.as_str())
1089 .unwrap_or("Unknown")
1090 .to_string();
1091 let mut data = raw;
1092 if let Some(obj) = data.as_object_mut() {
1093 obj.remove("type");
1094 }
1095 Ok(EventKind::Custom { event_type, data })
1096 }
1097 }
1098 }
1099}
1100
1101impl From<EventKindKnown> for EventKind {
1104 #[allow(clippy::too_many_lines)]
1105 fn from(k: EventKindKnown) -> Self {
1106 match k {
1107 EventKindKnown::UserMessage { content } => Self::UserMessage { content },
1108 EventKindKnown::ExternalSignal { signal_type, data } => {
1109 Self::ExternalSignal { signal_type, data }
1110 }
1111 EventKindKnown::SessionCreated { name, config } => {
1112 Self::SessionCreated { name, config }
1113 }
1114 EventKindKnown::SessionResumed { from_snapshot } => {
1115 Self::SessionResumed { from_snapshot }
1116 }
1117 EventKindKnown::SessionClosed { reason } => Self::SessionClosed { reason },
1118 EventKindKnown::BranchCreated {
1119 new_branch_id,
1120 fork_point_seq,
1121 name,
1122 } => Self::BranchCreated {
1123 new_branch_id,
1124 fork_point_seq,
1125 name,
1126 },
1127 EventKindKnown::BranchMerged {
1128 source_branch_id,
1129 merge_seq,
1130 } => Self::BranchMerged {
1131 source_branch_id,
1132 merge_seq,
1133 },
1134 EventKindKnown::PhaseEntered { phase } => Self::PhaseEntered { phase },
1135 EventKindKnown::DeliberationProposed {
1136 summary,
1137 proposed_tool,
1138 } => Self::DeliberationProposed {
1139 summary,
1140 proposed_tool,
1141 },
1142 EventKindKnown::RunStarted {
1143 provider,
1144 max_iterations,
1145 } => Self::RunStarted {
1146 provider,
1147 max_iterations,
1148 },
1149 EventKindKnown::RunFinished {
1150 reason,
1151 total_iterations,
1152 final_answer,
1153 usage,
1154 } => Self::RunFinished {
1155 reason,
1156 total_iterations,
1157 final_answer,
1158 usage,
1159 },
1160 EventKindKnown::RunErrored { error } => Self::RunErrored { error },
1161 EventKindKnown::StepStarted { index } => Self::StepStarted { index },
1162 EventKindKnown::StepFinished {
1163 index,
1164 stop_reason,
1165 directive_count,
1166 } => Self::StepFinished {
1167 index,
1168 stop_reason,
1169 directive_count,
1170 },
1171 EventKindKnown::AssistantTextDelta { delta, index } => {
1172 Self::AssistantTextDelta { delta, index }
1173 }
1174 EventKindKnown::AssistantMessageCommitted {
1175 role,
1176 content,
1177 model,
1178 token_usage,
1179 } => Self::AssistantMessageCommitted {
1180 role,
1181 content,
1182 model,
1183 token_usage,
1184 },
1185 EventKindKnown::TextDelta { delta, index } => Self::TextDelta { delta, index },
1186 EventKindKnown::Message {
1187 role,
1188 content,
1189 model,
1190 token_usage,
1191 } => Self::Message {
1192 role,
1193 content,
1194 model,
1195 token_usage,
1196 },
1197 EventKindKnown::ToolCallRequested {
1198 call_id,
1199 tool_name,
1200 arguments,
1201 category,
1202 } => Self::ToolCallRequested {
1203 call_id,
1204 tool_name,
1205 arguments,
1206 category,
1207 },
1208 EventKindKnown::ToolCallStarted {
1209 tool_run_id,
1210 tool_name,
1211 } => Self::ToolCallStarted {
1212 tool_run_id,
1213 tool_name,
1214 },
1215 EventKindKnown::ToolCallCompleted {
1216 tool_run_id,
1217 call_id,
1218 tool_name,
1219 result,
1220 duration_ms,
1221 status,
1222 } => Self::ToolCallCompleted {
1223 tool_run_id,
1224 call_id,
1225 tool_name,
1226 result,
1227 duration_ms,
1228 status,
1229 },
1230 EventKindKnown::ToolCallFailed {
1231 call_id,
1232 tool_name,
1233 error,
1234 } => Self::ToolCallFailed {
1235 call_id,
1236 tool_name,
1237 error,
1238 },
1239 EventKindKnown::FileWrite {
1240 path,
1241 blob_hash,
1242 size_bytes,
1243 content_type,
1244 } => Self::FileWrite {
1245 path,
1246 blob_hash,
1247 size_bytes,
1248 content_type,
1249 },
1250 EventKindKnown::FileDelete { path } => Self::FileDelete { path },
1251 EventKindKnown::FileRename { old_path, new_path } => {
1252 Self::FileRename { old_path, new_path }
1253 }
1254 EventKindKnown::FileMutated { path, content_hash } => {
1255 Self::FileMutated { path, content_hash }
1256 }
1257 EventKindKnown::StatePatched {
1258 index,
1259 patch,
1260 revision,
1261 } => Self::StatePatched {
1262 index,
1263 patch,
1264 revision,
1265 },
1266 EventKindKnown::StatePatchCommitted { new_version, patch } => {
1267 Self::StatePatchCommitted { new_version, patch }
1268 }
1269 EventKindKnown::ContextCompacted {
1270 dropped_count,
1271 tokens_before,
1272 tokens_after,
1273 } => Self::ContextCompacted {
1274 dropped_count,
1275 tokens_before,
1276 tokens_after,
1277 },
1278 EventKindKnown::PolicyEvaluated {
1279 tool_name,
1280 decision,
1281 rule_id,
1282 explanation,
1283 } => Self::PolicyEvaluated {
1284 tool_name,
1285 decision,
1286 rule_id,
1287 explanation,
1288 },
1289 EventKindKnown::ApprovalRequested {
1290 approval_id,
1291 call_id,
1292 tool_name,
1293 arguments,
1294 risk,
1295 } => Self::ApprovalRequested {
1296 approval_id,
1297 call_id,
1298 tool_name,
1299 arguments,
1300 risk,
1301 },
1302 EventKindKnown::ApprovalResolved {
1303 approval_id,
1304 decision,
1305 reason,
1306 } => Self::ApprovalResolved {
1307 approval_id,
1308 decision,
1309 reason,
1310 },
1311 EventKindKnown::SnapshotCreated {
1312 snapshot_id,
1313 snapshot_type,
1314 covers_through_seq,
1315 data_hash,
1316 } => Self::SnapshotCreated {
1317 snapshot_id,
1318 snapshot_type,
1319 covers_through_seq,
1320 data_hash,
1321 },
1322 EventKindKnown::SandboxCreated {
1323 sandbox_id,
1324 tier,
1325 config,
1326 } => Self::SandboxCreated {
1327 sandbox_id,
1328 tier,
1329 config,
1330 },
1331 EventKindKnown::SandboxExecuted {
1332 sandbox_id,
1333 command,
1334 exit_code,
1335 duration_ms,
1336 } => Self::SandboxExecuted {
1337 sandbox_id,
1338 command,
1339 exit_code,
1340 duration_ms,
1341 },
1342 EventKindKnown::SandboxViolation {
1343 sandbox_id,
1344 violation_type,
1345 details,
1346 } => Self::SandboxViolation {
1347 sandbox_id,
1348 violation_type,
1349 details,
1350 },
1351 EventKindKnown::SandboxDestroyed { sandbox_id } => {
1352 Self::SandboxDestroyed { sandbox_id }
1353 }
1354 EventKindKnown::ObservationAppended {
1355 scope,
1356 observation_ref,
1357 source_run_id,
1358 } => Self::ObservationAppended {
1359 scope,
1360 observation_ref,
1361 source_run_id,
1362 },
1363 EventKindKnown::ReflectionCompacted {
1364 scope,
1365 summary_ref,
1366 covers_through_seq,
1367 } => Self::ReflectionCompacted {
1368 scope,
1369 summary_ref,
1370 covers_through_seq,
1371 },
1372 EventKindKnown::MemoryProposed {
1373 scope,
1374 proposal_id,
1375 entries_ref,
1376 source_run_id,
1377 } => Self::MemoryProposed {
1378 scope,
1379 proposal_id,
1380 entries_ref,
1381 source_run_id,
1382 },
1383 EventKindKnown::MemoryCommitted {
1384 scope,
1385 memory_id,
1386 committed_ref,
1387 supersedes,
1388 } => Self::MemoryCommitted {
1389 scope,
1390 memory_id,
1391 committed_ref,
1392 supersedes,
1393 },
1394 EventKindKnown::MemoryTombstoned {
1395 scope,
1396 memory_id,
1397 reason,
1398 } => Self::MemoryTombstoned {
1399 scope,
1400 memory_id,
1401 reason,
1402 },
1403 EventKindKnown::Heartbeat {
1404 summary,
1405 checkpoint_id,
1406 } => Self::Heartbeat {
1407 summary,
1408 checkpoint_id,
1409 },
1410 EventKindKnown::StateEstimated { state, mode } => Self::StateEstimated { state, mode },
1411 EventKindKnown::BudgetUpdated { budget, reason } => {
1412 Self::BudgetUpdated { budget, reason }
1413 }
1414 EventKindKnown::ModeChanged { from, to, reason } => {
1415 Self::ModeChanged { from, to, reason }
1416 }
1417 EventKindKnown::GatesUpdated { gates, reason } => Self::GatesUpdated { gates, reason },
1418 EventKindKnown::CircuitBreakerTripped {
1419 reason,
1420 error_streak,
1421 } => Self::CircuitBreakerTripped {
1422 reason,
1423 error_streak,
1424 },
1425 EventKindKnown::CheckpointCreated {
1426 checkpoint_id,
1427 event_sequence,
1428 state_hash,
1429 } => Self::CheckpointCreated {
1430 checkpoint_id,
1431 event_sequence,
1432 state_hash,
1433 },
1434 EventKindKnown::CheckpointRestored {
1435 checkpoint_id,
1436 restored_to_seq,
1437 } => Self::CheckpointRestored {
1438 checkpoint_id,
1439 restored_to_seq,
1440 },
1441 EventKindKnown::VoiceSessionStarted {
1442 voice_session_id,
1443 adapter,
1444 model,
1445 sample_rate_hz,
1446 channels,
1447 } => Self::VoiceSessionStarted {
1448 voice_session_id,
1449 adapter,
1450 model,
1451 sample_rate_hz,
1452 channels,
1453 },
1454 EventKindKnown::VoiceInputChunk {
1455 voice_session_id,
1456 chunk_index,
1457 bytes,
1458 format,
1459 } => Self::VoiceInputChunk {
1460 voice_session_id,
1461 chunk_index,
1462 bytes,
1463 format,
1464 },
1465 EventKindKnown::VoiceOutputChunk {
1466 voice_session_id,
1467 chunk_index,
1468 bytes,
1469 format,
1470 } => Self::VoiceOutputChunk {
1471 voice_session_id,
1472 chunk_index,
1473 bytes,
1474 format,
1475 },
1476 EventKindKnown::VoiceSessionStopped {
1477 voice_session_id,
1478 reason,
1479 } => Self::VoiceSessionStopped {
1480 voice_session_id,
1481 reason,
1482 },
1483 EventKindKnown::VoiceAdapterError {
1484 voice_session_id,
1485 message,
1486 } => Self::VoiceAdapterError {
1487 voice_session_id,
1488 message,
1489 },
1490 EventKindKnown::WorldModelObserved { state_ref, meta } => {
1491 Self::WorldModelObserved { state_ref, meta }
1492 }
1493 EventKindKnown::WorldModelRollout {
1494 trajectory_ref,
1495 score,
1496 } => Self::WorldModelRollout {
1497 trajectory_ref,
1498 score,
1499 },
1500 EventKindKnown::IntentProposed {
1501 intent_id,
1502 kind,
1503 risk,
1504 } => Self::IntentProposed {
1505 intent_id,
1506 kind,
1507 risk,
1508 },
1509 EventKindKnown::IntentEvaluated {
1510 intent_id,
1511 allowed,
1512 requires_approval,
1513 reasons,
1514 } => Self::IntentEvaluated {
1515 intent_id,
1516 allowed,
1517 requires_approval,
1518 reasons,
1519 },
1520 EventKindKnown::IntentApproved { intent_id, actor } => {
1521 Self::IntentApproved { intent_id, actor }
1522 }
1523 EventKindKnown::IntentRejected { intent_id, reasons } => {
1524 Self::IntentRejected { intent_id, reasons }
1525 }
1526 EventKindKnown::HiveTaskCreated {
1527 hive_task_id,
1528 objective,
1529 agent_count,
1530 } => Self::HiveTaskCreated {
1531 hive_task_id,
1532 objective,
1533 agent_count,
1534 },
1535 EventKindKnown::HiveArtifactShared {
1536 hive_task_id,
1537 source_session_id,
1538 score,
1539 mutation_summary,
1540 } => Self::HiveArtifactShared {
1541 hive_task_id,
1542 source_session_id,
1543 score,
1544 mutation_summary,
1545 },
1546 EventKindKnown::HiveSelectionMade {
1547 hive_task_id,
1548 winning_session_id,
1549 winning_score,
1550 generation,
1551 } => Self::HiveSelectionMade {
1552 hive_task_id,
1553 winning_session_id,
1554 winning_score,
1555 generation,
1556 },
1557 EventKindKnown::HiveGenerationCompleted {
1558 hive_task_id,
1559 generation,
1560 best_score,
1561 agent_results,
1562 } => Self::HiveGenerationCompleted {
1563 hive_task_id,
1564 generation,
1565 best_score,
1566 agent_results,
1567 },
1568 EventKindKnown::HiveTaskCompleted {
1569 hive_task_id,
1570 total_generations,
1571 total_trials,
1572 final_score,
1573 } => Self::HiveTaskCompleted {
1574 hive_task_id,
1575 total_generations,
1576 total_trials,
1577 final_score,
1578 },
1579 EventKindKnown::Queued {
1580 queue_id,
1581 mode,
1582 message,
1583 } => Self::Queued {
1584 queue_id,
1585 mode,
1586 message,
1587 },
1588 EventKindKnown::Steered {
1589 queue_id,
1590 preempted_at,
1591 } => Self::Steered {
1592 queue_id,
1593 preempted_at,
1594 },
1595 EventKindKnown::QueueDrained {
1596 queue_id,
1597 processed,
1598 } => Self::QueueDrained {
1599 queue_id,
1600 processed,
1601 },
1602 EventKindKnown::ErrorRaised { message } => Self::ErrorRaised { message },
1603 EventKindKnown::Custom { event_type, data } => Self::Custom { event_type, data },
1604 }
1605 }
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610 use super::*;
1611
1612 fn make_envelope(kind: EventKind) -> EventEnvelope {
1613 EventEnvelope {
1614 event_id: EventId::from_string("EVT001"),
1615 session_id: SessionId::from_string("SESS001"),
1616 agent_id: AgentId::from_string("AGENT001"),
1617 branch_id: BranchId::from_string("main"),
1618 run_id: None,
1619 seq: 1,
1620 timestamp: 1_700_000_000_000_000,
1621 actor: EventActor::default(),
1622 schema: EventSchema::default(),
1623 parent_id: None,
1624 trace_id: None,
1625 span_id: None,
1626 digest: None,
1627 kind,
1628 metadata: HashMap::new(),
1629 schema_version: 1,
1630 }
1631 }
1632
1633 #[test]
1634 fn error_raised_roundtrip() {
1635 let kind = EventKind::ErrorRaised {
1636 message: "boom".into(),
1637 };
1638 let json = serde_json::to_string(&kind).unwrap();
1639 assert!(json.contains("\"type\":\"ErrorRaised\""));
1640 let back: EventKind = serde_json::from_str(&json).unwrap();
1641 assert!(matches!(back, EventKind::ErrorRaised { message } if message == "boom"));
1642 }
1643
1644 #[test]
1645 fn heartbeat_roundtrip() {
1646 let kind = EventKind::Heartbeat {
1647 summary: "alive".into(),
1648 checkpoint_id: None,
1649 };
1650 let json = serde_json::to_string(&kind).unwrap();
1651 let back: EventKind = serde_json::from_str(&json).unwrap();
1652 assert!(matches!(back, EventKind::Heartbeat { .. }));
1653 }
1654
1655 #[test]
1656 fn state_estimated_roundtrip() {
1657 let kind = EventKind::StateEstimated {
1658 state: AgentStateVector::default(),
1659 mode: OperatingMode::Execute,
1660 };
1661 let json = serde_json::to_string(&kind).unwrap();
1662 let back: EventKind = serde_json::from_str(&json).unwrap();
1663 assert!(matches!(back, EventKind::StateEstimated { .. }));
1664 }
1665
1666 #[test]
1667 fn unknown_variant_becomes_custom() {
1668 let json = r#"{"type":"FutureFeature","key":"value","num":42}"#;
1669 let kind: EventKind = serde_json::from_str(json).unwrap();
1670 if let EventKind::Custom { event_type, data } = kind {
1671 assert_eq!(event_type, "FutureFeature");
1672 assert_eq!(data["key"], "value");
1673 assert_eq!(data["num"], 42);
1674 } else {
1675 panic!("should be Custom");
1676 }
1677 }
1678
1679 #[test]
1680 fn full_envelope_roundtrip() {
1681 let envelope = make_envelope(EventKind::RunStarted {
1682 provider: "anthropic".into(),
1683 max_iterations: 10,
1684 });
1685 let json = serde_json::to_string(&envelope).unwrap();
1686 let back: EventEnvelope = serde_json::from_str(&json).unwrap();
1687 assert_eq!(back.seq, 1);
1688 assert_eq!(back.schema_version, 1);
1689 assert!(matches!(back.kind, EventKind::RunStarted { .. }));
1690 }
1691
1692 #[test]
1693 fn tool_call_lifecycle_roundtrip() {
1694 let requested = EventKind::ToolCallRequested {
1695 call_id: "c1".into(),
1696 tool_name: "read_file".into(),
1697 arguments: serde_json::json!({"path": "/etc/hosts"}),
1698 category: Some("fs".into()),
1699 };
1700 let json = serde_json::to_string(&requested).unwrap();
1701 let back: EventKind = serde_json::from_str(&json).unwrap();
1702 assert!(matches!(back, EventKind::ToolCallRequested { .. }));
1703 }
1704
1705 #[test]
1706 fn memory_events_roundtrip() {
1707 let proposed = EventKind::MemoryProposed {
1708 scope: MemoryScope::Agent,
1709 proposal_id: MemoryId::from_string("PROP001"),
1710 entries_ref: BlobHash::from_hex("abc"),
1711 source_run_id: None,
1712 };
1713 let json = serde_json::to_string(&proposed).unwrap();
1714 let back: EventKind = serde_json::from_str(&json).unwrap();
1715 assert!(matches!(back, EventKind::MemoryProposed { .. }));
1716 }
1717
1718 #[test]
1719 fn mode_changed_roundtrip() {
1720 let kind = EventKind::ModeChanged {
1721 from: OperatingMode::Execute,
1722 to: OperatingMode::Recover,
1723 reason: "error streak".into(),
1724 };
1725 let json = serde_json::to_string(&kind).unwrap();
1726 let back: EventKind = serde_json::from_str(&json).unwrap();
1727 assert!(matches!(back, EventKind::ModeChanged { .. }));
1728 }
1729
1730 #[test]
1731 fn schema_version_defaults_to_1() {
1732 let json = r#"{"event_id":"E1","session_id":"S1","branch_id":"main","seq":0,"timestamp":100,"kind":{"type":"ErrorRaised","message":"x"},"metadata":{}}"#;
1733 let envelope: EventEnvelope = serde_json::from_str(json).unwrap();
1734 assert_eq!(envelope.schema_version, 1);
1735 }
1736
1737 #[test]
1738 fn hive_task_created_roundtrip() {
1739 let kind = EventKind::HiveTaskCreated {
1740 hive_task_id: HiveTaskId::from_string("HIVE001"),
1741 objective: "optimize scoring".into(),
1742 agent_count: 3,
1743 };
1744 let json = serde_json::to_string(&kind).unwrap();
1745 assert!(json.contains("\"type\":\"HiveTaskCreated\""));
1746 let back: EventKind = serde_json::from_str(&json).unwrap();
1747 assert!(matches!(
1748 back,
1749 EventKind::HiveTaskCreated { agent_count: 3, .. }
1750 ));
1751 }
1752
1753 #[test]
1754 fn hive_artifact_shared_roundtrip() {
1755 let kind = EventKind::HiveArtifactShared {
1756 hive_task_id: HiveTaskId::from_string("HIVE001"),
1757 source_session_id: SessionId::from_string("SESS-A"),
1758 score: 0.87,
1759 mutation_summary: "rewrote parser".into(),
1760 };
1761 let json = serde_json::to_string(&kind).unwrap();
1762 let back: EventKind = serde_json::from_str(&json).unwrap();
1763 assert!(matches!(back, EventKind::HiveArtifactShared { .. }));
1764 }
1765
1766 #[test]
1767 fn hive_selection_made_roundtrip() {
1768 let kind = EventKind::HiveSelectionMade {
1769 hive_task_id: HiveTaskId::from_string("HIVE001"),
1770 winning_session_id: SessionId::from_string("SESS-B"),
1771 winning_score: 0.92,
1772 generation: 2,
1773 };
1774 let json = serde_json::to_string(&kind).unwrap();
1775 let back: EventKind = serde_json::from_str(&json).unwrap();
1776 assert!(matches!(
1777 back,
1778 EventKind::HiveSelectionMade { generation: 2, .. }
1779 ));
1780 }
1781
1782 #[test]
1783 fn hive_generation_completed_roundtrip() {
1784 let kind = EventKind::HiveGenerationCompleted {
1785 hive_task_id: HiveTaskId::from_string("HIVE001"),
1786 generation: 3,
1787 best_score: 0.95,
1788 agent_results: serde_json::json!({"agents": 3, "improved": true}),
1789 };
1790 let json = serde_json::to_string(&kind).unwrap();
1791 let back: EventKind = serde_json::from_str(&json).unwrap();
1792 assert!(matches!(
1793 back,
1794 EventKind::HiveGenerationCompleted { generation: 3, .. }
1795 ));
1796 }
1797
1798 #[test]
1799 fn hive_task_completed_roundtrip() {
1800 let kind = EventKind::HiveTaskCompleted {
1801 hive_task_id: HiveTaskId::from_string("HIVE001"),
1802 total_generations: 5,
1803 total_trials: 15,
1804 final_score: 0.98,
1805 };
1806 let json = serde_json::to_string(&kind).unwrap();
1807 let back: EventKind = serde_json::from_str(&json).unwrap();
1808 assert!(matches!(
1809 back,
1810 EventKind::HiveTaskCompleted {
1811 total_generations: 5,
1812 ..
1813 }
1814 ));
1815 }
1816
1817 #[test]
1818 fn hive_full_envelope_roundtrip() {
1819 let envelope = make_envelope(EventKind::HiveTaskCreated {
1820 hive_task_id: HiveTaskId::from_string("HIVE-ENV"),
1821 objective: "test envelope".into(),
1822 agent_count: 5,
1823 });
1824 let json = serde_json::to_string(&envelope).unwrap();
1825 let back: EventEnvelope = serde_json::from_str(&json).unwrap();
1826 assert!(matches!(
1827 back.kind,
1828 EventKind::HiveTaskCreated { agent_count: 5, .. }
1829 ));
1830 }
1831
1832 #[test]
1833 fn voice_events_roundtrip() {
1834 let kind = EventKind::VoiceSessionStarted {
1835 voice_session_id: "vs1".into(),
1836 adapter: "openai-realtime".into(),
1837 model: "gpt-4o-realtime".into(),
1838 sample_rate_hz: 24000,
1839 channels: 1,
1840 };
1841 let json = serde_json::to_string(&kind).unwrap();
1842 let back: EventKind = serde_json::from_str(&json).unwrap();
1843 assert!(matches!(back, EventKind::VoiceSessionStarted { .. }));
1844 }
1845}