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 ErrorRaised {
561 message: String,
562 },
563
564 Custom {
566 event_type: String,
567 data: serde_json::Value,
568 },
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(rename_all = "snake_case")]
576pub enum LoopPhase {
577 Perceive,
578 Deliberate,
579 Gate,
580 Execute,
581 Commit,
582 Reflect,
583 Sleep,
584}
585
586#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
588pub struct TokenUsage {
589 #[serde(default)]
590 pub prompt_tokens: u32,
591 #[serde(default)]
592 pub completion_tokens: u32,
593 #[serde(default)]
594 pub total_tokens: u32,
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
599#[serde(rename_all = "snake_case")]
600pub enum SpanStatus {
601 Ok,
602 Error,
603 Timeout,
604 Cancelled,
605}
606
607#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
609#[serde(rename_all = "snake_case")]
610pub enum RiskLevel {
611 Low,
612 Medium,
613 High,
614 Critical,
615}
616
617#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
619#[serde(rename_all = "snake_case")]
620pub enum ApprovalDecision {
621 Approved,
622 Denied,
623 Timeout,
624}
625
626#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
628#[serde(rename_all = "snake_case")]
629pub enum SnapshotType {
630 Full,
631 Incremental,
632}
633
634#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
636#[serde(rename_all = "snake_case")]
637pub enum PolicyDecisionKind {
638 Allow,
639 Deny,
640 RequireApproval,
641}
642
643#[derive(Deserialize)]
648#[serde(tag = "type")]
649enum EventKindKnown {
650 UserMessage {
651 content: String,
652 },
653 ExternalSignal {
654 signal_type: String,
655 data: serde_json::Value,
656 },
657 SessionCreated {
658 name: String,
659 config: serde_json::Value,
660 },
661 SessionResumed {
662 #[serde(default)]
663 from_snapshot: Option<SnapshotId>,
664 },
665 SessionClosed {
666 reason: String,
667 },
668 BranchCreated {
669 new_branch_id: BranchId,
670 fork_point_seq: SeqNo,
671 name: String,
672 },
673 BranchMerged {
674 source_branch_id: BranchId,
675 merge_seq: SeqNo,
676 },
677 PhaseEntered {
678 phase: LoopPhase,
679 },
680 DeliberationProposed {
681 summary: String,
682 #[serde(default)]
683 proposed_tool: Option<String>,
684 },
685 RunStarted {
686 provider: String,
687 max_iterations: u32,
688 },
689 RunFinished {
690 reason: String,
691 total_iterations: u32,
692 #[serde(default)]
693 final_answer: Option<String>,
694 #[serde(default)]
695 usage: Option<TokenUsage>,
696 },
697 RunErrored {
698 error: String,
699 },
700 StepStarted {
701 index: u32,
702 },
703 StepFinished {
704 index: u32,
705 stop_reason: String,
706 directive_count: usize,
707 },
708 AssistantTextDelta {
709 delta: String,
710 #[serde(default)]
711 index: Option<u32>,
712 },
713 AssistantMessageCommitted {
714 role: String,
715 content: String,
716 #[serde(default)]
717 model: Option<String>,
718 #[serde(default)]
719 token_usage: Option<TokenUsage>,
720 },
721 TextDelta {
722 delta: String,
723 #[serde(default)]
724 index: Option<u32>,
725 },
726 Message {
727 role: String,
728 content: String,
729 #[serde(default)]
730 model: Option<String>,
731 #[serde(default)]
732 token_usage: Option<TokenUsage>,
733 },
734 ToolCallRequested {
735 call_id: String,
736 tool_name: String,
737 arguments: serde_json::Value,
738 #[serde(default)]
739 category: Option<String>,
740 },
741 ToolCallStarted {
742 tool_run_id: ToolRunId,
743 tool_name: String,
744 },
745 ToolCallCompleted {
746 tool_run_id: ToolRunId,
747 #[serde(default)]
748 call_id: Option<String>,
749 tool_name: String,
750 result: serde_json::Value,
751 duration_ms: u64,
752 status: SpanStatus,
753 },
754 ToolCallFailed {
755 call_id: String,
756 tool_name: String,
757 error: String,
758 },
759 FileWrite {
760 path: String,
761 blob_hash: BlobHash,
762 size_bytes: u64,
763 #[serde(default)]
764 content_type: Option<String>,
765 },
766 FileDelete {
767 path: String,
768 },
769 FileRename {
770 old_path: String,
771 new_path: String,
772 },
773 FileMutated {
774 path: String,
775 content_hash: String,
776 },
777 StatePatched {
778 #[serde(default)]
779 index: Option<u32>,
780 patch: serde_json::Value,
781 revision: u64,
782 },
783 StatePatchCommitted {
784 new_version: u64,
785 patch: StatePatch,
786 },
787 ContextCompacted {
788 dropped_count: usize,
789 tokens_before: usize,
790 tokens_after: usize,
791 },
792 PolicyEvaluated {
793 tool_name: String,
794 decision: PolicyDecisionKind,
795 #[serde(default)]
796 rule_id: Option<String>,
797 #[serde(default)]
798 explanation: Option<String>,
799 },
800 ApprovalRequested {
801 approval_id: ApprovalId,
802 call_id: String,
803 tool_name: String,
804 arguments: serde_json::Value,
805 risk: RiskLevel,
806 },
807 ApprovalResolved {
808 approval_id: ApprovalId,
809 decision: ApprovalDecision,
810 #[serde(default)]
811 reason: Option<String>,
812 },
813 SnapshotCreated {
814 snapshot_id: SnapshotId,
815 snapshot_type: SnapshotType,
816 covers_through_seq: SeqNo,
817 data_hash: BlobHash,
818 },
819 SandboxCreated {
820 sandbox_id: String,
821 tier: String,
822 config: serde_json::Value,
823 },
824 SandboxExecuted {
825 sandbox_id: String,
826 command: String,
827 exit_code: i32,
828 duration_ms: u64,
829 },
830 SandboxViolation {
831 sandbox_id: String,
832 violation_type: String,
833 details: String,
834 },
835 SandboxDestroyed {
836 sandbox_id: String,
837 },
838 ObservationAppended {
839 scope: MemoryScope,
840 observation_ref: BlobHash,
841 #[serde(default)]
842 source_run_id: Option<String>,
843 },
844 ReflectionCompacted {
845 scope: MemoryScope,
846 summary_ref: BlobHash,
847 covers_through_seq: SeqNo,
848 },
849 MemoryProposed {
850 scope: MemoryScope,
851 proposal_id: MemoryId,
852 entries_ref: BlobHash,
853 #[serde(default)]
854 source_run_id: Option<String>,
855 },
856 MemoryCommitted {
857 scope: MemoryScope,
858 memory_id: MemoryId,
859 committed_ref: BlobHash,
860 #[serde(default)]
861 supersedes: Option<MemoryId>,
862 },
863 MemoryTombstoned {
864 scope: MemoryScope,
865 memory_id: MemoryId,
866 reason: String,
867 },
868 Heartbeat {
869 summary: String,
870 #[serde(default)]
871 checkpoint_id: Option<CheckpointId>,
872 },
873 StateEstimated {
874 state: AgentStateVector,
875 mode: OperatingMode,
876 },
877 BudgetUpdated {
878 budget: BudgetState,
879 reason: String,
880 },
881 ModeChanged {
882 from: OperatingMode,
883 to: OperatingMode,
884 reason: String,
885 },
886 GatesUpdated {
887 gates: serde_json::Value,
888 reason: String,
889 },
890 CircuitBreakerTripped {
891 reason: String,
892 error_streak: u32,
893 },
894 CheckpointCreated {
895 checkpoint_id: CheckpointId,
896 event_sequence: u64,
897 state_hash: String,
898 },
899 CheckpointRestored {
900 checkpoint_id: CheckpointId,
901 restored_to_seq: u64,
902 },
903 VoiceSessionStarted {
904 voice_session_id: String,
905 adapter: String,
906 model: String,
907 sample_rate_hz: u32,
908 channels: u8,
909 },
910 VoiceInputChunk {
911 voice_session_id: String,
912 chunk_index: u64,
913 bytes: usize,
914 format: String,
915 },
916 VoiceOutputChunk {
917 voice_session_id: String,
918 chunk_index: u64,
919 bytes: usize,
920 format: String,
921 },
922 VoiceSessionStopped {
923 voice_session_id: String,
924 reason: String,
925 },
926 VoiceAdapterError {
927 voice_session_id: String,
928 message: String,
929 },
930 WorldModelObserved {
931 state_ref: BlobHash,
932 meta: serde_json::Value,
933 },
934 WorldModelRollout {
935 trajectory_ref: BlobHash,
936 #[serde(default)]
937 score: Option<f32>,
938 },
939 IntentProposed {
940 intent_id: String,
941 kind: String,
942 #[serde(default)]
943 risk: Option<RiskLevel>,
944 },
945 IntentEvaluated {
946 intent_id: String,
947 allowed: bool,
948 requires_approval: bool,
949 #[serde(default)]
950 reasons: Vec<String>,
951 },
952 IntentApproved {
953 intent_id: String,
954 #[serde(default)]
955 actor: Option<String>,
956 },
957 IntentRejected {
958 intent_id: String,
959 #[serde(default)]
960 reasons: Vec<String>,
961 },
962 ErrorRaised {
963 message: String,
964 },
965 Custom {
966 event_type: String,
967 data: serde_json::Value,
968 },
969}
970
971impl<'de> Deserialize<'de> for EventKind {
973 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
974 where
975 D: serde::Deserializer<'de>,
976 {
977 let raw = serde_json::Value::deserialize(deserializer)?;
978 match serde_json::from_value::<EventKindKnown>(raw.clone()) {
979 Ok(known) => Ok(known.into()),
980 Err(_) => {
981 let event_type = raw
982 .get("type")
983 .and_then(|v| v.as_str())
984 .unwrap_or("Unknown")
985 .to_string();
986 let mut data = raw;
987 if let Some(obj) = data.as_object_mut() {
988 obj.remove("type");
989 }
990 Ok(EventKind::Custom { event_type, data })
991 }
992 }
993 }
994}
995
996impl From<EventKindKnown> for EventKind {
999 #[allow(clippy::too_many_lines)]
1000 fn from(k: EventKindKnown) -> Self {
1001 match k {
1002 EventKindKnown::UserMessage { content } => Self::UserMessage { content },
1003 EventKindKnown::ExternalSignal { signal_type, data } => {
1004 Self::ExternalSignal { signal_type, data }
1005 }
1006 EventKindKnown::SessionCreated { name, config } => {
1007 Self::SessionCreated { name, config }
1008 }
1009 EventKindKnown::SessionResumed { from_snapshot } => {
1010 Self::SessionResumed { from_snapshot }
1011 }
1012 EventKindKnown::SessionClosed { reason } => Self::SessionClosed { reason },
1013 EventKindKnown::BranchCreated {
1014 new_branch_id,
1015 fork_point_seq,
1016 name,
1017 } => Self::BranchCreated {
1018 new_branch_id,
1019 fork_point_seq,
1020 name,
1021 },
1022 EventKindKnown::BranchMerged {
1023 source_branch_id,
1024 merge_seq,
1025 } => Self::BranchMerged {
1026 source_branch_id,
1027 merge_seq,
1028 },
1029 EventKindKnown::PhaseEntered { phase } => Self::PhaseEntered { phase },
1030 EventKindKnown::DeliberationProposed {
1031 summary,
1032 proposed_tool,
1033 } => Self::DeliberationProposed {
1034 summary,
1035 proposed_tool,
1036 },
1037 EventKindKnown::RunStarted {
1038 provider,
1039 max_iterations,
1040 } => Self::RunStarted {
1041 provider,
1042 max_iterations,
1043 },
1044 EventKindKnown::RunFinished {
1045 reason,
1046 total_iterations,
1047 final_answer,
1048 usage,
1049 } => Self::RunFinished {
1050 reason,
1051 total_iterations,
1052 final_answer,
1053 usage,
1054 },
1055 EventKindKnown::RunErrored { error } => Self::RunErrored { error },
1056 EventKindKnown::StepStarted { index } => Self::StepStarted { index },
1057 EventKindKnown::StepFinished {
1058 index,
1059 stop_reason,
1060 directive_count,
1061 } => Self::StepFinished {
1062 index,
1063 stop_reason,
1064 directive_count,
1065 },
1066 EventKindKnown::AssistantTextDelta { delta, index } => {
1067 Self::AssistantTextDelta { delta, index }
1068 }
1069 EventKindKnown::AssistantMessageCommitted {
1070 role,
1071 content,
1072 model,
1073 token_usage,
1074 } => Self::AssistantMessageCommitted {
1075 role,
1076 content,
1077 model,
1078 token_usage,
1079 },
1080 EventKindKnown::TextDelta { delta, index } => Self::TextDelta { delta, index },
1081 EventKindKnown::Message {
1082 role,
1083 content,
1084 model,
1085 token_usage,
1086 } => Self::Message {
1087 role,
1088 content,
1089 model,
1090 token_usage,
1091 },
1092 EventKindKnown::ToolCallRequested {
1093 call_id,
1094 tool_name,
1095 arguments,
1096 category,
1097 } => Self::ToolCallRequested {
1098 call_id,
1099 tool_name,
1100 arguments,
1101 category,
1102 },
1103 EventKindKnown::ToolCallStarted {
1104 tool_run_id,
1105 tool_name,
1106 } => Self::ToolCallStarted {
1107 tool_run_id,
1108 tool_name,
1109 },
1110 EventKindKnown::ToolCallCompleted {
1111 tool_run_id,
1112 call_id,
1113 tool_name,
1114 result,
1115 duration_ms,
1116 status,
1117 } => Self::ToolCallCompleted {
1118 tool_run_id,
1119 call_id,
1120 tool_name,
1121 result,
1122 duration_ms,
1123 status,
1124 },
1125 EventKindKnown::ToolCallFailed {
1126 call_id,
1127 tool_name,
1128 error,
1129 } => Self::ToolCallFailed {
1130 call_id,
1131 tool_name,
1132 error,
1133 },
1134 EventKindKnown::FileWrite {
1135 path,
1136 blob_hash,
1137 size_bytes,
1138 content_type,
1139 } => Self::FileWrite {
1140 path,
1141 blob_hash,
1142 size_bytes,
1143 content_type,
1144 },
1145 EventKindKnown::FileDelete { path } => Self::FileDelete { path },
1146 EventKindKnown::FileRename { old_path, new_path } => {
1147 Self::FileRename { old_path, new_path }
1148 }
1149 EventKindKnown::FileMutated { path, content_hash } => {
1150 Self::FileMutated { path, content_hash }
1151 }
1152 EventKindKnown::StatePatched {
1153 index,
1154 patch,
1155 revision,
1156 } => Self::StatePatched {
1157 index,
1158 patch,
1159 revision,
1160 },
1161 EventKindKnown::StatePatchCommitted { new_version, patch } => {
1162 Self::StatePatchCommitted { new_version, patch }
1163 }
1164 EventKindKnown::ContextCompacted {
1165 dropped_count,
1166 tokens_before,
1167 tokens_after,
1168 } => Self::ContextCompacted {
1169 dropped_count,
1170 tokens_before,
1171 tokens_after,
1172 },
1173 EventKindKnown::PolicyEvaluated {
1174 tool_name,
1175 decision,
1176 rule_id,
1177 explanation,
1178 } => Self::PolicyEvaluated {
1179 tool_name,
1180 decision,
1181 rule_id,
1182 explanation,
1183 },
1184 EventKindKnown::ApprovalRequested {
1185 approval_id,
1186 call_id,
1187 tool_name,
1188 arguments,
1189 risk,
1190 } => Self::ApprovalRequested {
1191 approval_id,
1192 call_id,
1193 tool_name,
1194 arguments,
1195 risk,
1196 },
1197 EventKindKnown::ApprovalResolved {
1198 approval_id,
1199 decision,
1200 reason,
1201 } => Self::ApprovalResolved {
1202 approval_id,
1203 decision,
1204 reason,
1205 },
1206 EventKindKnown::SnapshotCreated {
1207 snapshot_id,
1208 snapshot_type,
1209 covers_through_seq,
1210 data_hash,
1211 } => Self::SnapshotCreated {
1212 snapshot_id,
1213 snapshot_type,
1214 covers_through_seq,
1215 data_hash,
1216 },
1217 EventKindKnown::SandboxCreated {
1218 sandbox_id,
1219 tier,
1220 config,
1221 } => Self::SandboxCreated {
1222 sandbox_id,
1223 tier,
1224 config,
1225 },
1226 EventKindKnown::SandboxExecuted {
1227 sandbox_id,
1228 command,
1229 exit_code,
1230 duration_ms,
1231 } => Self::SandboxExecuted {
1232 sandbox_id,
1233 command,
1234 exit_code,
1235 duration_ms,
1236 },
1237 EventKindKnown::SandboxViolation {
1238 sandbox_id,
1239 violation_type,
1240 details,
1241 } => Self::SandboxViolation {
1242 sandbox_id,
1243 violation_type,
1244 details,
1245 },
1246 EventKindKnown::SandboxDestroyed { sandbox_id } => {
1247 Self::SandboxDestroyed { sandbox_id }
1248 }
1249 EventKindKnown::ObservationAppended {
1250 scope,
1251 observation_ref,
1252 source_run_id,
1253 } => Self::ObservationAppended {
1254 scope,
1255 observation_ref,
1256 source_run_id,
1257 },
1258 EventKindKnown::ReflectionCompacted {
1259 scope,
1260 summary_ref,
1261 covers_through_seq,
1262 } => Self::ReflectionCompacted {
1263 scope,
1264 summary_ref,
1265 covers_through_seq,
1266 },
1267 EventKindKnown::MemoryProposed {
1268 scope,
1269 proposal_id,
1270 entries_ref,
1271 source_run_id,
1272 } => Self::MemoryProposed {
1273 scope,
1274 proposal_id,
1275 entries_ref,
1276 source_run_id,
1277 },
1278 EventKindKnown::MemoryCommitted {
1279 scope,
1280 memory_id,
1281 committed_ref,
1282 supersedes,
1283 } => Self::MemoryCommitted {
1284 scope,
1285 memory_id,
1286 committed_ref,
1287 supersedes,
1288 },
1289 EventKindKnown::MemoryTombstoned {
1290 scope,
1291 memory_id,
1292 reason,
1293 } => Self::MemoryTombstoned {
1294 scope,
1295 memory_id,
1296 reason,
1297 },
1298 EventKindKnown::Heartbeat {
1299 summary,
1300 checkpoint_id,
1301 } => Self::Heartbeat {
1302 summary,
1303 checkpoint_id,
1304 },
1305 EventKindKnown::StateEstimated { state, mode } => Self::StateEstimated { state, mode },
1306 EventKindKnown::BudgetUpdated { budget, reason } => {
1307 Self::BudgetUpdated { budget, reason }
1308 }
1309 EventKindKnown::ModeChanged { from, to, reason } => {
1310 Self::ModeChanged { from, to, reason }
1311 }
1312 EventKindKnown::GatesUpdated { gates, reason } => Self::GatesUpdated { gates, reason },
1313 EventKindKnown::CircuitBreakerTripped {
1314 reason,
1315 error_streak,
1316 } => Self::CircuitBreakerTripped {
1317 reason,
1318 error_streak,
1319 },
1320 EventKindKnown::CheckpointCreated {
1321 checkpoint_id,
1322 event_sequence,
1323 state_hash,
1324 } => Self::CheckpointCreated {
1325 checkpoint_id,
1326 event_sequence,
1327 state_hash,
1328 },
1329 EventKindKnown::CheckpointRestored {
1330 checkpoint_id,
1331 restored_to_seq,
1332 } => Self::CheckpointRestored {
1333 checkpoint_id,
1334 restored_to_seq,
1335 },
1336 EventKindKnown::VoiceSessionStarted {
1337 voice_session_id,
1338 adapter,
1339 model,
1340 sample_rate_hz,
1341 channels,
1342 } => Self::VoiceSessionStarted {
1343 voice_session_id,
1344 adapter,
1345 model,
1346 sample_rate_hz,
1347 channels,
1348 },
1349 EventKindKnown::VoiceInputChunk {
1350 voice_session_id,
1351 chunk_index,
1352 bytes,
1353 format,
1354 } => Self::VoiceInputChunk {
1355 voice_session_id,
1356 chunk_index,
1357 bytes,
1358 format,
1359 },
1360 EventKindKnown::VoiceOutputChunk {
1361 voice_session_id,
1362 chunk_index,
1363 bytes,
1364 format,
1365 } => Self::VoiceOutputChunk {
1366 voice_session_id,
1367 chunk_index,
1368 bytes,
1369 format,
1370 },
1371 EventKindKnown::VoiceSessionStopped {
1372 voice_session_id,
1373 reason,
1374 } => Self::VoiceSessionStopped {
1375 voice_session_id,
1376 reason,
1377 },
1378 EventKindKnown::VoiceAdapterError {
1379 voice_session_id,
1380 message,
1381 } => Self::VoiceAdapterError {
1382 voice_session_id,
1383 message,
1384 },
1385 EventKindKnown::WorldModelObserved { state_ref, meta } => {
1386 Self::WorldModelObserved { state_ref, meta }
1387 }
1388 EventKindKnown::WorldModelRollout {
1389 trajectory_ref,
1390 score,
1391 } => Self::WorldModelRollout {
1392 trajectory_ref,
1393 score,
1394 },
1395 EventKindKnown::IntentProposed {
1396 intent_id,
1397 kind,
1398 risk,
1399 } => Self::IntentProposed {
1400 intent_id,
1401 kind,
1402 risk,
1403 },
1404 EventKindKnown::IntentEvaluated {
1405 intent_id,
1406 allowed,
1407 requires_approval,
1408 reasons,
1409 } => Self::IntentEvaluated {
1410 intent_id,
1411 allowed,
1412 requires_approval,
1413 reasons,
1414 },
1415 EventKindKnown::IntentApproved { intent_id, actor } => {
1416 Self::IntentApproved { intent_id, actor }
1417 }
1418 EventKindKnown::IntentRejected { intent_id, reasons } => {
1419 Self::IntentRejected { intent_id, reasons }
1420 }
1421 EventKindKnown::ErrorRaised { message } => Self::ErrorRaised { message },
1422 EventKindKnown::Custom { event_type, data } => Self::Custom { event_type, data },
1423 }
1424 }
1425}
1426
1427#[cfg(test)]
1428mod tests {
1429 use super::*;
1430
1431 fn make_envelope(kind: EventKind) -> EventEnvelope {
1432 EventEnvelope {
1433 event_id: EventId::from_string("EVT001"),
1434 session_id: SessionId::from_string("SESS001"),
1435 agent_id: AgentId::from_string("AGENT001"),
1436 branch_id: BranchId::from_string("main"),
1437 run_id: None,
1438 seq: 1,
1439 timestamp: 1_700_000_000_000_000,
1440 actor: EventActor::default(),
1441 schema: EventSchema::default(),
1442 parent_id: None,
1443 trace_id: None,
1444 span_id: None,
1445 digest: None,
1446 kind,
1447 metadata: HashMap::new(),
1448 schema_version: 1,
1449 }
1450 }
1451
1452 #[test]
1453 fn error_raised_roundtrip() {
1454 let kind = EventKind::ErrorRaised {
1455 message: "boom".into(),
1456 };
1457 let json = serde_json::to_string(&kind).unwrap();
1458 assert!(json.contains("\"type\":\"ErrorRaised\""));
1459 let back: EventKind = serde_json::from_str(&json).unwrap();
1460 assert!(matches!(back, EventKind::ErrorRaised { message } if message == "boom"));
1461 }
1462
1463 #[test]
1464 fn heartbeat_roundtrip() {
1465 let kind = EventKind::Heartbeat {
1466 summary: "alive".into(),
1467 checkpoint_id: None,
1468 };
1469 let json = serde_json::to_string(&kind).unwrap();
1470 let back: EventKind = serde_json::from_str(&json).unwrap();
1471 assert!(matches!(back, EventKind::Heartbeat { .. }));
1472 }
1473
1474 #[test]
1475 fn state_estimated_roundtrip() {
1476 let kind = EventKind::StateEstimated {
1477 state: AgentStateVector::default(),
1478 mode: OperatingMode::Execute,
1479 };
1480 let json = serde_json::to_string(&kind).unwrap();
1481 let back: EventKind = serde_json::from_str(&json).unwrap();
1482 assert!(matches!(back, EventKind::StateEstimated { .. }));
1483 }
1484
1485 #[test]
1486 fn unknown_variant_becomes_custom() {
1487 let json = r#"{"type":"FutureFeature","key":"value","num":42}"#;
1488 let kind: EventKind = serde_json::from_str(json).unwrap();
1489 if let EventKind::Custom { event_type, data } = kind {
1490 assert_eq!(event_type, "FutureFeature");
1491 assert_eq!(data["key"], "value");
1492 assert_eq!(data["num"], 42);
1493 } else {
1494 panic!("should be Custom");
1495 }
1496 }
1497
1498 #[test]
1499 fn full_envelope_roundtrip() {
1500 let envelope = make_envelope(EventKind::RunStarted {
1501 provider: "anthropic".into(),
1502 max_iterations: 10,
1503 });
1504 let json = serde_json::to_string(&envelope).unwrap();
1505 let back: EventEnvelope = serde_json::from_str(&json).unwrap();
1506 assert_eq!(back.seq, 1);
1507 assert_eq!(back.schema_version, 1);
1508 assert!(matches!(back.kind, EventKind::RunStarted { .. }));
1509 }
1510
1511 #[test]
1512 fn tool_call_lifecycle_roundtrip() {
1513 let requested = EventKind::ToolCallRequested {
1514 call_id: "c1".into(),
1515 tool_name: "read_file".into(),
1516 arguments: serde_json::json!({"path": "/etc/hosts"}),
1517 category: Some("fs".into()),
1518 };
1519 let json = serde_json::to_string(&requested).unwrap();
1520 let back: EventKind = serde_json::from_str(&json).unwrap();
1521 assert!(matches!(back, EventKind::ToolCallRequested { .. }));
1522 }
1523
1524 #[test]
1525 fn memory_events_roundtrip() {
1526 let proposed = EventKind::MemoryProposed {
1527 scope: MemoryScope::Agent,
1528 proposal_id: MemoryId::from_string("PROP001"),
1529 entries_ref: BlobHash::from_hex("abc"),
1530 source_run_id: None,
1531 };
1532 let json = serde_json::to_string(&proposed).unwrap();
1533 let back: EventKind = serde_json::from_str(&json).unwrap();
1534 assert!(matches!(back, EventKind::MemoryProposed { .. }));
1535 }
1536
1537 #[test]
1538 fn mode_changed_roundtrip() {
1539 let kind = EventKind::ModeChanged {
1540 from: OperatingMode::Execute,
1541 to: OperatingMode::Recover,
1542 reason: "error streak".into(),
1543 };
1544 let json = serde_json::to_string(&kind).unwrap();
1545 let back: EventKind = serde_json::from_str(&json).unwrap();
1546 assert!(matches!(back, EventKind::ModeChanged { .. }));
1547 }
1548
1549 #[test]
1550 fn schema_version_defaults_to_1() {
1551 let json = r#"{"event_id":"E1","session_id":"S1","branch_id":"main","seq":0,"timestamp":100,"kind":{"type":"ErrorRaised","message":"x"},"metadata":{}}"#;
1552 let envelope: EventEnvelope = serde_json::from_str(json).unwrap();
1553 assert_eq!(envelope.schema_version, 1);
1554 }
1555
1556 #[test]
1557 fn voice_events_roundtrip() {
1558 let kind = EventKind::VoiceSessionStarted {
1559 voice_session_id: "vs1".into(),
1560 adapter: "openai-realtime".into(),
1561 model: "gpt-4o-realtime".into(),
1562 sample_rate_hz: 24000,
1563 channels: 1,
1564 };
1565 let json = serde_json::to_string(&kind).unwrap();
1566 let back: EventKind = serde_json::from_str(&json).unwrap();
1567 assert!(matches!(back, EventKind::VoiceSessionStarted { .. }));
1568 }
1569}