1#![cfg_attr(docsrs, feature(doc_cfg))]
2use serde::{Deserialize, Serialize};
36use std::collections::VecDeque;
37use std::fs::OpenOptions;
38use std::io::Write;
39use std::convert::TryInto;
40use blake3::Hash as Blake3Hash;
41use ed25519_dalek::{Signer, SigningKey};
42use chrono::Utc;
43use uuid::Uuid;
44use rand::rngs::OsRng;
45use std::path::Path;
46
47#[cfg(feature = "python")]
48use pyo3::prelude::*;
49#[cfg(feature = "python")]
50use hex;
51
52#[inline(always)]
57pub fn monotonic_raw_nanos() -> u64 {
58 #[cfg(target_os = "linux")]
59 {
60 use std::mem::MaybeUninit;
61 use libc::{clock_gettime, CLOCK_MONOTONIC_RAW, timespec};
62 let mut ts = MaybeUninit::<timespec>::uninit();
63 unsafe {
64 if clock_gettime(CLOCK_MONOTONIC_RAW, ts.as_mut_ptr()) == 0 {
65 let ts = ts.assume_init();
66 (ts.tv_sec as u64)
67 .wrapping_mul(1_000_000_000)
68 .wrapping_add(ts.tv_nsec as u64)
69 } else {
70 std::time::Instant::now().elapsed().as_nanos() as u64
71 }
72 }
73 }
74 #[cfg(not(target_os = "linux"))]
75 {
76 std::time::Instant::now().elapsed().as_nanos() as u64
77 }
78}
79
80#[derive(Debug, Serialize, Deserialize, Clone)]
82pub struct DeltaEmbedding {
83 pub vector: Vec<i8>,
85 pub confidence: f64,
87 pub delta_norm: f64,
89}
90
91#[derive(Debug, Serialize, Deserialize)]
93pub struct CoreInsightToken {
94 pub lesson: String,
96 pub confidence: f64,
98 pub delta: Option<DeltaEmbedding>,
100}
101
102#[cfg(feature = "python")]
104#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
105#[pyclass]
106#[derive(Debug, Serialize, Deserialize, Clone)]
107pub struct PPPTriplet {
108 #[pyo3(get, set)] pub provenance: String,
110 #[pyo3(get, set)] pub place: String,
112 #[pyo3(get, set)] pub purpose: String,
114}
115
116#[cfg(feature = "python")]
117#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
118#[pymethods]
119impl PPPTriplet {
120 #[new]
121 #[pyo3(signature = (provenance, place, purpose))]
122 pub fn new(provenance: String, place: String, purpose: String) -> Self {
123 Self { provenance, place, purpose }
124 }
125}
126
127#[cfg(feature = "python")]
129#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
130#[pyclass]
131#[derive(Debug, Serialize, Deserialize, Clone)]
132pub struct HumanDeltaChain {
133 #[pyo3(get, set)] pub chain_id: String,
135 #[pyo3(get, set)] pub agent_decision_ref: String,
137 #[pyo3(get, set)] pub resolved: bool,
139 #[pyo3(get, set)] pub terminal_node: String,
141}
142
143#[cfg(feature = "python")]
144#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
145#[pymethods]
146impl HumanDeltaChain {
147 #[new]
148 #[pyo3(signature = (agent_decision_ref, resolved, terminal_node, chain_id=None))]
149 pub fn new(
150 agent_decision_ref: String,
151 resolved: bool,
152 terminal_node: String,
153 chain_id: Option<String>,
154 ) -> Self {
155 Self {
156 chain_id: chain_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
157 agent_decision_ref,
158 resolved,
159 terminal_node,
160 }
161 }
162}
163
164#[cfg(feature = "python")]
166#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
167#[pyclass]
168#[derive(Serialize, Deserialize)]
169pub struct SealedRecord {
170 #[pyo3(get)] pub id: String,
172 #[pyo3(get)] pub timestamp: String,
174 #[pyo3(get)] pub monotonic_nanos: u64,
176 #[pyo3(get)] pub hash: String,
178 #[pyo3(get)] pub signature: Vec<u8>,
180 #[pyo3(get)] pub merkle_root: String,
182 #[pyo3(get)] pub coherence_score: f64,
184 #[pyo3(get)] pub reputation_scalar: f64,
186 #[pyo3(get)] pub ppp_json: String,
188 #[pyo3(get)] pub ctx_json: String,
190 #[pyo3(get)] pub prompt: String,
192 #[pyo3(get)] pub reasoning_trace_json: String,
194 #[pyo3(get)] pub output: String,
196 #[pyo3(get)] pub human_delta_chain_json: String,
198 #[pyo3(get)] pub core_insight_json: Option<String>,
200}
201
202pub fn load_or_generate_signing_key(wal_path: &str) -> SigningKey {
206 if wal_path == ":memory:" {
207 return SigningKey::generate(&mut OsRng);
208 }
209 let key_path = format!("{}.key", wal_path);
210 if Path::new(&key_path).exists() {
211 let bytes = std::fs::read(&key_path).expect("Failed to read signing key");
212 let arr: [u8; 32] = bytes.try_into().expect("Invalid key length");
213 SigningKey::from_bytes(&arr)
214 } else {
215 let key = SigningKey::generate(&mut OsRng);
216 std::fs::write(&key_path, key.to_bytes()).expect("Failed to write signing key");
217 key
218 }
219}
220
221pub struct AKIEngine {
223 pub signing_key: SigningKey,
225 pub merkle_root: Blake3Hash,
227 pub spine: VecDeque<[f32; 64]>,
229 pub reputation: f64,
231 pub coherence_threshold: f64,
233 pub wal_path: String,
235 pub max_spine_size: usize,
237}
238
239impl AKIEngine {
240 pub fn weighted_spine_average(&self) -> [f32; 64] {
242 let mut avg = [0.0f32; 64];
243 let n = self.spine.len();
244 if n == 0 { return avg; }
245 let lambda = 0.98f32;
246 let z = (1.0 - lambda.powi(n as i32)) / (1.0 - lambda);
247 for (i, emb) in self.spine.iter().enumerate() {
248 let w = lambda.powi(i as i32) / z;
249 for k in 0..64 {
250 avg[k] += w * emb[k];
251 }
252 }
253 avg
254 }
255
256 pub fn update_merkle_root(&mut self, leaf_hash: &Blake3Hash) -> Blake3Hash {
258 let combined = format!("{:?}{:?}", self.merkle_root, leaf_hash);
259 blake3::hash(combined.as_bytes())
260 }
261
262 #[cfg(feature = "python")]
266 pub fn append_to_wal(&self, record: &SealedRecord) {
267 if self.wal_path == ":memory:" { return; }
268 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&self.wal_path) {
269 let _ = writeln!(file, "{}", serde_json::to_string(record).unwrap_or_default());
270 }
271 }
272}
273
274#[cfg(feature = "python")]
276#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
277#[pyclass]
278#[pyo3(name = "AKIEngine")]
279pub struct PyAKIEngine {
280 pub inner: AKIEngine,
282}
283
284#[cfg(feature = "python")]
285#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
286#[pymethods]
287impl PyAKIEngine {
288 #[new]
289 pub fn new(wal_path: String) -> Self {
290 if wal_path != ":memory:" {
291 let _ = OpenOptions::new().create(true).append(true).open(&wal_path);
292 }
293 Self {
294 inner: AKIEngine {
295 signing_key: load_or_generate_signing_key(&wal_path),
296 merkle_root: blake3::hash(b"genesis"),
297 spine: VecDeque::with_capacity(500),
298 reputation: 0.5,
299 coherence_threshold: 0.92,
300 wal_path,
301 max_spine_size: 500,
302 },
303 }
304 }
305
306 #[pyo3(signature = (ctx, prompt, reasoning_trace, output, ppp_triplet, human_delta_chain, auto_insight=true))]
308 pub fn capture(
309 &mut self,
310 py: Python<'_>,
311 ctx: &Bound<'_, PyAny>,
312 prompt: String,
313 reasoning_trace: &Bound<'_, PyAny>,
314 output: String,
315 ppp_triplet: PyRef<PPPTriplet>,
316 human_delta_chain: PyRef<HumanDeltaChain>,
317 auto_insight: bool,
318 ) -> PyResult<SealedRecord> {
319 let json_module = py.import_bound("json")?;
320 let ctx_json: String = json_module.call_method1("dumps", (ctx,))?.extract()?;
321 let trace_json: String = json_module.call_method1("dumps", (reasoning_trace,))?.extract()?;
322
323 let ppp = ppp_triplet.clone();
324 let hdc = HumanDeltaChain {
325 chain_id: human_delta_chain.chain_id.clone(),
326 agent_decision_ref: human_delta_chain.agent_decision_ref.clone(),
327 resolved: human_delta_chain.resolved,
328 terminal_node: human_delta_chain.terminal_node.clone(),
329 };
330
331 let seed = (prompt.len() + output.len()) as f32;
332 let mut current = [0.0f32; 64];
333 for i in 0..64 {
334 current[i] = ((seed + i as f32 * 0.37) % 6.28).sin() * 0.6 + 0.4;
335 }
336
337 self.inner.spine.push_front(current);
338 if self.inner.spine.len() > self.inner.max_spine_size {
339 self.inner.spine.pop_back();
340 }
341
342 let spine_avg = self.inner.weighted_spine_average();
343
344 let mut dot = 0.0f64;
345 let mut norm_a = 0.0f64;
346 let mut norm_b = 0.0f64;
347 for i in 0..64 {
348 let a = current[i] as f64;
349 let b = spine_avg[i] as f64;
350 dot += a * b;
351 norm_a += a * a;
352 norm_b += b * b;
353 }
354 let coherence = if norm_a > 0.0 && norm_b > 0.0 {
355 (dot / (norm_a.sqrt() * norm_b.sqrt())).clamp(0.0, 1.0)
356 } else {
357 0.5
358 };
359
360 let core_insight = if auto_insight && coherence >= self.inner.coherence_threshold {
361 let mut delta_vec = vec![0i8; 64];
362 for i in 0..64 {
363 let diff = (current[i] - spine_avg[i]) * 127.0;
364 delta_vec[i] = diff.clamp(-128.0, 127.0) as i8;
365 }
366 Some(CoreInsightToken {
367 lesson: "Decision aligns well with historical pattern.".to_string(),
368 confidence: coherence,
369 delta: Some(DeltaEmbedding {
370 vector: delta_vec,
371 confidence: coherence,
372 delta_norm: 0.18,
373 }),
374 })
375 } else {
376 None
377 };
378
379 self.inner.reputation = 0.98 * self.inner.reputation + 0.02 * coherence;
380
381 let canonical = format!(
382 "{}{}{}{}{:?}{:?}",
383 ppp.provenance, ppp.place, ppp.purpose,
384 prompt, coherence, self.inner.reputation
385 );
386 let record_hash = blake3::hash(canonical.as_bytes());
387 let signature = self.inner.signing_key.sign(record_hash.as_bytes()).to_bytes().to_vec();
388 self.inner.merkle_root = self.inner.update_merkle_root(&record_hash);
389
390 let monotonic_ns = monotonic_raw_nanos();
391
392 let record = SealedRecord {
393 id: format!("aki_{}", Uuid::new_v4()),
394 timestamp: Utc::now().to_rfc3339(),
395 monotonic_nanos: monotonic_ns,
396 hash: record_hash.to_hex().to_string(),
397 signature,
398 merkle_root: self.inner.merkle_root.to_hex().to_string(),
399 coherence_score: coherence,
400 reputation_scalar: self.inner.reputation,
401 ppp_json: serde_json::to_string(&ppp).unwrap_or_default(),
402 ctx_json,
403 prompt,
404 reasoning_trace_json: trace_json,
405 output,
406 human_delta_chain_json: serde_json::to_string(&hdc).unwrap_or_default(),
407 core_insight_json: core_insight
408 .as_ref()
409 .and_then(|c| serde_json::to_string(c).ok()),
410 };
411
412 self.inner.append_to_wal(&record);
413 Ok(record)
414 }
415
416 pub fn public_key_hex(&self) -> String {
418 hex::encode(self.inner.signing_key.verifying_key().to_bytes())
419 }
420}
421
422#[cfg(feature = "python")]
424#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
425#[pymodule]
426fn agdr_aki(m: &Bound<'_, PyModule>) -> PyResult<()> {
427 m.add_class::<PyAKIEngine>()?;
428 m.add_class::<PPPTriplet>()?;
429 m.add_class::<HumanDeltaChain>()?;
430 m.add_class::<SealedRecord>()?;
431 Ok(())
432}