oracle_omen_core 0.1.0

Core types and abstractions for oracle.omen deterministic agent framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//! Tool trait and related types for deterministic tool execution.
//!
//! Tools declare:
//! - Name and version
//! - Input/output schemas
//! - Required capabilities
//! - Side effect declaration
//! - Determinism declaration
//! - Resource bounds

use std::string::String;
use std::vec::Vec;
use std::{fmt, time::Duration};

use crate::capability::Capability;
use crate::hash::Hash;

/// Unique tool identifier
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
pub struct ToolId {
    /// Tool name
    pub name: String,

    /// Tool version (semver-like)
    pub version: String,
}

impl ToolId {
    /// Create a new tool ID
    #[must_use]
    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            version: version.into(),
        }
    }

    /// Create a string identifier
    #[must_use]
    pub fn as_str(&self) -> String {
        format!("{}@{}", self.name, self.version)
    }
}

impl fmt::Display for ToolId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}@{}", self.name, self.version)
    }
}

impl Default for ToolId {
    fn default() -> Self {
        Self {
            name: String::new(),
            version: String::new(),
        }
    }
}

/// Whether a tool has side effects
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SideEffect {
    /// No side effects - pure function
    Pure,

    /// Has external side effects (IO, state changes, etc.)
    Impure,
}

/// Whether a tool is deterministic
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Determinism {
    /// Fully deterministic - same input always produces same output
    Deterministic,

    /// Non-deterministic but bounded (e.g., uses time, randomness with seed)
    BoundedNonDeterminism,

    /// Fully non-deterministic
    NonDeterministic,
}

/// Resource bounds for tool execution
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ResourceBounds {
    /// Maximum execution time
    pub timeout_ms: u64,

    /// Maximum memory in bytes
    pub max_memory_bytes: Option<u64>,

    /// Maximum number of operations (for WASM tools)
    pub max_fuel: Option<u64>,
}

impl ResourceBounds {
    /// Create default resource bounds
    #[must_use]
    pub fn default() -> Self {
        Self {
            timeout_ms: 30_000, // 30 seconds
            max_memory_bytes: None,
            max_fuel: None,
        }
    }

    /// Create with timeout only
    #[must_use]
    pub const fn with_timeout(timeout_ms: u64) -> Self {
        Self {
            timeout_ms,
            max_memory_bytes: None,
            max_fuel: None,
        }
    }

    /// Get timeout as Duration
    #[must_use]
    pub fn timeout(&self) -> Duration {
        Duration::from_millis(self.timeout_ms)
    }
}

/// Tool request metadata
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolRequestMetadata {
    /// Tool being called
    pub tool_id: ToolId,

    /// Request timestamp
    pub timestamp: u64,

    /// Request hash (for reproducibility)
    pub request_hash: Hash,

    /// Capabilities required
    pub required_capabilities: Vec<Capability>,
}

impl ToolRequestMetadata {
    /// Create new metadata
    #[must_use]
    pub fn new(
        tool_id: ToolId,
        timestamp: u64,
        request_hash: Hash,
        required_capabilities: Vec<Capability>,
    ) -> Self {
        Self {
            tool_id,
            timestamp,
            request_hash,
            required_capabilities,
        }
    }
}

/// Normalized tool response
///
/// Normalization ensures deterministic hashing and replay.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolResponse<T> {
    /// The response data
    pub data: T,

    /// Normalized response hash
    pub response_hash: Hash,

    /// Metadata about the response
    pub metadata: ToolResponseMetadata,
}

impl<T: serde::Serialize> ToolResponse<T> {
    /// Create a new normalized response
    pub fn new(data: T) -> Self {
        let response_hash = Hash::from_canonical(&data);
        Self {
            data,
            response_hash,
            metadata: ToolResponseMetadata::default(),
        }
    }

    /// Create with custom metadata
    pub fn with_metadata(data: T, metadata: ToolResponseMetadata) -> Self {
        let response_hash = Hash::from_canonical(&data);
        Self {
            data,
            response_hash,
            metadata,
        }
    }
}

/// Tool response metadata
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolResponseMetadata {
    /// Source of the response
    pub source: ResponseSource,

    /// Whether normalization was applied
    pub normalized: bool,

    /// Execution time in milliseconds
    pub duration_ms: u64,

    /// Additional metadata
    pub extra: Vec<(String, String)>,
}

impl ToolResponseMetadata {
    /// Create default metadata
    #[must_use]
    pub fn default() -> Self {
        Self {
            source: ResponseSource::Tool,
            normalized: true,
            duration_ms: 0,
            extra: Vec::new(),
        }
    }

