1use std::collections::HashMap;
4use std::fmt;
5
6use ainl_graph_extractor::ExtractionReport;
7use ainl_memory::{
8 AgentGraphSnapshot, AinlMemoryNode, AinlNodeType, GraphValidationReport, ProceduralNode,
9 SqliteGraphStore,
10};
11use ainl_persona::PersonaSnapshot;
12use chrono::{DateTime, Utc};
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16pub const EMIT_TO_EDGE: &str = "EMIT_TO";
18
19#[derive(Debug, Clone, Copy)]
25pub struct PatchDispatchContext<'a> {
26 pub patch_label: &'a str,
27 pub node: &'a AinlMemoryNode,
28 pub frame: &'a HashMap<String, serde_json::Value>,
29}
30
31impl<'a> PatchDispatchContext<'a> {
32 pub fn procedural(&self) -> Option<&'a ProceduralNode> {
33 match &self.node.node_type {
34 AinlNodeType::Procedural { procedural } => Some(procedural),
35 _ => None,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct PatchDispatchResult {
43 pub label: String,
44 pub patch_version: u32,
45 pub fitness_before: f32,
46 pub fitness_after: f32,
47 pub dispatched: bool,
48 pub skip_reason: Option<PatchSkipReason>,
49 pub adapter_output: Option<serde_json::Value>,
51 pub adapter_name: Option<String>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum PatchSkipReason {
57 MissingDeclaredRead(String),
58 Retired,
59 ZeroVersion,
60 NotProcedural,
62 PersistFailed(String),
64}
65
66impl fmt::Display for PatchSkipReason {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 PatchSkipReason::MissingDeclaredRead(s) => write!(f, "missing_declared_read:{s}"),
70 PatchSkipReason::Retired => write!(f, "retired"),
71 PatchSkipReason::ZeroVersion => write!(f, "zero_version"),
72 PatchSkipReason::NotProcedural => write!(f, "not_procedural"),
73 PatchSkipReason::PersistFailed(s) => write!(f, "persist_failed:{s}"),
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct AinlGraphArtifact {
81 pub agent_id: String,
82 pub snapshot: AgentGraphSnapshot,
83 pub validation: GraphValidationReport,
84}
85
86impl AinlGraphArtifact {
87 pub fn load(store: &SqliteGraphStore, agent_id: &str) -> Result<Self, String> {
89 let snapshot = store.export_graph(agent_id)?;
90 let validation = store.validate_graph(agent_id)?;
91 if !validation.is_valid {
92 let mut msg = String::from("graph validation failed: dangling edges");
93 for d in &validation.dangling_edge_details {
94 msg.push_str(&format!(
95 "; {} -> {} [{}]",
96 d.source_id, d.target_id, d.edge_type
97 ));
98 }
99 return Err(msg);
100 }
101 Ok(Self {
102 agent_id: agent_id.to_string(),
103 snapshot,
104 validation,
105 })
106 }
107
108 pub fn from_snapshot(snapshot: AgentGraphSnapshot) -> Self {
110 let agent_id = snapshot.agent_id.clone();
111 let node_count = snapshot.nodes.len();
112 let edge_count = snapshot.edges.len();
113 let validation = GraphValidationReport {
114 agent_id: agent_id.clone(),
115 node_count,
116 edge_count,
117 dangling_edges: Vec::new(),
118 dangling_edge_details: Vec::new(),
119 cross_agent_boundary_edges: 0,
120 orphan_nodes: Vec::new(),
121 is_valid: true,
122 };
123 Self {
124 agent_id,
125 snapshot,
126 validation,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
133#[serde(default)]
134pub struct TurnInput {
135 pub user_message: String,
136 pub tools_invoked: Vec<String>,
137 pub trace_event: Option<serde_json::Value>,
138 pub depth: u32,
140 pub frame: HashMap<String, serde_json::Value>,
142 pub emit_targets: Vec<Uuid>,
145}
146
147#[derive(Debug, Clone)]
149pub struct MemoryContext {
150 pub recent_episodes: Vec<AinlMemoryNode>,
151 pub relevant_semantic: Vec<AinlMemoryNode>,
152 pub active_patches: Vec<AinlMemoryNode>,
153 pub persona_snapshot: Option<PersonaSnapshot>,
154 pub compiled_at: DateTime<Utc>,
155}
156
157impl Default for MemoryContext {
158 fn default() -> Self {
159 Self {
160 recent_episodes: Vec::new(),
161 relevant_semantic: Vec::new(),
162 active_patches: Vec::new(),
163 persona_snapshot: None,
164 compiled_at: Utc::now(),
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct TurnOutput {
172 pub episode_id: Uuid,
173 pub persona_prompt_contribution: Option<String>,
174 pub memory_context: MemoryContext,
175 pub extraction_report: Option<ExtractionReport>,
176 pub steps_executed: u32,
177 pub outcome: TurnOutcome,
178 pub patch_dispatch_results: Vec<PatchDispatchResult>,
179}
180
181impl Default for TurnOutput {
182 fn default() -> Self {
183 Self {
184 episode_id: Uuid::nil(),
185 persona_prompt_contribution: None,
186 memory_context: MemoryContext::default(),
187 extraction_report: None,
188 steps_executed: 0,
189 outcome: TurnOutcome::Success,
190 patch_dispatch_results: Vec::new(),
191 }
192 }
193}
194
195#[derive(Debug, Clone)]
197pub enum TurnOutcome {
198 Success,
199 DepthLimitExceeded,
200 StepLimitExceeded {
201 steps_executed: u32,
202 },
203 GraphMemoryDisabled,
204 PartialSuccess {
205 episode_recorded: bool,
206 extraction_failed: bool,
207 patches_failed: Vec<String>,
208 warnings: Vec<String>,
209 },
210 Error(String),
211}