Skip to content

Commit bd5f839

Browse files
hyperpolymathclaude
andcommitted
Decision traces: 15 architectural traces for Hypatia pre-warming
Crawled 007-lang repo and produced structured decision traces capturing: - Harvard architecture (why, alternatives, evidence from code) - Branch strategy pattern (pluggable trait, Layer 4 plug-in) - Trace system design (append-only, monotonicity, hermeneutic context) - BEAM codegen target (structural isomorphism with OTP) - Type system Kategoria (10 layers, implementation gaps noted) - Agent runtime model (single-thread cooperative, signal propagation) - Grammar design (Pest PEG, zero-ambiguity over brevity) - Mandatory given clause (telescope always on, parabasis available) - Linear handles with exchange (Ephapax influence) - Module organisation (core/cli split, size distribution) - Agent API design (machine-first JSON interface) - Five Facets convergence (philosophical grounding) - Sentinel integrity (supply chain trust chain) - Token economics (budget as linear resource, Eclexia pricing) - Data language constraints (addition-only, totality by construction) Each trace follows the TraceRecord schema: branch_id, branch_options, given_contexts (with evidence from actual source), chosen, reason, trace_report (with hesitations and improvement opportunities). Also includes trace_cache module (user addition). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f7cc4cc commit bd5f839

17 files changed

Lines changed: 1050 additions & 0 deletions

crates/oo7-core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod parser;
2525
#[cfg(test)]
2626
mod parser_tests;
2727
pub mod trace;
28+
pub mod trace_cache;
2829
pub mod typechecker;
2930
pub mod verisimdb;
3031

