Skip to main content

machi_protocol/
observability.rs

1//! Stable tracing span names and field keys for OpenTelemetry-friendly hosts.
2
3/// Session lifetime span.
4pub const SPAN_SESSION: &str = "machi.session";
5/// Single turn span.
6pub const SPAN_TURN: &str = "machi.turn";
7/// One LLM sample call.
8pub const SPAN_SAMPLE: &str = "machi.sample";
9/// One tool execution.
10pub const SPAN_TOOL: &str = "machi.tool";
11/// Dispatched tool batch.
12pub const SPAN_TOOL_BATCH: &str = "machi.tool.batch";
13/// Nested agent spawn.
14pub const SPAN_SPAWN: &str = "machi.spawn";
15/// Workflow run.
16pub const SPAN_WORKFLOW: &str = "machi.workflow";
17/// One workflow host request.
18pub const SPAN_WORKFLOW_HOST: &str = "machi.workflow.host";
19/// Compaction pass.
20pub const SPAN_COMPACT: &str = "machi.compact";
21
22/// Canonical field names (string constants for hosts and tests).
23pub mod field {
24    /// Session id.
25    pub const SESSION_ID: &str = "machi.session_id";
26    /// Agent id.
27    pub const AGENT_ID: &str = "machi.agent_id";
28    /// Agent name.
29    pub const AGENT_NAME: &str = "machi.agent_name";
30    /// Run / turn id.
31    pub const RUN_ID: &str = "machi.run_id";
32    /// Step index within a turn.
33    pub const STEP: &str = "machi.step";
34    /// Tool name.
35    pub const TOOL_NAME: &str = "machi.tool_name";
36    /// Model id.
37    pub const MODEL: &str = "machi.model";
38    /// Input tokens.
39    pub const USAGE_INPUT: &str = "machi.usage.input_tokens";
40    /// Output tokens.
41    pub const USAGE_OUTPUT: &str = "machi.usage.output_tokens";
42    /// Workflow run id.
43    pub const WORKFLOW_RUN_ID: &str = "machi.workflow.run_id";
44    /// Workflow journal sequence.
45    pub const WORKFLOW_SEQ: &str = "machi.workflow.seq";
46}
47
48/// All required span names (contract test surface).
49///
50/// **Rename = break:** CI asserts [`span_catalogue_snapshot`].
51#[must_use]
52pub fn required_span_names() -> &'static [&'static str] {
53    &[
54        SPAN_SESSION,
55        SPAN_TURN,
56        SPAN_SAMPLE,
57        SPAN_TOOL,
58        SPAN_TOOL_BATCH,
59        SPAN_SPAWN,
60        SPAN_WORKFLOW,
61        SPAN_WORKFLOW_HOST,
62        SPAN_COMPACT,
63    ]
64}
65
66/// Exact newline-joined span catalogue for CI golden comparison.
67#[must_use]
68pub fn span_catalogue_snapshot() -> String {
69    required_span_names().join("\n")
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    const SPAN_CATALOGUE_GOLDEN: &str = "\
77machi.session
78machi.turn
79machi.sample
80machi.tool
81machi.tool.batch
82machi.spawn
83machi.workflow
84machi.workflow.host
85machi.compact";
86
87    #[test]
88    fn span_names_are_machi_prefixed_and_unique() {
89        let names = required_span_names();
90        assert_eq!(names.len(), 9, "expected nine spans");
91        let mut seen = std::collections::BTreeSet::new();
92        for name in names {
93            assert!(
94                name.starts_with("machi."),
95                "span {name} must start with machi."
96            );
97            assert!(seen.insert(*name), "duplicate span {name}");
98        }
99    }
100
101    #[test]
102    fn span_catalogue_snapshot_matches_golden() {
103        assert_eq!(
104            span_catalogue_snapshot(),
105            SPAN_CATALOGUE_GOLDEN,
106            "span catalogue changed — update golden only with deliberate contract change"
107        );
108    }
109
110    #[test]
111    fn field_keys_are_stable() {
112        assert_eq!(field::SESSION_ID, "machi.session_id");
113        assert_eq!(field::TOOL_NAME, "machi.tool_name");
114        assert_eq!(field::WORKFLOW_SEQ, "machi.workflow.seq");
115    }
116}