klieo_ops/runtime/
builder.rs1use crate::clock::{SharedClock, SystemClock};
4use crate::error::BuildError;
5use crate::redactor::{DefaultRedactor, SharedRedactor};
6use crate::runtime::supervised::SupervisedAgent;
7use crate::tenant::{SingleTenant, TenantResolver};
8use crate::types::TenantId;
9use crate::types::{AgentId, ProviderId};
10use klieo_core::Agent;
11use std::sync::Arc;
12#[cfg(feature = "gates")]
13use std::time::Duration;
14
15#[cfg(feature = "gates")]
17const DEFAULT_APPROVAL_TIMEOUT: Duration = Duration::from_secs(600);
18
19pub struct OpsRuntime;
21
22impl OpsRuntime {
23 #[must_use]
25 pub fn builder() -> OpsRuntimeBuilder {
26 OpsRuntimeBuilder::default()
27 }
28
29 #[must_use]
36 pub fn single_tenant(id: impl Into<String>) -> OpsRuntimeBuilder {
37 OpsRuntimeBuilder::default().tenant_resolver(Arc::new(SingleTenant(TenantId(id.into()))))
38 }
39}
40
41pub struct OpsRuntimeBuilder {
43 tenant_resolver: Option<Arc<dyn TenantResolver>>,
44 redactor: SharedRedactor,
45 clock: SharedClock,
46 agent_id: Option<AgentId>,
47 #[cfg(feature = "governor")]
48 governor: Option<Arc<dyn crate::governor::Governor>>,
49 #[cfg(feature = "governor")]
50 governor_provider: Option<ProviderId>,
51 #[cfg(feature = "escalation")]
52 escalation: Option<Arc<dyn crate::escalation::Escalation>>,
53 #[cfg(feature = "gates")]
54 gates: Vec<Arc<dyn crate::gates::Gate>>,
55 #[cfg(feature = "gates")]
56 approval_timeout: Duration,
57 #[cfg(feature = "gates")]
58 approval_timeout_policy: crate::gates::ApprovalTimeoutPolicy,
59}
60
61impl Default for OpsRuntimeBuilder {
64 fn default() -> Self {
65 Self {
66 tenant_resolver: None,
67 redactor: Arc::new(DefaultRedactor::new()),
68 clock: Arc::new(SystemClock::default()),
69 agent_id: None,
70 #[cfg(feature = "governor")]
71 governor: None,
72 #[cfg(feature = "governor")]
73 governor_provider: None,
74 #[cfg(feature = "escalation")]
75 escalation: None,
76 #[cfg(feature = "gates")]
77 gates: Vec::new(),
78 #[cfg(feature = "gates")]
79 approval_timeout: DEFAULT_APPROVAL_TIMEOUT,
80 #[cfg(feature = "gates")]
81 approval_timeout_policy: crate::gates::ApprovalTimeoutPolicy::default(),
82 }
83 }
84}
85
86impl OpsRuntimeBuilder {
87 #[must_use]
89 pub fn tenant_resolver(mut self, r: Arc<dyn TenantResolver>) -> Self {
90 self.tenant_resolver = Some(r);
91 self
92 }
93
94 #[must_use]
96 pub fn redactor(mut self, r: SharedRedactor) -> Self {
97 self.redactor = r;
98 self
99 }
100
101 #[must_use]
103 pub fn clock(mut self, c: SharedClock) -> Self {
104 self.clock = c;
105 self
106 }
107
108 #[must_use]
112 pub fn agent_id(mut self, id: AgentId) -> Self {
113 self.agent_id = Some(id);
114 self
115 }
116
117 #[cfg(feature = "governor")]
120 #[must_use]
121 pub fn governor(mut self, g: Arc<dyn crate::governor::Governor>) -> Self {
122 self.governor = Some(g);
123 self
124 }
125
126 #[cfg(feature = "governor")]
129 #[must_use]
130 pub fn governor_provider(mut self, p: ProviderId) -> Self {
131 self.governor_provider = Some(p);
132 self
133 }
134
135 #[cfg(feature = "escalation")]
138 #[must_use]
139 pub fn escalation(mut self, e: Arc<dyn crate::escalation::Escalation>) -> Self {
140 self.escalation = Some(e);
141 self
142 }
143
144 #[cfg(feature = "gates")]
147 #[must_use]
148 pub fn gate(mut self, gate: Arc<dyn crate::gates::Gate>) -> Self {
149 self.gates.push(gate);
150 self
151 }
152
153 #[cfg(feature = "gates")]
159 #[must_use]
160 pub fn approval_timeout(mut self, timeout: Duration) -> Self {
161 self.approval_timeout = timeout;
162 self
163 }
164
165 #[cfg(feature = "gates")]
171 #[must_use]
172 pub fn approval_timeout_policy(mut self, policy: crate::gates::ApprovalTimeoutPolicy) -> Self {
173 self.approval_timeout_policy = policy;
174 self
175 }
176
177 pub fn spawn<A>(self, agent: A) -> Result<SupervisedAgent<A>, BuildError>
185 where
186 A: Agent,
187 {
188 #[cfg(all(feature = "escalation", feature = "gates"))]
189 if self.escalation.is_none() && self.gates.iter().any(|g| g.may_require_approval()) {
190 return Err(BuildError::MissingDependency {
191 needed_by: "Gate (may_require_approval == true)".into(),
192 missing: "escalation".into(),
193 });
194 }
195
196 let tenant_resolver =
197 self.tenant_resolver
198 .ok_or_else(|| BuildError::MissingDependency {
199 needed_by: "OpsRuntime".into(),
200 missing: "tenant_resolver".into(),
201 })?;
202
203 let agent_id = self
204 .agent_id
205 .unwrap_or_else(|| AgentId(agent.name().to_string()));
206
207 let _ = (&self.redactor, &self.clock);
209
210 Ok(SupervisedAgent {
211 inner: agent,
212 tenant_resolver,
213 agent_id,
214 #[cfg(feature = "governor")]
215 governor: self.governor,
216 #[cfg(feature = "governor")]
217 governor_provider: self
218 .governor_provider
219 .unwrap_or_else(|| ProviderId("default".into())),
220 #[cfg(feature = "gates")]
221 gates: self.gates,
222 #[cfg(feature = "gates")]
223 approval_timeout: self.approval_timeout,
224 #[cfg(feature = "gates")]
225 approval_timeout_policy: self.approval_timeout_policy,
226 })
227 }
228}