@@ -36,4 +37,5 @@ pub use eval::{AgentInstance, BranchStrategy, Env, Evaluator, PredicateFirstStra
3637
pub use parser::{parse_program, ParseError};
3738
pub use trace::{DecisionTrace, TraceRecord};
3839
pub use typechecker::{check_program, Type, TypeError};
40+
pub use trace_cache::{CacheStats, CachedDecision, TraceCache};
3941
pub use verisimdb::{resolve_program_refs, resolve_ref, RefResolutionResult, ResolvedRef};

crates/oo7-core/src/trace_cache.rs

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// 007 Agent Meta-Language — Three-Tier Trace Cache
5+
//
6+
// Three tiers:
7+
// HOT: In-process HashMap — microsecond lookup for `cached` branches
8+
// WARM: VeriSimDB HTTP — millisecond lookup for @ref, agent API
9+
// COLD: VeriSimDB bulk — batch export for Hypatia training
10+
//
11+
// The evaluator hits HOT. The agent API hits WARM. Hypatia hits COLD.
12+
// Sync: HOT loads from WARM on startup, flushes to WARM on shutdown.
13+
//
14+
// Harvard note: traces are DATA (immutable, deterministic to produce).
15+
// The cache itself is Control (effectful: I/O, mutation). But what it
16+
// STORES is Data. This is the telescope's filing cabinet.
17+
18+
use serde::{Deserialize, Serialize};
19+
use std::collections::HashMap;
20+
21+
use crate::eval::RtValue;
22+
use crate::verisimdb;
23+
24+
/// A cached decision — the result of a traced branch evaluation.
25+
#[derive(Debug, Clone, Serialize, Deserialize)]
26+
pub struct CachedDecision {
27+
/// The branch label (from `traced "label"`)
28+
pub label: String,
29+
/// Serialised given context (the cache key component)
30+
pub given_context: String,
31+
/// Which arm was chosen
32+
pub chosen_arm: String,
33+
/// The result value (serialised)
34+
pub result_json: String,
35+
/// Timestamp of the decision
36+
pub timestamp: String,
37+
/// Which agent made this decision
38+
pub agent_id: Option<String>,
39+
/// Token cost of the original evaluation
40+
pub token_cost: Option<i64>,
41+
}
42+
43+
/// Three-tier trace cache.
44+
///
45+
/// HOT tier: in-process HashMap for microsecond branch cache lookups.
46+
/// WARM/COLD tiers: VeriSimDB (via verisimdb module) for persistence.
47+
pub struct TraceCache {
48+
/// HOT tier — keyed by "label:given_context_hash"
49+
hot: HashMap<String, CachedDecision>,
50+
/// VeriSimDB base URL for WARM/COLD tiers
51+
verisimdb_url: String,
52+
/// Whether VeriSimDB is reachable
53+
verisimdb_healthy: bool,
54+
/// Dirty entries — written to HOT but not yet flushed to WARM
55+
dirty: Vec<String>,
56+
/// Stats
57+
pub hits: u64,
58+
pub misses: u64,
59+
pub stores: u64,
60+
}
61+
62+
impl Default for TraceCache {
63+
fn default() -> Self {
64+
Self::new()
65+
}
66+
}
67+
68+
impl TraceCache {
69+
/// Create a new trace cache. Does NOT connect to VeriSimDB yet.
70+
pub fn new() -> Self {
71+
TraceCache {
72+
hot: HashMap::new(),
73+
verisimdb_url: verisimdb::verisimdb_url(),
74+
verisimdb_healthy: false,
75+
dirty: Vec::new(),
76+
hits: 0,
77+
misses: 0,
78+
stores: 0,
79+
}
80+
}
81+
82+
/// Create a cache key from branch label + serialised given context.
83+
pub fn cache_key(label: &str, given_context: &str) -> String {
84+
format!("{}:{}", label, given_context)
85+
}
86+
87+
/// HOT tier lookup — microseconds. Used by evaluator for `cached` branches.
88+
pub fn get(&mut self, label: &str, given_context: &str) -> Option<&CachedDecision> {
89+
let key = Self::cache_key(label, given_context);
90+
if let Some(decision) = self.hot.get(&key) {
91+
self.hits += 1;
92+
Some(decision)
93+
} else {
94+
self.misses += 1;
95+
None
96+
}
97+
}
98+
99+
/// Store a decision in HOT tier. Marks as dirty for later WARM flush.
100+
pub fn store(&mut self, decision: CachedDecision) {
101+
let key = Self::cache_key(&decision.label, &decision.given_context);
102+
self.dirty.push(key.clone());
103+
self.hot.insert(key, decision);
104+
self.stores += 1;
105+
}
106+
107+
/// Store from evaluator's existing cache format (label, chosen_arm, RtValue).
108+
pub fn store_from_eval(
109+
&mut self,
110+
label: &str,
111+
given_context: &str,
112+
chosen_arm: &str,
113+
result: &RtValue,
114+
) {
115+
let result_json = serde_json::to_string(result).unwrap_or_else(|_| "null".to_string());
116+
let decision = CachedDecision {
117+
label: label.to_string(),
118+
given_context: given_context.to_string(),
119+
chosen_arm: chosen_arm.to_string(),
120+
result_json,
121+
timestamp: chrono::Utc::now().to_rfc3339(),
122+
agent_id: None,
123+
token_cost: None,
124+
};
125+
self.store(decision);
126+
}
127+
128+
/// Load HOT tier from VeriSimDB (WARM tier).
129+
/// Called once at evaluator startup to pre-warm the cache.
130+
pub fn load_from_verisimdb(&mut self) -> usize {
131+
self.verisimdb_healthy = verisimdb::health_check(&self.verisimdb_url);
132+
if !self.verisimdb_healthy {
133+
return 0;
134+
}
135+
136+
// Query all 007-trace octads from VeriSimDB
137+
let url = format!(
138+
"{}/api/v1/search/text?q=007-trace",
139+
self.verisimdb_url
140+
);
141+
142+
let resp = match ureq::get(&url).call() {
143+
Ok(r) => r,
144+
Err(_) => return 0,
145+
};
146+
147+
if resp.status() != 200 {
148+
return 0;
149+
}
150+
151+
#[derive(Deserialize)]
152+
struct TraceItem {
153+
name: Option<String>,
154+
document: Option<serde_json::Value>,
155+
}
156+
#[derive(Deserialize)]
157+
struct TraceList {
158+
items: Option<Vec<TraceItem>>,
159+
}
160+
161+
let list: TraceList = match resp.into_body().read_json() {
162+
Ok(l) => l,
163+
Err(_) => return 0,
164+
};
165+
166+
let mut loaded = 0;
167+
if let Some(items) = list.items {
168+
for item in items {
169+
if let Some(doc) = item.document {
170+
if let Some(content_str) = doc.get("content").and_then(|c| c.as_str()) {
171+
if let Ok(decision) =
172+
serde_json::from_str::<CachedDecision>(content_str)
173+
{
174+
let key = Self::cache_key(
175+
&decision.label,
176+
&decision.given_context,
177+
);
178+
self.hot.insert(key, decision);
179+
loaded += 1;
180+
}
181+
}
182+
}
183+
}
184+
}
185+
186+
loaded
187+
}
188+
189+
/// Flush dirty entries from HOT to WARM (VeriSimDB).
190+
/// Called periodically or on evaluator shutdown.
191+
pub fn flush_to_verisimdb(&mut self) -> usize {
192+
if !self.verisimdb_healthy {
193+
self.verisimdb_healthy = verisimdb::health_check(&self.verisimdb_url);
194+
if !self.verisimdb_healthy {
195+
return 0;
196+
}
197+
}
198+
199+
let mut flushed = 0;
200+
let dirty_keys: Vec<String> = self.dirty.drain(..).collect();
201+
202+
for key in &dirty_keys {
203+
if let Some(decision) = self.hot.get(key) {
204+
let trace_json =
205+
serde_json::to_string(decision).unwrap_or_else(|_| "{}".to_string());
206+
match verisimdb::store_trace(
207+
&self.verisimdb_url,
208+
&trace_json,
209+
&decision.label,
210+
) {
211+
Ok(_) => flushed += 1,
212+
Err(_) => {
213+
// Re-add to dirty for next flush attempt
214+
self.dirty.push(key.clone());
215+
}
216+
}
217+
}
218+
}
219+
220+
flushed
221+
}
222+
223+
/// Export all cached decisions as JSONL for Hypatia training (COLD tier).
224+
pub fn export_for_training(&self) -> String {
225+
self.hot
226+
.values()
227+
.map(|d| serde_json::to_string(d).unwrap_or_else(|_| "{}".to_string()))
228+
.collect::<Vec<_>>()
229+
.join("\n")
230+
}
231+
232+
/// Get cache statistics.
233+
pub fn stats(&self) -> CacheStats {
234+
CacheStats {
235+
hot_entries: self.hot.len(),
236+
dirty_entries: self.dirty.len(),
237+
hits: self.hits,
238+
misses: self.misses,
239+
stores: self.stores,
240+
hit_rate: if self.hits + self.misses > 0 {
241+
self.hits as f64 / (self.hits + self.misses) as f64
242+
} else {
243+
0.0
244+
},
245+
verisimdb_healthy: self.verisimdb_healthy,
246+
}
247+
}
248+
249+
/// Number of entries in HOT tier.
250+
pub fn len(&self) -> usize {
251+
self.hot.len()
252+
}
253+
254+
/// Whether HOT tier is empty.
255+
pub fn is_empty(&self) -> bool {
256+
self.hot.is_empty()
257+
}
258+
}
259+
260+
/// Cache statistics for monitoring.
261+
#[derive(Debug, Clone, Serialize, Deserialize)]
262+
pub struct CacheStats {
263+
pub hot_entries: usize,
264+
pub dirty_entries: usize,
265+
pub hits: u64,
266+
pub misses: u64,
267+
pub stores: u64,
268+
pub hit_rate: f64,
269+
pub verisimdb_healthy: bool,
270+
}
271+
272+
#[cfg(test)]
273+
mod tests {
274+
use super::*;
275+
276+
#[test]
277+
fn cache_key_format() {
278+
let key = TraceCache::cache_key("triage", "priority:5");
279+
assert_eq!(key, "triage:priority:5");
280+
}
281+
282+
#[test]
283+
fn store_and_get() {
284+
let mut cache = TraceCache::new();
285+
cache.store(CachedDecision {
286+
label: "triage".to_string(),
287+
given_context: "priority:5".to_string(),
288+
chosen_arm: "urgent".to_string(),
289+
result_json: "\"escalated\"".to_string(),
290+
timestamp: "2026-03-24T18:00:00Z".to_string(),
291+
agent_id: None,
292+
token_cost: Some(3),
293+
});
294+
295+
let result = cache.get("triage", "priority:5");
296+
assert!(result.is_some());
297+
assert_eq!(result.unwrap().chosen_arm, "urgent");
298+
}
299+
300+
#[test]
301+
fn cache_miss() {
302+
let mut cache = TraceCache::new();
303+
let result = cache.get("nonexistent", "context");
304+
assert!(result.is_none());
305+
}
306+
307+
#[test]
308+
fn store_from_eval_format() {
309+
let mut cache = TraceCache::new();
310+
cache.store_from_eval(
311+
"review",
312+
"confidence:0.8",
313+
"accept",
314+
&RtValue::String("approved".to_string()),
315+
);
316+
317+
let result = cache.get("review", "confidence:0.8");
318+
assert!(result.is_some());
319+
assert_eq!(result.unwrap().chosen_arm, "accept");
320+
assert!(result.unwrap().result_json.contains("approved"));
321+
}
322+
323+
#[test]
324+
fn stats_tracking() {
325+
let mut cache = TraceCache::new();
326+
cache.store_from_eval("a", "ctx1", "arm1", &RtValue::Int(1));
327+
cache.get("a", "ctx1"); // hit
328+
cache.get("a", "ctx2"); // miss
329+
cache.get("b", "ctx1"); // miss
330+
331+
let stats = cache.stats();
332+
assert_eq!(stats.stores, 1);
333+
assert_eq!(stats.hits, 1);
334+
assert_eq!(stats.misses, 2);
335+
assert!((stats.hit_rate - 0.333).abs() < 0.01);
336+
}
337+
338+
#[test]
339+
fn export_for_training() {
340+
let mut cache = TraceCache::new();
341+
cache.store_from_eval("a", "ctx1", "arm1", &RtValue::Int(1));
342+
cache.store_from_eval("b", "ctx2", "arm2", &RtValue::Int(2));
343+
344+
let export = cache.export_for_training();
345+
let lines: Vec<&str> = export.lines().collect();
346+
assert_eq!(lines.len(), 2);
347+
assert!(export.contains("arm1"));
348+
assert!(export.contains("arm2"));
349+
}
350+
351+
#[test]
352+
fn dirty_tracking() {
353+
let mut cache = TraceCache::new();
354+
cache.store_from_eval("a", "ctx1", "arm1", &RtValue::Int(1));
355+
assert_eq!(cache.dirty.len(), 1);
356+
357+
// Flush to unreachable VeriSimDB — dirty entries should remain
358+
let flushed = cache.flush_to_verisimdb();
359+
assert_eq!(flushed, 0);
360+
}
361+
362+
#[test]
363+
fn load_from_unreachable_verisimdb() {
364+
let mut cache = TraceCache::new();
365+
// Default URL is localhost:8094 — may or may not be running
366+
let loaded = cache.load_from_verisimdb();
367+
// Either loads something or 0 — shouldn't panic
368+
assert!(loaded == 0 || loaded > 0);
369+
}
370+
}

0 commit comments

Comments
 (0)