use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct GoalTokenBudgetStatus {
#[allow(dead_code)] pub budget_per_check: Option<i64>,
pub budget_daily: Option<i64>,
pub tokens_used_today: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ScheduledRunHealth {
pub evidence_gain_count: usize,
pub total_successful_tool_calls: usize,
pub stall_count: usize,
pub consecutive_same_tool_count: usize,
pub consecutive_same_tool_unique_args: usize,
pub unrecovered_error_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduledRunState {
pub goal_id: String,
pub root_task_id: String,
pub effective_budget_per_check: i64,
pub tokens_used: i64,
pub budget_extensions_count: usize,
#[serde(default)]
pub health: ScheduledRunHealth,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Goal {
pub id: String,
pub description: String,
pub domain: String,
pub goal_type: String,
pub status: String,
pub priority: String,
pub conditions: Option<String>,
pub context: Option<String>,
pub resources: Option<String>,
pub budget_per_check: Option<i64>,
pub budget_daily: Option<i64>,
pub tokens_used_today: i64,
pub tokens_used_day: String,
pub last_useful_action: Option<String>,
pub created_at: String,
pub updated_at: String,
pub completed_at: Option<String>,
pub parent_goal_id: Option<String>,
pub session_id: String,
pub notified_at: Option<String>,
#[serde(default)]
pub notification_attempts: i32,
#[serde(default)]
pub dispatch_failures: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub progress_notes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_episode_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub legacy_int_id: Option<i64>,
}
impl Goal {
pub fn new_finite(description: &str, session_id: &str) -> Self {
let now = chrono::Utc::now().to_rfc3339();
let day = chrono::Utc::now().date_naive().to_string();
Self {
id: uuid::Uuid::new_v4().to_string(),
description: description.to_string(),
domain: "orchestration".to_string(),
goal_type: "finite".to_string(),
status: "active".to_string(),
priority: "medium".to_string(),
conditions: None,
context: None,
resources: None,
budget_per_check: Some(100_000),
budget_daily: Some(1_000_000),
tokens_used_today: 0,
tokens_used_day: day,
last_useful_action: None,
created_at: now.clone(),
updated_at: now,
completed_at: None,
parent_goal_id: None,
session_id: session_id.to_string(),
notified_at: None,
notification_attempts: 0,
dispatch_failures: 0,
progress_notes: None,
source_episode_id: None,
legacy_int_id: None,
}
}
pub fn new_personal(description: &str, session_id: &str) -> Self {
let now = chrono::Utc::now().to_rfc3339();
let day = chrono::Utc::now().date_naive().to_string();
Self {
id: uuid::Uuid::new_v4().to_string(),
description: description.to_string(),
domain: "personal".to_string(),
goal_type: "finite".to_string(),
status: "active".to_string(),
priority: "medium".to_string(),
conditions: None,
context: None,
resources: None,
budget_per_check: None,
budget_daily: None,
tokens_used_today: 0,
tokens_used_day: day,
last_useful_action: None,
created_at: now.clone(),
updated_at: now,
completed_at: None,
parent_goal_id: None,
session_id: session_id.to_string(),
notified_at: None,
notification_attempts: 0,
dispatch_failures: 0,
progress_notes: Some(Vec::new()),
source_episode_id: None,
legacy_int_id: None,
}
}
pub fn new_deferred_finite(description: &str, session_id: &str) -> Self {
let mut goal = Self::new_finite(description, session_id);
goal.status = "pending_confirmation".to_string();
goal
}
pub fn new_continuous(
description: &str,
session_id: &str,
budget_per_check: Option<i64>,
budget_daily: Option<i64>,
) -> Self {
let now = chrono::Utc::now().to_rfc3339();
let day = chrono::Utc::now().date_naive().to_string();
Self {
id: uuid::Uuid::new_v4().to_string(),
description: description.to_string(),
domain: "orchestration".to_string(),
goal_type: "continuous".to_string(),
status: "active".to_string(),
priority: "low".to_string(),
conditions: None,
context: None,
resources: None,
budget_per_check: budget_per_check.or(Some(100_000)),
budget_daily: budget_daily.or(Some(500_000)),
tokens_used_today: 0,
tokens_used_day: day,
last_useful_action: None,
created_at: now.clone(),
updated_at: now,
completed_at: None,
parent_goal_id: None,
session_id: session_id.to_string(),
notified_at: None,
notification_attempts: 0,
dispatch_failures: 0,
progress_notes: None,
source_episode_id: None,
legacy_int_id: None,
}
}
pub fn new_continuous_pending(
description: &str,
session_id: &str,
budget_per_check: Option<i64>,
budget_daily: Option<i64>,
) -> Self {
let mut goal =
Self::new_continuous(description, session_id, budget_per_check, budget_daily);
goal.status = "pending_confirmation".to_string();
goal
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoalSchedule {
pub id: String,
pub goal_id: String,
pub cron_expr: String,
pub tz: String,
pub original_schedule: Option<String>,
pub fire_policy: String,
pub is_one_shot: bool,
pub is_paused: bool,
pub last_run_at: Option<String>,
pub next_run_at: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)] pub struct Task {
pub id: String,
pub goal_id: String,
pub description: String,
pub status: String,
pub priority: String,
pub task_order: i32,
pub parallel_group: Option<String>,
pub depends_on: Option<String>,
pub agent_id: Option<String>,
pub context: Option<String>,
pub result: Option<String>,
pub error: Option<String>,
pub blocker: Option<String>,
pub idempotent: bool,
pub retry_count: i32,
pub max_retries: i32,
pub created_at: String,
pub started_at: Option<String>,
pub completed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)] pub struct TaskActivity {
pub id: i64,
pub task_id: String,
pub activity_type: String,
pub tool_name: Option<String>,
pub tool_args: Option<String>,
pub result: Option<String>,
pub success: Option<bool>,
pub tokens_used: Option<i64>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationEntry {
pub id: String,
pub goal_id: String,
pub session_id: String,
pub notification_type: String,
pub priority: String,
pub message: String,
pub created_at: String,
pub delivered_at: Option<String>,
pub attempts: i32,
pub expires_at: Option<String>,
}
impl NotificationEntry {
pub fn new(goal_id: &str, session_id: &str, notification_type: &str, message: &str) -> Self {
let now = chrono::Utc::now();
let priority = match notification_type {
"completed" | "failed" | "escalation" | "evergreen_alert" | "token_alert" => "critical",
_ => "status_update",
};
let expires_at = if priority == "status_update" {
Some((now + chrono::Duration::hours(24)).to_rfc3339())
} else {
None };
Self {
id: uuid::Uuid::new_v4().to_string(),
goal_id: goal_id.to_string(),
session_id: session_id.to_string(),
notification_type: notification_type.to_string(),
priority: priority.to_string(),
message: message.to_string(),
created_at: now.to_rfc3339(),
delivered_at: None,
attempts: 0,
expires_at,
}
}
}