use std::collections::HashMap;
use std::fmt;
use ainl_graph_extractor::ExtractionReport;
use ainl_memory::{AgentGraphSnapshot, AinlMemoryNode, GraphValidationReport, SqliteGraphStore};
use ainl_persona::PersonaSnapshot;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub const EMIT_TO_EDGE: &str = "EMIT_TO";
#[derive(Debug, Clone)]
pub struct PatchDispatchResult {
pub label: String,
pub patch_version: u32,
pub fitness_before: f32,
pub fitness_after: f32,
pub dispatched: bool,
pub skip_reason: Option<PatchSkipReason>,
pub adapter_output: Option<serde_json::Value>,
pub adapter_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatchSkipReason {
MissingDeclaredRead(String),
Retired,
ZeroVersion,
NotProcedural,
PersistFailed(String),
}
impl fmt::Display for PatchSkipReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PatchSkipReason::MissingDeclaredRead(s) => write!(f, "missing_declared_read:{s}"),
PatchSkipReason::Retired => write!(f, "retired"),
PatchSkipReason::ZeroVersion => write!(f, "zero_version"),
PatchSkipReason::NotProcedural => write!(f, "not_procedural"),
PatchSkipReason::PersistFailed(s) => write!(f, "persist_failed:{s}"),
}
}
}
#[derive(Debug, Clone)]
pub struct AinlGraphArtifact {
pub agent_id: String,
pub snapshot: AgentGraphSnapshot,
pub validation: GraphValidationReport,
}
impl AinlGraphArtifact {
pub fn load(store: &SqliteGraphStore, agent_id: &str) -> Result<Self, String> {
let snapshot = store.export_graph(agent_id)?;
let validation = store.validate_graph(agent_id)?;
if !validation.is_valid {
let mut msg = String::from("graph validation failed: dangling edges");
for d in &validation.dangling_edge_details {
msg.push_str(&format!(
"; {} -> {} [{}]",
d.source_id, d.target_id, d.edge_type
));
}
return Err(msg);
}
Ok(Self {
agent_id: agent_id.to_string(),
snapshot,
validation,
})
}
pub fn from_snapshot(snapshot: AgentGraphSnapshot) -> Self {
let agent_id = snapshot.agent_id.clone();
let node_count = snapshot.nodes.len();
let edge_count = snapshot.edges.len();
let validation = GraphValidationReport {
agent_id: agent_id.clone(),
node_count,
edge_count,
dangling_edges: Vec::new(),
dangling_edge_details: Vec::new(),
cross_agent_boundary_edges: 0,
orphan_nodes: Vec::new(),
is_valid: true,
};
Self {
agent_id,
snapshot,
validation,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct TurnInput {
pub user_message: String,
pub tools_invoked: Vec<String>,
pub trace_event: Option<serde_json::Value>,
pub depth: u32,
pub frame: HashMap<String, serde_json::Value>,
pub emit_targets: Vec<Uuid>,
}
#[derive(Debug, Clone)]
pub struct MemoryContext {
pub recent_episodes: Vec<AinlMemoryNode>,
pub relevant_semantic: Vec<AinlMemoryNode>,
pub active_patches: Vec<AinlMemoryNode>,
pub persona_snapshot: Option<PersonaSnapshot>,
pub compiled_at: DateTime<Utc>,
}
impl Default for MemoryContext {
fn default() -> Self {
Self {
recent_episodes: Vec::new(),
relevant_semantic: Vec::new(),
active_patches: Vec::new(),
persona_snapshot: None,
compiled_at: Utc::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct TurnOutput {
pub episode_id: Uuid,
pub persona_prompt_contribution: Option<String>,
pub memory_context: MemoryContext,
pub extraction_report: Option<ExtractionReport>,
pub steps_executed: u32,
pub outcome: TurnOutcome,
pub patch_dispatch_results: Vec<PatchDispatchResult>,
}
impl Default for TurnOutput {
fn default() -> Self {
Self {
episode_id: Uuid::nil(),
persona_prompt_contribution: None,
memory_context: MemoryContext::default(),
extraction_report: None,
steps_executed: 0,
outcome: TurnOutcome::Success,
patch_dispatch_results: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum TurnOutcome {
Success,
DepthLimitExceeded,
StepLimitExceeded { steps_executed: u32 },
GraphMemoryDisabled,
PartialSuccess {
episode_recorded: bool,
extraction_failed: bool,
patches_failed: Vec<String>,
warnings: Vec<String>,
},
Error(String),
}