Skip to main content

agentic_contracts/
hydra.rs

1//! Hydra integration placeholder traits.
2//!
3//! These traits define how sisters connect to the Hydra orchestrator.
4//! They are PLACEHOLDERS — the real implementations will come when
5//! Hydra is built. For now, they establish the contract shape so
6//! sisters can prepare.
7//!
8//! # Architecture
9//!
10//! ```text
11//! ┌─────────────────────────────────────────┐
12//! │                  HYDRA                   │
13//! │  ┌─────────┐ ┌──────────┐ ┌──────────┐  │
14//! │  │Execution│ │Capability│ │ Receipt  │  │
15//! │  │  Gate   │ │ Engine   │ │ Ledger   │  │
16//! │  └────┬────┘ └────┬─────┘ └────┬─────┘  │
17//! │       │           │            │         │
18//! │  ┌────┴───────────┴────────────┴──────┐  │
19//! │  │         HydraBridge trait          │  │
20//! │  └────────────────────────────────────┘  │
21//! └───────────────┬───────────────────────────┘
22//!                 │
23//!    ┌────────────┼────────────┐
24//!    ▼            ▼            ▼
25//! Memory       Vision      Codebase  ...
26//! ```
27
28use crate::context::SessionContext;
29use crate::errors::SisterResult;
30use crate::types::{Metadata, SisterType};
31use chrono::{DateTime, Utc};
32use serde::{Deserialize, Serialize};
33
34// ═══════════════════════════════════════════════════════════════════
35// HYDRA BRIDGE — How sisters connect to Hydra
36// ═══════════════════════════════════════════════════════════════════
37
38/// Summary of a sister's current state (for Hydra's context window).
39///
40/// This is the token-efficient summary Hydra uses to understand
41/// what each sister is doing without loading full state.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SisterSummary {
44    /// Which sister
45    pub sister_type: SisterType,
46
47    /// Brief status line for LLM context
48    pub status_line: String,
49
50    /// Item count (memories, captures, nodes, etc.)
51    pub item_count: usize,
52
53    /// Active session/workspace name
54    pub active_context: Option<String>,
55
56    /// Additional metadata
57    #[serde(default)]
58    pub metadata: Metadata,
59}
60
61/// A command from Hydra to a sister
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct HydraCommand {
64    /// Command type (sister interprets this)
65    pub command_type: String,
66
67    /// Command parameters
68    #[serde(default)]
69    pub params: Metadata,
70
71    /// Hydra run ID (for receipt chain)
72    pub run_id: String,
73
74    /// Step ID within the run
75    pub step_id: u64,
76}
77
78/// Result of executing a Hydra command
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct CommandResult {
81    /// Whether the command succeeded
82    pub success: bool,
83
84    /// Result data
85    pub data: serde_json::Value,
86
87    /// Error message (if failed)
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub error: Option<String>,
90
91    /// Evidence IDs produced by this command
92    #[serde(default)]
93    pub evidence_ids: Vec<String>,
94}
95
96/// The bridge between Hydra and individual sisters.
97///
98/// This is a PLACEHOLDER trait. Sisters should not implement it yet.
99/// It establishes the expected contract shape for when Hydra arrives.
100///
101/// When Hydra is built, this trait will require:
102/// `Sister + SessionManagement/WorkspaceManagement + Grounding + EventEmitter + Queryable`
103pub trait HydraBridge {
104    /// Get a token-efficient summary of current sister state.
105    /// Hydra calls this to build its context window
106    fn session_context(&self) -> SisterResult<SessionContext>;
107
108    /// Restore sister state from a previous session context.
109    /// Used when Hydra resumes a run
110    fn restore_session(&mut self, context: SessionContext) -> SisterResult<()>;
111
112    /// Get a brief summary for Hydra's context
113    fn summary(&self) -> SisterResult<SisterSummary>;
114
115    /// Execute a command from Hydra.
116    /// This is the escape hatch for Hydra-specific operations
117    fn execute(&mut self, command: HydraCommand) -> SisterResult<CommandResult>;
118}
119
120// ═══════════════════════════════════════════════════════════════════
121// EXECUTION GATE — Hydra's safety core (placeholder types)
122// ═══════════════════════════════════════════════════════════════════
123
124/// Risk level for an action
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
126#[serde(rename_all = "snake_case")]
127pub enum RiskLevel {
128    /// Low risk (0.0-0.3): auto-approve
129    Low,
130
131    /// Medium risk (0.3-0.6): log and proceed
132    Medium,
133
134    /// High risk (0.6-0.8): require confirmation
135    High,
136
137    /// Critical risk (0.8-1.0): block and escalate
138    Critical,
139}
140
141/// An action that needs to pass through the execution gate
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct GatedAction {
144    /// What sister is requesting this action
145    pub sister_type: SisterType,
146
147    /// Action type
148    pub action_type: String,
149
150    /// Assessed risk level
151    pub risk_level: RiskLevel,
152
153    /// Risk score (0.0-1.0)
154    pub risk_score: f64,
155
156    /// Required capability
157    pub capability: String,
158
159    /// When the action was requested
160    pub requested_at: DateTime<Utc>,
161
162    /// Action parameters
163    #[serde(default)]
164    pub params: Metadata,
165}
166
167/// Result of passing through the execution gate
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct GateDecision {
170    /// Whether the action is approved
171    pub approved: bool,
172
173    /// Reason for the decision
174    pub reason: String,
175
176    /// Approval ID (for receipt chain)
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub approval_id: Option<String>,
179
180    /// Conditions imposed on the action
181    #[serde(default)]
182    pub conditions: Vec<String>,
183}
184
185/// The Execution Gate trait (placeholder).
186///
187/// Hydra implements this, NOT sisters. Sisters submit actions
188/// to the gate; Hydra decides whether to approve.
189pub trait ExecutionGate {
190    /// Submit an action for approval
191    fn check(&self, action: GatedAction) -> SisterResult<GateDecision>;
192
193    /// Quick check if a capability is available
194    fn has_capability(&self, capability: &str) -> bool;
195
196    /// Get current risk threshold
197    fn risk_threshold(&self) -> RiskLevel;
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_risk_level_ordering() {
206        assert!(RiskLevel::Low < RiskLevel::Medium);
207        assert!(RiskLevel::Medium < RiskLevel::High);
208        assert!(RiskLevel::High < RiskLevel::Critical);
209    }
210
211    #[test]
212    fn test_sister_summary() {
213        let summary = SisterSummary {
214            sister_type: SisterType::Memory,
215            status_line: "590 nodes, session 42 active".into(),
216            item_count: 590,
217            active_context: Some("session_42".into()),
218            metadata: Metadata::new(),
219        };
220
221        assert_eq!(summary.sister_type, SisterType::Memory);
222        assert_eq!(summary.item_count, 590);
223    }
224
225    #[test]
226    fn test_command_result() {
227        let result = CommandResult {
228            success: true,
229            data: serde_json::json!({"added": 5}),
230            error: None,
231            evidence_ids: vec!["ev_1".into()],
232        };
233
234        assert!(result.success);
235        assert_eq!(result.evidence_ids.len(), 1);
236    }
237
238    #[test]
239    fn test_gate_decision() {
240        let decision = GateDecision {
241            approved: true,
242            reason: "Low risk action, auto-approved".into(),
243            approval_id: Some("approval_123".into()),
244            conditions: vec![],
245        };
246
247        assert!(decision.approved);
248    }
249}