1use chrono::{DateTime, Utc};
2use jamjet_core::node::NodeId;
3use jamjet_core::workflow::ExecutionId;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7pub type EventSequence = i64;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Event {
14 pub id: Uuid,
15 pub execution_id: ExecutionId,
16 pub sequence: EventSequence,
17 pub kind: EventKind,
18 pub created_at: DateTime<Utc>,
19}
20
21impl Event {
22 pub fn new(execution_id: ExecutionId, sequence: EventSequence, kind: EventKind) -> Self {
23 Self {
24 id: Uuid::new_v4(),
25 execution_id,
26 sequence,
27 kind,
28 created_at: Utc::now(),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
35pub struct ProvenanceMetadata {
36 pub model_id: Option<String>,
38 pub model_version: Option<String>,
40 pub confidence: Option<f64>,
42 #[serde(default)]
44 pub verified: bool,
45 pub source: Option<String>,
47 pub trust_domain: Option<String>,
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 pub evidence_refs: Vec<String>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(tag = "type", rename_all = "snake_case")]
57pub enum EventKind {
58 WorkflowStarted {
60 workflow_id: String,
61 workflow_version: String,
62 initial_input: serde_json::Value,
63 },
64 WorkflowCompleted {
65 final_state: serde_json::Value,
66 },
67 WorkflowFailed {
68 error: String,
69 },
70 WorkflowCancelled {
71 reason: Option<String>,
72 },
73
74 NodeScheduled {
76 node_id: NodeId,
77 queue_type: String,
78 },
79 NodeStarted {
80 node_id: NodeId,
81 worker_id: String,
82 attempt: u32,
83 },
84 NodeCompleted {
85 node_id: NodeId,
86 output: serde_json::Value,
87 state_patch: serde_json::Value,
89 duration_ms: u64,
90 #[serde(skip_serializing_if = "Option::is_none")]
93 gen_ai_system: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 gen_ai_model: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 input_tokens: Option<u64>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 output_tokens: Option<u64>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 finish_reason: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 cost_usd: Option<f64>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 provenance: Option<Box<ProvenanceMetadata>>,
112 },
113 NodeFailed {
114 node_id: NodeId,
115 error: String,
116 attempt: u32,
117 retryable: bool,
118 },
119 NodeSkipped {
120 node_id: NodeId,
121 reason: String,
122 },
123 NodeCancelled {
124 node_id: NodeId,
125 },
126
127 RetryScheduled {
129 node_id: NodeId,
130 attempt: u32,
131 delay_ms: u64,
132 },
133
134 InterruptRaised {
136 node_id: NodeId,
137 reason: String,
138 state_for_review: serde_json::Value,
139 },
140 ApprovalReceived {
141 node_id: NodeId,
142 user_id: String,
143 decision: ApprovalDecision,
144 comment: Option<String>,
145 state_patch: Option<serde_json::Value>,
146 },
147
148 TimerCreated {
150 node_id: NodeId,
151 fire_at: DateTime<Utc>,
152 correlation_key: Option<String>,
153 },
154 TimerFired {
155 node_id: NodeId,
156 correlation_key: Option<String>,
157 },
158
159 ExternalEventReceived {
161 correlation_key: String,
162 payload: serde_json::Value,
163 },
164
165 ChildWorkflowStarted {
167 node_id: NodeId,
168 child_execution_id: String,
169 child_workflow_id: String,
170 },
171 ChildWorkflowCompleted {
172 node_id: NodeId,
173 child_execution_id: String,
174 result: serde_json::Value,
175 },
176 ChildWorkflowFailed {
177 node_id: NodeId,
178 child_execution_id: String,
179 error: String,
180 },
181
182 BudgetExceeded {
184 node_id: NodeId,
185 kind: String,
186 limit: u64,
187 current: u64,
188 },
189 TokenBudgetExceeded {
190 node_id: NodeId,
191 kind: String,
193 limit: u64,
194 current: u64,
195 },
196 CostBudgetExceeded {
197 node_id: NodeId,
198 limit_usd: f64,
199 current_usd: f64,
200 },
201 AutonomyLimitReached {
202 node_id: NodeId,
203 agent_ref: String,
204 limit_type: String,
206 limit_value: serde_json::Value,
207 actual_value: serde_json::Value,
208 },
209 CircuitBreakerTripped {
210 node_id: NodeId,
211 agent_ref: String,
212 consecutive_errors: u32,
213 threshold: u32,
214 },
215 EscalationRequired {
216 node_id: NodeId,
217 agent_ref: String,
218 reason: String,
220 escalation_target: String,
222 },
223
224 PolicyViolation {
226 node_id: NodeId,
227 rule: String,
229 decision: String,
231 policy_scope: String,
233 },
234 ToolApprovalRequired {
235 node_id: NodeId,
236 tool_name: String,
237 approver: String,
238 context: serde_json::Value,
239 },
240
241 StrategyStarted {
244 strategy: String,
245 config: serde_json::Value,
246 },
247 PlanGenerated {
249 steps: Vec<String>,
250 },
251 IterationStarted {
253 iteration: u32,
254 },
255 ToolCalled {
257 node_id: NodeId,
258 tool: String,
259 },
260 CriticVerdict {
262 node_id: NodeId,
263 score: f64,
264 passed: bool,
265 feedback: Option<String>,
266 },
267 IterationCompleted {
269 iteration: u32,
270 cost_delta_usd: Option<f64>,
271 input_tokens: u64,
272 output_tokens: u64,
273 },
274 StrategyLimitHit {
277 limit_type: String,
278 limit_value: serde_json::Value,
279 actual_value: serde_json::Value,
280 },
281 StrategyCompleted {
283 iterations: u32,
284 total_cost_usd: Option<f64>,
285 },
286
287 CoordinatorDiscovery {
289 node_id: NodeId,
290 query_skills: Vec<String>,
291 query_trust_domain: Option<String>,
292 candidates: Vec<serde_json::Value>,
293 filtered_out: Vec<serde_json::Value>,
294 },
295 CoordinatorScoring {
296 node_id: NodeId,
297 rankings: Vec<serde_json::Value>,
298 spread: f64,
299 weights: serde_json::Value,
300 },
301 CoordinatorDecision {
302 node_id: NodeId,
303 selected: Option<String>,
304 method: String,
305 reasoning: Option<String>,
306 confidence: f64,
307 rejected: Vec<serde_json::Value>,
308 tiebreaker_tokens: Option<serde_json::Value>,
309 tiebreaker_cost: Option<f64>,
310 },
311
312 AgentToolInvoked {
314 node_id: NodeId,
315 agent_uri: String,
316 mode: String,
317 protocol: String,
318 input_hash: String,
319 },
320 AgentToolProgress {
321 node_id: NodeId,
322 chunk_index: u32,
323 partial_output_summary: String,
324 },
325 AgentToolTurn {
326 node_id: NodeId,
327 turn_number: u32,
328 direction: String,
329 content_summary: String,
330 tokens: u32,
331 cost: f64,
332 },
333 AgentToolCompleted {
334 node_id: NodeId,
335 output: serde_json::Value,
336 provenance: Option<serde_json::Value>,
337 total_cost: f64,
338 latency_ms: u64,
339 total_turns: Option<u32>,
340 },
341 AgentToolTerminated {
342 node_id: NodeId,
343 reason: String,
344 chunks_received: u32,
345 partial_output: Option<serde_json::Value>,
346 cost: f64,
347 },
348 AgentToolFailed {
349 node_id: NodeId,
350 failure_type: String,
351 message: String,
352 retryable: bool,
353 },
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357#[serde(rename_all = "snake_case")]
358pub enum ApprovalDecision {
359 Approved,
360 Rejected,
361}
362
363impl EventKind {
364 pub fn node_id(&self) -> Option<&str> {
366 match self {
367 Self::NodeScheduled { node_id, .. }
368 | Self::NodeStarted { node_id, .. }
369 | Self::NodeCompleted { node_id, .. }
370 | Self::NodeFailed { node_id, .. }
371 | Self::NodeSkipped { node_id, .. }
372 | Self::NodeCancelled { node_id }
373 | Self::RetryScheduled { node_id, .. }
374 | Self::InterruptRaised { node_id, .. }
375 | Self::ApprovalReceived { node_id, .. }
376 | Self::TimerCreated { node_id, .. }
377 | Self::TimerFired { node_id, .. }
378 | Self::BudgetExceeded { node_id, .. }
379 | Self::TokenBudgetExceeded { node_id, .. }
380 | Self::CostBudgetExceeded { node_id, .. }
381 | Self::AutonomyLimitReached { node_id, .. }
382 | Self::CircuitBreakerTripped { node_id, .. }
383 | Self::EscalationRequired { node_id, .. }
384 | Self::PolicyViolation { node_id, .. }
385 | Self::ToolApprovalRequired { node_id, .. }
386 | Self::ChildWorkflowStarted { node_id, .. }
387 | Self::ChildWorkflowCompleted { node_id, .. }
388 | Self::ChildWorkflowFailed { node_id, .. }
389 | Self::CoordinatorDiscovery { node_id, .. }
390 | Self::CoordinatorScoring { node_id, .. }
391 | Self::CoordinatorDecision { node_id, .. }
392 | Self::AgentToolInvoked { node_id, .. }
393 | Self::AgentToolProgress { node_id, .. }
394 | Self::AgentToolTurn { node_id, .. }
395 | Self::AgentToolCompleted { node_id, .. }
396 | Self::AgentToolTerminated { node_id, .. }
397 | Self::AgentToolFailed { node_id, .. } => Some(node_id.as_str()),
398 _ => None,
399 }
400 }
401}