1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5use uuid::Uuid;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum PersonaLifecycleState {
10 Inactive,
11 Starting,
12 #[default]
13 Idle,
14 Running,
15 Paused,
16 Draining,
17 Failed,
18 Disabled,
19}
20
21impl PersonaLifecycleState {
22 pub fn as_str(self) -> &'static str {
23 match self {
24 Self::Inactive => "inactive",
25 Self::Starting => "starting",
26 Self::Idle => "idle",
27 Self::Running => "running",
28 Self::Paused => "paused",
29 Self::Draining => "draining",
30 Self::Failed => "failed",
31 Self::Disabled => "disabled",
32 }
33 }
34}
35
36#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
37pub struct PersonaBudgetPolicy {
38 pub daily_usd: Option<f64>,
39 pub hourly_usd: Option<f64>,
40 pub run_usd: Option<f64>,
41 pub max_tokens: Option<u64>,
42}
43
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45pub struct PersonaRuntimeBinding {
46 pub name: String,
47 #[serde(default)]
48 pub template_ref: Option<String>,
49 pub entry_workflow: String,
50 #[serde(default)]
51 pub schedules: Vec<String>,
52 #[serde(default)]
53 pub triggers: Vec<String>,
54 #[serde(default)]
55 pub budget: PersonaBudgetPolicy,
56 #[serde(default)]
61 pub stages: Vec<StageDecl>,
62}
63
64#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
72pub struct StageDecl {
73 pub name: String,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub allowed_tools: Option<Vec<String>>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub side_effect_level: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub max_iterations: Option<u32>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub on_exit: Option<StageExit>,
91}
92
93#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
94pub struct StageExit {
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub on_complete: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub on_failure: Option<String>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub policy_override: Option<crate::orchestration::CapabilityPolicy>,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
104pub struct PersonaLease {
105 pub id: String,
106 pub holder: String,
107 pub work_key: String,
108 pub acquired_at_ms: i64,
109 pub expires_at_ms: i64,
110}
111
112#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
113pub struct PersonaBudgetStatus {
114 pub daily_usd: Option<f64>,
115 pub hourly_usd: Option<f64>,
116 pub run_usd: Option<f64>,
117 pub max_tokens: Option<u64>,
118 pub spent_today_usd: f64,
119 pub spent_this_hour_usd: f64,
120 pub spent_last_run_usd: f64,
121 pub tokens_today: u64,
122 pub remaining_today_usd: Option<f64>,
123 pub remaining_hour_usd: Option<f64>,
124 pub exhausted: bool,
125 pub reason: Option<String>,
126 pub last_receipt_id: Option<String>,
127}
128
129#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
130pub struct PersonaStatus {
131 pub name: String,
132 #[serde(default)]
133 pub template_ref: Option<String>,
134 pub state: PersonaLifecycleState,
135 pub entry_workflow: String,
136 #[serde(default)]
137 pub role: String,
138 #[serde(default)]
139 pub current_assignment: Option<PersonaAssignmentStatus>,
140 pub last_run: Option<String>,
141 pub next_scheduled_run: Option<String>,
142 pub active_lease: Option<PersonaLease>,
143 pub budget: PersonaBudgetStatus,
144 pub last_error: Option<String>,
145 pub queued_events: usize,
146 #[serde(default)]
147 pub queued_work: Vec<PersonaQueuedWork>,
148 #[serde(default)]
149 pub handoff_inbox: Vec<PersonaHandoffInboxItem>,
150 #[serde(default)]
151 pub value_receipts: Vec<PersonaValueReceipt>,
152 pub disabled_events: usize,
153 pub paused_event_policy: String,
154}
155
156#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
157pub struct PersonaAssignmentStatus {
158 pub work_key: String,
159 pub lease_id: String,
160 pub holder: String,
161 pub acquired_at: String,
162 pub expires_at: String,
163}
164
165#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub struct PersonaQueuedWork {
167 pub work_key: String,
168 pub provider: String,
169 pub kind: String,
170 pub queued_at: String,
171 pub reason: String,
172 pub source_event_id: Option<String>,
173 #[serde(default)]
174 pub metadata: BTreeMap<String, String>,
175}
176
177#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
178pub struct PersonaHandoffInboxItem {
179 pub work_key: String,
180 pub handoff_id: Option<String>,
181 pub handoff_kind: Option<String>,
182 pub source_persona: Option<String>,
183 pub task: Option<String>,
184 pub queued_at: String,
185 pub reason: String,
186}
187
188#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
189pub struct PersonaValueReceipt {
190 pub kind: PersonaValueEventKind,
191 pub run_id: Option<Uuid>,
192 pub occurred_at: String,
193 pub paid_cost_usd: f64,
194 pub avoided_cost_usd: f64,
195 pub deterministic_steps: i64,
196 pub llm_steps: i64,
197 pub metadata: serde_json::Value,
198}
199
200#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
201pub struct PersonaTriggerEnvelope {
202 pub provider: String,
203 pub kind: String,
204 pub subject_key: String,
205 pub source_event_id: Option<String>,
206 pub received_at_ms: i64,
207 #[serde(default)]
208 pub metadata: BTreeMap<String, String>,
209 #[serde(default)]
210 pub raw: serde_json::Value,
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
214pub struct PersonaRunReceipt {
215 pub status: String,
216 pub persona: String,
217 #[serde(default)]
218 pub run_id: Option<Uuid>,
219 pub work_key: String,
220 pub lease: Option<PersonaLease>,
221 pub queued: bool,
222 pub error: Option<String>,
223 pub budget_receipt_id: Option<String>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub result: Option<serde_json::Value>,
226}
227
228#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
229pub struct PersonaRunCost {
230 pub cost_usd: f64,
231 pub tokens: u64,
232 #[serde(default)]
233 pub avoided_cost_usd: f64,
234 #[serde(default)]
235 pub deterministic_steps: i64,
236 #[serde(default)]
237 pub llm_steps: i64,
238 #[serde(default)]
239 pub frontier_escalations: i64,
240 #[serde(default)]
241 pub metadata: serde_json::Value,
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
245#[serde(rename_all = "snake_case")]
246pub enum PersonaValueEventKind {
247 RunStarted,
248 RunCompleted,
249 AcceptedOutcome,
250 FrontierEscalation,
251 DeterministicExecution,
252 PromotionSavings,
253 ApprovalWait,
254}
255
256impl PersonaValueEventKind {
257 pub fn as_str(self) -> &'static str {
258 match self {
259 Self::RunStarted => "run_started",
260 Self::RunCompleted => "run_completed",
261 Self::AcceptedOutcome => "accepted_outcome",
262 Self::FrontierEscalation => "frontier_escalation",
263 Self::DeterministicExecution => "deterministic_execution",
264 Self::PromotionSavings => "promotion_savings",
265 Self::ApprovalWait => "approval_wait",
266 }
267 }
268}
269
270#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
271pub struct PersonaValueEvent {
272 pub persona_id: String,
273 pub template_ref: Option<String>,
274 pub run_id: Option<Uuid>,
275 pub kind: PersonaValueEventKind,
276 pub paid_cost_usd: f64,
277 pub avoided_cost_usd: f64,
278 pub deterministic_steps: i64,
279 pub llm_steps: i64,
280 pub metadata: serde_json::Value,
281 pub occurred_at: OffsetDateTime,
282}
283
284pub trait PersonaValueSink: Send + Sync {
285 fn handle_value_event(&self, event: &PersonaValueEvent);
286}
287
288#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
295#[serde(tag = "update_kind", rename_all = "snake_case")]
296pub enum PersonaSupervisionEvent {
297 QueuePosition(PersonaQueuePositionUpdate),
298 RepairWorkerStatus(PersonaRepairWorkerStatusUpdate),
299 Receipt(PersonaReceiptUpdate),
300 Checkpoint(PersonaCheckpointUpdate),
301}
302
303impl PersonaSupervisionEvent {
304 pub fn update_kind(&self) -> &'static str {
305 match self {
306 Self::QueuePosition(_) => "queue_position",
307 Self::RepairWorkerStatus(_) => "repair_worker_status",
308 Self::Receipt(_) => "receipt",
309 Self::Checkpoint(_) => "checkpoint",
310 }
311 }
312
313 pub fn persona_id(&self) -> &str {
314 match self {
315 Self::QueuePosition(update) => &update.persona_id,
316 Self::RepairWorkerStatus(update) => &update.persona_id,
317 Self::Receipt(update) => &update.persona_id,
318 Self::Checkpoint(update) => &update.persona_id,
319 }
320 }
321
322 pub fn template_ref(&self) -> Option<&str> {
323 match self {
324 Self::QueuePosition(update) => update.template_ref.as_deref(),
325 Self::RepairWorkerStatus(update) => update.template_ref.as_deref(),
326 Self::Receipt(update) => update.template_ref.as_deref(),
327 Self::Checkpoint(update) => update.template_ref.as_deref(),
328 }
329 }
330
331 pub fn occurred_at_ms(&self) -> i64 {
332 match self {
333 Self::QueuePosition(update) => update.occurred_at_ms,
334 Self::RepairWorkerStatus(update) => update.occurred_at_ms,
335 Self::Receipt(update) => update.occurred_at_ms,
336 Self::Checkpoint(update) => update.occurred_at_ms,
337 }
338 }
339}
340
341#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
342pub struct PersonaQueuePositionUpdate {
343 pub persona_id: String,
344 #[serde(default)]
345 pub template_ref: Option<String>,
346 pub work_key: String,
347 pub queue_depth: i64,
348 pub position: i64,
350 pub queued_at_ms: i64,
351 pub occurred_at_ms: i64,
352}
353
354#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
355#[serde(rename_all = "snake_case")]
356pub enum PersonaRepairWorkerLifecycle {
357 Pending,
358 Running,
359 Verifying,
360 Pushing,
361 Succeeded,
362 Failed,
363 Cancelled,
364}
365
366impl PersonaRepairWorkerLifecycle {
367 pub fn as_str(self) -> &'static str {
368 match self {
369 Self::Pending => "pending",
370 Self::Running => "running",
371 Self::Verifying => "verifying",
372 Self::Pushing => "pushing",
373 Self::Succeeded => "succeeded",
374 Self::Failed => "failed",
375 Self::Cancelled => "cancelled",
376 }
377 }
378}
379
380#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
381pub struct PersonaRepairWorkerStatusUpdate {
382 pub persona_id: String,
383 #[serde(default)]
384 pub template_ref: Option<String>,
385 pub repair_worker_id: String,
386 pub lifecycle: PersonaRepairWorkerLifecycle,
387 #[serde(default)]
388 pub work_key: Option<String>,
389 #[serde(default)]
390 pub lease_id: Option<String>,
391 #[serde(default)]
392 pub scratchpad_url: Option<String>,
393 pub last_heartbeat_ms: i64,
394 pub occurred_at_ms: i64,
395}
396
397#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
398pub struct PersonaReceiptUpdate {
399 pub persona_id: String,
400 #[serde(default)]
401 pub template_ref: Option<String>,
402 pub receipt: PersonaRunReceipt,
403 pub occurred_at_ms: i64,
404}
405
406#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
407pub struct PersonaCheckpointUpdate {
408 pub persona_id: String,
409 #[serde(default)]
410 pub template_ref: Option<String>,
411 pub action: PersonaCheckpointAction,
412 pub checkpoint_id: String,
413 #[serde(default)]
414 pub work_key: Option<String>,
415 #[serde(default)]
417 pub resumed_from: Option<PersonaCheckpointResume>,
418 pub occurred_at_ms: i64,
419}
420
421#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
422#[serde(rename_all = "snake_case")]
423pub enum PersonaCheckpointAction {
424 RestoreAcked,
425}
426
427impl PersonaCheckpointAction {
428 pub fn as_str(self) -> &'static str {
429 match self {
430 Self::RestoreAcked => "restore_acked",
431 }
432 }
433}
434
435#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
436pub struct PersonaCheckpointResume {
437 pub run_id: Option<Uuid>,
438 pub lease_id: Option<String>,
439 pub last_run_ms: Option<i64>,
440 pub queued_work_keys: Vec<String>,
441 #[serde(default)]
442 pub note: Option<String>,
443}
444
445pub trait PersonaSupervisionSink: Send + Sync {
446 fn handle_supervision_event(&self, event: &PersonaSupervisionEvent);
447}