use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::core::circuit::CircuitState;
use crate::core::session::SessionId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelTier {
Haiku,
Sonnet,
Opus,
}
impl ModelTier {
pub fn from_model_id(model: &str) -> Self {
let m = model.to_ascii_lowercase();
if m.contains("opus") {
ModelTier::Opus
} else if m.contains("haiku") {
ModelTier::Haiku
} else {
ModelTier::Sonnet
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DelegationId(pub Uuid);
impl DelegationId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl Default for DelegationId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DelegationStatus {
Queued,
Running,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Delegation {
pub id: DelegationId,
pub session: SessionId,
#[serde(default)]
pub parent: Option<DelegationId>,
pub agent: String,
pub tier: ModelTier,
pub status: DelegationStatus,
pub circuit: CircuitState,
#[serde(default)]
pub task: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl Delegation {
pub fn new(
session: SessionId,
parent: Option<DelegationId>,
agent: impl Into<String>,
tier: ModelTier,
task: impl Into<String>,
) -> Self {
Self {
id: DelegationId::new(),
session,
parent,
agent: agent.into(),
tier,
status: DelegationStatus::Queued,
circuit: CircuitState::Closed,
task: task.into(),
created_at: chrono::Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tier_parses_from_model_id() {
assert_eq!(ModelTier::from_model_id("claude-opus-4-7"), ModelTier::Opus);
assert_eq!(
ModelTier::from_model_id("claude-haiku-3-5"),
ModelTier::Haiku
);
assert_eq!(
ModelTier::from_model_id("claude-sonnet-4"),
ModelTier::Sonnet
);
assert_eq!(ModelTier::from_model_id("mystery-model"), ModelTier::Sonnet);
}
#[test]
fn delegation_round_trips() {
let d = Delegation::new(
SessionId::new(),
None,
"research",
ModelTier::Sonnet,
"find the bug",
);
let json = serde_json::to_string(&d).unwrap();
let back: Delegation = serde_json::from_str(&json).unwrap();
assert_eq!(back.agent, "research");
assert_eq!(back.status, DelegationStatus::Queued);
assert_eq!(back.tier, ModelTier::Sonnet);
assert!(back.parent.is_none());
}
#[test]
fn delegation_tree_parent_links() {
let session = SessionId::new();
let root = Delegation::new(session, None, "pm", ModelTier::Opus, "orchestrate");
let child = Delegation::new(
session,
Some(root.id),
"engineer",
ModelTier::Sonnet,
"implement",
);
assert_eq!(child.parent, Some(root.id));
}
}