    /// Create for cached response
    #[must_use]
    pub fn cached(duration_ms: u64) -> Self {
        Self {
            source: ResponseSource::Cache,
            normalized: true,
            duration_ms,
            extra: Vec::new(),
        }
    }
}

/// Source of a tool response
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ResponseSource {
    /// Direct tool execution
    Tool,

    /// Cached response
    Cache,

    /// Mocked response (testing)
    Mock,

    /// Error response
    Error,
}

/// Tool trait - must be implemented by all tools
///
/// # Safety
/// Tools must not:
/// - Use global mutable state
/// - Access system time directly
/// - Use unseeded randomness
/// - Panic in normal operation
pub trait Tool: Send + Sync {
    /// Tool identifier
    fn id(&self) -> &ToolId;

    /// Required capabilities
    fn required_capabilities(&self) -> &[Capability];

    /// Side effect declaration
    fn side_effects(&self) -> SideEffect;

    /// Determinism declaration
    fn determinism(&self) -> Determinism;

    /// Resource bounds
    fn resource_bounds(&self) -> &ResourceBounds;

    /// Execute the tool
    ///
    /// # Arguments
    /// - `input`: Serialized input data
    /// - `context`: Execution context (time, run ID, etc.)
    ///
    /// # Returns
    /// Serialized output or error
    fn execute(&self, input: &[u8], context: &ExecutionContext) -> ToolResult<Vec<u8>>;

    /// Get input schema
    fn input_schema(&self) -> &str;

    /// Get output schema
    fn output_schema(&self) -> &str;
}

/// Result type for tool execution
pub type ToolResult<T> = core::result::Result<T, ToolError>;

/// Tool execution errors
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ToolError {
    /// Tool not found
    NotFound(String),

    /// Capability denied
    Denied { capability: String, reason: String },

    /// Timeout
    Timeout { tool: String, duration_ms: u64 },

    /// Execution failed
    ExecutionFailed { tool: String, reason: String },

    /// Invalid input
    InvalidInput { tool: String, reason: String },

    /// Output serialization failed
    SerializationFailed { tool: String, reason: String },

    /// Resource limit exceeded
    ResourceExceeded { tool: String, limit: String },

    /// Other error
    Other(String),
}

impl fmt::Display for ToolError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ToolError::NotFound(name) => write!(f, "Tool not found: {}", name),
            ToolError::Denied { capability, reason } => {
                write!(f, "Capability denied '{}' - {}", capability, reason)
            }
            ToolError::Timeout { tool, duration_ms } => {
                write!(f, "Tool {} timed out after {}ms", tool, duration_ms)
            }
            ToolError::ExecutionFailed { tool, reason } => {
                write!(f, "Tool {} execution failed: {}", tool, reason)
            }
            ToolError::InvalidInput { tool, reason } => {
                write!(f, "Tool {} invalid input: {}", tool, reason)
            }
            ToolError::SerializationFailed { tool, reason } => {
                write!(f, "Tool {} serialization failed: {}", tool, reason)
            }
            ToolError::ResourceExceeded { tool, limit } => {
                write!(f, "Tool {} exceeded resource limit: {}", tool, limit)
            }
            ToolError::Other(msg) => write!(f, "Tool error: {}", msg),
        }
    }
}

/// Execution context provided to tools
///
/// Contains deterministic time and other context needed for reproducible execution.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ExecutionContext {
    /// Logical timestamp
    pub logical_time: u64,

    /// Run identifier
    pub run_id: u64,

    /// Injected random seed (if needed)
    pub random_seed: Option<u64>,
}

impl ExecutionContext {
    /// Create a new execution context
    #[must_use]
    pub fn new(logical_time: u64, run_id: u64) -> Self {
        Self {
            logical_time,
            run_id,
            random_seed: None,
        }
    }

    /// Create with random seed
    #[must_use]
    pub fn with_seed(logical_time: u64, run_id: u64, random_seed: u64) -> Self {
        Self {
            logical_time,
            run_id,
            random_seed: Some(random_seed),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tool_id() {
        let id = ToolId::new("test_tool", "1.0.0");
        assert_eq!(id.name, "test_tool");
        assert_eq!(id.version, "1.0.0");
        assert_eq!(id.as_str(), "test_tool@1.0.0");
    }

    #[test]
    fn test_resource_bounds() {
        let bounds = ResourceBounds::with_timeout(5000);
        assert_eq!(bounds.timeout_ms, 5000);
        assert_eq!(bounds.timeout(), Duration::from_millis(5000));
    }

    #[test]
    fn test_execution_context() {
        let ctx = ExecutionContext::with_seed(100, 42, 12345);
        assert_eq!(ctx.logical_time, 100);
        assert_eq!(ctx.run_id, 42);
        assert_eq!(ctx.random_seed, Some(12345));
    }
}