Skip to main content

a3s_ahp/protocol/
core.rs

1use super::EventContext;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// AHP event.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AhpEvent {
8    pub event_type: EventType,
9    pub session_id: String,
10    pub agent_id: String,
11    pub timestamp: String,
12    pub depth: u32,
13    pub payload: serde_json::Value,
14    /// Structured context for context-aware decisions.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub context: Option<EventContext>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub metadata: Option<HashMap<String, serde_json::Value>>,
19}
20
21/// Event types.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum EventType {
25    Handshake,
26    PreAction,
27    PostAction,
28    PrePrompt,
29    PostResponse,
30    SessionStart,
31    SessionEnd,
32    Error,
33    Query,
34    Heartbeat,
35    /// Agent is idle and asks whether background work should run.
36    Idle,
37    /// Context perception - model needs workspace knowledge (blocking).
38    ContextPerception,
39    /// Operation succeeded (fire-and-forget).
40    Success,
41    /// Memory recall - model needs to retrieve from memory (blocking).
42    MemoryRecall,
43    /// Task planning/decomposition (blocking).
44    Planning,
45    /// Chain-of-thought reasoning (blocking).
46    Reasoning,
47    /// Rate limit triggered and requires backpressure decision.
48    RateLimit,
49    /// User confirmation needed.
50    Confirmation,
51    /// Intent detection - detect user intent from prompt (blocking).
52    IntentDetection,
53    /// Durable run lifecycle transition (fire-and-forget).
54    RunLifecycle,
55    /// Authoritative task-list snapshot for a run (fire-and-forget).
56    TaskList,
57    /// Verification status snapshot for a run (fire-and-forget).
58    Verification,
59}
60
61impl EventType {
62    /// Returns true if this event type requires a response.
63    pub fn is_blocking(&self) -> bool {
64        matches!(
65            self,
66            EventType::Handshake
67                | EventType::PreAction
68                | EventType::PrePrompt
69                | EventType::Query
70                | EventType::Idle
71                | EventType::ContextPerception
72                | EventType::MemoryRecall
73                | EventType::Planning
74                | EventType::Reasoning
75                | EventType::RateLimit
76                | EventType::Confirmation
77                | EventType::IntentDetection
78        )
79    }
80
81    /// Returns true if this event returns a specialized decision type.
82    pub fn uses_specialized_decision(&self) -> bool {
83        matches!(
84            self,
85            EventType::Idle
86                | EventType::ContextPerception
87                | EventType::MemoryRecall
88                | EventType::Planning
89                | EventType::Reasoning
90                | EventType::RateLimit
91                | EventType::Confirmation
92                | EventType::IntentDetection
93        )
94    }
95
96    /// Returns true if this event can be included in a `BatchRequest`.
97    pub fn is_batchable(&self) -> bool {
98        !matches!(self, EventType::Handshake | EventType::Query)
99            && !self.uses_specialized_decision()
100    }
101}
102
103impl std::fmt::Display for EventType {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self {
106            EventType::Handshake => write!(f, "handshake"),
107            EventType::PreAction => write!(f, "pre_action"),
108            EventType::PostAction => write!(f, "post_action"),
109            EventType::PrePrompt => write!(f, "pre_prompt"),
110            EventType::PostResponse => write!(f, "post_response"),
111            EventType::SessionStart => write!(f, "session_start"),
112            EventType::SessionEnd => write!(f, "session_end"),
113            EventType::Error => write!(f, "error"),
114            EventType::Query => write!(f, "query"),
115            EventType::Heartbeat => write!(f, "heartbeat"),
116            EventType::Idle => write!(f, "idle"),
117            EventType::ContextPerception => write!(f, "context_perception"),
118            EventType::Success => write!(f, "success"),
119            EventType::MemoryRecall => write!(f, "memory_recall"),
120            EventType::Planning => write!(f, "planning"),
121            EventType::Reasoning => write!(f, "reasoning"),
122            EventType::RateLimit => write!(f, "rate_limit"),
123            EventType::Confirmation => write!(f, "confirmation"),
124            EventType::IntentDetection => write!(f, "intent_detection"),
125            EventType::RunLifecycle => write!(f, "run_lifecycle"),
126            EventType::TaskList => write!(f, "task_list"),
127            EventType::Verification => write!(f, "verification"),
128        }
129    }
130}
131
132/// Generic decision types used by the baseline AHP event flow.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(tag = "decision", rename_all = "lowercase")]
135pub enum Decision {
136    Allow {
137        #[serde(skip_serializing_if = "Option::is_none")]
138        modified_payload: Option<serde_json::Value>,
139        #[serde(skip_serializing_if = "Option::is_none")]
140        metadata: Option<HashMap<String, serde_json::Value>>,
141    },
142    Block {
143        reason: String,
144        #[serde(skip_serializing_if = "Option::is_none")]
145        metadata: Option<HashMap<String, serde_json::Value>>,
146    },
147    Modify {
148        modified_payload: serde_json::Value,
149        #[serde(skip_serializing_if = "Option::is_none")]
150        metadata: Option<HashMap<String, serde_json::Value>>,
151    },
152    Defer {
153        retry_after_ms: u64,
154        #[serde(skip_serializing_if = "Option::is_none")]
155        reason: Option<String>,
156    },
157    Escalate {
158        reason: String,
159        #[serde(skip_serializing_if = "Option::is_none")]
160        escalation_target: Option<String>,
161    },
162}
163
164/// Handshake request.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct HandshakeRequest {
167    pub protocol_version: String,
168    pub agent_info: AgentInfo,
169    pub session_id: String,
170    pub agent_id: String,
171}
172
173/// Agent information.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct AgentInfo {
176    pub framework: String,
177    pub version: String,
178    pub capabilities: Vec<String>,
179}
180
181/// Handshake response.
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct HandshakeResponse {
184    pub protocol_version: String,
185    pub harness_info: HarnessInfo,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub session_token: Option<String>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub config: Option<HarnessConfig>,
190}
191
192/// Harness information.
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct HarnessInfo {
195    pub name: String,
196    pub version: String,
197    pub capabilities: Vec<String>,
198}
199
200/// Harness configuration.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct HarnessConfig {
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub timeout_ms: Option<u64>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub batch_size: Option<usize>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub max_depth: Option<u32>,
209}
210
211/// Query request.
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct QueryRequest {
214    pub session_id: String,
215    pub agent_id: String,
216    pub query_type: String,
217    pub payload: serde_json::Value,
218}
219
220/// Query response.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct QueryResponse {
223    pub answer: serde_json::Value,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub reason: Option<String>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub alternatives: Option<Vec<String>>,
228}
229
230/// Batch request.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct BatchRequest {
233    pub events: Vec<AhpEvent>,
234}
235
236/// Batch response.
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct BatchResponse {
239    pub decisions: Vec<Decision>,
240}