Skip to main content

av_atif/
model.rs

1//! ATIF data model (serde), matching Harbor's published Pydantic models.
2
3use serde::{Deserialize, Serialize};
4
5/// The version this crate always writes.
6pub const ATIF_VERSION: &str = "ATIF-v1.7";
7
8/// Every published schema version the reader/validator accepts.
9pub const SUPPORTED_VERSIONS: &[&str] = &[
10    "ATIF-v1.0",
11    "ATIF-v1.1",
12    "ATIF-v1.2",
13    "ATIF-v1.3",
14    "ATIF-v1.4",
15    "ATIF-v1.5",
16    "ATIF-v1.6",
17    "ATIF-v1.7",
18];
19
20/// Step source.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23#[non_exhaustive]
24pub enum Source {
25    /// System / environment step.
26    System,
27    /// User message.
28    User,
29    /// Agent action or response.
30    Agent,
31}
32
33/// Root trajectory object.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct Trajectory {
36    /// `"ATIF-vX.Y"`.
37    pub schema_version: String,
38    /// Run-scoped session id (optional since v1.7).
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub session_id: Option<String>,
41    /// Unique trajectory id (v1.7, for single-file subagent embedding).
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub trajectory_id: Option<String>,
44    /// Agent configuration.
45    pub agent: Agent,
46    /// Ordered interaction steps (`step_id` sequential from 1).
47    pub steps: Vec<Step>,
48    /// Custom information, design notes, or explanations.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub notes: Option<String>,
51    /// Aggregate metrics.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub final_metrics: Option<FinalMetrics>,
54    /// Reference to a continuation trajectory file.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub continued_trajectory_ref: Option<String>,
57    /// Embedded subagent trajectories (v1.7).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub subagent_trajectories: Option<Vec<Trajectory>>,
60    /// Custom metadata (v1.1).
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub extra: Option<serde_json::Value>,
63}
64
65/// Agent configuration block.
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct Agent {
68    /// Agent name.
69    pub name: String,
70    /// Agent version.
71    pub version: String,
72    /// Backing model name.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub model_name: Option<String>,
75    /// Tool/function definitions in scope (v1.5, for SFT pipelines).
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub tool_definitions: Option<Vec<serde_json::Map<String, serde_json::Value>>>,
78    /// Custom metadata.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub extra: Option<serde_json::Value>,
81}
82
83/// One interaction step.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct Step {
86    /// Sequential id starting at 1.
87    pub step_id: u64,
88    /// ISO-8601 timestamp.
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub timestamp: Option<String>,
91    /// Who produced the step.
92    pub source: Source,
93    /// Message content (string or content-part array since v1.6).
94    pub message: serde_json::Value,
95    /// Agent-only: qualitative or quantitative reasoning effort.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub reasoning_effort: Option<ReasoningEffort>,
98    /// Agent-only: chain-of-thought / reasoning content.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub reasoning_content: Option<String>,
101    /// Agent-only: model that produced this step.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub model_name: Option<String>,
104    /// Agent-only: tool calls issued in this step.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub tool_calls: Option<Vec<ToolCall>>,
107    /// Environment feedback (agent steps; system steps since v1.2).
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub observation: Option<Observation>,
110    /// Agent-only: per-step LLM metrics.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub metrics: Option<Metrics>,
113    /// True when copied from a previous trajectory as context.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub is_copied_context: Option<bool>,
116    /// Number of LLM calls in this step (v1.7; valid on any step source).
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub llm_call_count: Option<u64>,
119    /// Custom metadata.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub extra: Option<serde_json::Value>,
122}
123
124/// String or numeric ATIF reasoning effort.
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126#[serde(untagged)]
127#[non_exhaustive]
128pub enum ReasoningEffort {
129    /// Qualitative effort label.
130    Text(String),
131    /// Numeric effort measure.
132    Numeric(f64),
133}
134
135/// A tool/function invocation.
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct ToolCall {
138    /// Correlation id referenced by observation results.
139    pub tool_call_id: String,
140    /// Invoked function name.
141    pub function_name: String,
142    /// Arguments object.
143    pub arguments: serde_json::Value,
144    /// Custom metadata (v1.7).
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub extra: Option<serde_json::Value>,
147}
148
149/// Environment feedback for a step.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct Observation {
152    /// Results, each referencing a `tool_call_id` from the same step.
153    pub results: Vec<ObservationResult>,
154}
155
156/// One observation result.
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct ObservationResult {
159    /// The `tool_call_id` this result answers (optional for free-form
160    /// system observations).
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub source_call_id: Option<String>,
163    /// Result content (string or content-part array since v1.6).
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub content: Option<serde_json::Value>,
166    /// References to delegated subagent trajectories.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub subagent_trajectory_ref: Option<Vec<SubagentTrajectoryRef>>,
169    /// Custom metadata (v1.7).
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub extra: Option<serde_json::Value>,
172}
173
174/// Resolvable reference to an embedded or external subagent trajectory.
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct SubagentTrajectoryRef {
177    /// Embedded trajectory document identifier.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub trajectory_id: Option<String>,
180    /// Informational run-scoped session identifier.
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub session_id: Option<String>,
183    /// External trajectory file or object-store path.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub trajectory_path: Option<String>,
186    /// Custom reference metadata.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub extra: Option<serde_json::Value>,
189}
190
191/// Per-step LLM metrics. `cached_tokens` preservation is the Module H
192/// replayable-KV-checkpoint requirement (success criterion R17/R28).
193#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
194pub struct Metrics {
195    /// Prompt tokens.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub prompt_tokens: Option<u64>,
198    /// Completion tokens.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub completion_tokens: Option<u64>,
201    /// Provider-cached prompt tokens.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub cached_tokens: Option<u64>,
204    /// Cost in USD.
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub cost_usd: Option<f64>,
207    /// Per-token logprobs (optional).
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub logprobs: Option<Vec<f64>>,
210    /// Completion token ids (v1.3, RL training).
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub completion_token_ids: Option<Vec<u64>>,
213    /// Prompt token ids (v1.4).
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub prompt_token_ids: Option<Vec<u64>>,
216    /// Additional operational metrics.
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub extra: Option<serde_json::Value>,
219}
220
221/// Trajectory-level aggregates.
222#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
223pub struct FinalMetrics {
224    /// Sum of prompt tokens.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub total_prompt_tokens: Option<u64>,
227    /// Sum of completion tokens.
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub total_completion_tokens: Option<u64>,
230    /// Sum of cached tokens.
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub total_cached_tokens: Option<u64>,
233    /// Total cost in USD.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub total_cost_usd: Option<f64>,
236    /// Number of steps.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub total_steps: Option<u64>,
239    /// Additional aggregate metrics.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub extra: Option<serde_json::Value>,
242}