Skip to main content

atomr_agents_core/
event.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ids::{AgentId, HarnessId, RunId, ToolId, WorkflowId};
4use crate::inference::FinishReason;
5
6/// Structured event emitted by every observable boundary in the
7/// framework. Fed to `atomr-telemetry`, used by traces, metrics, and
8/// the eval-suite replay path.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "kind", rename_all = "snake_case")]
11pub enum Event {
12    StrategyResolved {
13        strategy: String,
14        agent_id: Option<AgentId>,
15        elapsed_ms: u64,
16        tokens_used: u32,
17    },
18    ToolInvoked {
19        tool_id: ToolId,
20        args_hash: u64,
21        elapsed_ms: u64,
22        ok: bool,
23    },
24    /// Emitted when an inference turn streams a tool call before it
25    /// is dispatched. Lets tracers / UIs surface tool intent in
26    /// real-time, distinct from `ToolInvoked` which fires post-call.
27    ToolCallStreamed {
28        agent_id: AgentId,
29        tool_name: String,
30        arguments_hash: u64,
31        iteration: u32,
32    },
33    AgentTurn {
34        agent_id: AgentId,
35        input_tokens: u32,
36        output_tokens: u32,
37        /// Provider-reported reasoning tokens (e.g. o1-style).
38        /// Defaults to 0 for runtimes that don't surface them.
39        #[serde(default)]
40        reasoning_tokens: u32,
41        /// Cached prefix tokens (Anthropic prompt-cache, OpenAI cached
42        /// input). Priced lower than `input_tokens` by most providers.
43        #[serde(default)]
44        cached_tokens: u32,
45        finish_reason: Option<FinishReason>,
46        elapsed_ms: u64,
47    },
48    WorkflowStep {
49        workflow_id: WorkflowId,
50        step_id: String,
51        step_kind: String,
52        elapsed_ms: u64,
53        ok: bool,
54    },
55    HarnessIteration {
56        harness_id: HarnessId,
57        iteration: u64,
58        outcome: String,
59        budget_remaining_tokens: u32,
60    },
61    Backpressure {
62        actor_path: String,
63        queued: u32,
64        dropped: u32,
65    },
66}
67
68/// Tagged envelope around an event with timestamp + correlation id.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct EventEnvelope {
71    pub timestamp_ms: i64,
72    pub correlation_id: Option<String>,
73    /// LangSmith-style run identification. Optional so existing call
74    /// sites still compile; tracers and the run-tree builder require
75    /// these to be populated.
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub run_id: Option<RunId>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub parent_run_id: Option<RunId>,
80    #[serde(default, skip_serializing_if = "Vec::is_empty")]
81    pub tags: Vec<String>,
82    pub event: Event,
83}
84
85impl EventEnvelope {
86    pub fn now(event: Event) -> Self {
87        Self {
88            timestamp_ms: chrono::Utc::now().timestamp_millis(),
89            correlation_id: None,
90            run_id: None,
91            parent_run_id: None,
92            tags: Vec::new(),
93            event,
94        }
95    }
96
97    pub fn with_run(mut self, run_id: RunId, parent: Option<RunId>) -> Self {
98        self.run_id = Some(run_id);
99        self.parent_run_id = parent;
100        self
101    }
102
103    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
104        self.tags = tags;
105        self
106    }
107}