use crate::kernel::ExecutionId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InboxMessage {
Control(ControlMessage),
Guidance(GuidanceMessage),
Evidence(EvidenceUpdate),
A2a(A2aMessage),
}
impl InboxMessage {
pub fn message_type(&self) -> InboxMessageType {
match self {
InboxMessage::Control(_) => InboxMessageType::Control,
InboxMessage::Guidance(_) => InboxMessageType::Guidance,
InboxMessage::Evidence(_) => InboxMessageType::Evidence,
InboxMessage::A2a(_) => InboxMessageType::A2a,
}
}
pub fn id(&self) -> &str {
match self {
InboxMessage::Control(m) => &m.id,
InboxMessage::Guidance(m) => &m.id,
InboxMessage::Evidence(m) => &m.id,
InboxMessage::A2a(m) => &m.id,
}
}
pub fn execution_id(&self) -> &ExecutionId {
match self {
InboxMessage::Control(m) => &m.execution_id,
InboxMessage::Guidance(m) => &m.execution_id,
InboxMessage::Evidence(m) => &m.execution_id,
InboxMessage::A2a(m) => &m.execution_id,
}
}
pub fn priority_order(&self) -> u8 {
match self {
InboxMessage::Control(_) => 0, InboxMessage::Evidence(e) if e.impact == EvidenceImpact::ContradictsPlan => 1,
InboxMessage::Evidence(_) => 2,
InboxMessage::Guidance(g) if g.priority == GuidancePriority::High => 3,
InboxMessage::Guidance(_) => 4,
InboxMessage::A2a(_) => 5, }
}
pub fn is_control(&self) -> bool {
matches!(self, InboxMessage::Control(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InboxMessageType {
Control,
Guidance,
Evidence,
A2a,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlMessage {
pub id: String,
pub execution_id: ExecutionId,
pub action: ControlAction,
pub reason: Option<String>,
pub actor: String,
pub created_at: DateTime<Utc>,
}
impl ControlMessage {
pub fn new(execution_id: ExecutionId, action: ControlAction, actor: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
execution_id,
action,
reason: None,
actor: actor.into(),
created_at: Utc::now(),
}
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ControlAction {
Pause,
Resume,
Cancel,
Checkpoint,
Compact,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuidanceMessage {
pub id: String,
pub execution_id: ExecutionId,
pub from: GuidanceSource,
pub content: String,
pub context: Option<serde_json::Value>,
pub priority: GuidancePriority,
pub created_at: DateTime<Utc>,
}
impl GuidanceMessage {
pub fn from_user(execution_id: ExecutionId, content: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
execution_id,
from: GuidanceSource::User,
content: content.into(),
context: None,
priority: GuidancePriority::Medium,
created_at: Utc::now(),
}
}
pub fn with_priority(mut self, priority: GuidancePriority) -> Self {
self.priority = priority;
self
}
pub fn with_context(mut self, context: serde_json::Value) -> Self {
self.context = Some(context);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GuidanceSource {
User,
System,
Agent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GuidancePriority {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvidenceUpdate {
pub id: String,
pub execution_id: ExecutionId,
pub source: EvidenceSource,
pub title: String,
pub content: serde_json::Value,
pub confidence: Option<f64>,
pub impact: EvidenceImpact,
pub created_at: DateTime<Utc>,
}
impl EvidenceUpdate {
pub fn new(
execution_id: ExecutionId,
source: EvidenceSource,
title: impl Into<String>,
content: serde_json::Value,
impact: EvidenceImpact,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
execution_id,
source,
title: title.into(),
content,
confidence: None,
impact,
created_at: Utc::now(),
}
}
pub fn with_confidence(mut self, confidence: f64) -> Self {
self.confidence = Some(confidence.clamp(0.0, 1.0));
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceSource {
Discovery,
ToolResult,
External,
Memory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceImpact {
Informational,
RequiresReview,
ContradictsPlan,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2aMessage {
pub id: String,
pub execution_id: ExecutionId,
pub from_agent: String,
pub message_type: String,
pub payload: serde_json::Value,
pub created_at: DateTime<Utc>,
}
impl A2aMessage {
pub fn new(
execution_id: ExecutionId,
from_agent: impl Into<String>,
message_type: impl Into<String>,
payload: serde_json::Value,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
execution_id,
from_agent: from_agent.into(),
message_type: message_type.into(),
payload,
created_at: Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_message_priority() {
let exec_id = ExecutionId::new();
let control = InboxMessage::Control(ControlMessage::new(
exec_id.clone(),
ControlAction::Pause,
"test_user",
));
assert_eq!(control.priority_order(), 0);
assert!(control.is_control());
}
#[test]
fn test_evidence_priority_contradicts() {
let exec_id = ExecutionId::new();
let evidence = InboxMessage::Evidence(EvidenceUpdate::new(
exec_id,
EvidenceSource::Discovery,
"Found conflict",
serde_json::json!({}),
EvidenceImpact::ContradictsPlan,
));
assert_eq!(evidence.priority_order(), 1);
}
#[test]
fn test_guidance_priority() {
let exec_id = ExecutionId::new();
let high = InboxMessage::Guidance(
GuidanceMessage::from_user(exec_id.clone(), "Focus on EU")
.with_priority(GuidancePriority::High),
);
let low = InboxMessage::Guidance(
GuidanceMessage::from_user(exec_id, "Also check this")
.with_priority(GuidancePriority::Low),
);
assert_eq!(high.priority_order(), 3);
assert_eq!(low.priority_order(), 4);
}
#[test]
fn test_message_sorting() {
let exec_id = ExecutionId::new();
let mut messages = [
InboxMessage::Guidance(GuidanceMessage::from_user(exec_id.clone(), "test")),
InboxMessage::Control(ControlMessage::new(
exec_id.clone(),
ControlAction::Pause,
"user",
)),
InboxMessage::Evidence(EvidenceUpdate::new(
exec_id,
EvidenceSource::Discovery,
"Found",
serde_json::json!({}),
EvidenceImpact::Informational,
)),
];
messages.sort_by_key(|m| m.priority_order());
assert!(messages[0].is_control());
assert!(matches!(messages[1], InboxMessage::Evidence(_)));
assert!(matches!(messages[2], InboxMessage::Guidance(_)));
}
}