Skip to main content

klieo_ops/
types.rs

1//! Shared types used across klieo-ops primitives.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::time::Duration;
6
7/// Stable, persistence-safe agent identifier. Distinct from in-process
8/// handle. Two restarts of the same agent share an `AgentId`.
9#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
10pub struct AgentId(pub String);
11
12impl fmt::Display for AgentId {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        f.write_str(&self.0)
15    }
16}
17
18/// Stable, persistence-safe tenant identifier. Used for multi-tenant
19/// audit scoping. Always part of the canonical signed event payload.
20#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
21pub struct TenantId(pub String);
22
23impl fmt::Display for TenantId {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.write_str(&self.0)
26    }
27}
28
29/// LLM provider identifier. Free-form string for forward compatibility.
30#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
31pub struct ProviderId(pub String);
32
33impl fmt::Display for ProviderId {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str(&self.0)
36    }
37}
38
39/// Per-agent metadata registered with the supervisor.
40#[derive(Clone, Debug)]
41#[non_exhaustive]
42pub struct AgentMeta {
43    /// Stable id.
44    pub id: AgentId,
45    /// Logical role (e.g. `claims-triage`).
46    pub role: String,
47    /// Build version of the agent code.
48    pub version: String,
49    /// Ed25519 public key for agent-identity signatures (Phase B Handoff).
50    /// Phase A stores a 32-byte slice; identity verification activates with
51    /// Handoff (Phase B).
52    pub identity_pubkey: [u8; 32],
53    /// Expected per-step p99 latency. Influences supervisor stall threshold:
54    /// `max(15s, 3 × expected_step_p99)`. `None` ⇒ defaults to 30 s.
55    pub expected_step_p99: Option<Duration>,
56}
57
58/// Process-wide runtime lifecycle state.
59#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
60#[non_exhaustive]
61pub enum RuntimeState {
62    /// Normal operation.
63    #[default]
64    Running,
65    /// Draining in-flight work toward `Halted`.
66    Draining,
67    /// Refusing new step invocations; kill-switch tripped.
68    Halted,
69}
70
71impl fmt::Display for RuntimeState {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::Running => f.write_str("running"),
75            Self::Draining => f.write_str("draining"),
76            Self::Halted => f.write_str("halted"),
77        }
78    }
79}
80
81/// Scope of a governor budget assignment.
82#[derive(Clone, Debug, Eq, PartialEq)]
83#[non_exhaustive]
84pub enum BudgetScope {
85    /// Cluster-wide.
86    Global,
87    /// Per-tenant.
88    Tenant(TenantId),
89    /// Per-provider.
90    Provider(ProviderId),
91    /// Per-tenant × per-provider.
92    TenantProvider(TenantId, ProviderId),
93    /// Per-agent.
94    Agent(AgentId),
95}
96
97impl fmt::Display for BudgetScope {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::Global => f.write_str("global"),
101            Self::Tenant(t) => write!(f, "tenant:{t}"),
102            Self::Provider(p) => write!(f, "provider:{p}"),
103            Self::TenantProvider(t, p) => write!(f, "tenant:{t}:provider:{p}"),
104            Self::Agent(a) => write!(f, "agent:{a}"),
105        }
106    }
107}
108
109/// Reason a kill-switch was tripped.
110#[derive(Clone, Debug, Serialize, Deserialize)]
111pub struct KillReason(pub String);
112
113/// Source of a kill-switch trip.
114#[derive(Clone, Debug, Serialize, Deserialize)]
115#[non_exhaustive]
116pub enum KillTrigger {
117    /// Programmatic decision by the supervisor.
118    Programmatic,
119    /// External operator signal / HTTP / control plane.
120    External(String),
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn agent_id_round_trips_through_string() {
129        let a = AgentId("claims-triage".into());
130        let s = serde_json::to_string(&a).unwrap();
131        let b: AgentId = serde_json::from_str(&s).unwrap();
132        assert_eq!(a, b);
133    }
134
135    #[test]
136    fn runtime_state_default_is_running() {
137        assert_eq!(RuntimeState::default(), RuntimeState::Running);
138    }
139
140    #[test]
141    fn budget_scope_formats_distinctly() {
142        let g = BudgetScope::Global;
143        let t = BudgetScope::Tenant(TenantId("bl_auto".into()));
144        let p = BudgetScope::Provider(ProviderId("anthropic".into()));
145        assert_ne!(format!("{g}"), format!("{t}"));
146        assert_ne!(format!("{t}"), format!("{p}"));
147    }
148}