From bf516f99302347c2dd2f1787a51cf2e9ca259beb Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 23 Aug 2026 02:27:49 +0200 Subject: [PATCH] Delete aggregate Persona memory mirrors --- epiphany-core/src/agent_memory.rs | 2051 +---------------- epiphany-core/src/cultmesh_integration.rs | 350 --- epiphany-core/src/lib.rs | 25 - epiphany-core/src/mind_documents.rs | 9 + epiphany-core/src/persona_conversation.rs | 11 +- epiphany-core/src/persona_turn.rs | 52 +- epiphany-core/src/prompt_context.rs | 78 - epiphany-core/src/resident_self.rs | 220 -- epiphany-core/src/runtime_spine.rs | 28 - epiphany-core/src/surfaces/planning.rs | 1 + epiphany-core/src/surfaces/scene.rs | 1 + .../src/bin/epiphany-persona-service.rs | 41 +- notes/fresh-workspace-handoff.md | 17 +- state/ledgers.msgpack | Bin 652347 -> 653200 bytes state/map.yaml | 4 +- state/scratch.md | 9 +- 16 files changed, 98 insertions(+), 2799 deletions(-) diff --git a/epiphany-core/src/agent_memory.rs b/epiphany-core/src/agent_memory.rs index c4885d9b..688f1f7c 100644 --- a/epiphany-core/src/agent_memory.rs +++ b/epiphany-core/src/agent_memory.rs @@ -1,19 +1,15 @@ -use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use chrono::SecondsFormat; use chrono::Utc; use cultcache_rs::CultCache; -use cultcache_rs::CultSoaColumnValues; use cultcache_rs::DatabaseEntry; use cultcache_rs::SingleFileMessagePackBackingStore; -use cultcache_rs::SoaDocument; use serde::Deserialize; use serde::Serialize; use serde_json::Value; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; -use std::fs; use std::path::Path; pub const AGENT_MEMORY_TYPE: &str = "epiphany.agent_memory"; @@ -83,10 +79,6 @@ pub struct AgentMemoryMindAdmissionReceipt { #[cultcache(key = 7)] pub resulting_source_hash: String, } -pub const AGENT_STATE_SOA_TYPE: &str = "epiphany.agent_state_soa"; -pub const AGENT_STATE_SOA_SCHEMA_VERSION: &str = "epiphany.agent_state_soa.v0"; -pub const AGENT_STATE_SOA_KEY: &str = "swarm"; - #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.agent_memory_swarm_identity", @@ -318,132 +310,6 @@ pub struct EpiphanyAgentMemoryEntry { pub scenes: Vec, } -impl SoaDocument for EpiphanyAgentMemoryEntry { - fn soa_columns(rows: &[Self]) -> BTreeMap<&'static str, CultSoaColumnValues> { - let mut columns = BTreeMap::new(); - columns.insert( - "roleId", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.role_id.clone()) - .collect::>(), - ), - ); - columns.insert( - "agentId", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.agent_id.clone()) - .collect::>(), - ), - ); - columns.insert( - "displayName", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.identity.name.clone()) - .collect::>(), - ), - ); - columns.insert( - "profileKind", - CultSoaColumnValues::new( - rows.iter() - .map(|row| { - format!( - "{:?}", - organ_state_profile_for_role(&row.role_id).profile_kind - ) - }) - .collect::>(), - ), - ); - columns.insert( - "portableContract", - CultSoaColumnValues::new( - rows.iter() - .map(|row| organ_state_profile_for_role(&row.role_id).portable_contract) - .collect::>(), - ), - ); - columns.insert( - "semanticMemoryCount", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.memories.semantic.len() as u32) - .collect::>(), - ), - ); - columns.insert( - "episodicMemoryCount", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.memories.episodic.len() as u32) - .collect::>(), - ), - ); - columns.insert( - "relationshipMemoryCount", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.memories.relationship_summaries.len() as u32) - .collect::>(), - ), - ); - columns.insert( - "goalCount", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.goals.len() as u32) - .collect::>(), - ), - ); - columns.insert( - "valueCount", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.agent.canonical_state.values.len() as u32) - .collect::>(), - ), - ); - columns - } -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.agent_state_soa", - schema = "EpiphanyAgentStateSoaEntry" -)] -pub struct EpiphanyAgentStateSoaEntry { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub generated_at: String, - #[cultcache(key = 2)] - pub source_store: String, - #[cultcache(key = 3)] - pub role_ids: Vec, - #[cultcache(key = 4)] - pub agent_ids: Vec, - #[cultcache(key = 5)] - pub display_names: Vec, - #[cultcache(key = 6)] - pub profile_kinds: Vec, - #[cultcache(key = 7)] - pub portable_contracts: Vec, - #[cultcache(key = 8)] - pub semantic_memory_counts: Vec, - #[cultcache(key = 9)] - pub episodic_memory_counts: Vec, - #[cultcache(key = 10)] - pub relationship_memory_counts: Vec, - #[cultcache(key = 11)] - pub goal_counts: Vec, - #[cultcache(key = 12)] - pub value_counts: Vec, -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct GhostlightWorld { pub world_id: String, @@ -609,19 +475,6 @@ pub struct GhostlightMemory { pub linked_relationship_id: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -struct AgentMemoryProjection { - pub schema_version: String, - pub world: GhostlightWorld, - pub agents: Vec, - #[serde(default)] - pub relationships: Vec, - #[serde(default)] - pub events: Vec, - #[serde(default)] - pub scenes: Vec, -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AgentSelfPatch { @@ -660,82 +513,6 @@ pub struct SelfPatchMemory { pub linked_relationship_id: Option, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentMemoryLifecycleOperation { - #[serde(default)] - pub agent_id: Option, - pub reason: String, - pub actions: Vec, - #[serde(flatten)] - pub extra: BTreeMap, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", tag = "action")] -pub enum AgentMemoryLifecycleAction { - Revise { - bundle: AgentMemoryBundle, - #[serde(alias = "memoryId")] - memory_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - summary: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - salience: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - confidence: Option, - reason: String, - }, - Retire { - bundle: AgentMemoryBundle, - #[serde(alias = "memoryId")] - memory_id: String, - reason: String, - }, - Crystallize { - #[serde(alias = "fromBundle")] - from_bundle: AgentMemoryBundle, - #[serde(alias = "toBundle")] - to_bundle: AgentMemoryBundle, - #[serde(alias = "memoryId")] - memory_id: String, - #[serde(alias = "newMemoryId")] - #[serde(default, skip_serializing_if = "Option::is_none")] - new_memory_id: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - summary: Option, - reason: String, - }, - Prune { - bundle: AgentMemoryBundle, - #[serde(alias = "maxRecords")] - max_records: usize, - #[serde(alias = "minimumSalience")] - #[serde(default, skip_serializing_if = "Option::is_none")] - minimum_salience: Option, - reason: String, - }, - Merge { - bundle: AgentMemoryBundle, - #[serde(alias = "targetMemoryId")] - target_memory_id: String, - #[serde(alias = "sourceMemoryIds")] - source_memory_ids: Vec, - summary: String, - salience: f64, - confidence: f64, - reason: String, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum AgentMemoryBundle { - Semantic, - Episodic, - RelationshipSummaries, -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SelfPatchGoal { @@ -790,52 +567,8 @@ pub struct EpiphanyOrganStateProfile { pub notes: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentCanonicalTraitSeed { - pub role_id: String, - pub group_name: String, - pub trait_name: String, - pub mean: f64, - pub plasticity: f64, - pub current_activation: f64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source: Option, -} - pub const PERSONA_STATE_SCHEMA_VERSION: &str = "gamecult.persona_state.v0"; -pub fn migrate_agent_memory_json_dir_to_cultcache( - agent_dir: impl AsRef, - store_path: impl AsRef, -) -> Result { - let agent_dir = agent_dir.as_ref(); - let store_path = store_path.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - require_agent_memory_migration_open(&mut cache, "JSON agent-memory import")?; - let mut migrated = Vec::new(); - for (role_id, expected_agent_id, filename) in ROLE_TARGETS { - let path = agent_dir.join(filename); - let raw = fs::read_to_string(&path) - .with_context(|| format!("failed to read {}", path.display()))?; - let projection: AgentMemoryProjection = serde_json::from_str(&raw) - .with_context(|| format!("failed to decode {}", path.display()))?; - let entry = entry_from_projection(role_id, expected_agent_id, projection) - .with_context(|| format!("invalid role memory {}", path.display()))?; - cache.put(*role_id, &entry)?; - migrated.push(serde_json::json!({ - "roleId": role_id, - "agentId": expected_agent_id, - "source": path, - })); - } - Ok(serde_json::json!({ - "ok": true, - "store": store_path, - "migrated": migrated, - })) -} - pub fn validate_agent_memory_store(store_path: impl AsRef) -> Result> { let store_path = store_path.as_ref(); let mut cache = agent_memory_cache(store_path)?; @@ -851,150 +584,6 @@ pub fn validate_agent_memory_store(store_path: impl AsRef) -> Result) -> Result { - let store_path = store_path.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - require_agent_memory_migration_open(&mut cache, "agent-memory repair")?; - let mut repaired = Vec::new(); - - if let Some(mut modeling) = cache.get::("modeling")? - && modeling.agent.agent_id == "epiphany.proprioception" - { - modeling.agent.agent_id = "epiphany.modeling".to_string(); - if modeling.agent.identity.name == "Proprioception" { - modeling.agent.identity.name = "Modeling".to_string(); - } - if modeling - .agent - .identity - .roles - .iter() - .any(|role| role == "Proprioception") - { - modeling.agent.identity.roles = modeling - .agent - .identity - .roles - .into_iter() - .map(|role| { - if role == "Proprioception" { - "Modeling".to_string() - } else { - role - } - }) - .collect(); - } - cache.put("modeling".to_string(), &modeling)?; - repaired.push(serde_json::json!({ - "roleId": "modeling", - "repair": "renamed legacy epiphany.proprioception vessel to epiphany.modeling", - })); - } - - if let Some(mut modeling) = cache.get::("modeling")? { - let mut renamed = 0usize; - renamed += replace_deprecated_faculty_name(&mut modeling.agent.identity.public_description); - for note in &mut modeling.agent.identity.private_notes { - renamed += replace_deprecated_faculty_name(note); - } - for goal in &mut modeling.agent.goals { - renamed += replace_deprecated_faculty_name(&mut goal.description); - renamed += replace_deprecated_faculty_name(&mut goal.emotional_stake); - } - for memory in modeling - .agent - .memories - .semantic - .iter_mut() - .chain(modeling.agent.memories.episodic.iter_mut()) - .chain(modeling.agent.memories.relationship_summaries.iter_mut()) - { - renamed += replace_deprecated_faculty_name(&mut memory.summary); - } - if renamed > 0 { - cache.put("modeling".to_string(), &modeling)?; - repaired.push(serde_json::json!({ - "roleId": "modeling", - "repair": "replaced deprecated Proprioception prose with canonical Modeling doctrine", - "replacements": renamed, - })); - } - } - - if cache.get::("Persona")?.is_none() - && let Some(mut face) = cache.get::("face")? - { - face.role_id = "Persona".to_string(); - face.agent.agent_id = "epiphany.Persona".to_string(); - if face.agent.identity.name == "Face" { - face.agent.identity.name = "Persona".to_string(); - } - if face.agent.identity.roles.is_empty() { - face.agent.identity.roles.push("Persona".to_string()); - } - if !face - .agent - .identity - .roles - .iter() - .any(|role| role == "Persona") - { - face.agent.identity.roles.push("Persona".to_string()); - } - face.agent.identity.public_description = if face - .agent - .identity - .public_description - .trim() - .is_empty() - { - "Epiphany Persona is the public-facing project voice; Imagination projects context before speech and Mind interprets side effects after speech.".to_string() - } else { - face.agent.identity.public_description - }; - cache.put("Persona".to_string(), &face)?; - cache.delete::("face")?; - repaired.push(serde_json::json!({ - "roleId": "Persona", - "repair": "promoted legacy face memory entry to Persona role memory and removed obsolete face row", - })); - } - - if cache - .get::("reorientation")? - .is_some() - { - cache.delete::("reorientation")?; - repaired.push(serde_json::json!({ - "roleId": "reorientation", - "repair": "removed obsolete reorientation row; Continuity is protocol machinery, not a standing sub-agent identity", - })); - } - - let errors = validate_agent_memory_store(store_path)?; - let soa = refresh_agent_state_soa(store_path)?; - Ok(serde_json::json!({ - "ok": errors.is_empty(), - "store": store_path, - "repaired": repaired, - "errors": errors, - "soa": soa, - })) -} - -pub fn load_agent_memory_generation_witness( - store_path: impl AsRef, -) -> Result> { - let store_path = store_path.as_ref(); - if !store_path.exists() { - return Ok(None); - } - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.get(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY) -} - pub fn agent_memory_semantic_projection_input( store_path: impl AsRef, ) -> Result { @@ -1270,17 +859,6 @@ fn commit_agent_memory_generation( Ok(witness) } -pub fn admit_legacy_agent_memory_generation( - store_path: impl AsRef, -) -> Result { - admit_initial_agent_memory_generation( - store_path.as_ref(), - "legacy-bootstrap-admission", - "Admit existing canonical Mind rows without changing their content.", - Vec::new(), - ) -} - fn admit_initial_agent_memory_generation( store_path: &Path, mutation_kind: &str, @@ -1457,14 +1035,6 @@ fn require_agent_memory_migration_open(cache: &mut CultCache, operation: &str) - Ok(()) } -fn replace_deprecated_faculty_name(value: &mut String) -> usize { - let count = value.matches("Proprioception").count(); - if count > 0 { - *value = value.replace("Proprioception", "Modeling"); - } - count -} - pub fn load_agent_memory_entry_for_role( store_path: impl AsRef, role_id: &str, @@ -1580,6 +1150,27 @@ pub fn review_agent_self_patch_document( agent_memory_review(role_id, target_agent_id, store_path, reasons, None) } +fn agent_memory_review( + role_id: &str, + target_agent_id: &str, + store_path: &Path, + reasons: Vec, + applied: Option, +) -> AgentMemoryReview { + AgentMemoryReview { + status: if reasons.is_empty() { + "accepted".to_string() + } else { + "rejected".to_string() + }, + target_agent_id: target_agent_id.to_string(), + target_role_id: role_id.to_string(), + target_store: store_path.display().to_string(), + reasons, + applied, + } +} + pub(crate) fn review_agent_self_patch_contract( expected_agent_id: &str, patch: &AgentSelfPatch, @@ -1717,863 +1308,6 @@ pub fn apply_agent_self_patch_document( Ok(review) } -pub fn review_agent_memory_lifecycle_operation( - role_id: &str, - operation: &AgentMemoryLifecycleOperation, - store_path: impl AsRef, -) -> AgentMemoryReview { - let store_path = store_path.as_ref(); - let target_agent_id = match agent_id_for_role(role_id) { - Ok(agent_id) => agent_id, - Err(reason) => { - return AgentMemoryReview { - status: "rejected".to_string(), - target_agent_id: String::new(), - target_role_id: role_id.to_string(), - target_store: store_path.display().to_string(), - reasons: vec![reason], - applied: None, - }; - } - }; - let reasons = review_memory_lifecycle_contract(target_agent_id, operation); - agent_memory_review(role_id, target_agent_id, store_path, reasons, None) -} - -pub fn apply_agent_memory_lifecycle_operation( - role_id: &str, - operation: AgentMemoryLifecycleOperation, - store_path: impl AsRef, -) -> Result { - let store_path = store_path.as_ref(); - let mut review = review_agent_memory_lifecycle_operation(role_id, &operation, store_path); - if review.status != "accepted" { - return Ok(review); - } - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let mut entry = cache - .get::(role_id)? - .ok_or_else(|| anyhow!("CultCache has no role memory entry for {role_id:?}"))?; - let (expected_generation, expected_source_hash) = agent_memory_source_head(&mut cache)?; - - let reason = operation.reason.clone(); - - for action in operation.actions { - apply_memory_lifecycle_action(&mut entry, action)?; - } - commit_agent_memory_generation( - store_path, - expected_generation, - &expected_source_hash, - role_id, - entry, - "lifecycle", - &reason, - )?; - review.applied = Some(true); - Ok(review) -} - -fn review_memory_lifecycle_contract( - expected_agent_id: &str, - operation: &AgentMemoryLifecycleOperation, -) -> Vec { - let mut reasons = Vec::new(); - if operation.agent_id.as_deref() != Some(expected_agent_id) { - reasons.push(format!( - "lifecycle agentId {:?} does not match this lane; expected {:?}", - operation.agent_id, expected_agent_id - )); - } - check_patch_text(&operation.reason, "lifecycle reason", &mut reasons, 800); - if operation.actions.is_empty() { - reasons.push("lifecycle operation must contain at least one action".to_string()); - } - if operation.actions.len() > 8 { - reasons.push("lifecycle operation may contain at most 8 actions".to_string()); - } - for key in operation.extra.keys() { - reasons.push(format!( - "lifecycle field {key:?} is not part of the bounded memory lifecycle contract" - )); - } - for (index, action) in operation.actions.iter().enumerate() { - review_memory_lifecycle_action(index, action, &mut reasons); - } - reasons -} - -fn review_memory_lifecycle_action( - index: usize, - action: &AgentMemoryLifecycleAction, - reasons: &mut Vec, -) { - match action { - AgentMemoryLifecycleAction::Revise { - memory_id, - summary, - salience, - confidence, - reason, - .. - } => { - review_memory_lifecycle_id(index, "memoryId", memory_id, reasons); - if summary.is_none() && salience.is_none() && confidence.is_none() { - reasons.push(format!( - "lifecycle actions[{index}] revise must change summary, salience, or confidence" - )); - } - if let Some(summary) = summary { - check_patch_text( - summary, - &format!("lifecycle actions[{index}].summary"), - reasons, - 800, - ); - } - if let Some(salience) = salience { - check_patch_unit( - *salience, - &format!("lifecycle actions[{index}].salience"), - reasons, - ); - } - if let Some(confidence) = confidence { - check_patch_unit( - *confidence, - &format!("lifecycle actions[{index}].confidence"), - reasons, - ); - } - check_patch_text( - reason, - &format!("lifecycle actions[{index}].reason"), - reasons, - 500, - ); - } - AgentMemoryLifecycleAction::Retire { - memory_id, reason, .. - } => { - review_memory_lifecycle_id(index, "memoryId", memory_id, reasons); - check_patch_text( - reason, - &format!("lifecycle actions[{index}].reason"), - reasons, - 500, - ); - } - AgentMemoryLifecycleAction::Crystallize { - memory_id, - new_memory_id, - summary, - reason, - .. - } => { - review_memory_lifecycle_id(index, "memoryId", memory_id, reasons); - if let Some(new_memory_id) = new_memory_id { - review_memory_lifecycle_id(index, "newMemoryId", new_memory_id, reasons); - } - if let Some(summary) = summary { - check_patch_text( - summary, - &format!("lifecycle actions[{index}].summary"), - reasons, - 800, - ); - } - check_patch_text( - reason, - &format!("lifecycle actions[{index}].reason"), - reasons, - 500, - ); - } - AgentMemoryLifecycleAction::Prune { - max_records, - minimum_salience, - reason, - .. - } => { - if *max_records == 0 || *max_records > 128 { - reasons.push(format!( - "lifecycle actions[{index}].maxRecords must be between 1 and 128" - )); - } - if let Some(minimum_salience) = minimum_salience { - check_patch_unit( - *minimum_salience, - &format!("lifecycle actions[{index}].minimumSalience"), - reasons, - ); - } - check_patch_text( - reason, - &format!("lifecycle actions[{index}].reason"), - reasons, - 500, - ); - } - AgentMemoryLifecycleAction::Merge { - target_memory_id, - source_memory_ids, - summary, - salience, - confidence, - reason, - .. - } => { - review_memory_lifecycle_id(index, "targetMemoryId", target_memory_id, reasons); - if source_memory_ids.len() < 2 || source_memory_ids.len() > 8 { - reasons.push(format!( - "lifecycle actions[{index}].sourceMemoryIds must contain 2 to 8 ids" - )); - } - for source_id in source_memory_ids { - review_memory_lifecycle_id(index, "sourceMemoryIds", source_id, reasons); - } - check_patch_text( - summary, - &format!("lifecycle actions[{index}].summary"), - reasons, - 800, - ); - check_patch_unit( - *salience, - &format!("lifecycle actions[{index}].salience"), - reasons, - ); - check_patch_unit( - *confidence, - &format!("lifecycle actions[{index}].confidence"), - reasons, - ); - check_patch_text( - reason, - &format!("lifecycle actions[{index}].reason"), - reasons, - 500, - ); - } - } -} - -fn review_memory_lifecycle_id(index: usize, field: &str, value: &str, reasons: &mut Vec) { - if !valid_identifier(value, "mem-") { - reasons.push(format!( - "lifecycle actions[{index}].{field} must start with 'mem-' and avoid whitespace" - )); - } -} - -fn apply_memory_lifecycle_action( - entry: &mut EpiphanyAgentMemoryEntry, - action: AgentMemoryLifecycleAction, -) -> Result<()> { - match action { - AgentMemoryLifecycleAction::Revise { - bundle, - memory_id, - summary, - salience, - confidence, - .. - } => { - let memory = memory_bundle_mut(entry, bundle) - .iter_mut() - .find(|memory| memory.memory_id == memory_id) - .ok_or_else(|| anyhow!("memory {memory_id:?} not found for revise"))?; - if let Some(summary) = summary { - memory.summary = summary; - } - if let Some(salience) = salience { - memory.salience = salience; - } - if let Some(confidence) = confidence { - memory.confidence = confidence; - } - } - AgentMemoryLifecycleAction::Retire { - bundle, memory_id, .. - } => { - let records = memory_bundle_mut(entry, bundle); - let before = records.len(); - records.retain(|memory| memory.memory_id != memory_id); - if records.len() == before { - return Err(anyhow!("memory {memory_id:?} not found for retire")); - } - } - AgentMemoryLifecycleAction::Crystallize { - from_bundle, - to_bundle, - memory_id, - new_memory_id, - summary, - .. - } => { - let source = memory_bundle_mut(entry, from_bundle) - .iter() - .find(|memory| memory.memory_id == memory_id) - .cloned() - .ok_or_else(|| anyhow!("memory {memory_id:?} not found for crystallize"))?; - let mut crystallized = source; - if let Some(new_memory_id) = new_memory_id { - crystallized.memory_id = new_memory_id; - } - if let Some(summary) = summary { - crystallized.summary = summary; - } - upsert_ghostlight_memory(memory_bundle_mut(entry, to_bundle), crystallized); - } - AgentMemoryLifecycleAction::Prune { - bundle, - max_records, - minimum_salience, - .. - } => { - let records = memory_bundle_mut(entry, bundle); - if let Some(minimum_salience) = minimum_salience { - records.retain(|memory| memory.salience >= minimum_salience); - } - records.sort_by(|left, right| { - right - .salience - .partial_cmp(&left.salience) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| { - right - .confidence - .partial_cmp(&left.confidence) - .unwrap_or(std::cmp::Ordering::Equal) - }) - .then_with(|| left.memory_id.cmp(&right.memory_id)) - }); - records.truncate(max_records); - } - AgentMemoryLifecycleAction::Merge { - bundle, - target_memory_id, - source_memory_ids, - summary, - salience, - confidence, - .. - } => { - let records = memory_bundle_mut(entry, bundle); - for source_id in &source_memory_ids { - if !records.iter().any(|memory| memory.memory_id == *source_id) { - return Err(anyhow!("memory {source_id:?} not found for merge")); - } - } - records.retain(|memory| { - !source_memory_ids - .iter() - .any(|source_id| source_id == &memory.memory_id) - }); - upsert_ghostlight_memory( - records, - GhostlightMemory { - memory_id: target_memory_id, - summary, - salience, - confidence, - linked_event_ids: None, - linked_relationship_id: None, - }, - ); - } - } - Ok(()) -} - -fn memory_bundle_mut( - entry: &mut EpiphanyAgentMemoryEntry, - bundle: AgentMemoryBundle, -) -> &mut Vec { - match bundle { - AgentMemoryBundle::Semantic => &mut entry.agent.memories.semantic, - AgentMemoryBundle::Episodic => &mut entry.agent.memories.episodic, - AgentMemoryBundle::RelationshipSummaries => { - &mut entry.agent.memories.relationship_summaries - } - } -} - -fn upsert_ghostlight_memory(records: &mut Vec, incoming: GhostlightMemory) { - if let Some(existing) = records - .iter_mut() - .find(|memory| memory.memory_id == incoming.memory_id) - { - *existing = incoming; - } else { - records.push(incoming); - } -} - -fn agent_memory_review( - role_id: &str, - target_agent_id: &str, - store_path: &Path, - reasons: Vec, - applied: Option, -) -> AgentMemoryReview { - AgentMemoryReview { - status: if reasons.is_empty() { - "accepted" - } else { - "rejected" - } - .to_string(), - target_agent_id: target_agent_id.to_string(), - target_role_id: role_id.to_string(), - target_store: store_path.display().to_string(), - reasons, - applied, - } -} - -pub fn apply_agent_canonical_trait_seeds( - seeds: &[AgentCanonicalTraitSeed], - store_path: impl AsRef, -) -> Result { - let store_path = store_path.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - require_agent_memory_migration_open(&mut cache, "canonical trait seeding")?; - let mut applied = Vec::new(); - for seed in seeds { - if seed.trait_name.trim().is_empty() { - return Err(anyhow!( - "trait seed for role {:?} has an empty trait_name", - seed.role_id - )); - } - let vector = GhostlightTraitVector { - mean: clamp_unit(seed.mean), - plasticity: clamp_unit(seed.plasticity), - current_activation: clamp_unit(seed.current_activation), - }; - let mut entry = cache - .get::(&seed.role_id)? - .ok_or_else(|| anyhow!("CultCache has no role memory entry for {:?}", seed.role_id))?; - let group = canonical_group_mut(&mut entry.agent.canonical_state, &seed.group_name)?; - let previous = group.get(&seed.trait_name).cloned(); - if seed.trait_name != "baseline" { - group.remove("baseline"); - } - group.insert(seed.trait_name.clone(), vector.clone()); - cache.put(seed.role_id.clone(), &entry)?; - applied.push(serde_json::json!({ - "roleId": seed.role_id, - "groupName": seed.group_name, - "traitName": seed.trait_name, - "source": seed.source, - "previous": previous, - "vector": vector, - "status": "applied", - })); - } - Ok(serde_json::json!({ - "status": if applied.is_empty() { "no-seeds" } else { "applied" }, - "agentStore": store_path, - "applied": applied.len(), - "seeds": applied, - })) -} - -pub fn agent_memory_status(store_path: impl AsRef) -> Result { - let store_path = store_path.as_ref(); - if !store_path.exists() { - return Ok(serde_json::json!({ - "ok": false, - "store": store_path, - "present": false, - "entryType": AGENT_MEMORY_TYPE, - "roles": [], - })); - } - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let mut roles = Vec::new(); - for (role_id, expected_agent_id, _) in ROLE_TARGETS { - let entry = cache.get::(role_id)?; - roles.push(match entry { - Some(entry) => serde_json::json!({ - "roleId": role_id, - "agentId": entry.agent.agent_id, - "displayName": entry.agent.identity.name, - "organStateProfile": organ_state_profile_for_role(role_id), - "semanticMemories": entry.agent.memories.semantic.len(), - "episodicMemories": entry.agent.memories.episodic.len(), - "relationshipMemories": entry.agent.memories.relationship_summaries.len(), - "goals": entry.agent.goals.len(), - "values": entry.agent.canonical_state.values.len(), - }), - None => serde_json::json!({ - "roleId": role_id, - "agentId": expected_agent_id, - "missing": true, - }), - }); - } - let errors = validate_agent_memory_store(store_path)?; - let swarm_identity = cache - .get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? - .map(|identity| { - serde_json::json!({ - "schemaVersion": identity.schema_version, - "swarmId": identity.swarm_id, - }) - }); - let agent_state_soa = project_agent_state_soa_from_cache(store_path, &cache)?; - let persisted_agent_state_soa = cache - .get::(AGENT_STATE_SOA_KEY)? - .map(|entry| { - serde_json::json!({ - "schemaVersion": entry.schema_version, - "generatedAt": entry.generated_at, - "rowCount": entry.role_ids.len(), - "sourceStore": entry.source_store, - }) - }); - Ok(serde_json::json!({ - "ok": errors.is_empty(), - "store": store_path, - "present": true, - "entryType": AGENT_MEMORY_TYPE, - "swarmIdentityEntryType": AGENT_MEMORY_SWARM_IDENTITY_TYPE, - "swarmIdentity": swarm_identity, - "agentStateSoaEntryType": AGENT_STATE_SOA_TYPE, - "errors": errors, - "agentStateSoa": { - "schemaVersion": agent_state_soa.schema_version, - "rowCount": agent_state_soa.role_ids.len(), - "roleIds": agent_state_soa.role_ids, - "agentIds": agent_state_soa.agent_ids, - "displayNames": agent_state_soa.display_names, - "profileKinds": agent_state_soa.profile_kinds, - "portableContracts": agent_state_soa.portable_contracts, - "semanticMemoryCounts": agent_state_soa.semantic_memory_counts, - "episodicMemoryCounts": agent_state_soa.episodic_memory_counts, - "relationshipMemoryCounts": agent_state_soa.relationship_memory_counts, - "goalCounts": agent_state_soa.goal_counts, - "valueCounts": agent_state_soa.value_counts, - }, - "persistedAgentStateSoa": persisted_agent_state_soa, - "roles": roles, - })) -} - -pub fn refresh_agent_state_soa(store_path: impl AsRef) -> Result { - let store_path = store_path.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let entry = project_agent_state_soa_from_cache(store_path, &cache)?; - validate_agent_state_soa_entry(&entry)?; - cache.put(AGENT_STATE_SOA_KEY, &entry)?; - Ok(serde_json::json!({ - "ok": true, - "store": store_path, - "entryType": AGENT_STATE_SOA_TYPE, - "key": AGENT_STATE_SOA_KEY, - "rowCount": entry.role_ids.len(), - "roleIds": entry.role_ids, - "agentIds": entry.agent_ids, - })) -} - -pub fn load_agent_state_soa_entry( - store_path: impl AsRef, -) -> Result> { - let store_path = store_path.as_ref(); - if !store_path.exists() { - return Ok(None); - } - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.get::(AGENT_STATE_SOA_KEY) -} - -fn project_agent_state_soa_from_cache( - store_path: &Path, - cache: &CultCache, -) -> Result { - let table = cache.soa::()?; - let entry = EpiphanyAgentStateSoaEntry { - schema_version: AGENT_STATE_SOA_SCHEMA_VERSION.to_string(), - generated_at: now_rfc3339(), - source_store: store_path.display().to_string(), - role_ids: table.column::("roleId")?.values().to_vec(), - agent_ids: table.column::("agentId")?.values().to_vec(), - display_names: table.column::("displayName")?.values().to_vec(), - profile_kinds: table.column::("profileKind")?.values().to_vec(), - portable_contracts: table - .column::("portableContract")? - .values() - .to_vec(), - semantic_memory_counts: table - .column::("semanticMemoryCount")? - .values() - .to_vec(), - episodic_memory_counts: table - .column::("episodicMemoryCount")? - .values() - .to_vec(), - relationship_memory_counts: table - .column::("relationshipMemoryCount")? - .values() - .to_vec(), - goal_counts: table.column::("goalCount")?.values().to_vec(), - value_counts: table.column::("valueCount")?.values().to_vec(), - }; - validate_agent_state_soa_entry(&entry)?; - Ok(entry) -} - -fn validate_agent_state_soa_entry(entry: &EpiphanyAgentStateSoaEntry) -> Result<()> { - if entry.schema_version != AGENT_STATE_SOA_SCHEMA_VERSION { - return Err(anyhow!( - "agent state SoA schema_version must be {:?}", - AGENT_STATE_SOA_SCHEMA_VERSION - )); - } - let len = entry.role_ids.len(); - for (name, candidate) in [ - ("agentIds", entry.agent_ids.len()), - ("displayNames", entry.display_names.len()), - ("profileKinds", entry.profile_kinds.len()), - ("portableContracts", entry.portable_contracts.len()), - ("semanticMemoryCounts", entry.semantic_memory_counts.len()), - ("episodicMemoryCounts", entry.episodic_memory_counts.len()), - ( - "relationshipMemoryCounts", - entry.relationship_memory_counts.len(), - ), - ("goalCounts", entry.goal_counts.len()), - ("valueCounts", entry.value_counts.len()), - ] { - if candidate != len { - return Err(anyhow!( - "agent state SoA column {name} has length {candidate}, expected {len}" - )); - } - } - Ok(()) -} - -pub fn project_persona_state_for_role( - store_path: impl AsRef, - role_id: &str, -) -> Result { - let store_path = store_path.as_ref(); - let profile = organ_state_profile_for_role(role_id); - if profile.profile_kind != EpiphanyOrganStateProfileKind::Persona { - return Err(anyhow!( - "{role_id:?} is {:?}, not persona; use epiphany.work_organ_state.v0 for work organs", - profile.profile_kind - )); - } - let entry = load_agent_memory_entry_for_role(store_path, role_id)? - .ok_or_else(|| anyhow!("CultCache has no role memory entry for {role_id:?}"))?; - Ok(project_persona_state_from_entry(&entry, store_path)) -} - -pub fn project_persona_state_from_entry( - entry: &EpiphanyAgentMemoryEntry, - store_path: &Path, -) -> Value { - let exported_at = now_rfc3339(); - let persona_id = entry.agent.agent_id.clone(); - let repo_name = "EpiphanyAgent"; - serde_json::json!({ - "schemaVersion": PERSONA_STATE_SCHEMA_VERSION, - "provenance": { - "sourceSystem": "epiphany", - "sourceDocumentId": format!("{}#{}", store_path.display(), entry.role_id), - "sourceUpdatedAt": exported_at, - "exportedAt": exported_at, - "authority": "projection", - }, - "personaId": persona_id, - "publicName": &entry.agent.identity.name, - "publicDescription": &entry.agent.identity.public_description, - "presentation": { - "voiceSummary": &entry.agent.identity.public_description, - "defaultRenderer": "chat", - "homeContext": { - "kind": "repo", - "id": repo_name, - "label": repo_name, - }, - "jurisdiction": entry - .agent - .identity - .roles - .iter() - .map(String::as_str) - .collect::>() - .join(", "), - "publicHandles": [], - }, - "privateNotes": &entry.agent.identity.private_notes, - "values": entry.agent.canonical_state.values.iter().map(persona_value).collect::>(), - "activationProfile": { - "underlyingOrganization": persona_trait_map(&entry.agent.canonical_state.underlying_organization), - "stableDispositions": persona_trait_map(&entry.agent.canonical_state.stable_dispositions), - "behavioralDimensions": persona_trait_map(&entry.agent.canonical_state.behavioral_dimensions), - "presentationStrategy": persona_trait_map(&entry.agent.canonical_state.presentation_strategy), - "voiceStyle": persona_trait_map(&entry.agent.canonical_state.voice_style), - "situationalState": persona_trait_map(&entry.agent.canonical_state.situational_state), - }, - "thoughtMemory": { - "shortTerm": [], - "memories": entry - .agent - .memories - .semantic - .iter() - .chain(entry.agent.memories.episodic.iter()) - .chain(entry.agent.memories.relationship_summaries.iter()) - .map(|memory| persona_memory_thought(memory, &persona_id, &exported_at)) - .collect::>(), - "incubation": [], - }, - "agencyPressure": { - "pressures": entry.agent.goals.iter().map(|goal| persona_goal_thought(goal, &persona_id, &exported_at)).collect::>(), - }, - "candidateActions": { - "actions": [], - }, - "voidbotProjection": { - "candidateInterventions": [], - }, - "affect": { - "needs": [], - "socialBonds": [], - "statusReads": entry.agent.perceived_state_overlays.iter().map(|overlay| persona_status_read(overlay, &persona_id, &exported_at)).collect::>(), - "moodDimensions": [], - "socialBiases": [], - "doctrineStances": entry.agent.canonical_state.values.iter().map(|value| persona_doctrine_stance(value, &persona_id, &exported_at)).collect::>(), - }, - "updatedAt": exported_at, - }) -} - -fn persona_trait_map(vectors: &BTreeMap) -> Value { - let mut map = serde_json::Map::new(); - for (name, vector) in vectors { - map.insert( - name.clone(), - serde_json::json!({ - "mean": vector.mean, - "plasticity": vector.plasticity, - "currentActivation": vector.current_activation, - }), - ); - } - Value::Object(map) -} - -fn persona_value(value: &GhostlightValue) -> Value { - serde_json::json!({ - "id": value.value_id, - "label": value.label, - "priority": value.priority, - "summary": if value.unforgivable_if_betrayed { - "Protected value; betrayal is marked as unforgivable in local Persona state." - } else { - "Value projected from local Persona organ memory." - }, - }) -} - -fn persona_target(kind: &str, id: &str, label: &str) -> Value { - serde_json::json!({ - "kind": kind, - "id": id, - "label": label, - }) -} - -fn persona_memory_thought(memory: &GhostlightMemory, persona_id: &str, timestamp: &str) -> Value { - serde_json::json!({ - "id": memory.memory_id, - "status": "crystallized", - "target": persona_target("self", persona_id, persona_id), - "summary": memory.summary, - "claim": memory.summary, - "tension": "Projected from local Persona memory; consumers should preserve provenance and avoid treating this as omniscient project truth.", - "actionImplication": "Use as Persona memory pressure, not as direct action authority.", - "intensity": memory.salience, - "valence": 0, - "createdAt": timestamp, - "updatedAt": timestamp, - "tags": ["epiphany", "Persona-memory"], - }) -} - -fn persona_goal_thought(goal: &GhostlightGoal, persona_id: &str, timestamp: &str) -> Value { - serde_json::json!({ - "id": goal.goal_id, - "status": persona_goal_status(&goal.status), - "target": persona_target("self", persona_id, persona_id), - "summary": goal.description, - "claim": goal.description, - "tension": goal.emotional_stake, - "actionImplication": "This goal may create Persona agency pressure, but action still requires the caller's review path.", - "intensity": goal.priority, - "createdAt": timestamp, - "updatedAt": timestamp, - "tags": ["epiphany", "Persona-goal", &goal.scope], - }) -} - -fn persona_status_read( - overlay: &GhostlightPerceivedStateOverlay, - persona_id: &str, - timestamp: &str, -) -> Value { - serde_json::json!({ - "id": overlay.overlay_id, - "status": "active", - "target": persona_target("self", persona_id, &overlay.label), - "statusKind": "uncertainty", - "summary": overlay.summary, - "confidence": overlay.confidence, - "intensity": overlay.salience, - "valence": 0, - "updatedAt": timestamp, - "extensions": { - "source": overlay.source, - "linkedMemoryIds": overlay.linked_memory_ids, - }, - }) -} - -fn persona_doctrine_stance(value: &GhostlightValue, persona_id: &str, timestamp: &str) -> Value { - serde_json::json!({ - "id": format!("stance-{}", value.value_id), - "status": "active", - "target": persona_target("self", persona_id, persona_id), - "stanceKind": "aligned", - "principle": value.label, - "summary": value.label, - "actionImplication": "Let this value bend Persona speech and interpretation without granting automatic action authority.", - "intensity": value.priority, - "updatedAt": timestamp, - }) -} - -fn persona_goal_status(status: &str) -> &'static str { - match status { - "active" => "active", - "blocked" | "dormant" => "cooling", - "resolved" => "resolved", - "abandoned" => "retired", - _ => "draft", - } -} - fn now_rfc3339() -> String { Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true) } @@ -2621,45 +1355,6 @@ pub fn organ_state_profile_for_role(role_id: &str) -> EpiphanyOrganStateProfile } } -pub fn project_agent_memory_to_json_dir( - store_path: impl AsRef, - output_dir: impl AsRef, -) -> Result { - let store_path = store_path.as_ref(); - let output_dir = output_dir.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - fs::create_dir_all(output_dir) - .with_context(|| format!("failed to create {}", output_dir.display()))?; - let mut projected = Vec::new(); - for (role_id, _, filename) in ROLE_TARGETS { - let entry = cache - .get::(role_id)? - .ok_or_else(|| anyhow!("missing role memory entry for {role_id:?}"))?; - let projection = AgentMemoryProjection { - schema_version: entry.schema_version, - world: entry.world, - agents: vec![entry.agent], - relationships: entry.relationships, - events: entry.events, - scenes: entry.scenes, - }; - let path = output_dir.join(filename); - fs::write( - &path, - format!("{}\n", serde_json::to_string_pretty(&projection)?), - ) - .with_context(|| format!("failed to write {}", path.display()))?; - projected.push(serde_json::json!({"roleId": role_id, "path": path})); - } - Ok(serde_json::json!({ - "ok": true, - "store": store_path, - "outputDir": output_dir, - "projected": projected, - })) -} - fn agent_memory_cache(store_path: &Path) -> Result { let mut cache = CultCache::new(); cache.register_entry_type::()?; @@ -2675,64 +1370,10 @@ fn agent_memory_cache(store_path: &Path) -> Result { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(store_path)); Ok(cache) } -fn canonical_group_mut<'a>( - state: &'a mut GhostlightCanonicalState, - group_name: &str, -) -> Result<&'a mut BTreeMap> { - match group_name { - "underlying_organization" => Ok(&mut state.underlying_organization), - "stable_dispositions" => Ok(&mut state.stable_dispositions), - "behavioral_dimensions" => Ok(&mut state.behavioral_dimensions), - "presentation_strategy" => Ok(&mut state.presentation_strategy), - "voice_style" => Ok(&mut state.voice_style), - "situational_state" => Ok(&mut state.situational_state), - other => Err(anyhow!( - "unknown canonical_state group {:?}; expected one of the six Ghostlight trait bundles", - other - )), - } -} - -fn entry_from_projection( - role_id: &str, - expected_agent_id: &str, - projection: AgentMemoryProjection, -) -> Result { - if projection.schema_version != AGENT_MEMORY_SCHEMA_VERSION { - return Err(anyhow!( - "schema_version must be {:?}", - AGENT_MEMORY_SCHEMA_VERSION - )); - } - if projection.agents.len() != 1 { - return Err(anyhow!( - "role memory projection must contain exactly one agent" - )); - } - let agent = projection.agents.into_iter().next().expect("checked len"); - if agent.agent_id != expected_agent_id { - return Err(anyhow!( - "agent_id {:?} does not match expected {:?}", - agent.agent_id, - expected_agent_id - )); - } - Ok(EpiphanyAgentMemoryEntry { - schema_version: projection.schema_version, - role_id: role_id.to_string(), - world: projection.world, - agent, - relationships: projection.relationships, - events: projection.events, - scenes: projection.scenes, - }) -} - fn validate_agent_entry(entry: &EpiphanyAgentMemoryEntry, expected_agent_id: &str) -> Vec { let mut errors = Vec::new(); if entry.schema_version != AGENT_MEMORY_SCHEMA_VERSION { @@ -3007,14 +1648,6 @@ fn check_unit(value: f64, path: &str, errors: &mut Vec) { } } -fn clamp_unit(value: f64) -> f64 { - if !value.is_finite() { - 0.5 - } else { - value.clamp(0.0, 1.0) - } -} - fn agent_id_for_role(role_id: &str) -> std::result::Result<&'static str, String> { ROLE_TARGETS .iter() @@ -3316,659 +1949,19 @@ mod tests { use tempfile::tempdir; #[test] - fn fresh_agent_memory_initializes_all_organs_and_replays_exactly() -> Result<()> { + fn fresh_store_initialization_is_exact_and_idempotent() -> Result<()> { let temp = tempdir()?; let store = temp.path().join("fresh-agent-memory.cc"); let witness = initialize_fresh_agent_memory_store(&store, "fresh-swarm")?; assert_eq!(witness.generation, 1); - assert_eq!(witness.previous_generation, 0); - assert_eq!(witness.mutation_kind, "fresh-bootstrap-admission"); assert_eq!(witness.changed_role_ids.len(), ROLE_TARGETS.len()); assert_eq!( initialize_fresh_agent_memory_store(&store, "fresh-swarm")?, witness ); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - for (role_id, agent_id, _) in ROLE_TARGETS { - let entry = cache - .get::(role_id)? - .expect("fresh role entry"); - assert_eq!(entry.agent.agent_id, *agent_id); - assert!(validate_agent_entry(&entry, agent_id).is_empty()); - } Ok(()) } - - #[test] - fn legacy_mind_admission_preserves_rows_and_adds_exact_generation_pressure() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents-legacy-admission"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("legacy-admission.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "legacy-admission-swarm")?; - let mut before_cache = agent_memory_cache(&store)?; - before_cache.pull_all_backing_stores()?; - let before = before_cache - .snapshot_envelopes() - .into_iter() - .filter(|envelope| envelope.r#type == AGENT_MEMORY_TYPE) - .collect::>(); - - let witness = admit_legacy_agent_memory_generation(&store)?; - assert_eq!(witness.generation, 1); - assert_eq!(witness.previous_generation, 0); - assert_eq!(witness.mutation_kind, "legacy-bootstrap-admission"); - assert!(witness.changed_role_ids.is_empty()); - assert_eq!(admit_legacy_agent_memory_generation(&store)?, witness); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let after = cache - .snapshot_envelopes() - .into_iter() - .filter(|envelope| envelope.r#type == AGENT_MEMORY_TYPE) - .collect::>(); - assert_eq!(after, before); - let receipt = cache - .get::(&witness.authority_receipt_id)? - .expect("legacy admission receipt"); - assert_eq!(receipt.resulting_source_hash, witness.source_hash); - let obligations = cache - .get_all::()? - .into_iter() - .filter(|obligation| obligation.source_commit_id == witness.witness_id) - .collect::>(); - assert_eq!(obligations.len(), 1); - assert_eq!(obligations[0].partition, "mind"); - assert_eq!(obligations[0].source_generation, 1); - assert!(!obligations[0].source_model_hash.is_empty()); - let scope_id = format!("{}:{}", obligations[0].swarm_id, obligations[0].partition); - cache.put( - &scope_id, - &crate::MemorySemanticProjectionRetentionHead { - schema_version: crate::MEMORY_SEMANTIC_PROJECTION_RETENTION_HEAD_SCHEMA_VERSION - .to_string(), - scope_id: scope_id.clone(), - revision: 1, - retired_generation_count: 1, - retired_envelope_count: 1, - retired_type_counts: BTreeMap::from([("test.retired".to_string(), 1)]), - retired_status_counts: BTreeMap::from([("failed".to_string(), 1)]), - retired_chain_digest: format!("sha256:{}", "0".repeat(64)), - retained_at: "2026-07-15T00:00:01Z".to_string(), - private_state_exposed: false, - }, - )?; - let input = agent_memory_semantic_projection_input(&store)?; - assert_eq!(input.obligation, obligations[0]); - assert_eq!(input.snapshot.model_revision, 1); - assert_eq!(input.authority.envelopes.len(), ROLE_TARGETS.len() + 2); - assert!(repair_agent_memory_store(&store).is_err()); - Ok(()) - } - - #[test] - fn concurrent_mind_commits_have_one_generation_and_no_partial_companions() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents-concurrent"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("concurrent.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "concurrent-swarm")?; - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let (generation, source_hash) = agent_memory_source_head(&mut cache)?; - let base = cache.get_required::("modeling")?; - let barrier = std::sync::Arc::new(std::sync::Barrier::new(3)); - let mut joins = Vec::new(); - for suffix in ["alpha", "beta"] { - let mut next = base.clone(); - next.agent.identity.public_description = format!( - "Modeling generation {suffix} preserves a distinct concurrent candidate truth." - ); - let barrier = barrier.clone(); - let store = store.clone(); - let source_hash = source_hash.clone(); - joins.push(std::thread::spawn(move || { - barrier.wait(); - commit_agent_memory_generation( - &store, - generation, - &source_hash, - "modeling", - next, - "self_patch", - &format!("Concurrent {suffix} admission candidate"), - ) - })); - } - barrier.wait(); - let results = joins - .into_iter() - .map(|join| join.join().expect("Mind commit worker panicked")) - .collect::>(); - assert_eq!(results.iter().filter(|result| result.is_ok()).count(), 1); - assert_eq!(results.iter().filter(|result| result.is_err()).count(), 1); - let witness = load_agent_memory_generation_witness(&store)?.expect("generation witness"); - assert_eq!(witness.generation, 1); - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let receipt = - cache.get::(&witness.authority_receipt_id)?; - assert!(receipt.is_some()); - let obligations = cache - .get_all::()? - .into_iter() - .filter(|obligation| obligation.source_commit_id == witness.witness_id) - .collect::>(); - assert_eq!(obligations.len(), 1); - let obligation = &obligations[0]; - assert_eq!(obligation.source_generation, witness.generation); - assert_eq!(obligation.partition, "mind"); - assert_eq!(obligation.swarm_id, witness.swarm_id); - let admitted = cache.get_required::("modeling")?; - assert!(write_agent_memory_entry_for_role_migration(&store, &admitted).is_err()); - assert!(repair_agent_memory_store(&store).is_err()); - assert!( - apply_agent_canonical_trait_seeds( - &[AgentCanonicalTraitSeed { - role_id: "coordinator".to_string(), - group_name: "underlying_organization".to_string(), - trait_name: "forbidden_after_admission".to_string(), - mean: 0.5, - plasticity: 0.5, - current_activation: 0.5, - source: Some("hostile bypass test".to_string()), - }], - &store, - ) - .is_err() - ); - - let mut corrupted = admitted; - corrupted - .agent - .identity - .public_description - .push_str(" corrupt"); - cache.put("modeling", &corrupted)?; - assert!(agent_memory_source_head(&mut cache).is_err()); - Ok(()) - } - - #[test] - fn role_memory_migrates_reviews_and_applies_native_patch() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents"); - fs::create_dir_all(&agent_dir)?; - fs::write( - agent_dir.join("modeling.agent-state.json"), - sample_agent_json("epiphany.modeling", "Modeling"), - )?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - if *role_id == "modeling" { - continue; - } - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("agents.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "test-swarm")?; - assert!(validate_agent_memory_store(&store)?.is_empty()); - - let patch: AgentSelfPatch = serde_json::from_value(serde_json::json!({ - "agentId": "epiphany.modeling", - "reason": "Modeling should remember accepted graph growth must stay source-grounded.", - "semanticMemories": [{ - "memoryId": "mem-body-native-source-grounding", - "summary": "Native role memory patches update typed CultCache organ state rather than JSON dossier files.", - "salience": 0.74, - "confidence": 0.86 - }] - }))?; - let review = review_agent_self_patch_document("modeling", &patch, &store); - assert_eq!(review.status, "accepted"); - let applied = apply_agent_self_patch_document("modeling", patch, &store)?; - assert_eq!(applied.status, "accepted"); - assert_eq!(applied.applied, Some(true)); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let modeling = cache.get_required::("modeling")?; - assert!( - modeling - .agent - .memories - .semantic - .iter() - .any(|memory| memory.memory_id == "mem-body-native-source-grounding") - ); - Ok(()) - } - - #[test] - fn memory_lifecycle_operation_revises_crystallizes_merges_retires_and_prunes() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("agents.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "test-swarm")?; - - let patch: AgentSelfPatch = serde_json::from_value(serde_json::json!({ - "agentId": "epiphany.modeling", - "reason": "Seed memories so the lifecycle operation can prove maintenance without identity edits.", - "semanticMemories": [ - { - "memoryId": "mem-lifecycle-alpha", - "summary": "Alpha memory starts as short doctrine needing revision.", - "salience": 0.42, - "confidence": 0.7 - }, - { - "memoryId": "mem-lifecycle-beta", - "summary": "Beta support should merge with gamma.", - "salience": 0.31, - "confidence": 0.72 - }, - { - "memoryId": "mem-lifecycle-gamma", - "summary": "Gamma support should merge with beta.", - "salience": 0.33, - "confidence": 0.74 - } - ] - }))?; - assert_eq!( - apply_agent_self_patch_document("modeling", patch, &store)?.status, - "accepted" - ); - - let operation = AgentMemoryLifecycleOperation { - agent_id: Some("epiphany.modeling".to_string()), - reason: "Sleep maintenance is revising, crystallizing, merging, retiring, and pruning bounded role memory.".to_string(), - actions: vec![ - AgentMemoryLifecycleAction::Revise { - bundle: AgentMemoryBundle::Semantic, - memory_id: "mem-lifecycle-alpha".to_string(), - summary: Some("Alpha memory was revised before crystallization.".to_string()), - salience: Some(0.81), - confidence: None, - reason: "The old wording was too weak for durable modeling doctrine.".to_string(), - }, - AgentMemoryLifecycleAction::Crystallize { - from_bundle: AgentMemoryBundle::Semantic, - to_bundle: AgentMemoryBundle::Episodic, - memory_id: "mem-lifecycle-alpha".to_string(), - new_memory_id: Some("mem-lifecycle-alpha-episode".to_string()), - summary: Some("Alpha became an episodic scar from sleep maintenance.".to_string()), - reason: "The revised pressure should leave a separate episodic witness.".to_string(), - }, - AgentMemoryLifecycleAction::Merge { - bundle: AgentMemoryBundle::Semantic, - target_memory_id: "mem-lifecycle-merged".to_string(), - source_memory_ids: vec![ - "mem-lifecycle-beta".to_string(), - "mem-lifecycle-gamma".to_string(), - ], - summary: "Beta and gamma collapsed into one support memory.".to_string(), - salience: 0.77, - confidence: 0.82, - reason: "Duplicate support belongs in one clearer memory.".to_string(), - }, - AgentMemoryLifecycleAction::Retire { - bundle: AgentMemoryBundle::Semantic, - memory_id: "mem-lifecycle-alpha".to_string(), - reason: "The revised alpha pressure was crystallized and should leave semantic room.".to_string(), - }, - AgentMemoryLifecycleAction::Prune { - bundle: AgentMemoryBundle::Semantic, - max_records: 1, - minimum_salience: Some(0.5), - reason: "Keep only strong semantic residue after merge.".to_string(), - }, - ], - extra: BTreeMap::new(), - }; - - let review = review_agent_memory_lifecycle_operation("modeling", &operation, &store); - assert_eq!(review.status, "accepted"); - let applied = apply_agent_memory_lifecycle_operation("modeling", operation, &store)?; - assert_eq!(applied.status, "accepted"); - assert_eq!(applied.applied, Some(true)); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let modeling = cache.get_required::("modeling")?; - assert_eq!(modeling.agent.memories.semantic.len(), 1); - assert_eq!( - modeling.agent.memories.semantic[0].memory_id, - "mem-lifecycle-merged" - ); - assert!( - modeling - .agent - .memories - .episodic - .iter() - .any(|memory| memory.memory_id == "mem-lifecycle-alpha-episode") - ); - assert!(validate_agent_memory_store(&store)?.is_empty()); - Ok(()) - } - - #[test] - fn canonical_trait_seeds_replace_baseline_vectors() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("agents.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "test-swarm")?; - - let applied = apply_agent_canonical_trait_seeds( - &[AgentCanonicalTraitSeed { - role_id: "coordinator".to_string(), - group_name: "underlying_organization".to_string(), - trait_name: "routing_discipline".to_string(), - mean: 0.92, - plasticity: 0.22, - current_activation: 0.9, - source: Some("startup personality projection smoke".to_string()), - }], - &store, - )?; - assert_eq!(applied["applied"], 1); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let entry = cache.get_required::("coordinator")?; - assert!( - !entry - .agent - .canonical_state - .underlying_organization - .contains_key("baseline") - ); - assert_eq!( - entry - .agent - .canonical_state - .underlying_organization - .get("routing_discipline") - .map(|vector| vector.mean), - Some(0.92) - ); - Ok(()) - } - - #[test] - fn organ_state_profiles_distinguish_persona_from_lane_organs() { - let persona = organ_state_profile_for_role("Persona"); - assert_eq!(persona.profile_kind, EpiphanyOrganStateProfileKind::Persona); - assert_eq!(persona.state_density, "persona_grade"); - assert_eq!(persona.portable_contract, "gamecult.persona_state.v0"); - assert_eq!(persona.affect_model, "persona_affect_allowed_and_expected"); - - let hands = organ_state_profile_for_role("implementation"); - assert_eq!(hands.profile_kind, EpiphanyOrganStateProfileKind::WorkOrgan); - assert_eq!(hands.state_density, "lean_work_organ"); - assert_eq!(hands.portable_contract, "epiphany.work_organ_state.v0"); - assert_eq!(hands.affect_model, "no_affect_or_persona_machinery"); - } - - #[test] - fn agent_memory_projects_swarm_state_as_soa_columns() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - fs::write( - agent_dir.join(filename), - sample_agent_json(agent_id, &format!("Agent {role_id}")), - )?; - } - let store = temp.path().join("agents.msgpack"); - migrate_agent_memory_json_dir_to_cultcache(&agent_dir, &store)?; - ensure_agent_memory_swarm_identity(&store, "test-swarm")?; - - let refreshed = refresh_agent_state_soa(&store)?; - assert_eq!(refreshed["rowCount"], 7); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let entry = cache.get_required::(AGENT_STATE_SOA_KEY)?; - assert_eq!(entry.schema_version, AGENT_STATE_SOA_SCHEMA_VERSION); - assert_eq!(entry.role_ids.len(), 7); - assert!(entry.role_ids.iter().any(|role| role == "Persona")); - assert!( - entry - .agent_ids - .iter() - .any(|agent_id| agent_id == "epiphany.Persona") - ); - assert_eq!(entry.role_ids.len(), entry.semantic_memory_counts.len()); - assert!( - entry - .portable_contracts - .iter() - .any(|contract| contract == "gamecult.persona_state.v0") - ); - Ok(()) - } - - #[test] - fn repair_agent_memory_store_promotes_legacy_face_and_modeling_vessel() -> Result<()> { - let temp = tempdir()?; - let agent_dir = temp.path().join("agents"); - fs::create_dir_all(&agent_dir)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - if *role_id == "Persona" { - continue; - } - let actual_agent_id = if *role_id == "modeling" { - "epiphany.proprioception" - } else { - agent_id - }; - fs::write( - agent_dir.join(filename), - sample_agent_json(actual_agent_id, &format!("Agent {role_id}")), - )?; - } - fs::write( - agent_dir.join("face.agent-state.json"), - sample_agent_json("epiphany.face", "Face"), - )?; - - let store = temp.path().join("agents.msgpack"); - let mut cache = agent_memory_cache(&store)?; - for (role_id, agent_id, filename) in ROLE_TARGETS { - if *role_id == "Persona" { - continue; - } - let raw = fs::read_to_string(agent_dir.join(filename))?; - let projection: AgentMemoryProjection = serde_json::from_str(&raw)?; - let expected_agent_id = if *role_id == "modeling" { - "epiphany.proprioception" - } else { - agent_id - }; - let mut entry = entry_from_projection(role_id, expected_agent_id, projection)?; - if *role_id == "modeling" { - entry.agent.identity.public_description = - "Proprioception models source-grounded anatomy.".to_string(); - entry.agent.goals[0].emotional_stake = - "If Proprioception lies, Hands cuts blind.".to_string(); - entry.agent.memories.semantic.push(GhostlightMemory { - memory_id: "legacy-proprioception-prose".to_string(), - summary: "Proprioception leaves a verified trail.".to_string(), - salience: 0.8, - confidence: 0.8, - linked_event_ids: None, - linked_relationship_id: None, - }); - } - cache.put((*role_id).to_string(), &entry)?; - } - let raw = fs::read_to_string(agent_dir.join("face.agent-state.json"))?; - let projection: AgentMemoryProjection = serde_json::from_str(&raw)?; - let face = entry_from_projection("face", "epiphany.face", projection)?; - cache.put("face".to_string(), &face)?; - let projection: AgentMemoryProjection = - serde_json::from_str(&sample_agent_json("epiphany.life", "Life"))?; - let reorientation = entry_from_projection("reorientation", "epiphany.life", projection)?; - cache.put("reorientation".to_string(), &reorientation)?; - - let errors = validate_agent_memory_store(&store)?; - assert!(errors.iter().any(|error| error.contains("proprioception"))); - assert!( - errors - .iter() - .any(|error| error.contains("Persona: missing")) - ); - - let repaired = repair_agent_memory_store(&store)?; - assert_eq!(repaired["ok"], true); - assert!(validate_agent_memory_store(&store)?.is_empty()); - let refreshed = refresh_agent_state_soa(&store)?; - assert_eq!(refreshed["rowCount"], 7); - - let mut cache = agent_memory_cache(&store)?; - cache.pull_all_backing_stores()?; - let modeling = cache.get_required::("modeling")?; - assert_eq!(modeling.agent.agent_id, "epiphany.modeling"); - assert!( - !modeling - .agent - .identity - .public_description - .contains("Proprioception") - ); - assert!( - modeling - .agent - .goals - .iter() - .all(|goal| !goal.emotional_stake.contains("Proprioception")) - ); - assert!( - modeling - .agent - .memories - .semantic - .iter() - .all(|memory| { !memory.summary.contains("Proprioception") }) - ); - let persona = cache.get_required::("Persona")?; - assert_eq!(persona.role_id, "Persona"); - assert_eq!(persona.agent.agent_id, "epiphany.Persona"); - assert!( - persona - .agent - .identity - .roles - .iter() - .any(|role| role == "Persona") - ); - assert!( - cache - .get::("reorientation")? - .is_none() - ); - Ok(()) - } - - fn sample_agent_json(agent_id: &str, name: &str) -> String { - serde_json::json!({ - "schema_version": AGENT_MEMORY_SCHEMA_VERSION, - "world": { - "world_id": "epiphany-agent-memory", - "setting": "Epiphany local harness role memory", - "time": {"label": "standing memory"}, - "canon_context": ["Organ-state records preserve lane identity."] - }, - "agents": [{ - "agent_id": agent_id, - "identity": { - "name": name, - "roles": ["test"], - "origin": "test lane", - "public_description": "Test role memory.", - "private_notes": [] - }, - "canonical_state": { - "underlying_organization": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "stable_dispositions": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "behavioral_dimensions": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "presentation_strategy": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "voice_style": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "situational_state": {"test": {"mean": 0.5, "plasticity": 0.5, "current_activation": 0.5}}, - "values": [{ - "value_id": "value-test", - "label": "Test value.", - "priority": 0.5, - "unforgivable_if_betrayed": false - }] - }, - "goals": [{ - "goal_id": "goal-test", - "description": "Keep the test role valid.", - "scope": "life", - "priority": 0.5, - "emotional_stake": "Broken memory makes broken agents.", - "blockers": [], - "status": "active" - }], - "memories": { - "episodic": [], - "semantic": [{ - "memory_id": "mem-test", - "summary": "Test memory.", - "salience": 0.5, - "confidence": 0.5 - }], - "relationship_summaries": [] - }, - "perceived_state_overlays": [] - }], - "relationships": [], - "events": [], - "scenes": [] - }) - .to_string() - } } #[test] fn immutable_swarm_identity_separates_stores_and_refuses_collision() -> Result<()> { diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index 246db509..30219912 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -1,4 +1,3 @@ -use crate::EpiphanyAgentStateSoaEntry; use crate::default_continuity_cultnet_contracts; use crate::default_eyes_cultnet_contracts; use crate::default_hands_cultnet_contracts; @@ -30,10 +29,8 @@ use chrono::DateTime; use chrono::FixedOffset; use chrono::Utc; use cultcache_rs::CacheBackingStore; -use cultcache_rs::CultSoaColumnValues; use cultcache_rs::DatabaseEntry; use cultcache_rs::SingleFileMessagePackBackingStore; -use cultcache_rs::SoaDocument; use cultmesh_rs::CultMesh; use cultmesh_rs::CultMeshNode; use cultmesh_rs::CultMeshNodeOptions; @@ -87,12 +84,6 @@ pub const EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_SCHEMA_VERSION: &str = "epiphany.cultmesh.work_loop_telemetry.v0"; pub const EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_LATEST_KEY: &str = "epiphany-internal/work-loop-telemetry/latest"; -pub const EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_TYPE: &str = - "epiphany.cultmesh.agent_state_soa_summary"; -pub const EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION: &str = - "epiphany.cultmesh.agent_state_soa_summary.v0"; -pub const EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY: &str = - "epiphany-local/agent-state-soa-summary/latest"; pub const EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_TYPE: &str = "epiphany.cultmesh.repo_work_overview"; pub const EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_SCHEMA_VERSION: &str = "epiphany.cultmesh.repo_work_overview.v0"; @@ -681,95 +672,6 @@ pub struct EpiphanyCultMeshWorkLoopTelemetryEntry { pub verification_assertions: Vec, } -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.cultmesh.agent_state_soa_summary", - schema = "EpiphanyCultMeshAgentStateSoaSummaryEntry" -)] -pub struct EpiphanyCultMeshAgentStateSoaSummaryEntry { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub runtime_id: String, - #[cultcache(key = 2)] - pub verse_id: String, - #[cultcache(key = 3)] - pub summary_id: String, - #[cultcache(key = 4)] - pub generated_at: String, - #[cultcache(key = 5)] - pub source_store: String, - #[cultcache(key = 6)] - pub row_count: u32, - #[cultcache(key = 7)] - pub role_ids: Vec, - #[cultcache(key = 8)] - pub agent_ids: Vec, - #[cultcache(key = 9)] - pub display_names: Vec, - #[cultcache(key = 10)] - pub profile_kinds: Vec, - #[cultcache(key = 11)] - pub portable_contracts: Vec, - #[cultcache(key = 12)] - pub semantic_memory_counts: Vec, - #[cultcache(key = 13)] - pub episodic_memory_counts: Vec, - #[cultcache(key = 14)] - pub relationship_memory_counts: Vec, - #[cultcache(key = 15)] - pub goal_counts: Vec, - #[cultcache(key = 16)] - pub value_counts: Vec, - #[cultcache(key = 17)] - pub private_state_exposed: bool, - #[cultcache(key = 18)] - pub notes: Vec, -} - -impl SoaDocument for EpiphanyCultMeshAgentStateSoaSummaryEntry { - fn soa_columns(rows: &[Self]) -> std::collections::BTreeMap<&'static str, CultSoaColumnValues> { - let mut columns = std::collections::BTreeMap::new(); - columns.insert( - "summaryId", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.summary_id.clone()) - .collect::>(), - ), - ); - columns.insert( - "rowCount", - CultSoaColumnValues::new(rows.iter().map(|row| row.row_count).collect::>()), - ); - columns.insert( - "privateStateExposed", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.private_state_exposed) - .collect::>(), - ), - ); - columns.insert( - "roleIds", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.role_ids.clone()) - .collect::>(), - ), - ); - columns.insert( - "portableContracts", - CultSoaColumnValues::new( - rows.iter() - .map(|row| row.portable_contracts.clone()) - .collect::>(), - ), - ); - columns - } -} - #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.cultmesh.repo_work_overview", @@ -2439,7 +2341,6 @@ pub struct EpiphanyLocalVerseContext { pub latest_hands_action_gate: Option, pub latest_role_review_event: Option, pub latest_work_loop_summary: Option, - pub latest_agent_state_soa_summary: Option, pub latest_repo_work_overview: Option, pub latest_repo_work_map_entry: Option, pub contract_summaries: Vec, @@ -2488,7 +2389,6 @@ cultmesh_documents!(EpiphanyCultMeshDocuments { EpiphanyCultMeshHandsActionGateEntry => EPIPHANY_CULTMESH_HANDS_ACTION_GATE_SCHEMA_VERSION, EpiphanyCultMeshRoleReviewEventEntry => EPIPHANY_CULTMESH_ROLE_REVIEW_EVENT_SCHEMA_VERSION, EpiphanyCultMeshWorkLoopTelemetryEntry => EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_SCHEMA_VERSION, - EpiphanyCultMeshAgentStateSoaSummaryEntry => EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION, EpiphanyCultMeshRepoWorkOverviewEntry => EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_SCHEMA_VERSION, EpiphanyCultMeshRepoWorkReadinessEntry => EPIPHANY_CULTMESH_REPO_WORK_READINESS_SCHEMA_VERSION, EpiphanyCultMeshRepoWorkMapEntry => EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_SCHEMA_VERSION, @@ -5939,136 +5839,6 @@ pub fn epiphany_local_verse_work_loop_summary( } } -pub fn epiphany_cultmesh_agent_state_soa_summary_from_entry( - runtime_id: impl Into, - summary_id: impl Into, - soa: &EpiphanyAgentStateSoaEntry, -) -> EpiphanyCultMeshAgentStateSoaSummaryEntry { - EpiphanyCultMeshAgentStateSoaSummaryEntry { - schema_version: EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION.to_string(), - runtime_id: runtime_id.into(), - verse_id: EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID.to_string(), - summary_id: summary_id.into(), - generated_at: soa.generated_at.clone(), - source_store: soa.source_store.clone(), - row_count: soa.role_ids.len() as u32, - role_ids: soa.role_ids.clone(), - agent_ids: soa.agent_ids.clone(), - display_names: soa.display_names.clone(), - profile_kinds: soa.profile_kinds.clone(), - portable_contracts: soa.portable_contracts.clone(), - semantic_memory_counts: soa.semantic_memory_counts.clone(), - episodic_memory_counts: soa.episodic_memory_counts.clone(), - relationship_memory_counts: soa.relationship_memory_counts.clone(), - goal_counts: soa.goal_counts.clone(), - value_counts: soa.value_counts.clone(), - private_state_exposed: false, - notes: vec![ - "Summary mirrors persisted epiphany.agent_state_soa.v0 column shape for local Verse discovery; agent memory text remains in the agent-memory store.".to_string(), - "CultMesh carries row/column topology for Odin, Eve, and prompt assembly without becoming the agent-memory owner.".to_string(), - ], - } -} - -pub fn write_epiphany_cultmesh_agent_state_soa_summary( - store_path: impl AsRef, - summary: EpiphanyCultMeshAgentStateSoaSummaryEntry, -) -> Result { - validate_agent_state_soa_summary(&summary)?; - let mut node = open_epiphany_cultmesh_node(&store_path, summary.runtime_id.clone())?; - let written = node.put(summary.summary_id.clone(), &summary)?; - let current_latest = node.get::( - EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY, - )?; - if current_latest.as_ref().is_none_or(|current| { - agent_state_soa_generation_key(&written) >= agent_state_soa_generation_key(current) - }) { - node.put( - EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY, - &written, - )?; - } - node.flush()?; - Ok(written) -} - -pub fn load_latest_epiphany_cultmesh_agent_state_soa_summary( - store_path: impl AsRef, - runtime_id: impl Into, -) -> Result> { - let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; - node.get(EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY) -} - -fn validate_agent_state_soa_summary( - summary: &EpiphanyCultMeshAgentStateSoaSummaryEntry, -) -> Result<()> { - if summary.private_state_exposed { - return Err(anyhow!( - "agent state SoA summaries must not expose private state" - )); - } - if summary.schema_version != EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION { - return Err(anyhow!( - "agent state SoA summary schema_version must be {:?}", - EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION - )); - } - if summary.verse_id != EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID { - return Err(anyhow!( - "agent state SoA summary belongs in the local-area Verse" - )); - } - DateTime::parse_from_rfc3339(&summary.generated_at) - .map_err(|error| anyhow!("agent state SoA summary has invalid generated_at: {error}"))?; - let len = summary.role_ids.len(); - if summary.row_count as usize != len { - return Err(anyhow!( - "agent state SoA summary row_count is {}, expected {}", - summary.row_count, - len - )); - } - for (name, candidate) in [ - ("agentIds", summary.agent_ids.len()), - ("displayNames", summary.display_names.len()), - ("profileKinds", summary.profile_kinds.len()), - ("portableContracts", summary.portable_contracts.len()), - ("semanticMemoryCounts", summary.semantic_memory_counts.len()), - ("episodicMemoryCounts", summary.episodic_memory_counts.len()), - ( - "relationshipMemoryCounts", - summary.relationship_memory_counts.len(), - ), - ("goalCounts", summary.goal_counts.len()), - ("valueCounts", summary.value_counts.len()), - ] { - if candidate != len { - return Err(anyhow!( - "agent state SoA summary column {name} has length {candidate}, expected {len}" - )); - } - } - if summary - .role_ids - .iter() - .any(|role_id| role_id.trim().is_empty()) - { - return Err(anyhow!("agent state SoA summary contains an empty role id")); - } - Ok(()) -} - -fn agent_state_soa_generation_key( - summary: &EpiphanyCultMeshAgentStateSoaSummaryEntry, -) -> (DateTime, &str) { - ( - DateTime::parse_from_rfc3339(&summary.generated_at) - .expect("validated agent state SoA generation timestamp"), - summary.summary_id.as_str(), - ) -} - pub fn load_latest_epiphany_cultmesh_repo_work_overview( store_path: impl AsRef, runtime_id: impl Into, @@ -6375,8 +6145,6 @@ pub fn query_epiphany_local_verse_context( )? .as_ref() .map(epiphany_local_verse_work_loop_summary), - latest_agent_state_soa_summary: node - .get(EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY)?, latest_repo_work_overview: node.get(EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_LATEST_KEY)?, latest_repo_work_map_entry: node.get(EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_LATEST_KEY)?, contract_summaries, @@ -9485,124 +9253,6 @@ mod tests { Ok(()) } - #[test] - fn agent_state_soa_summary_mirrors_agent_store_shape_without_memory_text() -> Result<()> { - let temp = tempfile::tempdir()?; - let store = temp.path().join("epiphany-local-verse.ccmp"); - let soa = EpiphanyAgentStateSoaEntry { - schema_version: "epiphany.agent_state_soa.v0".to_string(), - generated_at: "2026-06-18T00:00:00Z".to_string(), - source_store: "state/agents.msgpack".to_string(), - role_ids: vec!["Persona".to_string(), "implementation".to_string()], - agent_ids: vec!["epiphany.Persona".to_string(), "epiphany.hands".to_string()], - display_names: vec!["Persona".to_string(), "Hands".to_string()], - profile_kinds: vec!["Persona".to_string(), "WorkOrgan".to_string()], - portable_contracts: vec![ - "gamecult.persona_state.v0".to_string(), - "epiphany.work_organ_state.v0".to_string(), - ], - semantic_memory_counts: vec![3, 2], - episodic_memory_counts: vec![1, 0], - relationship_memory_counts: vec![2, 0], - goal_counts: vec![1, 1], - value_counts: vec![4, 3], - }; - let summary = epiphany_cultmesh_agent_state_soa_summary_from_entry( - "epiphany-test", - "agent-state-soa-summary-test", - &soa, - ); - write_epiphany_cultmesh_agent_state_soa_summary(&store, summary.clone())?; - assert_eq!( - load_latest_epiphany_cultmesh_agent_state_soa_summary(&store, "epiphany-test")?, - Some(summary.clone()) - ); - let context = query_epiphany_local_verse_context(&store, "epiphany-test")?; - assert_eq!( - context.latest_agent_state_soa_summary, - Some(summary.clone()) - ); - let node = open_epiphany_cultmesh_node(&store, "epiphany-test")?; - assert!( - node.documents() - .binding(EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_TYPE) - .is_some() - ); - let summary_table = node.soa::()?; - assert_eq!(summary_table.len(), 2); - assert!( - summary_table - .column::("summaryId")? - .values() - .contains(&"agent-state-soa-summary-test".to_string()) - ); - assert!( - summary_table - .column::("rowCount")? - .values() - .contains(&2) - ); - assert!( - summary_table - .column::("privateStateExposed")? - .values() - .iter() - .all(|exposed| !exposed) - ); - assert!( - summary_table - .column::>("portableContracts")? - .values() - .iter() - .any(|contracts| contracts.contains(&"gamecult.persona_state.v0".to_string())) - ); - let serialized = serde_json::to_string(&summary)?; - assert!(serialized.contains("gamecult.persona_state.v0")); - assert!(!serialized.contains("privateNotes")); - assert!(!summary.private_state_exposed); - - let mut newer = summary.clone(); - newer.summary_id = "agent-state-soa-summary-newer".to_string(); - newer.generated_at = "2026-06-18T01:00:00Z".to_string(); - write_epiphany_cultmesh_agent_state_soa_summary(&store, newer.clone())?; - - let mut delayed = summary.clone(); - delayed.summary_id = "agent-state-soa-summary-delayed".to_string(); - delayed.generated_at = "2026-06-17T23:00:00Z".to_string(); - write_epiphany_cultmesh_agent_state_soa_summary(&store, delayed)?; - assert_eq!( - load_latest_epiphany_cultmesh_agent_state_soa_summary(&store, "epiphany-test")?, - Some(newer) - ); - - let mut invalid_time = summary.clone(); - invalid_time.summary_id = "agent-state-soa-summary-invalid".to_string(); - invalid_time.generated_at = "not-a-time".to_string(); - let err = write_epiphany_cultmesh_agent_state_soa_summary(&store, invalid_time) - .expect_err("invalid agent summary time must be refused"); - assert!(err.to_string().contains("invalid generated_at")); - - let mut wrong_verse = summary.clone(); - wrong_verse.summary_id = "agent-state-soa-summary-wrong-verse".to_string(); - wrong_verse.verse_id = EPIPHANY_CULTMESH_INTERNAL_VERSE_ID.to_string(); - let err = write_epiphany_cultmesh_agent_state_soa_summary(&store, wrong_verse) - .expect_err("agent summary outside local-area Verse must be refused"); - assert!(err.to_string().contains("local-area Verse")); - - let mut leaked = summary.clone(); - leaked.private_state_exposed = true; - let err = write_epiphany_cultmesh_agent_state_soa_summary(&store, leaked) - .expect_err("private-state agent summaries must be refused"); - assert!(err.to_string().contains("must not expose private state")); - - let mut drifted = summary; - drifted.agent_ids.pop(); - let err = write_epiphany_cultmesh_agent_state_soa_summary(&store, drifted) - .expect_err("column length drift must be refused"); - assert!(err.to_string().contains("column agentIds has length")); - Ok(()) - } - #[test] fn builtin_verse_policies_keep_public_and_private_boundaries_apart() -> Result<()> { let temp = tempfile::tempdir()?; diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 3756f42a..cfc21e2d 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -114,17 +114,12 @@ pub use agent_memory::AGENT_MEMORY_MIND_ADMISSION_TYPE; pub use agent_memory::AGENT_MEMORY_SWARM_IDENTITY_KEY; pub use agent_memory::AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION; pub use agent_memory::AGENT_MEMORY_SWARM_IDENTITY_TYPE; -pub use agent_memory::AgentCanonicalTraitSeed; -pub use agent_memory::AgentMemoryBundle; pub use agent_memory::AgentMemoryGenerationWitness; -pub use agent_memory::AgentMemoryLifecycleAction; -pub use agent_memory::AgentMemoryLifecycleOperation; pub use agent_memory::AgentMemoryMindAdmissionReceipt; pub use agent_memory::AgentMemoryReview; pub use agent_memory::AgentMemorySwarmIdentity; pub use agent_memory::AgentSelfPatch; pub use agent_memory::EpiphanyAgentMemoryEntry; -pub use agent_memory::EpiphanyAgentStateSoaEntry; pub use agent_memory::EpiphanyOrganStateProfile; pub use agent_memory::EpiphanyOrganStateProfileKind; pub use agent_memory::GhostlightAgent; @@ -141,28 +136,15 @@ pub use agent_memory::GhostlightTraitVector; pub use agent_memory::GhostlightValue; pub use agent_memory::GhostlightWorld; pub use agent_memory::PERSONA_STATE_SCHEMA_VERSION; -pub use agent_memory::admit_legacy_agent_memory_generation; pub use agent_memory::agent_memory_role_ids; pub use agent_memory::agent_memory_semantic_projection_input; -pub use agent_memory::agent_memory_status; -pub use agent_memory::apply_agent_canonical_trait_seeds; -pub use agent_memory::apply_agent_memory_lifecycle_operation; pub use agent_memory::apply_agent_self_patch; pub use agent_memory::apply_agent_self_patch_document; pub use agent_memory::ensure_agent_memory_swarm_identity; pub use agent_memory::initialize_fresh_agent_memory_store; pub use agent_memory::load_agent_memory_entry_for_role; -pub use agent_memory::load_agent_memory_generation_witness; pub use agent_memory::load_agent_memory_swarm_identity; -pub use agent_memory::load_agent_state_soa_entry; -pub use agent_memory::migrate_agent_memory_json_dir_to_cultcache; pub use agent_memory::organ_state_profile_for_role; -pub use agent_memory::project_agent_memory_to_json_dir; -pub use agent_memory::project_persona_state_for_role; -pub use agent_memory::project_persona_state_from_entry; -pub use agent_memory::refresh_agent_state_soa; -pub use agent_memory::repair_agent_memory_store; -pub use agent_memory::review_agent_memory_lifecycle_operation; pub use agent_memory::review_agent_self_patch; pub use agent_memory::review_agent_self_patch_document; pub use agent_memory::validate_agent_memory_store; @@ -205,9 +187,6 @@ pub use cultcache_rs::RedbMessagePackBackingStore; pub use cultcache_rs::SingleFileMessagePackBackingStore; pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_ID; pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_OWNER; -pub use cultmesh_integration::EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_LATEST_KEY; -pub use cultmesh_integration::EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_SCHEMA_VERSION; -pub use cultmesh_integration::EPIPHANY_CULTMESH_AGENT_STATE_SOA_SUMMARY_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_BIFROST_ARTIFACT_ACCEPTANCE_RECEIPT_ARRIVAL_LATEST_KEY; pub use cultmesh_integration::EPIPHANY_CULTMESH_BIFROST_ARTIFACT_ACCEPTANCE_RECEIPT_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_BIFROST_ARTIFACT_ACCEPTANCE_RECEIPT_TYPE; @@ -326,7 +305,6 @@ pub use cultmesh_integration::EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_SCHEMA_VERSI pub use cultmesh_integration::EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_TYPE; pub use cultmesh_integration::EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_DAEMON_ID; pub use cultmesh_integration::EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID; -pub use cultmesh_integration::EpiphanyCultMeshAgentStateSoaSummaryEntry; pub use cultmesh_integration::EpiphanyCultMeshBifrostArtifactAcceptanceReceiptEntry; pub use cultmesh_integration::EpiphanyCultMeshBifrostBodyChangePublicationIntentEntry; pub use cultmesh_integration::EpiphanyCultMeshBifrostBodyChangePublicationReceiptEntry; @@ -385,7 +363,6 @@ pub use cultmesh_integration::canonical_epiphany_swarm_brake_protected_surfaces; pub use cultmesh_integration::default_epiphany_cultmesh_swarm_brake; pub use cultmesh_integration::engage_epiphany_cultmesh_swarm_brake; pub use cultmesh_integration::epiphany_cluster_service_execution_audit_report; -pub use cultmesh_integration::epiphany_cultmesh_agent_state_soa_summary_from_entry; #[cfg(test)] pub use cultmesh_integration::epiphany_cultmesh_bifrost_artifact_acceptance_receipt_for_map_entry; pub use cultmesh_integration::epiphany_cultmesh_bifrost_body_change_publication_intent; @@ -456,7 +433,6 @@ pub use cultmesh_integration::load_epiphany_cultmesh_repo_work_readiness_reports pub use cultmesh_integration::load_epiphany_cultmesh_semantic_projection_health; pub use cultmesh_integration::load_epiphany_cultmesh_status; pub use cultmesh_integration::load_epiphany_cultmesh_swarm_brake; -pub use cultmesh_integration::load_latest_epiphany_cultmesh_agent_state_soa_summary; pub use cultmesh_integration::load_latest_epiphany_cultmesh_coordinator_run_receipt; pub use cultmesh_integration::load_latest_epiphany_cultmesh_daemon_heartbeat; pub use cultmesh_integration::load_latest_epiphany_cultmesh_daemon_poke_intent; @@ -487,7 +463,6 @@ pub use cultmesh_integration::release_epiphany_cultmesh_swarm_brake; pub use cultmesh_integration::retire_epiphany_cultmesh_legacy_provider_documents; pub use cultmesh_integration::retire_epiphany_cultmesh_operator_status_documents; pub use cultmesh_integration::seed_epiphany_local_verse_context; -pub use cultmesh_integration::write_epiphany_cultmesh_agent_state_soa_summary; #[cfg(test)] pub use cultmesh_integration::write_epiphany_cultmesh_bifrost_artifact_acceptance_receipt; pub use cultmesh_integration::write_epiphany_cultmesh_bifrost_body_change_publication_intent; diff --git a/epiphany-core/src/mind_documents.rs b/epiphany-core/src/mind_documents.rs index 740c35f2..ec6d291c 100644 --- a/epiphany-core/src/mind_documents.rs +++ b/epiphany-core/src/mind_documents.rs @@ -351,6 +351,7 @@ pub struct EpiphanyMindView { pub invariants: Vec, pub observations: Vec, pub evidence: Vec, + pub persona_memories: Vec, pub verification_audits: Vec, pub reorientation_decisions: Vec, pub reorientation_failures: Vec, @@ -588,6 +589,10 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result(&cache, |value| value.value)?; let mut evidence = values::(&cache, |value| value.value)?; + let mut persona_memories = cache.get_all::()?; + for memory in &persona_memories { + memory.validate()?; + } let mut verification_audits = cache.get_all::()?; let mut reorientation_decisions = cache.get_all::()?; @@ -638,6 +643,7 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result) -> Result()?; assert_eq!(first.len(), 1); - let memories = cache.get_all::()?; + let mut memories = cache.get_all::()?; + memories.sort_by(|left, right| left.memory_id.cmp(&right.memory_id)); assert_eq!(memories.len(), 2); assert!(memories.iter().all(|memory| { memory.agent_id == request.agent_id && memory.decision_context_id == document.decision_context_id })); + let view = crate::assemble_mind_view(&runtime)?; + assert_eq!(view.persona_memories, memories); + assert!(view.persona_memories.iter().all(|memory| { + view.source_documents.iter().any(|source| { + source.document_type == crate::EpiphanyMindPersonaMemoryDocument::TYPE + && source.document_key == memory.memory_id + }) + })); assert_eq!( admit_persona_state_notes( diff --git a/epiphany-core/src/persona_turn.rs b/epiphany-core/src/persona_turn.rs index bee322da..1cae4b5b 100644 --- a/epiphany-core/src/persona_turn.rs +++ b/epiphany-core/src/persona_turn.rs @@ -1,4 +1,4 @@ -use crate::EpiphanyAgentMemoryEntry; +use crate::EpiphanyMindPersonaMemoryDocument; use crate::EpiphanyOrganDependency; use crate::HeartbeatPendingMention; use crate::default_organ_dependencies_for; @@ -239,7 +239,7 @@ pub struct PersonaSocialAffordance { pub struct PersonaProjectorInput { pub identity: PersonaIdentity, #[serde(default)] - pub memory: Option, + pub memories: Vec, #[serde(default)] pub semantic_memory_recall: String, #[serde(default)] @@ -283,11 +283,7 @@ pub fn build_persona_projector_prompt_with_transcript( input: &PersonaProjectorInput, transcript: &[PersonaTranscriptMessage], ) -> String { - let memory = input - .memory - .as_ref() - .map(render_memory_packet) - .unwrap_or_else(|| "No durable Persona memory entry is loaded.".to_string()); + let memory = render_memory_packet(&input.memories); let dependencies = if input.organ_dependencies.is_empty() { vec![default_organ_dependencies_for("Persona")] } else { @@ -603,38 +599,20 @@ fn render_identity(identity: &PersonaIdentity) -> String { ) } -fn render_memory_packet(memory: &EpiphanyAgentMemoryEntry) -> String { - let values = memory - .agent - .canonical_state - .values +fn render_memory_packet(memories: &[EpiphanyMindPersonaMemoryDocument]) -> String { + if memories.is_empty() { + return "- no durable Persona memories".to_string(); + } + memories .iter() - .map(|value| format!("- {} ({:.2})", value.label, value.priority)) + .map(|memory| { + format!( + "- {} [{}; salience {:.2}; confidence {:.2}]", + memory.summary, memory.memory_kind, memory.salience, memory.confidence + ) + }) .collect::>() - .join("\n"); - let goals = memory - .agent - .goals - .iter() - .map(|goal| format!("- {} [{}]", goal.description, goal.status)) - .collect::>() - .join("\n"); - let notes = memory - .agent - .identity - .private_notes - .iter() - .map(|note| format!("- {note}")) - .collect::>() - .join("\n"); - format!( - "Identity: {}\nPublic description: {}\nPrivate notes:\n{}\nValues:\n{}\nGoals:\n{}", - memory.agent.identity.name, - memory.agent.identity.public_description, - fallback(¬es, "- none"), - fallback(&values, "- none"), - fallback(&goals, "- none"), - ) + .join("\n") } fn render_pending_mentions(mentions: &[HeartbeatPendingMention]) -> String { diff --git a/epiphany-core/src/prompt_context.rs b/epiphany-core/src/prompt_context.rs index c21b4187..48f652e3 100644 --- a/epiphany-core/src/prompt_context.rs +++ b/epiphany-core/src/prompt_context.rs @@ -103,53 +103,6 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str ); } - if let Some(summary) = input.local_verse.latest_agent_state_soa_summary.as_ref() { - lines.push("## Agent State SoA".to_string()); - lines.push(format!( - "- Summary `{}`: rows={}, source={}, private_state_exposed={}", - summary.summary_id, - summary.row_count, - summary.source_store, - summary.private_state_exposed - )); - for index in 0..summary.role_ids.len().min(7) { - lines.push(format!( - "- `{}` / `{}`: profile={}, contract={}, semantic={}, episodic={}, relationship={}, goals={}, values={}", - summary.role_ids[index], - summary.agent_ids.get(index).cloned().unwrap_or_default(), - summary.profile_kinds.get(index).cloned().unwrap_or_default(), - summary - .portable_contracts - .get(index) - .cloned() - .unwrap_or_default(), - summary - .semantic_memory_counts - .get(index) - .copied() - .unwrap_or_default(), - summary - .episodic_memory_counts - .get(index) - .copied() - .unwrap_or_default(), - summary - .relationship_memory_counts - .get(index) - .copied() - .unwrap_or_default(), - summary.goal_counts.get(index).copied().unwrap_or_default(), - summary.value_counts.get(index).copied().unwrap_or_default() - )); - } - push_omitted_count( - &mut lines, - summary.role_ids.len(), - 7, - "agent state SoA rows", - ); - } - if let Some(intent) = input .local_verse .latest_daemon_tool_invocation_intent @@ -404,15 +357,12 @@ mod tests { use super::*; use crate::EPIPHANY_CULTMESH_INTERNAL_VERSE_ID; use crate::EPIPHANY_CULTMESH_WORK_LOOP_TELEMETRY_SCHEMA_VERSION; - use crate::EpiphanyAgentStateSoaEntry; use crate::EpiphanyCultMeshWorkLoopTelemetryEntry; - use crate::epiphany_cultmesh_agent_state_soa_summary_from_entry; use crate::epiphany_cultmesh_bifrost_body_change_publication_intent; use crate::epiphany_cultmesh_bifrost_body_change_publication_receipt_for_intent; use crate::epiphany_cultmesh_bifrost_github_publication_receipt_for_publication; use crate::query_epiphany_local_verse_context; use crate::seed_epiphany_local_verse_context; - use crate::write_epiphany_cultmesh_agent_state_soa_summary; use crate::write_epiphany_cultmesh_bifrost_body_change_publication_intent; use crate::write_epiphany_cultmesh_bifrost_body_change_publication_receipt; use crate::write_epiphany_cultmesh_bifrost_github_publication_receipt; @@ -520,30 +470,6 @@ mod tests { verification_assertions: vec!["prompt renders digest only".to_string()], }, )?; - let agent_state_soa = EpiphanyAgentStateSoaEntry { - schema_version: "epiphany.agent_state_soa.v0".to_string(), - generated_at: "2026-06-18T00:00:00Z".to_string(), - source_store: "state/agents.msgpack".to_string(), - role_ids: vec!["Persona".to_string(), "implementation".to_string()], - agent_ids: vec!["epiphany.Persona".to_string(), "epiphany.hands".to_string()], - display_names: vec!["Persona".to_string(), "Hands".to_string()], - profile_kinds: vec!["Persona".to_string(), "WorkOrgan".to_string()], - portable_contracts: vec![ - "gamecult.persona_state.v0".to_string(), - "epiphany.work_organ_state.v0".to_string(), - ], - semantic_memory_counts: vec![3, 2], - episodic_memory_counts: vec![1, 0], - relationship_memory_counts: vec![2, 0], - goal_counts: vec![1, 1], - value_counts: vec![4, 3], - }; - let agent_state_summary = epiphany_cultmesh_agent_state_soa_summary_from_entry( - "epiphany-test", - "agent-state-soa-summary-prompt-test", - &agent_state_soa, - ); - write_epiphany_cultmesh_agent_state_soa_summary(&store, agent_state_summary)?; let local_verse = query_epiphany_local_verse_context(&store, "epiphany-test")?; let memory_context = EpiphanyMemoryContextPacket { id: "memctx-test".to_string(), @@ -596,10 +522,6 @@ mod tests { assert!(rendered.contains("private_state_exposed=false")); assert!(!rendered.contains("inspectCompactSurface")); assert!(!rendered.contains("watchTypedReceipts")); - assert!(rendered.contains("Agent State SoA")); - assert!(rendered.contains("agent-state-soa-summary-prompt-test")); - assert!(rendered.contains("gamecult.persona_state.v0")); - assert!(rendered.contains("epiphany.work_organ_state.v0")); assert!(rendered.contains("Bifrost Publication Gate")); assert!(rendered.contains("bifrost-publication-intent-prompt-test")); assert!(rendered.contains("github_receipt=github-publication-prompt-test")); diff --git a/epiphany-core/src/resident_self.rs b/epiphany-core/src/resident_self.rs index b1a32f28..eae59419 100644 --- a/epiphany-core/src/resident_self.rs +++ b/epiphany-core/src/resident_self.rs @@ -805,206 +805,6 @@ pub fn resident_prepared_launch_thread_id(prepared: &ResidentSelfPreparedLaunch) prepared_coordinator_thread_id(&prepared.argv) } -#[cfg(test)] -fn reconcile_resident_self( - state: &mut ResidentSelfState, - policy: &ResidentSelfPolicy, - ports: &mut impl ResidentSelfPorts, - now_millis: u64, - wake: Option, -) -> Result<(ResidentSelfOutcome, ResidentSelfRuntimeReceipt)> { - policy.validate()?; - let receipt_revision = state.revision + 1; - let receipt = |status: &str, - reason: &str, - turn_id: Option, - coordinator_receipt_id: Option, - process_id: Option| { - ResidentSelfRuntimeReceipt { - schema_version: RESIDENT_SELF_RUNTIME_RECEIPT_SCHEMA_VERSION.into(), - receipt_id: format!("resident-self-{now_millis}-{receipt_revision}"), - occurred_at_millis: now_millis, - status: status.into(), - reason: reason.into(), - turn_id, - coordinator_receipt_id, - process_id, - private_state_exposed: false, - } - }; - if ports.brake_engaged()? && state.active_turn.is_none() { - return Ok(( - ResidentSelfOutcome::Braked, - receipt( - "braked", - "local Verse swarm brake is engaged", - state.active_turn.as_ref().map(|v| v.turn_id.clone()), - None, - state.active_turn.as_ref().map(|v| v.process_id), - ), - )); - } - if let Some(active) = state.active_turn.clone() { - if ports.brake_engaged()? { - match ports.observe_child(&active)? { - ChildObservation::Running => { - ports.request_child_stop(&active)?; - return Ok(( - ResidentSelfOutcome::Draining, - receipt( - "draining", - "local Verse brake is engaged; exact active coordinator stop requested and lease remains active until termination is proven", - Some(active.turn_id), - None, - Some(active.process_id), - ), - )); - } - ChildObservation::Exited(_) | ChildObservation::Missing => { - // Continue into terminal reconciliation. A brake does not erase receipt obligations. - } - } - } - match ports.observe_child(&active)? { - ChildObservation::Running => { - return Ok(( - ResidentSelfOutcome::Running, - receipt( - "running", - "bounded coordinator turn remains active", - Some(active.turn_id), - None, - Some(active.process_id), - ), - )); - } - ChildObservation::Exited(0) => { - if let Some(coordinator_receipt_id) = - ports.coordinator_receipt_since(&active.turn_id, active.started_at_millis)? - { - state.active_turn = None; - state.last_coordinator_receipt_id = Some(coordinator_receipt_id.clone()); - state.next_eligible_at_millis = - now_millis.saturating_add(policy.cooldown_seconds * 1000); - state.consecutive_failures = 0; - state.revision += 1; - return Ok(( - ResidentSelfOutcome::Completed, - receipt( - "completed", - "coordinator exited successfully with typed run receipt", - Some(active.turn_id), - Some(coordinator_receipt_id), - Some(active.process_id), - ), - )); - } - state.active_turn = None; - state.consecutive_failures += 1; - state.next_eligible_at_millis = - now_millis.saturating_add(policy.failure_backoff_seconds * 1000); - state.revision += 1; - return Ok(( - ResidentSelfOutcome::Failed, - receipt( - "failed", - "coordinator exited zero without a typed run receipt", - Some(active.turn_id), - None, - Some(active.process_id), - ), - )); - } - ChildObservation::Exited(code) => { - state.active_turn = None; - state.consecutive_failures += 1; - state.next_eligible_at_millis = - now_millis.saturating_add(policy.failure_backoff_seconds * 1000); - state.revision += 1; - return Ok(( - ResidentSelfOutcome::Failed, - receipt( - "failed", - &format!("coordinator exited with status {code}"), - Some(active.turn_id), - None, - Some(active.process_id), - ), - )); - } - ChildObservation::Missing => { - state.active_turn = None; - state.consecutive_failures += 1; - state.next_eligible_at_millis = - now_millis.saturating_add(policy.failure_backoff_seconds * 1000); - state.revision += 1; - return Ok(( - ResidentSelfOutcome::Failed, - receipt( - "failed", - "coordinator process disappeared without a typed run receipt", - Some(active.turn_id), - None, - Some(active.process_id), - ), - )); - } - } - } - if now_millis < state.next_eligible_at_millis || wake.is_none() { - return Ok(( - ResidentSelfOutcome::Sleeping, - receipt( - "sleeping", - if wake.is_none() { - "no explicit or existing idle wake is pending; proactive Body and feedback wake derivation is not implemented" - } else { - "cooldown or failure backoff remains active" - }, - None, - None, - None, - ), - )); - } - let wake = wake.expect("checked above"); - let turn_id = format!("resident-self-turn-{now_millis}-{}", state.revision + 1); - let launch = CoordinatorLaunch { - turn_id: turn_id.clone(), - wake: wake.clone(), - argv: coordinator_argv(policy, &policy.release_runtime_id, &turn_id, None, &wake), - }; - let process = ports.launch_coordinator(&launch)?; - let process_id = process.process_id; - state.active_turn = Some(ResidentSelfTurnLease { - turn_id: turn_id.clone(), - wake, - process_id, - process_creation_token: process.process_creation_token, - process_executable_path: process.process_executable_path, - started_at_millis: now_millis, - grant_id: String::new(), - launch_digest: String::new(), - policy_digest: String::new(), - argv_digest: String::new(), - objective_digest: String::new(), - release_commit: policy.release_commit.clone(), - release_manifest_digest: policy.release_manifest_digest.clone(), - coordinator_executable_digest: String::new(), - }); - state.revision += 1; - Ok(( - ResidentSelfOutcome::Launched, - receipt( - "launched", - "one bounded coordinator turn admitted", - Some(turn_id), - None, - Some(process_id), - ), - )) -} - fn state_cache(path: &Path) -> Result { let mut cache = CultCache::new(); cache.register_entry_type::()?; @@ -2565,26 +2365,6 @@ pub fn validate_resident_self_prepared_objective( )) } -#[cfg(test)] -fn complete_resident_self_turn( - path: &Path, - lease: &ResidentSelfTurnLease, - coordinator: &crate::EpiphanyCoordinatorRunReceipt, - now_millis: u64, - cooldown_seconds: u64, -) -> Result { - validate_resident_self_coordinator_receipt(lease, coordinator)?; - complete_resident_self_turn_with_terminal( - path, - lease, - &coordinator.receipt_id, - &coordinator.status, - now_millis, - cooldown_seconds, - false, - ) -} - pub fn validate_resident_self_coordinator_receipt( lease: &ResidentSelfTurnLease, coordinator: &crate::EpiphanyCoordinatorRunReceipt, diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index eb901367..36c4086a 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -11181,34 +11181,6 @@ pub fn runtime_soul_verdict_receipt( cache.get::(receipt_id) } -// Continuity recovery receipts are accepted-reorientation prerequisites and -// publish atomically with the coordinator state transition. Direct insertion -// is fixture-only. -#[cfg(test)] -pub(crate) fn put_continuity_recovery_receipt( - store_path: impl AsRef, - receipt: &ContinuityRecoveryReceipt, -) -> Result<()> { - validate_non_empty(&receipt.receipt_id, "Continuity recovery receipt id")?; - validate_non_empty( - &receipt.source_result_id, - "Continuity recovery source result", - )?; - validate_non_empty(&receipt.source_job_id, "Continuity recovery source job")?; - validate_non_empty(&receipt.binding_id, "Continuity recovery binding")?; - validate_non_empty(&receipt.mode, "Continuity recovery mode")?; - validate_non_empty( - &receipt.checkpoint_still_valid, - "Continuity recovery checkpoint validity", - )?; - validate_non_empty(&receipt.emitted_at, "Continuity recovery timestamp")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - cache.put(&receipt.receipt_id, receipt)?; - Ok(()) -} - pub fn runtime_continuity_recovery_receipt( store_path: impl AsRef, receipt_id: &str, diff --git a/epiphany-core/src/surfaces/planning.rs b/epiphany-core/src/surfaces/planning.rs index 0869fa8f..e1485997 100644 --- a/epiphany-core/src/surfaces/planning.rs +++ b/epiphany-core/src/surfaces/planning.rs @@ -130,6 +130,7 @@ mod tests { invariants: Vec::new(), observations: Vec::new(), evidence: Vec::new(), + persona_memories: Vec::new(), verification_audits: Vec::new(), reorientation_decisions: Vec::new(), reorientation_failures: Vec::new(), diff --git a/epiphany-core/src/surfaces/scene.rs b/epiphany-core/src/surfaces/scene.rs index a3f3154f..9f48221d 100644 --- a/epiphany-core/src/surfaces/scene.rs +++ b/epiphany-core/src/surfaces/scene.rs @@ -426,6 +426,7 @@ mod tests { ], observations: Vec::new(), evidence: Vec::new(), + persona_memories: Vec::new(), verification_audits: Vec::new(), reorientation_decisions: Vec::new(), reorientation_failures: Vec::new(), diff --git a/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs b/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs index e6e515ba..30381010 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs @@ -3,13 +3,13 @@ use std::{env, net::SocketAddr, path::PathBuf, thread, time::Duration}; use anyhow::{Context, Result, anyhow}; use cultcache_rs::{CacheBackingStore, DatabaseEntry, SingleFileMessagePackBackingStore}; use epiphany_core::{ - EpiphanyAgentMemoryEntry, EpiphanyHeartbeatStateEntry, EpiphanyMindDocumentVersion, + EpiphanyHeartbeatStateEntry, EpiphanyMindDocumentVersion, EpiphanyMindPersonaMemoryDocument, EpiphanyMindPersonaPassInputDocument, PersonaIdentity, PersonaProjectorInput, PersonaRepoActivity, PersonaSocialAffordance, PersonaTranscriptMessage, PersonaTurnRequest, PersonaTurnTerminalOptions, admit_persona_pass_input, assemble_mind_view, complete_persona_turn_request_store, default_organ_dependencies_for, exchange_persona_discord_delivery_rudp, load_admitted_persona_pass_input, - load_agent_memory_entry_for_role, load_epiphany_cultmesh_swarm_brake, + load_epiphany_cultmesh_swarm_brake, load_heartbeat_state_entry, load_persona_discord_receipt_anchor, load_persona_discord_service_anchor, open_persona_discord_request_identity, pending_persona_discord_delivery_request_for_turn, persona_delivery_receipt_exists_for_turn, @@ -186,9 +186,13 @@ fn ensure_persona_pass_input_admitted( return Ok(()); } - let memory = load_agent_memory_entry_for_role(&options.agent_store, &request.role_id) - .ok() - .flatten(); + let mind = assemble_mind_view(&options.runtime_store)?; + let memories = mind + .persona_memories + .iter() + .filter(|memory| memory.agent_id == request.agent_id) + .cloned() + .collect::>(); let identity = PersonaIdentity { identity_id: request.agent_id.clone(), display_name: options.persona_name.clone(), @@ -227,7 +231,6 @@ fn ensure_persona_pass_input_admitted( recent_message_ids: vec![mention.message_id.clone()], }) .collect(); - let mind = assemble_mind_view(&options.runtime_store)?; let repo_activity = mind .repository_body_observation .as_ref() @@ -241,10 +244,9 @@ fn ensure_persona_pass_input_admitted( }) .into_iter() .collect(); - let has_memory = memory.is_some(); let projector_input = PersonaProjectorInput { identity, - memory, + memories: memories.clone(), semantic_memory_recall: semantic_recall.clone(), pending_mentions: request.mentions.clone(), repo_activity, @@ -259,18 +261,16 @@ fn ensure_persona_pass_input_admitted( let heartbeat_source = EpiphanyMindDocumentVersion::from_envelope("epiphany-heartbeat", &heartbeat_envelope)?; let mut observed_sources = vec![heartbeat_source.clone()]; - if has_memory { - let agent_envelope = SingleFileMessagePackBackingStore::new(&options.agent_store) - .pull_all()? - .into_iter() - .find(|entry| { - entry.r#type == EpiphanyAgentMemoryEntry::TYPE && entry.key == request.role_id + for memory in &memories { + let source = mind + .source_documents + .iter() + .find(|source| { + source.document_type == EpiphanyMindPersonaMemoryDocument::TYPE + && source.document_key == memory.memory_id }) - .ok_or_else(|| anyhow!("Persona pass input lost its agent-memory source"))?; - observed_sources.push(EpiphanyMindDocumentVersion::from_envelope( - "epiphany-agent-memory", - &agent_envelope, - )?); + .ok_or_else(|| anyhow!("Persona memory is absent from the canonical Mind basis"))?; + observed_sources.push(source.clone()); } if let Some(source) = mind.source_documents.iter().find(|source| { source.document_type == epiphany_core::EpiphanyMindRepositoryBodyObservationDocument::TYPE @@ -303,7 +303,6 @@ fn ensure_persona_pass_input_admitted( struct Options { runtime_store: PathBuf, heartbeat_store: PathBuf, - agent_store: PathBuf, cultmesh_store: PathBuf, codex_home: PathBuf, provider_credential_path: Option, @@ -351,7 +350,6 @@ impl Options { Ok(Self { runtime_store: path("--runtime-store")?, heartbeat_store: path("--heartbeat-store")?, - agent_store: path("--agent-store")?, cultmesh_store: path("--cultmesh-store")?, codex_home: path("--codex-home")?, provider_credential_path: values.get("--provider-credential").map(PathBuf::from), @@ -452,7 +450,6 @@ mod tests { let options = Options { runtime_store: runtime_store.clone(), heartbeat_store: absent.join("heartbeat.cc"), - agent_store: absent.join("agent.cc"), cultmesh_store: absent.join("cultmesh.cc"), codex_home: absent.join("codex-home"), provider_credential_path: None, diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index d5eb71ce..ceef3803 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -455,7 +455,7 @@ the builder target root to 4 KiB. Exact `1d5a1f17` landed the first causal cut: - core library tests remain 494/494 and Hands remains 5/5 under the sole release package owner. -The second verified cut is not yet committed. It deletes the +Exact `387afe49` is the second verified cut. It deletes the `runtime_store_migration` module/test, all 29 remaining unshipped core executables, the shipped `epiphany-verse-query` parallel control plane, the local PowerShell launcher, and its orphan Rider bridge. `epiphany-core` now @@ -463,9 +463,18 @@ owns only `epiphany-prepare-compaction`; the release bundle owns 25 runtime executables. Focused core, supervisor, and release-construction tests pass 493/493, 32/32, and 21/21. -Next, split the few live Persona/heartbeat inputs from `agent_memory`, then -delete its aggregate identity, generation, SoA, migration/repair, and generic -selfPatch authorities with the tests that preserve them. Do not run a +The current third cut deletes the agent-memory migration, repair, lifecycle, +trait-seed, and SoA authorities with the tests that preserved them. It also +deletes the second CultMesh SoA mirror, its timestamp-selected latest head, and +its prompt injection. Persona now reads learned memory only from keyed +`EpiphanyMindPersonaMemoryDocument` rows in the assembled Mind view; those +documents participate in the exact projection digest and source set. Dead +test-only alternate Resident Self and direct Continuity writers are also gone. +The final core library passes 483/483 and Persona service re-entry passes 1/1. + +Next delete the global Mind semantic partition and heartbeat +personality/rumination selfPatch consumers, then delete the aggregate +agent-memory identity/generation store they alone preserve. Do not run a full-workspace compile, package, deployment, or Ox root until the subtraction audit closes. Never resume Ox10, Ox12, Ox13, Ox15, or Ox16. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 71adc387462e6d8e37b7eb75ca75210b9bd3a704..8c1baa8fa7f1cc271745f66519ddb1b5f06af007 100644 GIT binary patch delta 835 zcmZXS!E4k&6voY}t$Py%QKay|Lob_Tn@YR1z374$DHPk2w@LDLGjuW&=S{i>ggsR7 z>cM6I1J9xd5%1z3;z95r_2Ai;WVcv7d;~IY-hAKhz0cp*U;kWxchkGRdDQRkMB6*j z;OUo3-QW3#d)_n8`*Aw!yqa}9R{g!qy2853T4AlSu6>+!x^K^Jy!jOMqbHdMkPIX|v^Cv(0abP9zvdEuZCE+d#omx0Sr?pY9} z%Z!z-@(YWE_C)G2)b}ZX1GHqc0ENP8OkrV9n+YT)pTBt7y|cEgNljAboPj{-+!9|_x#ZQ$i8;TC7&r!D$ykLe~ECdbq~3}WQaf^@-u)7qXF4_EF^!$(pJ z#lGs9Q5iG*q@E0rW6G_20xV>a30?U~9YV?YJYLQI{IqC!^6dVxv=MFu@c2URhP9j` zf#8