use crate::node::{AinlMemoryNode, TrajectoryNode};
use crate::trajectory_table::TrajectoryDetailRecord;
use crate::GraphMemory;
use ainl_contracts::{TrajectoryOutcome, TrajectoryStep};
use uuid::Uuid;
#[must_use]
pub fn trajectory_env_enabled() -> bool {
match std::env::var("AINL_TRAJECTORY_ENABLED") {
Ok(s) => {
let v = s.trim().to_ascii_lowercase();
!(v == "0" || v == "false" || v == "no" || v == "off")
}
Err(_) => true,
}
}
fn coarse_steps_from_tools(tools: &[String]) -> Vec<TrajectoryStep> {
let base_ms = chrono::Utc::now().timestamp_millis();
tools
.iter()
.enumerate()
.map(|(i, name)| TrajectoryStep {
step_id: format!("step_{i}"),
timestamp_ms: base_ms + i as i64,
adapter: "builtin".into(),
operation: name.clone(),
inputs_preview: None,
outputs_preview: None,
duration_ms: 0,
success: true,
error: None,
vitals: None,
freshness_at_step: None,
frame_vars: None,
tool_telemetry: None,
})
.collect()
}
#[allow(clippy::too_many_arguments)] pub fn persist_trajectory_for_episode(
memory: &GraphMemory,
agent_id: &str,
episode_graph_id: Uuid,
steps: Vec<TrajectoryStep>,
outcome: TrajectoryOutcome,
session_id: &str,
project_id: Option<&str>,
ainl_source_hash: Option<&str>,
duration_ms: u64,
frame_vars: Option<serde_json::Value>,
fitness_delta: Option<f32>,
) -> Result<(Uuid, Uuid), String> {
let recorded_at = chrono::Utc::now().timestamp();
let traj_body = TrajectoryNode {
episode_id: episode_graph_id,
recorded_at,
session_id: session_id.to_string(),
project_id: project_id.map(str::to_string),
ainl_source_hash: ainl_source_hash.map(str::to_string),
outcome,
steps: steps.clone(),
duration_ms,
frame_vars: frame_vars.clone(),
fitness_delta,
};
let mut node = AinlMemoryNode::new_trajectory(traj_body, agent_id);
if let Some(p) = project_id.map(str::trim).filter(|s| !s.is_empty()) {
node.project_id = Some(p.to_string());
}
let graph_traj_id = node.id;
memory.write_node(&node)?;
memory.insert_graph_edge_checked(graph_traj_id, episode_graph_id, "trajectory_of")?;
let detail_id = Uuid::new_v4();
let row = TrajectoryDetailRecord {
id: detail_id,
episode_id: episode_graph_id,
graph_trajectory_node_id: Some(graph_traj_id),
agent_id: agent_id.to_string(),
session_id: session_id.to_string(),
project_id: project_id.map(str::to_string),
recorded_at,
outcome,
ainl_source_hash: ainl_source_hash.map(str::to_string),
duration_ms,
steps,
frame_vars,
fitness_delta,
};
memory.insert_trajectory_detail(&row)?;
Ok((graph_traj_id, detail_id))
}
#[inline]
#[allow(clippy::too_many_arguments)] pub fn persist_trajectory_coarse_tools(
memory: &GraphMemory,
agent_id: &str,
episode_graph_id: Uuid,
tools: &[String],
outcome: TrajectoryOutcome,
session_id: &str,
project_id: Option<&str>,
ainl_source_hash: Option<&str>,
) -> Result<(Uuid, Uuid), String> {
let steps = coarse_steps_from_tools(tools);
persist_trajectory_for_episode(
memory,
agent_id,
episode_graph_id,
steps,
outcome,
session_id,
project_id,
ainl_source_hash,
0,
None,
None,
)
}