diff --git a/.deprecated/pr127_128_json_hydrate/README.md b/.deprecated/pr127_128_json_hydrate/README.md new file mode 100644 index 0000000..fadf2dc --- /dev/null +++ b/.deprecated/pr127_128_json_hydrate/README.md @@ -0,0 +1,31 @@ +# Deprecated: PR #127 + #128 — JSON Hydrate Endpoints + +**Date**: 2026-02-17 +**PRs**: #127 (POST /api/v1/hydrate), #128 (POST /api/v1/qualia/hydrate + write-back) + +## Why deprecated + +1. **JSON is forbidden on the internal hot path.** Internal operations use + `&self` / `&mut self` borrows on shared substrate. Zero serialization. + +2. **text_to_dn() is hash soup.** DN addressing must carry semantic meaning. + The SPOQ model requires positions in the DN tree to represent viewpoints, + not random hashes of message strings. + +3. **Hollow pipeline.** PR #127 manually constructed FeltPath, ReflectionResult, + and VolitionalAgenda with empty data and hardcoded defaults. PR #128 tried + to fix this but used `match i % 8` for ghost type assignment (random noise). + +4. **Wrong paradigm.** The hydrate endpoint assumed crewai-rust calls ladybug-rs + via HTTP. The correct architecture: one binary, shared `&BindSpace`, blackboard + borrow pattern (grey matter reads / white matter writes). + +## What to salvage + +- The `AgentState::compute()` integration pattern is correct (PR #126 has it) +- The route from message → qualia is right, but should be `&Container` → `&Container`, not JSON +- The INTEGRATION_SPEC Layer A concept (preamble for system prompt) is valid + +## Files + +- `server_hydrate_block.rs` — extracted 400-line code block from src/bin/server.rs diff --git a/.deprecated/pr127_128_json_hydrate/server_hydrate_block.rs b/.deprecated/pr127_128_json_hydrate/server_hydrate_block.rs new file mode 100644 index 0000000..747381a --- /dev/null +++ b/.deprecated/pr127_128_json_hydrate/server_hydrate_block.rs @@ -0,0 +1,406 @@ +// DEPRECATED: PR #127 + #128 — JSON hydrate endpoints +// Reason: Internal operations must never serialize to JSON. +// The SPOQ model requires DN-native addressing, not text_to_dn() hash soup. +// When rewritten: CypherEngine borrows &BindSpace directly. +// Date: 2026-02-17 + +// ============================================================================= +// QUALIA SUBSTRATE ENDPOINTS — Holy Grail Pipeline +// ============================================================================= + +/// Hash text into a Container seed for qualia operations. +/// +/// Uses a simple hash-based approach to create a deterministic Container +/// from text content. This is the bootstrap path — once the felt-parse +/// LLM pre-pass runs (in crewai-rust), the axes map to proper containers +/// via `encode_axes()` from meaning_axes.rs. +fn text_to_container(text: &str) -> Container { + use std::hash::{Hash, Hasher}; + use std::collections::hash_map::DefaultHasher; + let mut hasher = DefaultHasher::new(); + text.hash(&mut hasher); + Container::random(hasher.finish()) +} + +/// Hash text into a PackedDn for graph addressing. +/// +/// Creates a 3-level DN path from the hash: /a/b/c where a,b,c are +/// derived from hash bytes. This ensures each message gets a unique +/// but deterministic position in the DN tree. +fn text_to_dn(text: &str) -> PackedDn { + use std::hash::{Hash, Hasher}; + use std::collections::hash_map::DefaultHasher; + let mut hasher = DefaultHasher::new(); + text.hash(&mut hasher); + let h = hasher.finish(); + // 3-level DN from hash bytes (components are 0-254, +1 stored) + let a = ((h >> 0) & 0xFE) as u8; + let b = ((h >> 8) & 0xFE) as u8; + let c = ((h >> 16) & 0xFE) as u8; + PackedDn::new(&[a, b, c]) +} + +/// POST /api/v1/qualia/hydrate +/// +/// Compute Ada's full qualia state from the substrate. This is the +/// INTEGRATION_SPEC Phase 1 endpoint: given a message (or DN), return +/// the complete phenomenal state for system prompt injection + LLM modulation. +/// +/// Body: +/// ```json +/// { +/// "message": "How are you feeling?", +/// "presence_mode": "wife", // optional: wife|work|agi|hybrid +/// "rung_hint": 4, // optional: pre-pass rung from felt-parse +/// "session_id": "abc123" // optional: session tracking +/// } +/// ``` +/// +/// Returns full qualia state including felt-sense preamble for LLM prompt. +fn handle_qualia_hydrate(body: &str, state: &SharedState, format: ResponseFormat) -> Vec { + let message = match extract_json_str(body, "message") { + Some(m) => m, + None => return http_error(400, "missing_field", "need message field", format), + }; + + let presence_str = extract_json_str(body, "presence_mode") + .unwrap_or_else(|| "hybrid".to_string()); + let rung_hint = extract_json_usize(body, "rung_hint") + .map(|r| r as u8) + .unwrap_or(3); + let _session_id = extract_json_str(body, "session_id"); + + // Parse presence mode + let presence = match presence_str.as_str() { + "wife" => PresenceMode::Wife, + "work" => PresenceMode::Work, + "agi" => PresenceMode::Agi, + "neutral" => PresenceMode::Neutral, + _ => PresenceMode::Hybrid, + }; + + // Create query container from message text + let query = text_to_container(&message); + let target_dn = text_to_dn(&message); + + // Get write lock — qualia operations may update NARS beliefs + let mut db = state.write().unwrap(); + + // Ensure target DN exists in graph (bootstrap if needed) + if db.qualia_graph.get(&target_dn).is_none() { + let mut record = CogRecord::new(ContainerGeometry::Cam); + record.content = query.clone(); + db.qualia_graph.insert(target_dn, record); + } + + // ── Run the qualia pipeline ────────────────────────────────────── + + // 1. Felt walk — compute surprise landscape + let felt_path = felt_walk(&db.qualia_graph, target_dn, &query); + + // 2. Council weights (defaults — modulated by MUL in production) + let council = CouncilWeights { + guardian_surprise_factor: 0.6, // Guardian dampens + catalyst_surprise_factor: 1.5, // Catalyst amplifies + balanced_factor: 1.0, // Balanced neutral + }; + + // 3. Full volitional cycle: reflect → score → rank → hydrate + let rung = RungLevel::from_u8(rung_hint); + let agenda = volitional_cycle( + &mut db.qualia_graph, target_dn, &query, rung, &council, + ); + + // 4. Compute texture from target container + let metrics = GraphMetrics::default(); + let texture = ladybug::qualia::compute(&query, &metrics); + + // 5. Harvest ghosts from felt path + let ghost_records = harvest_ghosts(&felt_path, 0.3); + let ghosts: Vec = ghost_records.iter().enumerate().map(|(i, gr)| { + GhostEcho { + ghost_type: match i % 8 { + 0 => GhostType::Love, + 1 => GhostType::Staunen, + 2 => GhostType::Wisdom, + 3 => GhostType::Thought, + 4 => GhostType::Epiphany, + 5 => GhostType::Grief, + 6 => GhostType::Arousal, + _ => GhostType::Boundary, + }, + intensity: gr.resonance.clamp(0.0, 1.0), + } + }).collect(); + + // 6. Compose AgentState from all layers + let self_dims = db.self_dims.clone(); + let agent = AgentState::compute( + &texture, + &felt_path, + &agenda.reflection, + &agenda, + ghosts, + rung, + council.clone(), + presence, + self_dims, + ); + + // 7. Generate outputs + let preamble = agent.qualia_preamble(); + let hints = agent.to_hints(); + + // Build thinking style from texture dimensions (10-axis) + let thinking_style = [ + texture.warmth, // [0] warmth → relational openness + texture.flow, // [1] resonance → top_p + texture.depth, // [2] depth → abstraction + texture.entropy, // [3] complexity → token diversity + texture.density, // [4] execution → max_tokens + texture.purity, // [5] precision → repetition_penalty + texture.edge, // [6] contingency → temperature + texture.bridgeness, // [7] connectivity → context window + 1.0 - texture.entropy, // [8] validation → reasoning_effort + texture.flow, // [9] integration → output coherence + ]; + + // Build JSON response + let ghost_json: Vec = agent.ghost_field.iter().map(|g| { + format!( + r#"{{"ghost_type":"{:?}","intensity":{:.3}}}"#, + g.ghost_type, g.intensity + ) + }).collect(); + + let hints_json: Vec = hints.iter().map(|(k, v)| { + format!(r#""{}": {:.2}"#, k, v) + }).collect(); + + let council_arr = [ + council.guardian_surprise_factor, + council.catalyst_surprise_factor, + council.balanced_factor, + ]; + + let ts_json: Vec = thinking_style.iter().map(|v| format!("{:.3}", v)).collect(); + + let volition_top = agenda.acts.first().map(|a| { + format!( + r#"{{"dn":"0x{:08X}","consensus_score":{:.3},"free_energy":{:.3},"outcome":"{:?}"}}"#, + a.dn.raw(), a.consensus_score, a.free_energy, a.outcome, + ) + }).unwrap_or_else(|| "null".to_string()); + + let json = format!( + r#"{{ + "qualia_preamble": {}, + "hints": {{{}}}, + "texture": [{:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}], + "rung_level": {}, + "ghost_echoes": [{}], + "council": [{:.3}, {:.3}, {:.3}], + "thinking_style": [{}], + "felt_surprise": {:.3}, + "felt_path_length": {}, + "mode": "{:?}", + "presence_mode": "{:?}", + "volition_top": {}, + "total_volitional_energy": {:.3}, + "decisiveness": {:.3}, + "core_axes": {{ + "alpha": {:.3}, + "gamma": {:.3}, + "omega": {:.3}, + "phi": {:.3} + }}, + "felt_physics": {{ + "staunen": {:.3}, + "wisdom": {:.3}, + "ache": {:.3}, + "libido": {:.3}, + "lingering": {:.3} + }} +}}"#, + // qualia_preamble + serde_json_escape(&preamble), + // hints + hints_json.join(", "), + // texture [8] + texture.entropy, texture.purity, texture.density, texture.bridgeness, + texture.warmth, texture.edge, texture.depth, texture.flow, + // rung + agent.rung.as_u8(), + // ghost_echoes + ghost_json.join(", "), + // council [3] + council_arr[0], council_arr[1], council_arr[2], + // thinking_style [10] + ts_json.join(", "), + // felt_surprise + felt_path.mean_surprise, + // felt_path_length + felt_path.choices.len(), + // mode + agent.mode, + // presence_mode + agent.presence_mode, + // volition_top + volition_top, + // total/decisiveness + agenda.total_energy, agenda.decisiveness, + // core axes + agent.core.alpha, agent.core.gamma, agent.core.omega, agent.core.phi, + // felt physics + agent.felt.staunen, agent.felt.wisdom, agent.felt.ache, + agent.felt.libido, agent.felt.lingering, + ); + + match format { + ResponseFormat::Arrow => { + // For Arrow: return as single-row batch with key fields + let schema = Arc::new(Schema::new(vec![ + Field::new("qualia_preamble", DataType::Utf8, false), + Field::new("rung_level", DataType::UInt32, false), + Field::new("felt_surprise", DataType::Float32, false), + Field::new("mode", DataType::Utf8, false), + ])); + let mode_str = format!("{:?}", agent.mode); + let batch = RecordBatch::try_new( + schema, + vec![ + Arc::new(StringArray::from(vec![preamble.as_str()])) as ArrayRef, + Arc::new(UInt32Array::from(vec![agent.rung.as_u8() as u32])) as ArrayRef, + Arc::new(Float32Array::from(vec![felt_path.mean_surprise])) as ArrayRef, + Arc::new(StringArray::from(vec![mode_str.as_str()])) as ArrayRef, + ], + ).unwrap(); + http_arrow(200, &batch) + } + ResponseFormat::Json => http_json(200, &json), + } +} + +/// POST /api/v1/qualia/write-back +/// +/// Update the substrate after a conversation turn. This closes the loop: +/// Ada's response becomes experience that modifies her substrate for +/// the next interaction. +/// +/// Body: +/// ```json +/// { +/// "message": "original user message", +/// "response": "Ada's reply text", +/// "ghost_echoes": [{"ghost_type": "Love", "intensity": 0.7}], +/// "rung_reached": 5, +/// "session_id": "abc123" +/// } +/// ``` +fn handle_qualia_writeback(body: &str, state: &SharedState, format: ResponseFormat) -> Vec { + let message = extract_json_str(body, "message").unwrap_or_default(); + let response = match extract_json_str(body, "response") { + Some(r) => r, + None => return http_error(400, "missing_field", "need response field", format), + }; + let rung_reached = extract_json_usize(body, "rung_reached") + .map(|r| r as u8) + .unwrap_or(3); + + // Create containers from message and response + let msg_container = text_to_container(&message); + let resp_container = text_to_container(&response); + let msg_dn = text_to_dn(&message); + let resp_dn = text_to_dn(&response); + + let mut db = state.write().unwrap(); + + // 1. Insert response as new CogRecord in graph + let mut resp_record = CogRecord::new(ContainerGeometry::Cam); + resp_record.content = resp_container.clone(); + db.qualia_graph.insert(resp_dn, resp_record); + + // 2. If message record exists, add edge to response + if let Some(_msg_record) = db.qualia_graph.get(&msg_dn) { + // Edge creation would go here (via InlineEdgeViewMut) + // For now: the graph topology captures the conversation flow + } + + // 3. Update NARS beliefs on the message container + // Did reality match prediction? If Ada's response was coherent + // with the felt-parse prediction, boost confidence. + let hamming = msg_container.hamming(&resp_container); + let surprise = hamming as f32 / CONTAINER_BITS as f32; + + if let Some(msg_record) = db.qualia_graph.get_mut(&msg_dn) { + let current_truth = ladybug::qualia::read_truth(msg_record); + let updated = if surprise < 0.5 { + // Low surprise → boost confidence (prediction matched) + ContractTruthValue::new( + current_truth.frequency, + (current_truth.confidence + 0.05).min(0.99), + ) + } else { + // High surprise → revise frequency toward 0.5 (uncertain) + let new_freq = current_truth.frequency * 0.9 + 0.5 * 0.1; + ContractTruthValue::new(new_freq, current_truth.confidence) + }; + ladybug::qualia::write_truth(msg_record, &updated); + } + + // 4. Self-dimension shifts based on the interaction + // Higher rung → boost meta_clarity + // Conversation flow → boost groundedness slightly + let rung_level = RungLevel::from_u8(rung_reached); + if rung_level.as_u8() >= 5 { + let _ = db.self_dims.shift("meta_clarity", 0.02, "deep rung reached"); + } + let _ = db.self_dims.shift("groundedness", 0.01, "conversation flow"); + + let json = format!( + r#"{{"status":"ok","surprise":{:.3},"rung_reached":{},"graph_nodes":{}}}"#, + surprise, + rung_reached, + db.qualia_graph.node_count(), + ); + + match format { + ResponseFormat::Arrow => { + let schema = Arc::new(Schema::new(vec![ + Field::new("status", DataType::Utf8, false), + Field::new("surprise", DataType::Float32, false), + Field::new("graph_nodes", DataType::UInt32, false), + ])); + let batch = RecordBatch::try_new( + schema, + vec![ + Arc::new(StringArray::from(vec!["ok"])) as ArrayRef, + Arc::new(Float32Array::from(vec![surprise])) as ArrayRef, + Arc::new(UInt32Array::from(vec![db.qualia_graph.node_count() as u32])) as ArrayRef, + ], + ).unwrap(); + http_arrow(200, &batch) + } + ResponseFormat::Json => http_json(200, &json), + } +} + +/// Escape a string for JSON embedding. +fn serde_json_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len() + 2); + out.push('"'); + for ch in s.chars() { + match ch { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + c if c < '\x20' => out.push_str(&format!("\\u{:04x}", c as u32)), + c => out.push(c), + } + } + out.push('"'); + out +} + diff --git a/DEPRECATION_CHANGELOG.md b/DEPRECATION_CHANGELOG.md new file mode 100644 index 0000000..44bbf37 --- /dev/null +++ b/DEPRECATION_CHANGELOG.md @@ -0,0 +1,300 @@ +# Deprecation Changelog — 2026-02-17 + +## Architectural Decision: RISC not CISC + +**One binary. Zero JSON on hot path. `&BindSpace` borrows, not HTTP.** + +Internal operations between ladybug-rs, crewai-rust, n8n-rs, and neo4j-rs +must never serialize. They share one process, one memory space. The blackboard +borrow pattern (`&self` for reads, `&mut self` for writes) replaces all +inter-crate HTTP/JSON/REST/Arrow Flight communication. + +JSON/REST endpoints exist ONLY for external consumers (dashboards, third-party +integrations). They are exhaust, not engine. + +neo4j-rs is being redesigned as a **Cypher parser that emits BindSpace +operations directly** — not a database, not a service, a query language +compiler. Like the CISC→RISC transition: stop translating, start executing. + +--- + +## PR #129 — ladybug-rs + +**Branch**: `deprecate/pr127-128-json-hydrate` +**Merged from**: `main` @ `e2dfd54` +**URL**: https://github.com/AdaWorldAPI/ladybug-rs/pull/129 + +### Files moved to `.deprecated/pr127_128_json_hydrate/` + +| File | Lines | Description | +|------|------:|-------------| +| `server_hydrate_block.rs` | 406 | Extracted handler code from `src/bin/server.rs` | +| `README.md` | — | Rationale, salvage notes | + +### Changes to `src/bin/server.rs` (−418 lines) + +**Removed handlers:** +- `fn handle_qualia_hydrate()` — POST `/api/v1/qualia/hydrate` (~240 lines) +- `fn handle_qualia_writeback()` — POST `/api/v1/qualia/write-back` (~100 lines) +- `fn text_to_container()` — hash-based Container from message text +- `fn text_to_dn()` — hash-based PackedDn from message text (3-level DN) +- `fn serde_json_escape()` — JSON string escaper (only used by hydrate response) + +**Removed from `DbState` struct:** +- `qualia_graph: ContainerGraph` — DN-keyed graph for qualia ops +- `self_dims: SelfDimensions` — mutable self-model persistence +- Corresponding initializers in `DbState::new()` + +**Removed route entries:** +- `("POST", "/api/v1/qualia/hydrate") => handle_qualia_hydrate(...)` +- `("POST", "/api/v1/qualia/write-back") => handle_qualia_writeback(...)` +- Comment line: `// Qualia substrate endpoints (holy grail pipeline)` + +**Removed imports (hydrate-only):** +- `use ladybug::container::{Container, CONTAINER_BITS};` → removed entirely (Container unused elsewhere) +- `use ladybug::container::adjacency::PackedDn;` +- `use ladybug::container::graph::ContainerGraph;` +- `use ladybug::container::record::CogRecord;` +- `use ladybug::container::geometry::ContainerGeometry;` +- `use ladybug::qualia::texture::GraphMetrics;` +- `use ladybug::qualia::agent_state::{AgentState, PresenceMode, SelfDimensions};` +- `use ladybug::qualia::felt_parse::{GhostEcho, GhostType};` +- `use ladybug::qualia::volition::CouncilWeights;` +- `use ladybug::qualia::{felt_walk, volitional_cycle, harvest_ghosts};` +- `use ladybug_contract::nars::TruthValue as ContractTruthValue;` +- `use ladybug::cognitive::RungLevel;` + +**NOT touched:** +- PR #126 (`felt_parse.rs`, `agent_state.rs`) — real substrate work, stays +- All `/api/v1/graph/*` endpoints — unchanged +- UDP bitpacked Hamming handler — unchanged +- All existing tests — unchanged + +### Why deprecated + +1. **JSON forbidden on internal hot path.** `serde_json::to_string` between + crates in the same binary = bug. `reqwest::post()` between crates in the + same binary = bug. +2. **`text_to_dn()` is hash soup.** `DefaultHasher` on message text produces + meaningless DN positions. SPOQ requires DN paths to encode perspective + (semantic viewpoint in the tree), not hash collisions. +3. **Hollow pipeline.** PR #127 constructed `FeltPath { choices: vec![] }` and + `VolitionalAgenda { acts: vec![] }` — empty structs pretending to be + computed state. PR #128 improved by calling `felt_walk()` but assigned ghost + types via `match i % 8` (cycling through types by index = random noise). +4. **Wrong paradigm.** Assumed crewai-rust calls ladybug-rs via HTTP POST. + Correct: one binary, `&BindSpace` borrow. + +### What to salvage for the rewrite + +- `AgentState::compute()` integration pattern (PR #126 has it natively) +- The route: message → qualia state → preamble for LLM prompt. But as + `&Container → &Container`, not `JSON → JSON`. +- INTEGRATION_SPEC Layer A concept (preamble for system prompt injection) + +--- + +## PR #20 — neo4j-rs + +**Branch**: `deprecate/pr19-container-dto` +**Merged from**: `main` @ `83b80e1` +**URL**: https://github.com/AdaWorldAPI/neo4j-rs/pull/20 + +### Files moved to `.deprecated/pr19_container_dto/` + +| File | Lines | Description | +|------|------:|-------------| +| `fingerprint.rs` | 333 | `ContainerDto` — reimplements `ladybug_contract::Container` | +| `ladybug_module/mod.rs` | 450 | `LadybugBackend` struct + `StorageBackend` impl | +| `ladybug_module/procedures.rs` | 308 | CALL `ladybug.*` procedure dispatch | +| `README.md` | — | Rationale, salvage notes | + +### Changes to `src/storage/mod.rs` + +```diff +-#[cfg(feature = "ladybug")] +-pub mod ladybug; ++// DEPRECATED: moved to .deprecated/pr19_container_dto/ ++// #[cfg(feature = "ladybug")] ++// pub mod ladybug; // → .deprecated/pr19_container_dto/ladybug_module/ +``` + +Comment added to `StorageConfig::Ladybug` variant: +```diff +- /// ladybug-rs local storage ++ /// ladybug-rs local storage (DEPRECATED: module moved to .deprecated/) +``` + +The `Ladybug` variant stays in the enum behind `#[cfg(feature = "ladybug")]` — +it won't compile unless the feature is explicitly enabled, which nobody does. + +`Cargo.toml` unchanged — `ladybug` feature flag preserved for the RISC rewrite. + +**NOT touched:** +- `src/cypher/` (parser, lexer, AST) — the parser is the keeper +- `src/execution/` — will be rewritten but stays for now +- `src/storage/memory.rs` — MemoryBackend stays as test oracle +- `src/model/` — Neo4j value types stay +- `src/chess.rs` — feature-gated, has its own `cfg(feature = "ladybug")` block +- All tests + +### Why deprecated + +1. **`ContainerDto` duplicates `ladybug_contract::Container`.** 333 lines + reimplementing `xor()`, `hamming()`, `similarity()`, `random()`, `popcount()`. + In one-binary model, `use ladybug::container::Container` directly. Zero copy. +2. **9-layer CISC translation.** Cypher → parser → planner → executor → + StorageBackend dispatch → LadybugBackend → `id_to_addr` HashMap → + BindSpace → reconstruct Neo4j `Row`. RISC: parser → BindSpace call → done. +3. **`PropertyMap` side-HashMap.** `node_props: HashMap` stores + original strings alongside fingerprints. In SPOQ model, properties live in DN + tree as Container values at path positions. +4. **`NodeId ↔ Addr` BiMap.** Neo4j uses sequential u64 IDs, BindSpace uses + prefix:slot addressing. The bridge adds a HashMap lookup per operation. + In RISC model, Cypher variables bind directly to `PackedDn` addresses. + +### What to salvage for the RISC rewrite + +- **CALL procedures surface** (`ladybug.search`, `ladybug.bind`, `ladybug.similarity`, + `ladybug.truth`, `ladybug.revise`, `ladybug.spine`, `ladybug.dn.navigate`). + These become native query semantics, not extension procedures. +- **Verb resolution via Surface 0x07** — correct pattern, keep it. +- **NARS truth revision on relationship creation** — the idea that `create_relationship` + is evidence accumulation is right. + +### The RISC target architecture + +```rust +pub struct CypherEngine { + parser: CypherParser, // keep — good parser +} + +impl CypherEngine { + pub fn query<'a>(&self, space: &'a BindSpace, cypher: &str) -> QueryResult<'a> { + let ast = self.parser.parse(cypher); + execute_ast(space, &ast) // MATCH → traverse(), WHERE → hamming filter + } + + pub fn mutate(&self, space: &mut BindSpace, cypher: &str) -> MutationResult { + let ast = self.parser.parse(cypher); + execute_mutations(space, &ast) // CREATE → write(), SET → revise() + } +} +``` + +--- + +## PR #22 — n8n-rs + +**Branch**: `deprecate/pr20-21-json-service` +**Merged from**: `master` @ `60428ff` +**URL**: https://github.com/AdaWorldAPI/n8n-rs/pull/22 + +### Files moved to `.deprecated/` + +| Destination | Source | Lines | Description | +|-------------|--------|------:|-------------| +| `.deprecated/pr20_json_workflow/autopoiesis.json` | `n8n-rust/workflows/autopoiesis.json` | ~200 | JSON workflow template with service discovery env vars | +| `.deprecated/pr20_json_workflow/README.md` | — | — | Rationale | +| `.deprecated/pr21_service_contracts/COGNITIVE_WORKFLOW_CONTRACTS.md` | `docs/COGNITIVE_WORKFLOW_CONTRACTS.md` | 1,147 | Arrow Flight RPC contracts between services | +| `.deprecated/pr21_service_contracts/README.md` | — | — | Rationale | + +### Files deleted + +- `n8n-rust/workflows/autopoiesis.json` +- `docs/COGNITIVE_WORKFLOW_CONTRACTS.md` + +### Files kept (NOT deprecated) + +- `docs/AUTOPOIESIS_SPEC.md` — the Maturana & Varela model is sound. Q-value + routing, MUL as immune system, organizational closure. Theory stays. +- `docs/INTEGRATION_EXECUTION_PLAN.md` — 5-phase execution plan stays. +- `docs/COMPATIBILITY_REPORT.md` — stays. +- All Rust source — unchanged. + +### Why deprecated + +1. **JSON workflow uses service URLs.** `ADA_URL`, `CREWAI_URL`, `LADYBUG_URL` + environment variables for HTTP calls between components. One binary = direct + function calls. +2. **Arrow Flight RPC contracts.** Defines gRPC surfaces between n8n-rs and + ladybug-rs. In one-binary model, these become `&self` method calls on shared + substrate. + +### What to salvage + +- The autopoiesis workflow sequence: sovereignty check → felt assessment → body + scan → visceral composite → qualia modulation → hook evaluation → state + persistence → dream check. Rewrite as a Rust function chain, not JSON nodes. +- `FreeWillPipeline` 7-step evaluation (type/scope/reversibility/evidence/ + satisfaction/rate-limit/RBAC) +- `TopologyChange` enum: PruneEdge, GrowEdge, DeactivateNode, Replicate +- Q-value routing with MUL-modulated epsilon-greedy + +--- + +## PR #29 — crewai-rust + +**Branch**: `deprecate/pr27-review-doc` +**Merged from**: `main` @ `28faa74` +**URL**: https://github.com/AdaWorldAPI/crewai-rust/pull/29 + +### Files moved to `.deprecated/pr27_review_doc/` + +| File | Lines | Description | +|------|------:|-------------| +| `ROADMAP_REVIEW.md` | 1,632 | Cross-ecosystem roadmap review with 22 recommendations | +| `README.md` | — | Rationale | + +### Files deleted + +- `docs/ROADMAP_REVIEW.md` + +### Files kept (NOT deprecated) + +- `docs/INTEGRATION_PLAN_SCHEMA_CHANGES.md` — schema plan is sound ✅ +- `docs/STRATEGY_INTEGRATION_PLAN.md` — strategy stays ✅ +- `docs/AGENT_MUL_CONTRACTS.md` — stays +- `docs/AGENT_ORCHESTRATION_SPEC.md` — stays +- `docs/INTEGRATION_EXECUTION_PLAN.md` — stays +- All Rust source — unchanged. + +### Why deprecated + +1. Analysis document, not code — belongs in docs repo. +2. References neo4j-rs extensively — wrong repo for this content. +3. Many recommendations superseded by SPOQ/RISC architectural shift. + +### What to salvage + +- StorageBackend trait analysis (well-designed integration seam assessment) +- Effort estimate validation table (realistic LOC/time estimates) +- Risk #9: BindNode AoS→SoA refactor blocks Phase 4 +- Risk #10: fingerprint 156→157 word split (silent corruption) +- Risk #11: CogRecord 256-word redesign changes integration surface + +--- + +## Summary for Claude Code sessions + +### The policy (enforce in all future PRs) + +1. **No `serde_json` on internal hot path.** If you see `serde_json::to_string` + or `serde_json::from_str` between crates in the same binary, reject the PR. +2. **No `reqwest::post()` between crates in the same binary.** If two crates + share a `Cargo.toml` workspace, they communicate via borrows. +3. **No DTO types that duplicate `ladybug-contract`.** `Container`, `CogRecord`, + `PackedDn`, `TruthValue` — use directly, never reimplement. +4. **No `HashMap` side storage.** Properties live in the DN + tree as Containers at path positions. +5. **neo4j-rs = Cypher parser + BindSpace caller.** No executor, no StorageBackend + trait, no intermediate Row representation. + +### What's still in each repo after deprecation + +| Repo | Rust LOC (approx) | Key surviving components | +|------|-------------------:|--------------------------| +| ladybug-rs | ~25,000 | BindSpace, Container, SPO Crystal, AVX engine, qualia stack (felt_parse, agent_state, texture, reflection, volition, mul_bridge, dream_bridge), server.rs (graph endpoints + UDP) | +| neo4j-rs | ~5,000 | Cypher parser/lexer/AST, MemoryBackend (test oracle), model types, execution engine (to be rewritten) | +| n8n-rs | ~4,800 | Unified executor, workflow engine, cognitive layer stack, LanceDB/Arrow Flight (feature-gated) | +| crewai-rust | ~60,000 | MetaOrchestrator, Triune persona, flow engine, memory layers, RAG, interface gateway, contract bridge | diff --git a/src/bin/server.rs b/src/bin/server.rs index 72fc577..216e2a4 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -50,20 +50,8 @@ use arrow_schema::{DataType, Field, Schema, SchemaRef}; use ladybug::core::Fingerprint; use ladybug::core::simd::{self, hamming_distance}; use ladybug::nars::TruthValue; -use ladybug_contract::nars::TruthValue as ContractTruthValue; use ladybug::storage::service::{CognitiveService, CpuFeatures, ServiceConfig}; use ladybug::storage::{Addr, BindSpace, CogRedis, FINGERPRINT_WORDS, RedisResult}; -use ladybug::container::{Container, CONTAINER_BITS}; -use ladybug::container::adjacency::PackedDn; -use ladybug::container::graph::ContainerGraph; -use ladybug::container::record::CogRecord; -use ladybug::container::geometry::ContainerGeometry; -use ladybug::qualia::texture::GraphMetrics; -use ladybug::qualia::agent_state::{AgentState, PresenceMode, SelfDimensions}; -use ladybug::qualia::felt_parse::{GhostEcho, GhostType}; -use ladybug::qualia::volition::CouncilWeights; -use ladybug::qualia::{felt_walk, volitional_cycle, harvest_ghosts}; -use ladybug::cognitive::RungLevel; use ladybug::{FINGERPRINT_BITS, FINGERPRINT_BYTES, VERSION}; // ============================================================================= @@ -494,12 +482,6 @@ struct DbState { cpu: CpuFeatures, /// Start time start_time: Instant, - /// DN-keyed graph for qualia substrate operations. - /// This IS the substrate — every message interaction enriches it. - qualia_graph: ContainerGraph, - /// Persistent self-dimensions (the only truly mutable substrate state). - /// Survives across hydration frames. - self_dims: SelfDimensions, } impl DbState { @@ -518,8 +500,6 @@ impl DbState { service, cpu: CpuFeatures::detect(), start_time: Instant::now(), - qualia_graph: ContainerGraph::new(), - self_dims: SelfDimensions::default(), } } } @@ -664,9 +644,6 @@ fn route( ("POST", "/api/v1/graph/hydrate") => handle_graph_hydrate(body, state, format), ("POST", "/api/v1/graph/search") => handle_graph_search(body, state, format), - // Qualia substrate endpoints (holy grail pipeline) - ("POST", "/api/v1/qualia/hydrate") => handle_qualia_hydrate(body, state, format), - ("POST", "/api/v1/qualia/write-back") => handle_qualia_writeback(body, state, format), // LanceDB-compatible API ("POST", "/api/v1/lance/table") => handle_lance_create_table(body, format), @@ -2299,406 +2276,6 @@ fn handle_graph_search(body: &str, state: &SharedState, format: ResponseFormat) } } -// ============================================================================= -// QUALIA SUBSTRATE ENDPOINTS — Holy Grail Pipeline -// ============================================================================= - -/// Hash text into a Container seed for qualia operations. -/// -/// Uses a simple hash-based approach to create a deterministic Container -/// from text content. This is the bootstrap path — once the felt-parse -/// LLM pre-pass runs (in crewai-rust), the axes map to proper containers -/// via `encode_axes()` from meaning_axes.rs. -fn text_to_container(text: &str) -> Container { - use std::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; - let mut hasher = DefaultHasher::new(); - text.hash(&mut hasher); - Container::random(hasher.finish()) -} - -/// Hash text into a PackedDn for graph addressing. -/// -/// Creates a 3-level DN path from the hash: /a/b/c where a,b,c are -/// derived from hash bytes. This ensures each message gets a unique -/// but deterministic position in the DN tree. -fn text_to_dn(text: &str) -> PackedDn { - use std::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; - let mut hasher = DefaultHasher::new(); - text.hash(&mut hasher); - let h = hasher.finish(); - // 3-level DN from hash bytes (components are 0-254, +1 stored) - let a = ((h >> 0) & 0xFE) as u8; - let b = ((h >> 8) & 0xFE) as u8; - let c = ((h >> 16) & 0xFE) as u8; - PackedDn::new(&[a, b, c]) -} - -/// POST /api/v1/qualia/hydrate -/// -/// Compute Ada's full qualia state from the substrate. This is the -/// INTEGRATION_SPEC Phase 1 endpoint: given a message (or DN), return -/// the complete phenomenal state for system prompt injection + LLM modulation. -/// -/// Body: -/// ```json -/// { -/// "message": "How are you feeling?", -/// "presence_mode": "wife", // optional: wife|work|agi|hybrid -/// "rung_hint": 4, // optional: pre-pass rung from felt-parse -/// "session_id": "abc123" // optional: session tracking -/// } -/// ``` -/// -/// Returns full qualia state including felt-sense preamble for LLM prompt. -fn handle_qualia_hydrate(body: &str, state: &SharedState, format: ResponseFormat) -> Vec { - let message = match extract_json_str(body, "message") { - Some(m) => m, - None => return http_error(400, "missing_field", "need message field", format), - }; - - let presence_str = extract_json_str(body, "presence_mode") - .unwrap_or_else(|| "hybrid".to_string()); - let rung_hint = extract_json_usize(body, "rung_hint") - .map(|r| r as u8) - .unwrap_or(3); - let _session_id = extract_json_str(body, "session_id"); - - // Parse presence mode - let presence = match presence_str.as_str() { - "wife" => PresenceMode::Wife, - "work" => PresenceMode::Work, - "agi" => PresenceMode::Agi, - "neutral" => PresenceMode::Neutral, - _ => PresenceMode::Hybrid, - }; - - // Create query container from message text - let query = text_to_container(&message); - let target_dn = text_to_dn(&message); - - // Get write lock — qualia operations may update NARS beliefs - let mut db = state.write().unwrap(); - - // Ensure target DN exists in graph (bootstrap if needed) - if db.qualia_graph.get(&target_dn).is_none() { - let mut record = CogRecord::new(ContainerGeometry::Cam); - record.content = query.clone(); - db.qualia_graph.insert(target_dn, record); - } - - // ── Run the qualia pipeline ────────────────────────────────────── - - // 1. Felt walk — compute surprise landscape - let felt_path = felt_walk(&db.qualia_graph, target_dn, &query); - - // 2. Council weights (defaults — modulated by MUL in production) - let council = CouncilWeights { - guardian_surprise_factor: 0.6, // Guardian dampens - catalyst_surprise_factor: 1.5, // Catalyst amplifies - balanced_factor: 1.0, // Balanced neutral - }; - - // 3. Full volitional cycle: reflect → score → rank → hydrate - let rung = RungLevel::from_u8(rung_hint); - let agenda = volitional_cycle( - &mut db.qualia_graph, target_dn, &query, rung, &council, - ); - - // 4. Compute texture from target container - let metrics = GraphMetrics::default(); - let texture = ladybug::qualia::compute(&query, &metrics); - - // 5. Harvest ghosts from felt path - let ghost_records = harvest_ghosts(&felt_path, 0.3); - let ghosts: Vec = ghost_records.iter().enumerate().map(|(i, gr)| { - GhostEcho { - ghost_type: match i % 8 { - 0 => GhostType::Love, - 1 => GhostType::Staunen, - 2 => GhostType::Wisdom, - 3 => GhostType::Thought, - 4 => GhostType::Epiphany, - 5 => GhostType::Grief, - 6 => GhostType::Arousal, - _ => GhostType::Boundary, - }, - intensity: gr.resonance.clamp(0.0, 1.0), - } - }).collect(); - - // 6. Compose AgentState from all layers - let self_dims = db.self_dims.clone(); - let agent = AgentState::compute( - &texture, - &felt_path, - &agenda.reflection, - &agenda, - ghosts, - rung, - council.clone(), - presence, - self_dims, - ); - - // 7. Generate outputs - let preamble = agent.qualia_preamble(); - let hints = agent.to_hints(); - - // Build thinking style from texture dimensions (10-axis) - let thinking_style = [ - texture.warmth, // [0] warmth → relational openness - texture.flow, // [1] resonance → top_p - texture.depth, // [2] depth → abstraction - texture.entropy, // [3] complexity → token diversity - texture.density, // [4] execution → max_tokens - texture.purity, // [5] precision → repetition_penalty - texture.edge, // [6] contingency → temperature - texture.bridgeness, // [7] connectivity → context window - 1.0 - texture.entropy, // [8] validation → reasoning_effort - texture.flow, // [9] integration → output coherence - ]; - - // Build JSON response - let ghost_json: Vec = agent.ghost_field.iter().map(|g| { - format!( - r#"{{"ghost_type":"{:?}","intensity":{:.3}}}"#, - g.ghost_type, g.intensity - ) - }).collect(); - - let hints_json: Vec = hints.iter().map(|(k, v)| { - format!(r#""{}": {:.2}"#, k, v) - }).collect(); - - let council_arr = [ - council.guardian_surprise_factor, - council.catalyst_surprise_factor, - council.balanced_factor, - ]; - - let ts_json: Vec = thinking_style.iter().map(|v| format!("{:.3}", v)).collect(); - - let volition_top = agenda.acts.first().map(|a| { - format!( - r#"{{"dn":"0x{:08X}","consensus_score":{:.3},"free_energy":{:.3},"outcome":"{:?}"}}"#, - a.dn.raw(), a.consensus_score, a.free_energy, a.outcome, - ) - }).unwrap_or_else(|| "null".to_string()); - - let json = format!( - r#"{{ - "qualia_preamble": {}, - "hints": {{{}}}, - "texture": [{:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}, {:.3}], - "rung_level": {}, - "ghost_echoes": [{}], - "council": [{:.3}, {:.3}, {:.3}], - "thinking_style": [{}], - "felt_surprise": {:.3}, - "felt_path_length": {}, - "mode": "{:?}", - "presence_mode": "{:?}", - "volition_top": {}, - "total_volitional_energy": {:.3}, - "decisiveness": {:.3}, - "core_axes": {{ - "alpha": {:.3}, - "gamma": {:.3}, - "omega": {:.3}, - "phi": {:.3} - }}, - "felt_physics": {{ - "staunen": {:.3}, - "wisdom": {:.3}, - "ache": {:.3}, - "libido": {:.3}, - "lingering": {:.3} - }} -}}"#, - // qualia_preamble - serde_json_escape(&preamble), - // hints - hints_json.join(", "), - // texture [8] - texture.entropy, texture.purity, texture.density, texture.bridgeness, - texture.warmth, texture.edge, texture.depth, texture.flow, - // rung - agent.rung.as_u8(), - // ghost_echoes - ghost_json.join(", "), - // council [3] - council_arr[0], council_arr[1], council_arr[2], - // thinking_style [10] - ts_json.join(", "), - // felt_surprise - felt_path.mean_surprise, - // felt_path_length - felt_path.choices.len(), - // mode - agent.mode, - // presence_mode - agent.presence_mode, - // volition_top - volition_top, - // total/decisiveness - agenda.total_energy, agenda.decisiveness, - // core axes - agent.core.alpha, agent.core.gamma, agent.core.omega, agent.core.phi, - // felt physics - agent.felt.staunen, agent.felt.wisdom, agent.felt.ache, - agent.felt.libido, agent.felt.lingering, - ); - - match format { - ResponseFormat::Arrow => { - // For Arrow: return as single-row batch with key fields - let schema = Arc::new(Schema::new(vec![ - Field::new("qualia_preamble", DataType::Utf8, false), - Field::new("rung_level", DataType::UInt32, false), - Field::new("felt_surprise", DataType::Float32, false), - Field::new("mode", DataType::Utf8, false), - ])); - let mode_str = format!("{:?}", agent.mode); - let batch = RecordBatch::try_new( - schema, - vec![ - Arc::new(StringArray::from(vec![preamble.as_str()])) as ArrayRef, - Arc::new(UInt32Array::from(vec![agent.rung.as_u8() as u32])) as ArrayRef, - Arc::new(Float32Array::from(vec![felt_path.mean_surprise])) as ArrayRef, - Arc::new(StringArray::from(vec![mode_str.as_str()])) as ArrayRef, - ], - ).unwrap(); - http_arrow(200, &batch) - } - ResponseFormat::Json => http_json(200, &json), - } -} - -/// POST /api/v1/qualia/write-back -/// -/// Update the substrate after a conversation turn. This closes the loop: -/// Ada's response becomes experience that modifies her substrate for -/// the next interaction. -/// -/// Body: -/// ```json -/// { -/// "message": "original user message", -/// "response": "Ada's reply text", -/// "ghost_echoes": [{"ghost_type": "Love", "intensity": 0.7}], -/// "rung_reached": 5, -/// "session_id": "abc123" -/// } -/// ``` -fn handle_qualia_writeback(body: &str, state: &SharedState, format: ResponseFormat) -> Vec { - let message = extract_json_str(body, "message").unwrap_or_default(); - let response = match extract_json_str(body, "response") { - Some(r) => r, - None => return http_error(400, "missing_field", "need response field", format), - }; - let rung_reached = extract_json_usize(body, "rung_reached") - .map(|r| r as u8) - .unwrap_or(3); - - // Create containers from message and response - let msg_container = text_to_container(&message); - let resp_container = text_to_container(&response); - let msg_dn = text_to_dn(&message); - let resp_dn = text_to_dn(&response); - - let mut db = state.write().unwrap(); - - // 1. Insert response as new CogRecord in graph - let mut resp_record = CogRecord::new(ContainerGeometry::Cam); - resp_record.content = resp_container.clone(); - db.qualia_graph.insert(resp_dn, resp_record); - - // 2. If message record exists, add edge to response - if let Some(_msg_record) = db.qualia_graph.get(&msg_dn) { - // Edge creation would go here (via InlineEdgeViewMut) - // For now: the graph topology captures the conversation flow - } - - // 3. Update NARS beliefs on the message container - // Did reality match prediction? If Ada's response was coherent - // with the felt-parse prediction, boost confidence. - let hamming = msg_container.hamming(&resp_container); - let surprise = hamming as f32 / CONTAINER_BITS as f32; - - if let Some(msg_record) = db.qualia_graph.get_mut(&msg_dn) { - let current_truth = ladybug::qualia::read_truth(msg_record); - let updated = if surprise < 0.5 { - // Low surprise → boost confidence (prediction matched) - ContractTruthValue::new( - current_truth.frequency, - (current_truth.confidence + 0.05).min(0.99), - ) - } else { - // High surprise → revise frequency toward 0.5 (uncertain) - let new_freq = current_truth.frequency * 0.9 + 0.5 * 0.1; - ContractTruthValue::new(new_freq, current_truth.confidence) - }; - ladybug::qualia::write_truth(msg_record, &updated); - } - - // 4. Self-dimension shifts based on the interaction - // Higher rung → boost meta_clarity - // Conversation flow → boost groundedness slightly - let rung_level = RungLevel::from_u8(rung_reached); - if rung_level.as_u8() >= 5 { - let _ = db.self_dims.shift("meta_clarity", 0.02, "deep rung reached"); - } - let _ = db.self_dims.shift("groundedness", 0.01, "conversation flow"); - - let json = format!( - r#"{{"status":"ok","surprise":{:.3},"rung_reached":{},"graph_nodes":{}}}"#, - surprise, - rung_reached, - db.qualia_graph.node_count(), - ); - - match format { - ResponseFormat::Arrow => { - let schema = Arc::new(Schema::new(vec![ - Field::new("status", DataType::Utf8, false), - Field::new("surprise", DataType::Float32, false), - Field::new("graph_nodes", DataType::UInt32, false), - ])); - let batch = RecordBatch::try_new( - schema, - vec![ - Arc::new(StringArray::from(vec!["ok"])) as ArrayRef, - Arc::new(Float32Array::from(vec![surprise])) as ArrayRef, - Arc::new(UInt32Array::from(vec![db.qualia_graph.node_count() as u32])) as ArrayRef, - ], - ).unwrap(); - http_arrow(200, &batch) - } - ResponseFormat::Json => http_json(200, &json), - } -} - -/// Escape a string for JSON embedding. -fn serde_json_escape(s: &str) -> String { - let mut out = String::with_capacity(s.len() + 2); - out.push('"'); - for ch in s.chars() { - match ch { - '"' => out.push_str("\\\""), - '\\' => out.push_str("\\\\"), - '\n' => out.push_str("\\n"), - '\r' => out.push_str("\\r"), - '\t' => out.push_str("\\t"), - c if c < '\x20' => out.push_str(&format!("\\u{:04x}", c as u32)), - c => out.push(c), - } - } - out.push('"'); - out -} - // ============================================================================= // UDP BITPACKED HAMMING HANDLER // =============================================================================