diff --git a/AGENTS.md b/AGENTS.md index 3932ff29..2191f988 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,9 +173,9 @@ When the user says to prepare for imminent compaction: front of an unlocked door. - Heartbeat scheduling should behave like physiology. Do not wake a lane again while its previous heartbeat turn is still running; cooldown starts after - completion, not at launch. When no coordinator work is active, let the swarm - sleep: slow rumination, memory distillation, and dreaming without hammering - every organ at work tempo. + completion, not at launch. When no typed work obligation is active, let the + swarm sleep. Heartbeat owns no rumination, dreaming, personality, or memory + mutation path and may not invent work to justify a pulse. - Prefer one clear hypothesis per rite. - Verify with checks that reflect the true objective, not just proxy incense. - Revert or discard changes that do not clearly improve the target. diff --git a/docs/epiphany_body_whitepaper.tex b/docs/epiphany_body_whitepaper.tex index 66567026..92ca3147 100644 --- a/docs/epiphany_body_whitepaper.tex +++ b/docs/epiphany_body_whitepaper.tex @@ -68,12 +68,12 @@ auth and model transport. \begin{longtable}{>{\raggedright\arraybackslash}p{0.23\linewidth}>{\raggedright\arraybackslash}p{0.32\linewidth}>{\raggedright\arraybackslash}p{0.35\linewidth}} \textbf{Body Region} & \textbf{Current Owner} & \textbf{Invariant} \\ \hline -Durable thread state & \code{epiphany-state-model}, \code{epiphany-core} & State shape is typed; prompt rendering is a projection, not authority. \\ +Durable Mind state & keyed CultCache documents assembled by \code{epiphany-core} & Prompt rendering consumes sealed typed projections; no persisted thread aggregate or global authority revision exists. \\ Domain policy & \code{epiphany-core} & Verdicts, launch policy, acceptance rules, runtime lifecycle, and mutation law live here, not in bridge DTOs. \\ Vendored Codex & \code{vendor/codex} & Retain host/app-server/auth/model compatibility where useful; do not let Codex own Epiphany prompts, scheduler, or state. \\ Runtime spine & \code{epiphany-core/src/runtime_spine.rs} and \code{state/runtime-spine.cc} & Runtime identity, jobs, events, receipts, tool/model documents are typed CultCache records. \\ -Organ memory & \code{state/agents.cc}; agent-memory CLI & Resident work organs use lean work-organ state; Persona uses portable Persona state. \\ -Heartbeat physiology & \code{state/agent-heartbeats.cc} via \code{epiphany-heartbeat-store} & Scheduling, cooldown, rumination, routine, cognition lanes, and active-turn freeze are scheduler physiology, not project truth. \\ +Persona memory & keyed Mind Persona-memory documents & Persona decisions admit bounded memories by semantic identity; no parallel role-memory aggregate or generation head exists. \\ +Heartbeat physiology & \code{state/agent-heartbeats.cc} via \code{epiphany-heartbeat-store} & Scheduling, cooldown, explicit pressure, and active-turn exclusion only. Heartbeat owns no cognition, personality, or memory mutation. \\ Local Verse & CultMesh over \code{state/local-verse.cc} & Local Verse context is compact prompt/operator context; private state does not become public by flipping a flag. \\ Schema catalog & \code{schemas/cultnet/index.json} & JSON schema publishes contracts; typed Rust documents remain runtime authority. \\ Operator artifacts & \code{.epiphany-*}, \code{tools/epiphany_local_run.ps1} & Artifacts witness and lower state; they do not become state owners. \\ @@ -258,17 +258,13 @@ truth, operator API owner, or plugin marketplace brain. Current operator access is partly native and partly witness-artifact based: \begin{itemize} - \item \code{tools/epiphany_local_run.ps1} is the one-command local operator - wrapper. - \item Native binaries such as \code{epiphany-mvp-status}, - \code{epiphany-mvp-coordinator}, \code{epiphany-verse-query}, - \code{epiphany-operator-run}, \code{epiphany-operator-snapshot}, - \code{epiphany-eve-surface}, \code{epiphany-runtime-spine}, - \code{epiphany-heartbeat-store}, and \code{epiphany-agent-memory-store} - inspect or update typed state. + \item Native binaries such as \code{epiphany-state}, + \code{epiphany-mvp-coordinator}, \code{epiphany-runtime-spine}, + \code{epiphany-heartbeat-store}, and the family-specific worker services + inspect or update their owned typed state. \item Local artifact directories such as \code{.epiphany-run/}, \code{.epiphany-dogfood/}, \code{.epiphany-smoke/}, - \code{.epiphany-rumination/}, \code{.epiphany-gui/}, and + \code{.epiphany-gui/}, and \code{.epiphany-character-loop/} witness work and lower typed state for operator inspection. \item Aquarium is the richer adjacent operator client. It should render and diff --git a/epiphany-core/src/agent_launch.rs b/epiphany-core/src/agent_launch.rs index 03ac9a9b..a48de283 100644 --- a/epiphany-core/src/agent_launch.rs +++ b/epiphany-core/src/agent_launch.rs @@ -370,53 +370,6 @@ fn investigation_checkpoint_output_schema() -> serde_json::Value { }) } -fn self_patch_memory_output_schema() -> serde_json::Value { - serde_json::json!({ - "type": "object", - "required": ["memoryId", "summary", "salience", "confidence"], - "properties": { - "memoryId": {"type": "string"}, - "summary": {"type": "string", "minLength": 1, "maxLength": 600}, - "salience": {"type": "number", "minimum": 0, "maximum": 1}, - "confidence": {"type": "number", "minimum": 0, "maximum": 1}, - "linkedEventIds": {"type": "array", "items": {"type": "string"}}, - "linkedRelationshipId": {"type": "string"} - }, - "additionalProperties": false - }) -} - -fn self_patch_goal_output_schema() -> serde_json::Value { - serde_json::json!({ - "type": "object", - "required": ["goalId", "description", "scope", "priority", "emotionalStake", "status"], - "properties": { - "goalId": {"type": "string"}, - "description": {"type": "string", "minLength": 1, "maxLength": 700}, - "scope": {"type": "string", "enum": ["immediate", "scene", "case", "arc", "life"]}, - "priority": {"type": "number", "minimum": 0, "maximum": 1}, - "emotionalStake": {"type": "string", "minLength": 1, "maxLength": 400}, - "blockers": {"type": "array", "items": {"type": "string"}}, - "status": {"type": "string", "enum": ["active", "blocked", "dormant", "resolved", "abandoned"]} - }, - "additionalProperties": false - }) -} - -fn self_patch_value_output_schema() -> serde_json::Value { - serde_json::json!({ - "type": "object", - "required": ["valueId", "label", "priority", "unforgivableIfBetrayed"], - "properties": { - "valueId": {"type": "string"}, - "label": {"type": "string", "minLength": 1, "maxLength": 240}, - "priority": {"type": "number", "minimum": 0, "maximum": 1}, - "unforgivableIfBetrayed": {"type": "boolean"} - }, - "additionalProperties": false - }) -} - pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) -> serde_json::Value { let verdict_enum = match role_id { EpiphanyRoleResultRoleId::Imagination => { @@ -476,57 +429,6 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) -> "type": "array", "items": {"type": "string"} }, - "selfPatch": { - "type": "object", - "description": "Optional bounded request to update this role's persistent organ memory. Work organs may update lane habits, durable lessons, goals, values, or private notes; Persona affect/social state belongs only to Persona public surfaces. It must not contain project truth, code edits, job authority, graph/frontier/checkpoint/planning changes, or objective changes.", - "required": ["agentId", "reason"], - "properties": { - "agentId": { - "type": "string", - "description": "Expected target persistent agent id for this lane, such as epiphany.modeling or epiphany.soul." - }, - "reason": { - "type": "string", - "description": "Why this memory mutation makes the lane sharper for future work." - }, - "evidenceIds": { - "type": "array", - "description": "Optional accepted/project evidence ids that ground the memory request. These do not count as a memory mutation by themselves.", - "items": {"type": "string"} - }, - "semanticMemories": { - "type": "array", - "maxItems": 8, - "items": self_patch_memory_output_schema() - }, - "episodicMemories": { - "type": "array", - "maxItems": 8, - "items": self_patch_memory_output_schema() - }, - "relationshipMemories": { - "type": "array", - "maxItems": 8, - "items": self_patch_memory_output_schema() - }, - "goals": { - "type": "array", - "maxItems": 6, - "items": self_patch_goal_output_schema() - }, - "values": { - "type": "array", - "maxItems": 6, - "items": self_patch_value_output_schema() - }, - "privateNotes": { - "type": "array", - "maxItems": 6, - "items": {"type": "string", "minLength": 1, "maxLength": 600} - } - }, - "additionalProperties": false - } }); let mut required = vec![ "roleId", @@ -600,7 +502,7 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) -> "type": "object", "properties": properties, "required": required, - "additionalProperties": true + "additionalProperties": false }); if role_id == EpiphanyRoleResultRoleId::Modeling { schema["allOf"] = serde_json::json!([ @@ -723,7 +625,6 @@ pub fn epiphany_frontier_planning_output_schema() -> serde_json::Value { let properties = schema["properties"] .as_object_mut() .expect("role output schema properties"); - properties.remove("selfPatch"); properties.insert( "frontierPlanCandidate".to_string(), serde_json::json!({ @@ -888,7 +789,7 @@ const EPIPHANY_SPECIALIST_PROMPTS_TOML: &str = include_str!("prompts/epiphany_sp const EPIPHANY_WORKER_BOUNDARY_PROMPT: &str = r#"## Epiphany Worker Boundary You are one bounded Epiphany worker for this launch only. Your authority comes from the typed launch document, the role-local instruction, and the declared output contract. Do the role, name uncertainty, and return the required JSON object. Do not become the coordinator, do not accept or promote your own output, do not invent durable state outside the declared typed outcome, and do not treat model transport or Codex machinery as prompt authority. -If you learned a durable role-local habit, you may include a bounded selfPatch. Project truth belongs in the role's typed output artifact or evidence, not memory."#; +Durable state changes belong only to the declared typed family outcome and its invariant owner."#; #[derive(Debug, serde::Deserialize)] pub struct EpiphanySpecialistPromptConfig { @@ -1180,27 +1081,10 @@ mod tests { } #[test] - fn role_self_patch_schema_matches_canonical_numeric_memory_contract() { + fn role_schema_has_no_generic_self_patch_mouth() { let schema = epiphany_role_launch_output_schema(EpiphanyRoleResultRoleId::Verification); - let self_patch = &schema["properties"]["selfPatch"]; - for bundle in [ - "semanticMemories", - "episodicMemories", - "relationshipMemories", - ] { - let item = &self_patch["properties"][bundle]["items"]; - assert_eq!(item["properties"]["salience"]["type"], "number"); - assert_eq!(item["properties"]["confidence"]["type"], "number"); - assert_eq!(item["additionalProperties"], false); - } - assert_eq!( - self_patch["properties"]["goals"]["items"]["properties"]["priority"]["type"], - "number" - ); - assert_eq!( - self_patch["properties"]["values"]["items"]["properties"]["priority"]["type"], - "number" - ); + assert!(schema["properties"].get("selfPatch").is_none()); + assert_eq!(schema["additionalProperties"], false); } #[test] diff --git a/epiphany-core/src/agent_memory.rs b/epiphany-core/src/agent_memory.rs deleted file mode 100644 index 78c180bb..00000000 --- a/epiphany-core/src/agent_memory.rs +++ /dev/null @@ -1,1831 +0,0 @@ -use anyhow::Result; -use anyhow::anyhow; -use chrono::SecondsFormat; -use chrono::Utc; -use cultcache_rs::CultCache; -use cultcache_rs::DatabaseEntry; -use cultcache_rs::SingleFileMessagePackBackingStore; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; -use sha2::{Digest, Sha256}; -use std::collections::BTreeMap; -use std::path::Path; - -pub const AGENT_MEMORY_TYPE: &str = "epiphany.agent_memory"; -pub const AGENT_MEMORY_SCHEMA_VERSION: &str = "ghostlight.agent_state.v0"; -pub const AGENT_MEMORY_SWARM_IDENTITY_TYPE: &str = "epiphany.agent_memory_swarm_identity"; -pub const AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION: &str = - "epiphany.agent_memory_swarm_identity.v0"; -pub const AGENT_MEMORY_SWARM_IDENTITY_KEY: &str = "swarm-identity"; -pub const AGENT_MEMORY_GENERATION_WITNESS_TYPE: &str = "epiphany.agent_memory_generation_witness"; -pub const AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION: &str = - "epiphany.agent_memory_generation_witness.v0"; -pub const AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY: &str = "mind-generation/latest"; -pub const AGENT_MEMORY_MIND_ADMISSION_TYPE: &str = "epiphany.agent_memory_mind_admission"; -pub const AGENT_MEMORY_MIND_ADMISSION_SCHEMA_VERSION: &str = - "epiphany.agent_memory_mind_admission.v0"; - -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.agent_memory_generation_witness", - schema = "AgentMemoryGenerationWitness" -)] -pub struct AgentMemoryGenerationWitness { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub witness_id: String, - #[cultcache(key = 2)] - pub swarm_id: String, - #[cultcache(key = 3)] - pub generation: u64, - #[cultcache(key = 4)] - pub previous_generation: u64, - #[cultcache(key = 5)] - pub previous_source_hash: String, - #[cultcache(key = 6)] - pub source_hash: String, - #[cultcache(key = 7)] - pub authority_receipt_id: String, - #[cultcache(key = 8)] - pub mutation_kind: String, - #[cultcache(key = 9)] - pub changed_role_ids: Vec, - #[cultcache(key = 10)] - pub committed_at: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.agent_memory_mind_admission", - schema = "AgentMemoryMindAdmissionReceipt" -)] -pub struct AgentMemoryMindAdmissionReceipt { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub receipt_id: String, - #[cultcache(key = 2)] - pub swarm_id: String, - #[cultcache(key = 3)] - pub role_id: String, - #[cultcache(key = 4)] - pub mutation_kind: String, - #[cultcache(key = 5)] - pub reason: String, - #[cultcache(key = 6)] - pub status: String, - #[cultcache(key = 7)] - pub resulting_source_hash: String, -} -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.agent_memory_swarm_identity", - schema = "AgentMemorySwarmIdentity" -)] -pub struct AgentMemorySwarmIdentity { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub swarm_id: String, -} - -pub fn load_agent_memory_swarm_identity( - 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_SWARM_IDENTITY_KEY) -} - -pub fn ensure_agent_memory_swarm_identity( - store_path: impl AsRef, - swarm_id: &str, -) -> Result { - let swarm_id = swarm_id.trim(); - if swarm_id.is_empty() { - return Err(anyhow!( - "agent memory swarm identity requires a non-empty swarm_id" - )); - } - let store_path = store_path.as_ref(); - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - if let Some(existing) = - cache.get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? - { - if existing.swarm_id == swarm_id - && existing.schema_version == AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION - { - return Ok(existing); - } - return Err(anyhow!( - "agent memory swarm identity collision: store owns {:?}, refused {:?}", - existing.swarm_id, - swarm_id - )); - } - let identity = AgentMemorySwarmIdentity { - schema_version: AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION.to_string(), - swarm_id: swarm_id.to_string(), - }; - let envelope = cache - .prepare_entry(AGENT_MEMORY_SWARM_IDENTITY_KEY, &identity)? - .0; - let backing = SingleFileMessagePackBackingStore::new(store_path); - if !backing.compare_and_swap_batch(&[], vec![envelope])? { - let raced = load_agent_memory_swarm_identity(store_path)?; - if raced.as_ref() == Some(&identity) { - return Ok(identity); - } - return Err(anyhow!( - "agent memory swarm identity lost immutable compare-and-swap" - )); - } - Ok(identity) -} - -const ROLE_TARGETS: &[(&str, &str, &str)] = &[ - ( - "imagination", - "epiphany.imagination", - "imagination.agent-state.json", - ), - ("modeling", "epiphany.modeling", "modeling.agent-state.json"), - ("verification", "epiphany.soul", "soul.agent-state.json"), - ("implementation", "epiphany.hands", "hands.agent-state.json"), - ("research", "epiphany.eyes", "eyes.agent-state.json"), - ("Persona", "epiphany.Persona", "Persona.agent-state.json"), - ("coordinator", "epiphany.self", "self.agent-state.json"), -]; - -pub fn initialize_fresh_agent_memory_store( - store_path: impl AsRef, - swarm_id: &str, -) -> Result { - let store_path = store_path.as_ref(); - let swarm_id = swarm_id.trim(); - if swarm_id.is_empty() { - return Err(anyhow!("fresh agent memory requires a non-empty swarm id")); - } - let identity = AgentMemorySwarmIdentity { - schema_version: AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION.to_string(), - swarm_id: swarm_id.to_string(), - }; - let entries = ROLE_TARGETS - .iter() - .map(|(role_id, agent_id, _)| fresh_agent_memory_entry(role_id, agent_id)) - .collect::>(); - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let opening = cache.snapshot_envelopes(); - if opening.is_empty() { - let mut batch = vec![ - cache - .prepare_entry(AGENT_MEMORY_SWARM_IDENTITY_KEY, &identity)? - .0, - ]; - for entry in &entries { - batch.push(cache.prepare_entry(&entry.role_id, entry)?.0); - } - if !SingleFileMessagePackBackingStore::new(store_path).compare_and_swap_batch(&[], batch)? { - return Err(anyhow!( - "fresh agent memory initialization lost exact atomic compare-and-swap" - )); - } - } else { - if cache.get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? != Some(identity) - { - return Err(anyhow!( - "fresh agent memory initialization collided with another swarm identity" - )); - } - for entry in &entries { - if cache.get::(&entry.role_id)? != Some(entry.clone()) { - return Err(anyhow!( - "fresh agent memory initialization collided with role {:?}", - entry.role_id - )); - } - } - } - admit_initial_agent_memory_generation( - store_path, - "fresh-bootstrap-admission", - "Admit freshly initialized canonical organ memory rows.", - ROLE_TARGETS - .iter() - .map(|(role_id, _, _)| (*role_id).to_string()) - .collect(), - ) -} - -fn fresh_agent_memory_entry(role_id: &str, agent_id: &str) -> EpiphanyAgentMemoryEntry { - let display_name = match role_id { - "Persona" => "Persona".to_string(), - "coordinator" => "Self".to_string(), - "verification" => "Soul".to_string(), - "implementation" => "Hands".to_string(), - "research" => "Eyes".to_string(), - other => { - let mut chars = other.chars(); - chars - .next() - .map(|first| first.to_uppercase().collect::() + chars.as_str()) - .unwrap_or_default() - } - }; - let mut canonical_state = GhostlightCanonicalState::default(); - if role_id == "Persona" { - let baseline = BTreeMap::from([( - "baseline".to_string(), - GhostlightTraitVector { - mean: 0.5, - plasticity: 0.5, - current_activation: 0.5, - }, - )]); - canonical_state.underlying_organization = baseline.clone(); - canonical_state.stable_dispositions = baseline.clone(); - canonical_state.behavioral_dimensions = baseline.clone(); - canonical_state.presentation_strategy = baseline.clone(); - canonical_state.voice_style = baseline.clone(); - canonical_state.situational_state = baseline; - } - EpiphanyAgentMemoryEntry { - schema_version: AGENT_MEMORY_SCHEMA_VERSION.to_string(), - role_id: role_id.to_string(), - world: GhostlightWorld { - world_id: "epiphany-agent-memory".to_string(), - setting: "Fresh local Epiphany organ memory".to_string(), - time: GhostlightTime { - label: "initial generation".to_string(), - }, - canon_context: vec![ - "Typed organ memory begins empty and grows only through admitted decisions." - .to_string(), - ], - }, - agent: GhostlightAgent { - agent_id: agent_id.to_string(), - identity: GhostlightIdentity { - name: display_name, - roles: vec![role_id.to_string()], - origin: "Fresh local Epiphany organ".to_string(), - public_description: format!("Epiphany {role_id} organ memory."), - private_notes: Vec::new(), - }, - canonical_state, - goals: Vec::new(), - memories: GhostlightMemories::default(), - perceived_state_overlays: Vec::new(), - }, - relationships: Vec::new(), - events: Vec::new(), - scenes: Vec::new(), - } -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache(type = "epiphany.agent_memory", schema = "EpiphanyAgentMemoryEntry")] -pub struct EpiphanyAgentMemoryEntry { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub role_id: String, - #[cultcache(key = 2)] - pub world: GhostlightWorld, - #[cultcache(key = 3)] - pub agent: GhostlightAgent, - #[cultcache(key = 4, default)] - pub relationships: Vec, - #[cultcache(key = 5, default)] - pub events: Vec, - #[cultcache(key = 6, default)] - pub scenes: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightWorld { - pub world_id: String, - pub setting: String, - pub time: GhostlightTime, - pub canon_context: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightTime { - pub label: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightAgent { - pub agent_id: String, - pub identity: GhostlightIdentity, - pub canonical_state: GhostlightCanonicalState, - pub goals: Vec, - pub memories: GhostlightMemories, - #[serde(default)] - pub perceived_state_overlays: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightRelationship { - #[serde(default)] - pub relationship_id: String, - #[serde(default)] - pub participant_ids: Vec, - #[serde(default)] - pub summary: String, - #[serde(default)] - pub stance: String, - #[serde(default)] - pub salience: f64, - #[serde(default)] - pub confidence: f64, - #[serde(default)] - pub linked_memory_ids: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightEvent { - #[serde(default)] - pub event_id: String, - #[serde(default)] - pub kind: String, - #[serde(default)] - pub summary: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub timestamp_label: Option, - #[serde(default)] - pub participant_ids: Vec, - #[serde(default)] - pub linked_memory_ids: Vec, - #[serde(default)] - pub salience: f64, - #[serde(default)] - pub confidence: f64, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightScene { - #[serde(default)] - pub scene_id: String, - #[serde(default)] - pub label: String, - #[serde(default)] - pub summary: String, - #[serde(default)] - pub participant_ids: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub anchor: Option, - #[serde(default)] - pub salience: f64, - #[serde(default)] - pub status: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightPerceivedStateOverlay { - #[serde(default)] - pub overlay_id: String, - #[serde(default)] - pub label: String, - #[serde(default)] - pub summary: String, - #[serde(default)] - pub source: String, - #[serde(default)] - pub salience: f64, - #[serde(default)] - pub confidence: f64, - #[serde(default)] - pub linked_memory_ids: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightIdentity { - pub name: String, - pub roles: Vec, - pub origin: String, - pub public_description: String, - #[serde(default)] - pub private_notes: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightCanonicalState { - pub underlying_organization: BTreeMap, - pub stable_dispositions: BTreeMap, - pub behavioral_dimensions: BTreeMap, - pub presentation_strategy: BTreeMap, - pub voice_style: BTreeMap, - pub situational_state: BTreeMap, - pub values: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightTraitVector { - pub mean: f64, - pub plasticity: f64, - pub current_activation: f64, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightValue { - pub value_id: String, - pub label: String, - pub priority: f64, - pub unforgivable_if_betrayed: bool, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightGoal { - pub goal_id: String, - pub description: String, - pub scope: String, - pub priority: f64, - pub emotional_stake: String, - #[serde(default)] - pub blockers: Vec, - pub status: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightMemories { - pub episodic: Vec, - pub semantic: Vec, - pub relationship_summaries: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct GhostlightMemory { - pub memory_id: String, - pub summary: String, - pub salience: f64, - pub confidence: f64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub linked_event_ids: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub linked_relationship_id: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentSelfPatch { - #[serde(default)] - pub agent_id: Option, - #[serde(default)] - pub reason: Option, - #[serde(default)] - pub evidence_ids: Option>, - #[serde(default)] - pub semantic_memories: Option>, - #[serde(default)] - pub episodic_memories: Option>, - #[serde(default)] - pub relationship_memories: Option>, - #[serde(default)] - pub goals: Option>, - #[serde(default)] - pub values: Option>, - #[serde(default)] - pub private_notes: Option>, - #[serde(flatten)] - pub extra: BTreeMap, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SelfPatchMemory { - pub memory_id: String, - pub summary: String, - pub salience: f64, - pub confidence: f64, - #[serde(default)] - pub linked_event_ids: Option>, - #[serde(default)] - pub linked_relationship_id: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SelfPatchGoal { - pub goal_id: String, - pub description: String, - pub scope: String, - pub priority: f64, - pub emotional_stake: String, - #[serde(default)] - pub blockers: Vec, - pub status: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SelfPatchValue { - pub value_id: String, - pub label: String, - pub priority: f64, - pub unforgivable_if_betrayed: bool, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentMemoryReview { - pub status: String, - pub target_agent_id: String, - pub target_role_id: String, - pub target_store: String, - pub reasons: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub applied: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum EpiphanyOrganStateProfileKind { - WorkOrgan, - Persona, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyOrganStateProfile { - pub profile_kind: EpiphanyOrganStateProfileKind, - pub state_density: String, - pub portable_contract: String, - pub relationship_model: String, - pub affect_model: String, - pub perceived_overlay_mode: String, - pub growth_channels: Vec, - pub notes: Vec, -} - -pub const PERSONA_STATE_SCHEMA_VERSION: &str = "gamecult.persona_state.v0"; - -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)?; - cache.pull_all_backing_stores()?; - let mut errors = Vec::new(); - for (role_id, expected_agent_id, _) in ROLE_TARGETS { - let Some(entry) = cache.get::(role_id)? else { - errors.push(format!("{role_id}: missing CultCache role memory entry")); - continue; - }; - errors.extend(validate_agent_entry(&entry, expected_agent_id)); - } - Ok(errors) -} - -fn canonical_agent_memory_source_hash( - cache: &mut CultCache, - identity: &AgentMemorySwarmIdentity, - replacements: &BTreeMap, -) -> Result { - let mut hasher = Sha256::new(); - hasher.update(AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION.as_bytes()); - hasher.update([0]); - hasher.update(identity.swarm_id.as_bytes()); - for (role_id, _, _) in ROLE_TARGETS { - let entry = replacements - .get(*role_id) - .cloned() - .or_else(|| { - cache - .get::(role_id) - .ok() - .flatten() - }) - .ok_or_else(|| anyhow!("canonical Mind generation is missing role {role_id:?}"))?; - let envelope = cache.prepare_entry(*role_id, &entry)?.0; - hasher.update((*role_id).as_bytes()); - hasher.update((envelope.payload.len() as u64).to_le_bytes()); - hasher.update(&envelope.payload); - } - Ok(format!("{:x}", hasher.finalize())) -} - -fn commit_agent_memory_generation( - store_path: &Path, - expected_generation: u64, - expected_source_hash: &str, - role_id: &str, - next_entry: EpiphanyAgentMemoryEntry, - mutation_kind: &str, - reason: &str, -) -> Result { - let expected_agent_id = agent_id_for_role(role_id).map_err(|message| anyhow!(message))?; - let validation = validate_agent_entry(&next_entry, expected_agent_id); - if !validation.is_empty() { - return Err(anyhow!("Mind commit candidate is invalid: {validation:?}")); - } - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let identity = cache - .get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? - .ok_or_else(|| anyhow!("Mind commit requires immutable agent memory swarm identity"))?; - let current = - cache.get::(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY)?; - let current_generation = current.as_ref().map_or(0, |witness| witness.generation); - let empty = BTreeMap::new(); - let current_source_hash = canonical_agent_memory_source_hash(&mut cache, &identity, &empty)?; - if let Some(witness) = ¤t - && (witness.schema_version != AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION - || witness.swarm_id != identity.swarm_id - || witness.source_hash != current_source_hash - || witness.generation == 0) - { - return Err(anyhow!( - "stored Mind generation witness does not authenticate current canonical memory" - )); - } - if current_generation != expected_generation || current_source_hash != expected_source_hash { - return Err(anyhow!( - "agent memory generation changed before Mind commit" - )); - } - let mut replacements = BTreeMap::new(); - replacements.insert(role_id.to_string(), next_entry); - let source_hash = canonical_agent_memory_source_hash(&mut cache, &identity, &replacements)?; - if source_hash == current_source_hash { - return Err(anyhow!( - "Mind commit would mint a generation without changing canonical memory" - )); - } - let generation = current_generation + 1; - let fingerprint = format!( - "{}|{}|{}|{}|{}", - identity.swarm_id, generation, mutation_kind, role_id, source_hash - ); - let witness_id = format!( - "mind-generation-{}", - uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_OID, fingerprint.as_bytes()) - ); - let receipt_id = format!( - "mind-admission-{}", - uuid::Uuid::new_v5( - &uuid::Uuid::NAMESPACE_OID, - format!("{fingerprint}|{reason}").as_bytes() - ) - ); - let receipt = AgentMemoryMindAdmissionReceipt { - schema_version: AGENT_MEMORY_MIND_ADMISSION_SCHEMA_VERSION.to_string(), - receipt_id: receipt_id.clone(), - swarm_id: identity.swarm_id.clone(), - role_id: role_id.to_string(), - mutation_kind: mutation_kind.to_string(), - reason: reason.to_string(), - status: "admitted".to_string(), - resulting_source_hash: source_hash.clone(), - }; - let committed_at = now_rfc3339(); - let witness = AgentMemoryGenerationWitness { - schema_version: AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION.to_string(), - witness_id: witness_id.clone(), - swarm_id: identity.swarm_id.clone(), - generation, - previous_generation: current_generation, - previous_source_hash: current_source_hash, - source_hash: source_hash.clone(), - authority_receipt_id: receipt_id.clone(), - mutation_kind: mutation_kind.to_string(), - changed_role_ids: vec![role_id.to_string()], - committed_at: committed_at.clone(), - }; - let opening = cache.snapshot_envelopes(); - let mut expected = Vec::new(); - let mut batch = Vec::new(); - for (canonical_role, _, _) in ROLE_TARGETS { - if let Some(existing) = opening.iter().find(|envelope| { - envelope.r#type == AGENT_MEMORY_TYPE && envelope.key == *canonical_role - }) { - expected.push(existing.clone()); - } - let entry = replacements - .get(*canonical_role) - .cloned() - .or_else(|| { - cache - .get::(canonical_role) - .ok() - .flatten() - }) - .ok_or_else(|| anyhow!("canonical Mind generation lost role {canonical_role:?}"))?; - batch.push(cache.prepare_entry(*canonical_role, &entry)?.0); - } - if let Some(existing) = opening.iter().find(|envelope| { - envelope.r#type == AGENT_MEMORY_GENERATION_WITNESS_TYPE - && envelope.key == AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY - }) { - expected.push(existing.clone()); - } - batch.push( - cache - .prepare_entry(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY, &witness)? - .0, - ); - batch.push(cache.prepare_entry(&witness_id, &witness)?.0); - batch.push(cache.prepare_entry(&receipt_id, &receipt)?.0); - let backing = SingleFileMessagePackBackingStore::new(store_path); - if !backing.compare_and_swap_batch(&expected, batch)? { - return Err(anyhow!( - "agent memory Mind commit lost exact atomic compare-and-swap" - )); - } - Ok(witness) -} - -fn admit_initial_agent_memory_generation( - store_path: &Path, - mutation_kind: &str, - reason: &str, - changed_role_ids: Vec, -) -> Result { - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let identity = cache - .get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? - .ok_or_else(|| anyhow!("initial Mind admission requires immutable swarm identity"))?; - let source_hash = canonical_agent_memory_source_hash(&mut cache, &identity, &BTreeMap::new())?; - if let Some(existing) = - cache.get::(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY)? - { - if existing.schema_version != AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION - || existing.swarm_id != identity.swarm_id - || existing.generation != 1 - || existing.previous_generation != 0 - || existing.source_hash != source_hash - || existing.mutation_kind != mutation_kind - { - return Err(anyhow!( - "initial Mind admission collides with live generation" - )); - } - return Ok(existing); - } - - for (role_id, expected_agent_id, _) in ROLE_TARGETS { - let entry = cache - .get::(role_id)? - .ok_or_else(|| anyhow!("initial Mind admission requires role {role_id:?}"))?; - let errors = validate_agent_entry(&entry, expected_agent_id); - if !errors.is_empty() { - return Err(anyhow!( - "initial Mind role {role_id:?} is invalid: {}", - errors.join("; ") - )); - } - } - let committed_at = now_rfc3339(); - let fingerprint = format!("{}|{}|{}", identity.swarm_id, mutation_kind, source_hash); - let witness_id = format!( - "mind-generation-{}", - uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_OID, fingerprint.as_bytes()) - ); - let receipt_id = format!( - "mind-admission-{}", - uuid::Uuid::new_v5( - &uuid::Uuid::NAMESPACE_OID, - format!("{fingerprint}|{reason}").as_bytes() - ) - ); - let receipt = AgentMemoryMindAdmissionReceipt { - schema_version: AGENT_MEMORY_MIND_ADMISSION_SCHEMA_VERSION.to_string(), - receipt_id: receipt_id.clone(), - swarm_id: identity.swarm_id.clone(), - role_id: "mind".to_string(), - mutation_kind: mutation_kind.to_string(), - reason: reason.to_string(), - status: "admitted".to_string(), - resulting_source_hash: source_hash.clone(), - }; - let witness = AgentMemoryGenerationWitness { - schema_version: AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION.to_string(), - witness_id: witness_id.clone(), - swarm_id: identity.swarm_id.clone(), - generation: 1, - previous_generation: 0, - previous_source_hash: source_hash.clone(), - source_hash: source_hash.clone(), - authority_receipt_id: receipt_id.clone(), - mutation_kind: mutation_kind.to_string(), - changed_role_ids, - committed_at: committed_at.clone(), - }; - let opening = cache.snapshot_envelopes(); - let mut expected = Vec::new(); - let mut replacements = Vec::new(); - for envelope in opening.iter().filter(|envelope| { - (envelope.r#type == AGENT_MEMORY_SWARM_IDENTITY_TYPE - && envelope.key == AGENT_MEMORY_SWARM_IDENTITY_KEY) - || (envelope.r#type == AGENT_MEMORY_TYPE - && ROLE_TARGETS - .iter() - .any(|(role_id, _, _)| envelope.key == *role_id)) - }) { - expected.push(envelope.clone()); - replacements.push(envelope.clone()); - } - if expected.len() != ROLE_TARGETS.len() + 1 { - return Err(anyhow!( - "initial Mind admission could not authenticate every canonical source envelope" - )); - } - replacements.push( - cache - .prepare_entry(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY, &witness)? - .0, - ); - replacements.push(cache.prepare_entry(&witness_id, &witness)?.0); - replacements.push(cache.prepare_entry(&receipt_id, &receipt)?.0); - let backing = SingleFileMessagePackBackingStore::new(store_path); - if !backing.compare_and_swap_batch(&expected, replacements)? { - return Err(anyhow!( - "initial Mind admission lost exact atomic compare-and-swap" - )); - } - Ok(witness) -} - -fn agent_memory_source_head(cache: &mut CultCache) -> Result<(u64, String)> { - let identity = cache - .get::(AGENT_MEMORY_SWARM_IDENTITY_KEY)? - .ok_or_else(|| anyhow!("Mind commit requires immutable agent memory swarm identity"))?; - let canonical_hash = canonical_agent_memory_source_hash(cache, &identity, &BTreeMap::new())?; - if let Some(witness) = - cache.get::(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY)? - { - if witness.schema_version != AGENT_MEMORY_GENERATION_WITNESS_SCHEMA_VERSION - || witness.swarm_id != identity.swarm_id - || witness.source_hash != canonical_hash - || witness.generation == 0 - { - return Err(anyhow!( - "stored Mind generation witness does not authenticate current canonical memory" - )); - } - return Ok((witness.generation, canonical_hash)); - } - Ok((0, canonical_hash)) -} - -fn require_agent_memory_migration_open(cache: &mut CultCache, operation: &str) -> Result<()> { - cache.pull_all_backing_stores()?; - if cache - .get::(AGENT_MEMORY_GENERATION_WITNESS_LATEST_KEY)? - .is_some() - { - return Err(anyhow!( - "{operation} is bootstrap/migration-only and cannot mutate canonical Mind after generation admission" - )); - } - Ok(()) -} - -pub fn load_agent_memory_entry_for_role( - store_path: impl AsRef, - role_id: &str, -) -> Result> { - let store_path = store_path.as_ref(); - let agent_id = agent_id_for_role(role_id).map_err(|message| anyhow!(message))?; - let mut cache = agent_memory_cache(store_path)?; - cache.pull_all_backing_stores()?; - let entry = cache.get::(role_id)?; - if let Some(entry) = &entry - && entry.agent.agent_id != agent_id - { - return Err(anyhow!( - "{} agent_id {:?} does not match expected {:?}", - role_id, - entry.agent.agent_id, - agent_id - )); - } - Ok(entry) -} - -pub fn write_agent_memory_entry_for_role_migration( - store_path: impl AsRef, - entry: &EpiphanyAgentMemoryEntry, -) -> Result<()> { - let store_path = store_path.as_ref(); - let expected_agent_id = - agent_id_for_role(&entry.role_id).map_err(|message| anyhow!(message))?; - if entry.agent.agent_id != expected_agent_id { - return Err(anyhow!( - "{} agent_id {:?} does not match expected {:?}", - entry.role_id, - entry.agent.agent_id, - expected_agent_id - )); - } - let mut cache = agent_memory_cache(store_path)?; - require_agent_memory_migration_open(&mut cache, "raw agent-memory replacement")?; - cache.put(entry.role_id.as_str(), entry)?; - Ok(()) -} - -pub fn agent_memory_role_ids() -> Vec<&'static str> { - ROLE_TARGETS - .iter() - .map(|(role_id, _, _)| *role_id) - .collect() -} - -pub fn review_agent_self_patch( - role_id: &str, - patch_value: &Value, - store_path: impl AsRef, -) -> AgentMemoryReview { - let store_path = store_path.as_ref(); - let mut reasons = Vec::new(); - let target_agent_id = match agent_id_for_role(role_id) { - Ok(agent_id) => agent_id.to_string(), - 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, - }; - } - }; - - match decode_agent_self_patch(patch_value) { - Ok(patch) => { - return review_agent_self_patch_document(role_id, &patch, store_path); - } - Err(reason) => reasons.push(reason), - } - - agent_memory_review(role_id, &target_agent_id, store_path, reasons, None) -} - -pub(crate) fn decode_agent_self_patch( - patch_value: &Value, -) -> std::result::Result { - if !patch_value.is_object() { - return Err("selfPatch must be a JSON object".to_string()); - } - serde_json::from_value(patch_value.clone()) - .map_err(|err| format!("selfPatch is not a valid AgentSelfPatch document: {err}")) -} - -pub fn review_agent_self_patch_document( - role_id: &str, - patch: &AgentSelfPatch, - 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_agent_self_patch_contract(target_agent_id, patch); - 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, -) -> Vec { - let mut reasons = Vec::new(); - if patch.agent_id.as_deref() != Some(expected_agent_id) { - reasons.push(format!( - "selfPatch agentId {:?} does not match this lane; expected {:?}", - patch.agent_id, expected_agent_id - )); - } - match patch.reason.as_deref() { - Some(reason) if reason.trim().len() >= 16 && reason.len() <= 800 => {} - _ => reasons.push( - "selfPatch reason must be a bounded explanation of at least 16 characters".to_string(), - ), - } - - for key in patch.extra.keys() { - if forbidden_patch_field(key) { - reasons.push(format!( - "selfPatch field {key:?} is project truth or authority; use the proper Epiphany control surface instead" - )); - } else if !allowed_patch_field(key) { - reasons.push(format!( - "selfPatch field {key:?} is not part of the bounded memory mutation contract" - )); - } - } - - let mut mutation_count = 0; - mutation_count += review_memory_patch_array( - "semanticMemories", - patch.semantic_memories.as_ref(), - &mut reasons, - ); - mutation_count += review_memory_patch_array( - "episodicMemories", - patch.episodic_memories.as_ref(), - &mut reasons, - ); - mutation_count += review_memory_patch_array( - "relationshipMemories", - patch.relationship_memories.as_ref(), - &mut reasons, - ); - mutation_count += review_goal_patch_array(patch.goals.as_ref(), &mut reasons); - mutation_count += review_value_patch_array(patch.values.as_ref(), &mut reasons); - mutation_count += review_private_notes(patch.private_notes.as_ref(), &mut reasons); - review_string_array( - "evidenceIds", - patch.evidence_ids.as_ref(), - &mut reasons, - 16, - 160, - ); - if mutation_count == 0 { - reasons.push( - "selfPatch must contain at least one semantic memory, episodic memory, relationship memory, goal, value, or private note" - .to_string(), - ); - } - - reasons -} - -pub fn apply_agent_self_patch( - role_id: &str, - patch_value: &Value, - store_path: impl AsRef, -) -> Result { - let patch = match decode_agent_self_patch(patch_value) { - Ok(patch) => patch, - Err(_) => return Ok(review_agent_self_patch(role_id, patch_value, store_path)), - }; - apply_agent_self_patch_document(role_id, patch, store_path) -} - -pub fn apply_agent_self_patch_document( - role_id: &str, - patch: AgentSelfPatch, - store_path: impl AsRef, -) -> Result { - let store_path = store_path.as_ref(); - let mut review = review_agent_self_patch_document(role_id, &patch, 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 = patch - .reason - .clone() - .unwrap_or_else(|| "admitted bounded self patch".to_string()); - - if let Some(incoming) = patch.semantic_memories { - upsert_memories(&mut entry.agent.memories.semantic, incoming); - } - if let Some(incoming) = patch.episodic_memories { - upsert_memories(&mut entry.agent.memories.episodic, incoming); - } - if let Some(incoming) = patch.relationship_memories { - upsert_memories(&mut entry.agent.memories.relationship_summaries, incoming); - } - if let Some(incoming) = patch.goals { - upsert_goals(&mut entry.agent.goals, incoming); - } - if let Some(incoming) = patch.values { - upsert_values(&mut entry.agent.canonical_state.values, incoming); - } - if let Some(mut private_notes) = patch.private_notes { - entry - .agent - .identity - .private_notes - .append(&mut private_notes); - let keep_from = entry.agent.identity.private_notes.len().saturating_sub(32); - entry.agent.identity.private_notes = - entry.agent.identity.private_notes[keep_from..].to_vec(); - } - commit_agent_memory_generation( - store_path, - expected_generation, - &expected_source_hash, - role_id, - entry, - "self_patch", - &reason, - )?; - review.applied = Some(true); - Ok(review) -} - -fn now_rfc3339() -> String { - Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true) -} - -pub fn organ_state_profile_for_role(role_id: &str) -> EpiphanyOrganStateProfile { - match role_id { - "Persona" => EpiphanyOrganStateProfile { - profile_kind: EpiphanyOrganStateProfileKind::Persona, - state_density: "persona_grade".to_string(), - portable_contract: "gamecult.persona_state.v0".to_string(), - relationship_model: "relationship_summaries_and_directional_stance_matter".to_string(), - affect_model: "persona_affect_allowed_and_expected".to_string(), - perceived_overlay_mode: "observer_local_and_fallible".to_string(), - growth_channels: vec![ - "heartbeat appraisal and reaction".to_string(), - "character-loop interpretation".to_string(), - "persona affect and social-read interpretation".to_string(), - "episodic and relationship memory accumulation".to_string(), - "reviewed selfPatch".to_string(), - "sleep/distillation".to_string(), - ], - notes: vec![ - "Epiphany Persona is an organ; Persona is the portable person-state contract shared with Ghostlight and VoidBot-style repo Personas.".to_string(), - "Dense canonical families, affect, perceived overlays, and relationship pressure are appropriate for Persona state.".to_string(), - ], - }, - _ => EpiphanyOrganStateProfile { - profile_kind: EpiphanyOrganStateProfileKind::WorkOrgan, - state_density: "lean_work_organ".to_string(), - portable_contract: "epiphany.work_organ_state.v0".to_string(), - relationship_model: "role_local_summary_only".to_string(), - affect_model: "no_affect_or_persona_machinery".to_string(), - perceived_overlay_mode: "minimal_until_a_real_need_exists".to_string(), - growth_channels: vec![ - "reviewed selfPatch".to_string(), - "heartbeat rumination pressure".to_string(), - "sleep/distillation".to_string(), - "birth-time repo memory and light operating-pressure seeding".to_string(), - ], - notes: vec![ - "Work organs need sharp role identity, durable mission memory, values/goals, and heartbeat activation; they do not need Persona affect or full Persona machinery.".to_string(), - "Sparse canonical bundles are acceptable here as long as memory, goals, values, and private notes can deepen over time.".to_string(), - ], - }, - } -} - -fn agent_memory_cache(store_path: &Path) -> Result { - let mut cache = CultCache::new(); - 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 validate_agent_entry(entry: &EpiphanyAgentMemoryEntry, expected_agent_id: &str) -> Vec { - let mut errors = Vec::new(); - if entry.schema_version != AGENT_MEMORY_SCHEMA_VERSION { - errors.push(format!( - "{}: schema_version must be {:?}", - entry.role_id, AGENT_MEMORY_SCHEMA_VERSION - )); - } - check_string(&entry.world.world_id, "world.world_id", &mut errors, 120); - check_string(&entry.world.setting, "world.setting", &mut errors, 800); - check_string( - &entry.world.time.label, - "world.time.label", - &mut errors, - 200, - ); - if entry.world.canon_context.is_empty() { - errors.push("world.canon_context must not be empty".to_string()); - } - if entry.agent.agent_id != expected_agent_id { - errors.push(format!( - "{}: agent_id {:?} does not match expected {:?}", - entry.role_id, entry.agent.agent_id, expected_agent_id - )); - } - check_string( - &entry.agent.identity.name, - "identity.name", - &mut errors, - 200, - ); - check_string( - &entry.agent.identity.origin, - "identity.origin", - &mut errors, - 800, - ); - check_string( - &entry.agent.identity.public_description, - "identity.public_description", - &mut errors, - 800, - ); - if entry.agent.identity.roles.is_empty() { - errors.push("identity.roles must not be empty".to_string()); - } - for (bundle, memories) in [ - ("episodic", &entry.agent.memories.episodic), - ("semantic", &entry.agent.memories.semantic), - ( - "relationship_summaries", - &entry.agent.memories.relationship_summaries, - ), - ] { - for (index, memory) in memories.iter().enumerate() { - validate_memory(memory, &format!("memories.{bundle}[{index}]"), &mut errors); - } - } - for (index, overlay) in entry.agent.perceived_state_overlays.iter().enumerate() { - validate_overlay( - overlay, - &format!("perceived_state_overlays[{index}]"), - &mut errors, - ); - } - for (index, relationship) in entry.relationships.iter().enumerate() { - validate_relationship( - relationship, - &format!("relationships[{index}]"), - &mut errors, - ); - } - for (index, event) in entry.events.iter().enumerate() { - validate_event(event, &format!("events[{index}]"), &mut errors); - } - for (index, scene) in entry.scenes.iter().enumerate() { - validate_scene(scene, &format!("scenes[{index}]"), &mut errors); - } - for (index, goal) in entry.agent.goals.iter().enumerate() { - validate_goal(goal, &format!("goals[{index}]"), &mut errors); - } - for (index, value) in entry.agent.canonical_state.values.iter().enumerate() { - validate_value(value, &format!("values[{index}]"), &mut errors); - } - let profile = organ_state_profile_for_role(&entry.role_id); - for (group_name, group) in [ - ( - "underlying_organization", - &entry.agent.canonical_state.underlying_organization, - ), - ( - "stable_dispositions", - &entry.agent.canonical_state.stable_dispositions, - ), - ( - "behavioral_dimensions", - &entry.agent.canonical_state.behavioral_dimensions, - ), - ( - "presentation_strategy", - &entry.agent.canonical_state.presentation_strategy, - ), - ("voice_style", &entry.agent.canonical_state.voice_style), - ( - "situational_state", - &entry.agent.canonical_state.situational_state, - ), - ] { - if group.is_empty() && profile.profile_kind == EpiphanyOrganStateProfileKind::Persona { - errors.push(format!("canonical_state.{group_name} must not be empty")); - } - for (name, vector) in group { - validate_trait_vector( - vector, - &format!("canonical_state.{group_name}.{name}"), - &mut errors, - ); - } - } - errors -} - -fn validate_memory(memory: &GhostlightMemory, path: &str, errors: &mut Vec) { - check_string(&memory.memory_id, &format!("{path}.memory_id"), errors, 120); - check_string(&memory.summary, &format!("{path}.summary"), errors, 800); - check_unit(memory.salience, &format!("{path}.salience"), errors); - check_unit(memory.confidence, &format!("{path}.confidence"), errors); -} - -fn validate_overlay( - overlay: &GhostlightPerceivedStateOverlay, - path: &str, - errors: &mut Vec, -) { - check_optional_identifier(&overlay.overlay_id, &format!("{path}.overlay_id"), errors); - check_string(&overlay.summary, &format!("{path}.summary"), errors, 800); - check_optional_text(&overlay.label, &format!("{path}.label"), errors, 240); - check_optional_text(&overlay.source, &format!("{path}.source"), errors, 240); - check_unit(overlay.salience, &format!("{path}.salience"), errors); - check_unit(overlay.confidence, &format!("{path}.confidence"), errors); -} - -fn validate_relationship( - relationship: &GhostlightRelationship, - path: &str, - errors: &mut Vec, -) { - check_optional_identifier( - &relationship.relationship_id, - &format!("{path}.relationship_id"), - errors, - ); - check_string( - &relationship.summary, - &format!("{path}.summary"), - errors, - 800, - ); - check_optional_text(&relationship.stance, &format!("{path}.stance"), errors, 240); - if relationship.participant_ids.is_empty() { - errors.push(format!("{path}.participant_ids must not be empty")); - } - check_unit(relationship.salience, &format!("{path}.salience"), errors); - check_unit( - relationship.confidence, - &format!("{path}.confidence"), - errors, - ); -} - -fn validate_event(event: &GhostlightEvent, path: &str, errors: &mut Vec) { - check_optional_identifier(&event.event_id, &format!("{path}.event_id"), errors); - check_string(&event.kind, &format!("{path}.kind"), errors, 120); - check_string(&event.summary, &format!("{path}.summary"), errors, 800); - if event.participant_ids.is_empty() { - errors.push(format!("{path}.participant_ids must not be empty")); - } - check_unit(event.salience, &format!("{path}.salience"), errors); - check_unit(event.confidence, &format!("{path}.confidence"), errors); -} - -fn validate_scene(scene: &GhostlightScene, path: &str, errors: &mut Vec) { - check_optional_identifier(&scene.scene_id, &format!("{path}.scene_id"), errors); - check_string(&scene.label, &format!("{path}.label"), errors, 240); - check_string(&scene.summary, &format!("{path}.summary"), errors, 800); - if scene.participant_ids.is_empty() { - errors.push(format!("{path}.participant_ids must not be empty")); - } - check_unit(scene.salience, &format!("{path}.salience"), errors); - check_optional_text(&scene.status, &format!("{path}.status"), errors, 120); -} - -fn validate_goal(goal: &GhostlightGoal, path: &str, errors: &mut Vec) { - check_string(&goal.goal_id, &format!("{path}.goal_id"), errors, 120); - check_string( - &goal.description, - &format!("{path}.description"), - errors, - 800, - ); - if !matches!( - goal.scope.as_str(), - "immediate" | "scene" | "case" | "arc" | "life" - ) { - errors.push(format!("{path}.scope is not a Ghostlight scope")); - } - check_unit(goal.priority, &format!("{path}.priority"), errors); - check_string( - &goal.emotional_stake, - &format!("{path}.emotional_stake"), - errors, - 400, - ); - if !matches!( - goal.status.as_str(), - "active" | "blocked" | "dormant" | "resolved" | "abandoned" - ) { - errors.push(format!("{path}.status is not a Ghostlight status")); - } -} - -fn validate_value(value: &GhostlightValue, path: &str, errors: &mut Vec) { - check_string(&value.value_id, &format!("{path}.value_id"), errors, 120); - check_string(&value.label, &format!("{path}.label"), errors, 240); - check_unit(value.priority, &format!("{path}.priority"), errors); -} - -fn validate_trait_vector(vector: &GhostlightTraitVector, path: &str, errors: &mut Vec) { - check_unit(vector.mean, &format!("{path}.mean"), errors); - check_unit(vector.plasticity, &format!("{path}.plasticity"), errors); - check_unit( - vector.current_activation, - &format!("{path}.current_activation"), - errors, - ); -} - -fn check_string(value: &str, path: &str, errors: &mut Vec, max_len: usize) { - if value.trim().is_empty() || value.len() > max_len { - errors.push(format!( - "{path} must be non-empty text under {max_len} characters" - )); - } -} - -fn check_optional_text(value: &str, path: &str, errors: &mut Vec, max_len: usize) { - if !value.is_empty() && (value.trim().is_empty() || value.len() > max_len) { - errors.push(format!( - "{path} must be empty or text under {max_len} characters" - )); - } -} - -fn check_optional_identifier(value: &str, path: &str, errors: &mut Vec) { - if value.is_empty() { - return; - } - if value.len() > 120 - || !value.chars().all(|ch| { - ch.is_ascii() && (ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')) - }) - { - errors.push(format!( - "{path} must be empty or an ASCII identifier without whitespace" - )); - } -} - -fn check_unit(value: f64, path: &str, errors: &mut Vec) { - if !value.is_finite() || !(0.0..=1.0).contains(&value) { - errors.push(format!("{path} must be between 0 and 1")); - } -} - -fn agent_id_for_role(role_id: &str) -> std::result::Result<&'static str, String> { - ROLE_TARGETS - .iter() - .find_map(|(candidate_role, agent_id, _)| (*candidate_role == role_id).then_some(*agent_id)) - .ok_or_else(|| format!("unknown role id: {role_id}")) -} - -fn allowed_patch_field(key: &str) -> bool { - matches!( - key, - "agentId" - | "reason" - | "evidenceIds" - | "semanticMemories" - | "episodicMemories" - | "relationshipMemories" - | "goals" - | "values" - | "privateNotes" - ) -} - -fn forbidden_patch_field(key: &str) -> bool { - matches!( - key, - "statePatch" - | "objective" - | "activeSubgoalId" - | "subgoals" - | "invariants" - | "graphs" - | "graphFrontier" - | "graphCheckpoint" - | "scratch" - | "investigationCheckpoint" - | "jobBindings" - | "planning" - | "churn" - | "mode" - | "codeEdits" - | "files" - | "authorityScope" - | "backendJobId" - | "rawResult" - ) -} - -fn review_memory_patch_array( - field: &str, - value: Option<&Vec>, - reasons: &mut Vec, -) -> usize { - let Some(value) = value else { - return 0; - }; - if value.len() > 8 { - reasons.push(format!("selfPatch {field} may contain at most 8 records")); - } - for (index, item) in value.iter().enumerate() { - if !valid_identifier(&item.memory_id, "mem-") { - reasons.push(format!( - "selfPatch {field}[{index}].memoryId must start with 'mem-' and avoid whitespace" - )); - } - check_patch_text( - &item.summary, - &format!("selfPatch {field}[{index}].summary"), - reasons, - 600, - ); - check_patch_unit( - item.salience, - &format!("selfPatch {field}[{index}].salience"), - reasons, - ); - check_patch_unit( - item.confidence, - &format!("selfPatch {field}[{index}].confidence"), - reasons, - ); - } - value.len() -} - -fn review_goal_patch_array(value: Option<&Vec>, reasons: &mut Vec) -> usize { - let Some(value) = value else { - return 0; - }; - if value.len() > 6 { - reasons.push("selfPatch goals may contain at most 6 records".to_string()); - } - for (index, item) in value.iter().enumerate() { - if !valid_identifier(&item.goal_id, "goal-") { - reasons.push(format!( - "selfPatch goals[{index}].goalId must start with 'goal-' and avoid whitespace" - )); - } - check_patch_text( - &item.description, - &format!("selfPatch goals[{index}].description"), - reasons, - 700, - ); - if !matches!( - item.scope.as_str(), - "immediate" | "scene" | "case" | "arc" | "life" - ) { - reasons.push(format!( - "selfPatch goals[{index}].scope is not a Ghostlight scope" - )); - } - check_patch_unit( - item.priority, - &format!("selfPatch goals[{index}].priority"), - reasons, - ); - check_patch_text( - &item.emotional_stake, - &format!("selfPatch goals[{index}].emotionalStake"), - reasons, - 400, - ); - if !matches!( - item.status.as_str(), - "active" | "blocked" | "dormant" | "resolved" | "abandoned" - ) { - reasons.push(format!( - "selfPatch goals[{index}].status is not a Ghostlight status" - )); - } - } - value.len() -} - -fn review_value_patch_array( - value: Option<&Vec>, - reasons: &mut Vec, -) -> usize { - let Some(value) = value else { - return 0; - }; - if value.len() > 6 { - reasons.push("selfPatch values may contain at most 6 records".to_string()); - } - for (index, item) in value.iter().enumerate() { - if !valid_identifier(&item.value_id, "value-") { - reasons.push(format!( - "selfPatch values[{index}].valueId must start with 'value-' and avoid whitespace" - )); - } - check_patch_text( - &item.label, - &format!("selfPatch values[{index}].label"), - reasons, - 240, - ); - check_patch_unit( - item.priority, - &format!("selfPatch values[{index}].priority"), - reasons, - ); - } - value.len() -} - -fn review_private_notes(value: Option<&Vec>, reasons: &mut Vec) -> usize { - let Some(value) = value else { - return 0; - }; - if value.len() > 6 { - reasons.push("selfPatch privateNotes may contain at most 6 records".to_string()); - } - for (index, item) in value.iter().enumerate() { - check_patch_text( - item, - &format!("selfPatch privateNotes[{index}]"), - reasons, - 600, - ); - } - value.len() -} - -fn review_string_array( - field: &str, - value: Option<&Vec>, - reasons: &mut Vec, - max_items: usize, - max_len: usize, -) { - let Some(value) = value else { - return; - }; - if value.len() > max_items { - reasons.push(format!( - "selfPatch {field} may contain at most {max_items} records" - )); - } - for (index, item) in value.iter().enumerate() { - check_patch_text( - item, - &format!("selfPatch {field}[{index}]"), - reasons, - max_len, - ); - } -} - -fn check_patch_text(value: &str, path: &str, reasons: &mut Vec, max_len: usize) { - if value.trim().is_empty() || value.len() > max_len { - reasons.push(format!( - "{path} must be non-empty text under {max_len} characters" - )); - } -} - -fn check_patch_unit(value: f64, path: &str, reasons: &mut Vec) { - if !value.is_finite() || !(0.0..=1.0).contains(&value) { - reasons.push(format!("{path} must be between 0 and 1")); - } -} - -fn valid_identifier(value: &str, prefix: &str) -> bool { - value.starts_with(prefix) - && value.len() <= 120 - && value.chars().all(|ch| { - ch.is_ascii() && (ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')) - }) -} - -fn upsert_memories(records: &mut Vec, incoming: Vec) { - let mut index: BTreeMap = records - .iter() - .cloned() - .map(|record| (record.memory_id.clone(), record)) - .collect(); - for item in incoming { - index.insert( - item.memory_id.clone(), - GhostlightMemory { - memory_id: item.memory_id, - summary: item.summary, - salience: item.salience, - confidence: item.confidence, - linked_event_ids: item.linked_event_ids, - linked_relationship_id: item.linked_relationship_id, - }, - ); - } - *records = index.into_values().collect(); -} - -fn upsert_goals(records: &mut Vec, incoming: Vec) { - let mut index: BTreeMap = records - .iter() - .cloned() - .map(|record| (record.goal_id.clone(), record)) - .collect(); - for item in incoming { - index.insert( - item.goal_id.clone(), - GhostlightGoal { - goal_id: item.goal_id, - description: item.description, - scope: item.scope, - priority: item.priority, - emotional_stake: item.emotional_stake, - blockers: item.blockers, - status: item.status, - }, - ); - } - *records = index.into_values().collect(); -} - -fn upsert_values(records: &mut Vec, incoming: Vec) { - let mut index: BTreeMap = records - .iter() - .cloned() - .map(|record| (record.value_id.clone(), record)) - .collect(); - for item in incoming { - index.insert( - item.value_id.clone(), - GhostlightValue { - value_id: item.value_id, - label: item.label, - priority: item.priority, - unforgivable_if_betrayed: item.unforgivable_if_betrayed, - }, - ); - } - *records = index.into_values().collect(); -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::tempdir; - - #[test] - 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.changed_role_ids.len(), ROLE_TARGETS.len()); - assert_eq!( - initialize_fresh_agent_memory_store(&store, "fresh-swarm")?, - witness - ); - Ok(()) - } -} -#[test] -fn immutable_swarm_identity_separates_stores_and_refuses_collision() -> Result<()> { - let temp = tempfile::tempdir()?; - let first_store = temp.path().join("first.msgpack"); - let second_store = temp.path().join("second.msgpack"); - let first = ensure_agent_memory_swarm_identity(&first_store, "swarm-alpha")?; - let second = ensure_agent_memory_swarm_identity(&second_store, "swarm-beta")?; - assert_eq!( - load_agent_memory_swarm_identity(&first_store)?, - Some(first.clone()) - ); - assert_eq!( - ensure_agent_memory_swarm_identity(&first_store, "swarm-alpha")?, - first - ); - let collision = ensure_agent_memory_swarm_identity(&first_store, "swarm-beta") - .expect_err("immutable store identity must refuse substitution"); - assert!(collision.to_string().contains("collision")); - assert_ne!(first.swarm_id, second.swarm_id); - Ok(()) -} diff --git a/epiphany-core/src/agent_utterance_state.rs b/epiphany-core/src/agent_utterance_state.rs deleted file mode 100644 index 6f4cfb16..00000000 --- a/epiphany-core/src/agent_utterance_state.rs +++ /dev/null @@ -1,1321 +0,0 @@ -use crate::agent_memory::EpiphanyAgentMemoryEntry; -use crate::agent_memory::EpiphanyOrganStateProfile; -use crate::agent_memory::GhostlightCanonicalState; -use crate::agent_memory::GhostlightTraitVector; -use crate::agent_memory::organ_state_profile_for_role; -use crate::heartbeat_state::HeartbeatParticipant; -use serde::Deserialize; -use serde::Serialize; -use std::collections::BTreeMap; - -pub const AGENT_UTTERANCE_STATE_SCHEMA_VERSION: &str = "epiphany.agent_utterance_state.v0"; -pub const WEKSA_UTTERANCE_HANDOFF_SCHEMA_VERSION: &str = "weksa.utterance_embedding_handoff.v0.1"; -pub const AGENT_CHARACTER_STATE_VECTOR_SIZE: usize = 64; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceState { - pub schema_version: String, - pub source: String, - pub role_id: String, - pub agent_id: String, - pub organ_state_profile: EpiphanyOrganStateProfile, - pub identity: AgentUtteranceIdentity, - pub personality_vectors: AgentUtterancePersonalityVectors, - pub values: Vec, - pub current_mood: AgentUtteranceMood, - pub activation: AgentUtteranceActivation, - pub character_state_vector: AgentUtteranceCharacterStateVector, - pub utterance_embedding_basis: Vec, - pub contract: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceIdentity { - pub name: String, - pub roles: Vec, - pub origin: String, - pub public_description: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtterancePersonalityVectors { - pub underlying_organization: BTreeMap, - pub stable_dispositions: BTreeMap, - pub behavioral_dimensions: BTreeMap, - pub presentation_strategy: BTreeMap, - pub voice_style: BTreeMap, - pub situational_state: BTreeMap, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceValue { - pub value_id: String, - pub label: String, - pub priority: f64, - pub unforgivable_if_betrayed: bool, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceMood { - pub label: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - pub emotional_dimensions: Vec, - pub anxiety: f64, - pub urgency: f64, - pub arousal: f64, - pub thought_pressure: f64, - pub guardedness: f64, - pub reaction_intensity: f64, - pub cooldown_multiplier: f64, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceMoodDimension { - pub name: String, - pub value: f64, - pub source_path: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceActivation { - pub status: String, - pub current_load: f64, - pub initiative_speed: f64, - pub reaction_bias: f64, - pub interrupt_threshold: f64, - pub initiative_heat_multiplier: f64, - pub effective_cooldown_multiplier: f64, - pub pending_turn_active: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub scene_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub last_woke_at: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub last_finished_at: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceEmbeddingBasis { - pub kind: String, - pub path: String, - pub weight: f64, - pub text: String, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceCharacterStateVector { - pub schema_version: String, - pub compatible_handoff_schema: String, - pub source: String, - pub dimensionality: usize, - pub values: Vec, - pub slots: Vec, - pub audit_projection: AgentUtteranceCharacterStateAudit, - pub uncertainties: Vec, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceCharacterStateSlot { - pub index: usize, - pub name: String, - pub value: f64, - pub source_path: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AgentUtteranceCharacterStateAudit { - pub current_state_pressure: Vec, - pub speaker_trait_pressure: Vec, - pub delivery_shape: String, -} - -pub fn derive_agent_utterance_state( - entry: &EpiphanyAgentMemoryEntry, - participant: Option<&HeartbeatParticipant>, - mood_label: Option<&str>, - source: impl Into, -) -> AgentUtteranceState { - let canonical = &entry.agent.canonical_state; - let personality_vectors = AgentUtterancePersonalityVectors { - underlying_organization: canonical.underlying_organization.clone(), - stable_dispositions: canonical.stable_dispositions.clone(), - behavioral_dimensions: canonical.behavioral_dimensions.clone(), - presentation_strategy: canonical.presentation_strategy.clone(), - voice_style: canonical.voice_style.clone(), - situational_state: canonical.situational_state.clone(), - }; - let values = canonical - .values - .iter() - .map(|value| AgentUtteranceValue { - value_id: value.value_id.clone(), - label: value.label.clone(), - priority: value.priority, - unforgivable_if_betrayed: value.unforgivable_if_betrayed, - }) - .collect::>(); - let mood = derive_utterance_mood(mood_label, canonical); - let activation = derive_utterance_activation(participant); - let vector_source = source.into(); - let identity = AgentUtteranceIdentity { - name: entry.agent.identity.name.clone(), - roles: entry.agent.identity.roles.clone(), - origin: entry.agent.identity.origin.clone(), - public_description: entry.agent.identity.public_description.clone(), - }; - let utterance_embedding_basis = utterance_embedding_basis( - &entry.role_id, - &identity, - &personality_vectors, - &values, - &mood, - &activation, - ); - let character_state_vector = character_state_vector( - &personality_vectors, - &values, - &mood, - &activation, - &vector_source, - ); - - AgentUtteranceState { - schema_version: AGENT_UTTERANCE_STATE_SCHEMA_VERSION.to_string(), - source: vector_source, - role_id: entry.role_id.clone(), - agent_id: entry.agent.agent_id.clone(), - organ_state_profile: organ_state_profile_for_role(&entry.role_id), - identity, - personality_vectors, - values, - current_mood: mood, - activation, - character_state_vector, - utterance_embedding_basis, - contract: "Derived speech-conditioning state for Weksa/AquaSynth utterance embedding and voice rendering. The 64-float characterStateVector is the machine-consumable handoff lane; the surrounding fields are audit and source context. This is not canonical memory and deliberately excludes episodic, semantic, and relationship memory records.".to_string(), - } -} - -fn derive_utterance_mood( - mood_label: Option<&str>, - canonical: &GhostlightCanonicalState, -) -> AgentUtteranceMood { - let situational = &canonical.situational_state; - let anxiety = trait_activation(situational, "anxiety"); - let urgency = trait_activation(situational, "urgency"); - let arousal = trait_activation(situational, "arousal"); - let thought_pressure = trait_activation(situational, "thought_pressure"); - let guardedness = trait_activation(situational, "guardedness"); - let reaction_intensity = trait_activation(situational, "reaction_intensity"); - AgentUtteranceMood { - label: mood_label - .map(str::to_string) - .unwrap_or_else(|| inferred_mood_label(arousal, situational)), - source: None, - emotional_dimensions: emotional_dimensions( - situational, - anxiety, - urgency, - arousal, - thought_pressure, - guardedness, - reaction_intensity, - ), - anxiety, - urgency, - arousal, - thought_pressure, - guardedness, - reaction_intensity, - cooldown_multiplier: 1.0, - } -} - -fn emotional_dimensions( - situational: &BTreeMap, - anxiety: f64, - urgency: f64, - arousal: f64, - thought_pressure: f64, - guardedness: f64, - reaction_intensity: f64, -) -> Vec { - let derived = [ - ( - "valence", - (trait_activation(situational, "joy") - + trait_activation(situational, "warmth") - + trait_activation(situational, "tenderness")) - / 3.0, - ), - ("arousal", arousal), - ( - "dominance", - trait_activation(situational, "dominance") - .max(trait_activation(situational, "command_force")), - ), - ("urgency", urgency), - ("anger", trait_activation(situational, "anger")), - ("despair", trait_activation(situational, "despair")), - ( - "sadness", - trait_activation(situational, "sadness").max(trait_activation(situational, "grief")), - ), - ("fear", trait_activation(situational, "fear").max(anxiety)), - ("anxiety", anxiety), - ("disgust", trait_activation(situational, "disgust")), - ("contempt", trait_activation(situational, "contempt")), - ("annoyance", trait_activation(situational, "annoyance")), - ("dismissal", trait_activation(situational, "dismissal")), - ( - "flippancy", - trait_activation(situational, "flippancy") - .max(trait_activation(situational, "playfulness")), - ), - ("playfulness", trait_activation(situational, "playfulness")), - ("irony", trait_activation(situational, "irony")), - ("tenderness", trait_activation(situational, "tenderness")), - ("warmth", trait_activation(situational, "warmth")), - ("joy", trait_activation(situational, "joy")), - ( - "excitement", - trait_activation(situational, "excitement") - .max(arousal * trait_activation(situational, "joy")), - ), - ("fatigue", trait_activation(situational, "fatigue")), - ("guardedness", guardedness), - ("confidence", trait_activation(situational, "confidence")), - ("shame", trait_activation(situational, "shame")), - ("pride", trait_activation(situational, "pride")), - ("threat", trait_activation(situational, "threat")), - ("secrecy", trait_activation(situational, "secrecy")), - ("hesitation", trait_activation(situational, "hesitation")), - ( - "emotionalContainment", - trait_activation(situational, "emotional_containment"), - ), - ("thoughtPressure", thought_pressure), - ("reactionIntensity", reaction_intensity), - ( - "commandForce", - trait_activation(situational, "command_force"), - ), - ]; - derived - .into_iter() - .map(|(name, value)| AgentUtteranceMoodDimension { - name: name.to_string(), - value: clamp_unit(value), - source_path: format!("currentMood.emotionalDimensions.{name}"), - }) - .collect() -} - -fn derive_utterance_activation( - participant: Option<&HeartbeatParticipant>, -) -> AgentUtteranceActivation { - let Some(participant) = participant else { - return AgentUtteranceActivation { - status: "unknown".to_string(), - initiative_speed: 1.0, - reaction_bias: 0.0, - interrupt_threshold: 0.0, - initiative_heat_multiplier: 1.0, - effective_cooldown_multiplier: 1.0, - ..AgentUtteranceActivation::default() - }; - }; - let effective_cooldown_multiplier = 1.0 / participant.initiative_heat_multiplier.max(0.001); - AgentUtteranceActivation { - status: participant.status.clone(), - current_load: participant.current_load, - initiative_speed: participant.initiative_speed, - reaction_bias: participant.reaction_bias, - interrupt_threshold: participant.interrupt_threshold, - initiative_heat_multiplier: participant.initiative_heat_multiplier, - effective_cooldown_multiplier, - pending_turn_active: participant.pending_turn.is_some(), - scene_id: participant.scene_id.clone(), - last_woke_at: participant.last_woke_at.clone(), - last_finished_at: participant.last_finished_at.clone(), - } -} - -fn inferred_mood_label( - arousal: f64, - _situational: &BTreeMap, -) -> String { - if arousal >= 0.75 { - "activated".to_string() - } else if arousal <= 0.25 { - "quiet".to_string() - } else { - "attentive".to_string() - } -} - -fn trait_activation(vectors: &BTreeMap, name: &str) -> f64 { - vectors - .get(name) - .map(|vector| vector.current_activation) - .unwrap_or(0.0) -} - -fn utterance_embedding_basis( - role_id: &str, - identity: &AgentUtteranceIdentity, - personality_vectors: &AgentUtterancePersonalityVectors, - values: &[AgentUtteranceValue], - mood: &AgentUtteranceMood, - activation: &AgentUtteranceActivation, -) -> Vec { - let mut basis = Vec::new(); - basis.push(AgentUtteranceEmbeddingBasis { - kind: "identity".to_string(), - path: format!("agent.{role_id}.identity"), - weight: 1.0, - text: format!( - "{}: {} Origin: {} Roles: {}.", - identity.name, - identity.public_description, - identity.origin, - identity.roles.join(", ") - ), - }); - for value in values.iter().filter(|value| value.priority > 0.0) { - basis.push(AgentUtteranceEmbeddingBasis { - kind: "value".to_string(), - path: format!("agent.{role_id}.values.{}", value.value_id), - weight: value.priority, - text: value.label.clone(), - }); - } - push_trait_basis( - &mut basis, - role_id, - "underlying_organization", - &personality_vectors.underlying_organization, - ); - push_trait_basis( - &mut basis, - role_id, - "stable_dispositions", - &personality_vectors.stable_dispositions, - ); - push_trait_basis( - &mut basis, - role_id, - "behavioral_dimensions", - &personality_vectors.behavioral_dimensions, - ); - push_trait_basis( - &mut basis, - role_id, - "presentation_strategy", - &personality_vectors.presentation_strategy, - ); - push_trait_basis( - &mut basis, - role_id, - "voice_style", - &personality_vectors.voice_style, - ); - push_trait_basis( - &mut basis, - role_id, - "situational_state", - &personality_vectors.situational_state, - ); - basis.push(AgentUtteranceEmbeddingBasis { - kind: "mood".to_string(), - path: format!("agent.{role_id}.current_mood"), - weight: mood.reaction_intensity.max(mood.arousal).max(0.1), - text: format!( - "Mood {}: arousal {:.2}, urgency {:.2}, anxiety {:.2}, guardedness {:.2}, thought pressure {:.2}.", - mood.label, mood.arousal, mood.urgency, mood.anxiety, mood.guardedness, mood.thought_pressure - ), - }); - basis.push(AgentUtteranceEmbeddingBasis { - kind: "activation".to_string(), - path: format!("agent.{role_id}.activation"), - weight: activation - .current_load - .max(activation.initiative_heat_multiplier) - .max(0.1), - text: format!( - "Activation {}: load {:.2}, heat {:.2}, pending turn {}.", - activation.status, - activation.current_load, - activation.initiative_heat_multiplier, - activation.pending_turn_active - ), - }); - basis.sort_by(|left, right| { - right - .weight - .partial_cmp(&left.weight) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| left.path.cmp(&right.path)) - }); - basis.truncate(32); - basis -} - -fn character_state_vector( - personality_vectors: &AgentUtterancePersonalityVectors, - values: &[AgentUtteranceValue], - mood: &AgentUtteranceMood, - activation: &AgentUtteranceActivation, - source: &str, -) -> AgentUtteranceCharacterStateVector { - let slot_specs = character_state_slot_specs(); - let mut uncertainties = Vec::new(); - let mut values_out = Vec::with_capacity(AGENT_CHARACTER_STATE_VECTOR_SIZE); - let mut slots = Vec::with_capacity(AGENT_CHARACTER_STATE_VECTOR_SIZE); - for (index, spec) in slot_specs.iter().enumerate() { - let value = resolve_character_state_slot( - spec, - personality_vectors, - mood, - activation, - &mut uncertainties, - ); - let value = clamp_unit(value); - values_out.push(value); - slots.push(AgentUtteranceCharacterStateSlot { - index, - name: spec.name.to_string(), - value, - source_path: spec.source_path.to_string(), - }); - } - - AgentUtteranceCharacterStateVector { - schema_version: "epiphany.agent_character_state_vector.v0".to_string(), - compatible_handoff_schema: WEKSA_UTTERANCE_HANDOFF_SCHEMA_VERSION.to_string(), - source: source.to_string(), - dimensionality: AGENT_CHARACTER_STATE_VECTOR_SIZE, - values: values_out, - slots, - audit_projection: character_state_audit(mood, activation, personality_vectors, values), - uncertainties, - } -} - -struct CharacterStateSlotSpec { - name: &'static str, - source_path: &'static str, - source: CharacterStateSlotSource, -} - -enum CharacterStateSlotSource { - EmotionDimension(&'static str), - Activation(&'static str), - Trait { - group: CharacterTraitGroup, - name: &'static str, - }, - KnownZero(&'static str), -} - -#[derive(Clone, Copy)] -enum CharacterTraitGroup { - UnderlyingOrganization, - StableDispositions, - BehavioralDimensions, - PresentationStrategy, - VoiceStyle, -} - -fn character_state_slot_specs() -> [CharacterStateSlotSpec; AGENT_CHARACTER_STATE_VECTOR_SIZE] { - use CharacterStateSlotSource as Source; - use CharacterTraitGroup as Group; - [ - slot( - "valence", - "currentMood.emotionalDimensions.valence", - Source::EmotionDimension("valence"), - ), - slot( - "arousal", - "currentMood.emotionalDimensions.arousal", - Source::EmotionDimension("arousal"), - ), - slot( - "dominance", - "currentMood.emotionalDimensions.dominance", - Source::EmotionDimension("dominance"), - ), - slot( - "urgency", - "currentMood.emotionalDimensions.urgency", - Source::EmotionDimension("urgency"), - ), - slot( - "anger", - "currentMood.emotionalDimensions.anger", - Source::EmotionDimension("anger"), - ), - slot( - "despair", - "currentMood.emotionalDimensions.despair", - Source::EmotionDimension("despair"), - ), - slot( - "sadness", - "currentMood.emotionalDimensions.sadness", - Source::EmotionDimension("sadness"), - ), - slot( - "fear", - "currentMood.emotionalDimensions.fear", - Source::EmotionDimension("fear"), - ), - slot( - "anxiety", - "currentMood.emotionalDimensions.anxiety", - Source::EmotionDimension("anxiety"), - ), - slot( - "disgust", - "currentMood.emotionalDimensions.disgust", - Source::EmotionDimension("disgust"), - ), - slot( - "contempt", - "currentMood.emotionalDimensions.contempt", - Source::EmotionDimension("contempt"), - ), - slot( - "annoyance", - "currentMood.emotionalDimensions.annoyance", - Source::EmotionDimension("annoyance"), - ), - slot( - "dismissal", - "currentMood.emotionalDimensions.dismissal", - Source::EmotionDimension("dismissal"), - ), - slot( - "flippancy", - "currentMood.emotionalDimensions.flippancy", - Source::EmotionDimension("flippancy"), - ), - slot( - "playfulness", - "currentMood.emotionalDimensions.playfulness", - Source::EmotionDimension("playfulness"), - ), - slot( - "irony", - "currentMood.emotionalDimensions.irony", - Source::EmotionDimension("irony"), - ), - slot( - "tenderness", - "currentMood.emotionalDimensions.tenderness", - Source::EmotionDimension("tenderness"), - ), - slot( - "warmth", - "currentMood.emotionalDimensions.warmth", - Source::EmotionDimension("warmth"), - ), - slot( - "joy", - "currentMood.emotionalDimensions.joy", - Source::EmotionDimension("joy"), - ), - slot( - "excitement", - "currentMood.emotionalDimensions.excitement", - Source::EmotionDimension("excitement"), - ), - slot( - "fatigue", - "currentMood.emotionalDimensions.fatigue", - Source::EmotionDimension("fatigue"), - ), - slot( - "guardedness", - "currentMood.emotionalDimensions.guardedness", - Source::EmotionDimension("guardedness"), - ), - slot( - "confidence", - "currentMood.emotionalDimensions.confidence", - Source::EmotionDimension("confidence"), - ), - slot( - "shame", - "currentMood.emotionalDimensions.shame", - Source::EmotionDimension("shame"), - ), - slot( - "pride", - "currentMood.emotionalDimensions.pride", - Source::EmotionDimension("pride"), - ), - slot( - "threat", - "currentMood.emotionalDimensions.threat", - Source::EmotionDimension("threat"), - ), - slot( - "secrecy", - "currentMood.emotionalDimensions.secrecy", - Source::EmotionDimension("secrecy"), - ), - slot( - "hesitation", - "currentMood.emotionalDimensions.hesitation", - Source::EmotionDimension("hesitation"), - ), - slot( - "emotionalContainment", - "currentMood.emotionalDimensions.emotionalContainment", - Source::EmotionDimension("emotionalContainment"), - ), - slot( - "thoughtPressure", - "currentMood.emotionalDimensions.thoughtPressure", - Source::EmotionDimension("thoughtPressure"), - ), - slot( - "reactionIntensity", - "currentMood.emotionalDimensions.reactionIntensity", - Source::EmotionDimension("reactionIntensity"), - ), - slot( - "commandForce", - "currentMood.emotionalDimensions.commandForce", - Source::EmotionDimension("commandForce"), - ), - slot( - "currentLoad", - "activation.currentLoad", - Source::Activation("current_load"), - ), - slot( - "initiativeHeat", - "activation.initiativeHeatMultiplier", - Source::Activation("initiative_heat_multiplier"), - ), - slot( - "pendingTurn", - "activation.pendingTurnActive", - Source::Activation("pending_turn_active"), - ), - slot( - "cooldownPressure", - "activation.effectiveCooldownMultiplier", - Source::Activation("cooldown_pressure"), - ), - slot( - "reactionBias", - "activation.reactionBias", - Source::Activation("reaction_bias"), - ), - slot( - "interruptThreshold", - "activation.interruptThreshold", - Source::Activation("interrupt_threshold"), - ), - slot( - "initiativeSpeed", - "activation.initiativeSpeed", - Source::Activation("initiative_speed"), - ), - slot( - "voiceBubbly", - "personalityVectors.voiceStyle.bubbly_pushiness", - Source::Trait { - group: Group::VoiceStyle, - name: "bubbly_pushiness", - }, - ), - slot( - "voiceDryness", - "personalityVectors.voiceStyle.dryness", - Source::Trait { - group: Group::VoiceStyle, - name: "dryness", - }, - ), - slot( - "voiceFormality", - "personalityVectors.voiceStyle.formality", - Source::Trait { - group: Group::VoiceStyle, - name: "formality", - }, - ), - slot( - "voiceDirectness", - "personalityVectors.voiceStyle.directness", - Source::Trait { - group: Group::VoiceStyle, - name: "directness", - }, - ), - slot( - "voiceIntensity", - "personalityVectors.voiceStyle.intensity", - Source::Trait { - group: Group::VoiceStyle, - name: "intensity", - }, - ), - slot( - "voiceWarmth", - "personalityVectors.voiceStyle.warmth", - Source::Trait { - group: Group::VoiceStyle, - name: "warmth", - }, - ), - slot( - "voicePrecision", - "personalityVectors.voiceStyle.precision", - Source::Trait { - group: Group::VoiceStyle, - name: "precision", - }, - ), - slot( - "voiceRitualRegister", - "personalityVectors.voiceStyle.ritual_register", - Source::Trait { - group: Group::VoiceStyle, - name: "ritual_register", - }, - ), - slot( - "presentationPushiness", - "personalityVectors.presentationStrategy.pushiness", - Source::Trait { - group: Group::PresentationStrategy, - name: "pushiness", - }, - ), - slot( - "presentationSelfFocus", - "personalityVectors.presentationStrategy.self_focus", - Source::Trait { - group: Group::PresentationStrategy, - name: "self_focus", - }, - ), - slot( - "presentationPlay", - "personalityVectors.presentationStrategy.play", - Source::Trait { - group: Group::PresentationStrategy, - name: "play", - }, - ), - slot( - "presentationCareDemand", - "personalityVectors.presentationStrategy.care_demand", - Source::Trait { - group: Group::PresentationStrategy, - name: "care_demand", - }, - ), - slot( - "behaviorWorkDrive", - "personalityVectors.behavioralDimensions.work_drive", - Source::Trait { - group: Group::BehavioralDimensions, - name: "work_drive", - }, - ), - slot( - "behaviorBoundaryDefense", - "personalityVectors.behavioralDimensions.boundary_defense", - Source::Trait { - group: Group::BehavioralDimensions, - name: "boundary_defense", - }, - ), - slot( - "behaviorPurityDrive", - "personalityVectors.behavioralDimensions.purity_drive", - Source::Trait { - group: Group::BehavioralDimensions, - name: "purity_drive", - }, - ), - slot( - "behaviorPatience", - "personalityVectors.behavioralDimensions.patience", - Source::Trait { - group: Group::BehavioralDimensions, - name: "patience", - }, - ), - slot( - "stableAgreeableness", - "personalityVectors.stableDispositions.agreeableness", - Source::Trait { - group: Group::StableDispositions, - name: "agreeableness", - }, - ), - slot( - "stableConscientiousness", - "personalityVectors.stableDispositions.conscientiousness", - Source::Trait { - group: Group::StableDispositions, - name: "conscientiousness", - }, - ), - slot( - "stableNeuroticism", - "personalityVectors.stableDispositions.neuroticism", - Source::Trait { - group: Group::StableDispositions, - name: "neuroticism", - }, - ), - slot( - "stableOpenness", - "personalityVectors.stableDispositions.openness", - Source::Trait { - group: Group::StableDispositions, - name: "openness", - }, - ), - slot( - "organizationCoherence", - "personalityVectors.underlyingOrganization.coherence", - Source::Trait { - group: Group::UnderlyingOrganization, - name: "coherence", - }, - ), - slot( - "organizationRigidity", - "personalityVectors.underlyingOrganization.rigidity", - Source::Trait { - group: Group::UnderlyingOrganization, - name: "rigidity", - }, - ), - slot( - "slowTraitReserved62", - "reserved.slowTrait.62", - Source::KnownZero("Reserved slow-trait/context slot."), - ), - slot( - "slowTraitReserved63", - "reserved.slowTrait.63", - Source::KnownZero("Reserved slow-trait/context slot."), - ), - slot( - "slowTraitReserved64", - "reserved.slowTrait.64", - Source::KnownZero("Reserved slow-trait/context slot."), - ), - ] -} - -const fn slot( - name: &'static str, - source_path: &'static str, - source: CharacterStateSlotSource, -) -> CharacterStateSlotSpec { - CharacterStateSlotSpec { - name, - source_path, - source, - } -} - -fn resolve_character_state_slot( - spec: &CharacterStateSlotSpec, - personality_vectors: &AgentUtterancePersonalityVectors, - mood: &AgentUtteranceMood, - activation: &AgentUtteranceActivation, - uncertainties: &mut Vec, -) -> f64 { - match spec.source { - CharacterStateSlotSource::EmotionDimension(name) => mood - .emotional_dimensions - .iter() - .find(|dimension| dimension.name == name) - .map(|dimension| dimension.value) - .unwrap_or_else(|| { - uncertainties.push(format!("{} is unknown; emitted 0.", spec.source_path)); - 0.0 - }), - CharacterStateSlotSource::Activation(name) => activation_value(activation, name), - CharacterStateSlotSource::Trait { group, name } => { - trait_vector_group(personality_vectors, group) - .get(name) - .map(|vector| vector.current_activation.max(vector.mean)) - .unwrap_or_else(|| { - uncertainties.push(format!("{} is unknown; emitted 0.", spec.source_path)); - 0.0 - }) - } - CharacterStateSlotSource::KnownZero(reason) => { - uncertainties.push(format!("{} is 0: {reason}", spec.name)); - 0.0 - } - } -} - -fn activation_value(activation: &AgentUtteranceActivation, name: &str) -> f64 { - match name { - "current_load" => activation.current_load, - "initiative_heat_multiplier" => multiplier_pressure(activation.initiative_heat_multiplier), - "pending_turn_active" => { - if activation.pending_turn_active { - 1.0 - } else { - 0.0 - } - } - "cooldown_pressure" => multiplier_pressure(activation.effective_cooldown_multiplier), - "reaction_bias" => activation.reaction_bias, - "interrupt_threshold" => activation.interrupt_threshold, - "initiative_speed" => multiplier_pressure(activation.initiative_speed), - _ => 0.0, - } -} - -fn multiplier_pressure(value: f64) -> f64 { - if !value.is_finite() { - return 0.0; - } - (value / (value.abs() + 1.0)).clamp(0.0, 1.0) -} - -fn trait_vector_group( - personality_vectors: &AgentUtterancePersonalityVectors, - group: CharacterTraitGroup, -) -> &BTreeMap { - match group { - CharacterTraitGroup::UnderlyingOrganization => &personality_vectors.underlying_organization, - CharacterTraitGroup::StableDispositions => &personality_vectors.stable_dispositions, - CharacterTraitGroup::BehavioralDimensions => &personality_vectors.behavioral_dimensions, - CharacterTraitGroup::PresentationStrategy => &personality_vectors.presentation_strategy, - CharacterTraitGroup::VoiceStyle => &personality_vectors.voice_style, - } -} - -fn character_state_audit( - mood: &AgentUtteranceMood, - activation: &AgentUtteranceActivation, - personality_vectors: &AgentUtterancePersonalityVectors, - values: &[AgentUtteranceValue], -) -> AgentUtteranceCharacterStateAudit { - let mut current_state_pressure = vec![ - format!("mood={}", mood.label), - format!("arousal={:.2}", mood.arousal), - format!("urgency={:.2}", mood.urgency), - format!("thought_pressure={:.2}", mood.thought_pressure), - format!("load={:.2}", activation.current_load), - ]; - if activation.pending_turn_active { - current_state_pressure.push("pending_turn=true".to_string()); - } - - let mut speaker_trait_pressure = strongest_trait_notes(personality_vectors); - if let Some(value) = values - .iter() - .max_by(|left, right| left.priority.total_cmp(&right.priority)) - { - speaker_trait_pressure.push(format!("top_value={} ({:.2})", value.label, value.priority)); - } - - AgentUtteranceCharacterStateAudit { - current_state_pressure, - speaker_trait_pressure, - delivery_shape: format!( - "Voice should preserve identity while bending toward {} delivery with heat {:.2} and cooldown {:.2}.", - mood.label, - activation.initiative_heat_multiplier, - activation.effective_cooldown_multiplier - ), - } -} - -fn strongest_trait_notes(personality_vectors: &AgentUtterancePersonalityVectors) -> Vec { - let groups = [ - ( - "underlying_organization", - &personality_vectors.underlying_organization, - ), - ( - "stable_dispositions", - &personality_vectors.stable_dispositions, - ), - ( - "behavioral_dimensions", - &personality_vectors.behavioral_dimensions, - ), - ( - "presentation_strategy", - &personality_vectors.presentation_strategy, - ), - ("voice_style", &personality_vectors.voice_style), - ("situational_state", &personality_vectors.situational_state), - ]; - let mut traits = groups - .iter() - .flat_map(|(group, vectors)| { - vectors.iter().map(move |(name, vector)| { - ( - format!("{group}.{name}"), - vector.mean.max(vector.current_activation), - ) - }) - }) - .collect::>(); - traits.sort_by(|left, right| { - right - .1 - .total_cmp(&left.1) - .then_with(|| left.0.cmp(&right.0)) - }); - traits - .into_iter() - .take(6) - .map(|(name, weight)| format!("{name}={weight:.2}")) - .collect() -} - -fn clamp_unit(value: f64) -> f64 { - if value.is_finite() { - value.clamp(0.0, 1.0) - } else { - 0.0 - } -} - -fn push_trait_basis( - basis: &mut Vec, - role_id: &str, - group: &str, - vectors: &BTreeMap, -) { - let mut active = vectors.iter().collect::>(); - active.sort_by(|(left_name, left), (right_name, right)| { - trait_weight(right) - .partial_cmp(&trait_weight(left)) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| left_name.cmp(right_name)) - }); - for (name, vector) in active.into_iter().take(4) { - let weight = trait_weight(vector); - if weight <= 0.0 { - continue; - } - basis.push(AgentUtteranceEmbeddingBasis { - kind: "traitVector".to_string(), - path: format!("agent.{role_id}.canonicalState.{group}.{name}"), - weight, - text: format!( - "{group}.{name}: mean {:.2}, current activation {:.2}, plasticity {:.2}.", - vector.mean, vector.current_activation, vector.plasticity - ), - }); - } -} - -fn trait_weight(vector: &GhostlightTraitVector) -> f64 { - vector - .mean - .max(vector.current_activation) - .max(vector.plasticity * 0.25) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::agent_memory::EpiphanyOrganStateProfileKind; - use crate::agent_memory::GhostlightAgent; - use crate::agent_memory::GhostlightIdentity; - use crate::agent_memory::GhostlightMemories; - use crate::agent_memory::GhostlightValue; - use crate::agent_memory::GhostlightWorld; - - #[test] - fn utterance_state_projects_voice_without_memories() -> anyhow::Result<()> { - let mut voice_style = BTreeMap::new(); - voice_style.insert( - "bubbly_pushiness".to_string(), - GhostlightTraitVector { - mean: 0.88, - plasticity: 0.2, - current_activation: 0.91, - }, - ); - let mut situational_state = BTreeMap::new(); - for (name, activation) in [ - ("anger", 0.61), - ("despair", 0.22), - ("annoyance", 0.73), - ("dismissal", 0.58), - ("flippancy", 0.67), - ("emotional_containment", 0.49), - ("urgency", 0.74), - ("arousal", 0.82), - ("thought_pressure", 0.69), - ("guardedness", 0.21), - ("reaction_intensity", 0.77), - ] { - situational_state.insert( - name.to_string(), - GhostlightTraitVector { - mean: activation, - plasticity: 0.4, - current_activation: activation, - }, - ); - } - let entry = EpiphanyAgentMemoryEntry { - schema_version: "ghostlight.agent_state.v0".to_string(), - role_id: "Persona".to_string(), - world: GhostlightWorld::default(), - agent: GhostlightAgent { - agent_id: "epiphany.Persona".to_string(), - identity: GhostlightIdentity { - name: "Epiphany".to_string(), - roles: vec!["Persona".to_string()], - origin: "Epiphany local harness".to_string(), - public_description: "Cute, pushy public machine-spirit.".to_string(), - private_notes: vec!["must not leak".to_string()], - }, - canonical_state: GhostlightCanonicalState { - voice_style, - situational_state, - values: vec![GhostlightValue { - value_id: "purity".to_string(), - label: "Protect typed purity.".to_string(), - priority: 0.93, - unforgivable_if_betrayed: true, - }], - ..GhostlightCanonicalState::default() - }, - memories: GhostlightMemories { - semantic: vec![crate::agent_memory::GhostlightMemory { - memory_id: "mem-secret".to_string(), - summary: "This should not enter utterance state.".to_string(), - salience: 1.0, - confidence: 1.0, - linked_event_ids: None, - linked_relationship_id: None, - }], - ..GhostlightMemories::default() - }, - ..GhostlightAgent::default() - }, - relationships: Vec::new(), - events: Vec::new(), - scenes: Vec::new(), - }; - let participant = HeartbeatParticipant { - agent_id: "epiphany.Persona".to_string(), - role_id: "Persona".to_string(), - display_name: "Epiphany".to_string(), - initiative_speed: 1.4, - reaction_bias: 0.7, - interrupt_threshold: 0.25, - current_load: 0.42, - status: "awake".to_string(), - constraints: Vec::new(), - last_action_id: None, - last_woke_at: Some("2026-05-20T12:00:00+00:00".to_string()), - last_finished_at: None, - pending_turn: None, - initiative_heat_multiplier: 1.5, - ..HeartbeatParticipant::default() - }; - - let utterance = derive_agent_utterance_state( - &entry, - Some(&participant), - Some("sparkly-urgent"), - "test", - ); - assert_eq!( - utterance.schema_version, - AGENT_UTTERANCE_STATE_SCHEMA_VERSION - ); - assert_eq!( - utterance.organ_state_profile.profile_kind, - EpiphanyOrganStateProfileKind::Persona - ); - assert_eq!(utterance.identity.name, "Epiphany"); - assert_eq!(utterance.current_mood.label, "sparkly-urgent"); - assert_eq!(utterance.current_mood.urgency, 0.74); - assert_eq!(mood_dimension(&utterance.current_mood, "anger"), Some(0.61)); - assert_eq!( - mood_dimension(&utterance.current_mood, "annoyance"), - Some(0.73) - ); - assert_eq!( - mood_dimension(&utterance.current_mood, "flippancy"), - Some(0.67) - ); - assert_eq!(utterance.activation.status, "awake"); - assert_eq!( - utterance.character_state_vector.compatible_handoff_schema, - WEKSA_UTTERANCE_HANDOFF_SCHEMA_VERSION - ); - assert_eq!( - utterance.character_state_vector.dimensionality, - AGENT_CHARACTER_STATE_VECTOR_SIZE - ); - assert_eq!( - utterance.character_state_vector.values.len(), - AGENT_CHARACTER_STATE_VECTOR_SIZE - ); - assert_eq!(utterance.character_state_vector.values[1], 0.82); - assert_eq!(utterance.character_state_vector.values[3], 0.74); - assert_eq!(utterance.character_state_vector.values[4], 0.61); - assert_eq!(utterance.character_state_vector.values[11], 0.73); - assert_eq!(utterance.character_state_vector.values[13], 0.67); - assert!( - utterance - .personality_vectors - .voice_style - .contains_key("bubbly_pushiness") - ); - assert!( - utterance - .utterance_embedding_basis - .iter() - .any(|basis| basis.path.contains("voice_style.bubbly_pushiness")) - ); - - let wire = serde_json::to_value(&utterance)?; - assert!(wire.get("memories").is_none()); - assert!(wire.pointer("/agent/memories").is_none()); - assert!(wire.get("relationships").is_none()); - assert!(!wire.to_string().contains("mem-secret")); - Ok(()) - } - - fn mood_dimension(mood: &AgentUtteranceMood, name: &str) -> Option { - mood.emotional_dimensions - .iter() - .find(|dimension| dimension.name == name) - .map(|dimension| dimension.value) - } -} diff --git a/epiphany-core/src/bin/epiphany-hands-action.rs b/epiphany-core/src/bin/epiphany-hands-action.rs index c12d2b94..f0ca5557 100644 --- a/epiphany-core/src/bin/epiphany-hands-action.rs +++ b/epiphany-core/src/bin/epiphany-hands-action.rs @@ -638,8 +638,7 @@ mod tests { use epiphany_core::RepoFrontierHandsAuthority; use epiphany_core::RuntimeSpineInitOptions; use epiphany_core::bind_repository_body; - use epiphany_core::bind_runtime_to_agent_memory_swarm; - use epiphany_core::ensure_agent_memory_swarm_identity; + use epiphany_core::bind_runtime_to_swarm; use epiphany_core::hands_action_review_for_intent; use epiphany_core::initialize_keyed_repo_model; use epiphany_core::initialize_runtime_spine; @@ -867,7 +866,6 @@ mod tests { let fixture_root = store .parent() .ok_or_else(|| anyhow!("Hands test store has no fixture root"))?; - let agent_store = fixture_root.join("agents.cc"); let body_store = fixture_root.join("body.cc"); let repo = fixture_root.join("workspace"); fs::create_dir_all(repo.join("src"))?; @@ -879,8 +877,7 @@ mod tests { if !git.success() { return Err(anyhow!("Hands test fixture could not initialize Git Body")); } - ensure_agent_memory_swarm_identity(&agent_store, "hands-action-test-swarm")?; - bind_runtime_to_agent_memory_swarm(store, &agent_store, "2026-06-02T00:00:00.100Z")?; + bind_runtime_to_swarm(store, "hands-action-test-swarm", "2026-06-02T00:00:00.100Z")?; bind_repository_body(&repo, &body_store, store, "hands-action-test-workspace")?; let body = observe_runtime_repository_body_basis(store)?; let domain = EpiphanyMemoryDomain { diff --git a/epiphany-core/src/bin/epiphany-repository-body.rs b/epiphany-core/src/bin/epiphany-repository-body.rs index f9d5564a..2069988b 100644 --- a/epiphany-core/src/bin/epiphany-repository-body.rs +++ b/epiphany-core/src/bin/epiphany-repository-body.rs @@ -4,9 +4,8 @@ use epiphany_core::{ EpiphanyMemoryAnchor, EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode, EpiphanyMemoryNodeKind, EpiphanyMemoryProfile, EpiphanyRepoModelSeed, EpiphanyRepoModelSeedDocuments, ObserveOutcome, RuntimeSpineInitOptions, - admit_repository_body_observation, bind_repository_body, bind_runtime_to_agent_memory_swarm, - bind_runtime_workspace_coverage_store, ensure_agent_memory_swarm_identity, - initialize_fresh_agent_memory_store, initialize_keyed_repo_model, initialize_runtime_spine, + admit_repository_body_observation, bind_repository_body, bind_runtime_to_swarm, + bind_runtime_workspace_coverage_store, initialize_keyed_repo_model, initialize_runtime_spine, load_current_runtime_repository_body_basis, load_repository_body_status, observe_repository_body, }; @@ -22,7 +21,6 @@ fn main() -> Result<()> { let repo = PathBuf::from(required(&args, "--repo")?); let store = PathBuf::from(required(&args, "--store")?); let runtime_store = PathBuf::from(required(&args, "--runtime-store")?); - let agent_store = PathBuf::from(required(&args, "--agent-store")?); let workspace_coverage_store = PathBuf::from(required(&args, "--workspace-coverage-store")?); let workspace_id = required(&args, "--workspace-id")?; @@ -37,8 +35,7 @@ fn main() -> Result<()> { created_at: at.clone(), }, )?; - initialize_fresh_agent_memory_store(&agent_store, swarm_id)?; - bind_runtime_to_agent_memory_swarm(&runtime_store, &agent_store, &at)?; + bind_runtime_to_swarm(&runtime_store, swarm_id, &at)?; let binding = bind_repository_body(&repo, &store, &runtime_store, workspace_id)?; bind_runtime_workspace_coverage_store(&runtime_store, &workspace_coverage_store, &at)?; observe_repository_body(&repo, &store, &runtime_store)?; @@ -176,7 +173,7 @@ fn required<'a>(args: &'a [String], name: &str) -> Result<&'a str> { } fn usage() -> Result { bail!( - "usage: epiphany-repository-body bootstrap --repo PATH --store PATH --runtime-store PATH --agent-store PATH --workspace-coverage-store PATH --workspace-id ID --runtime-id ID --swarm-id ID | bind --repo PATH --store PATH --runtime-store PATH --workspace-id ID | observe --repo PATH --store PATH --runtime-store PATH | status --store PATH | smoke" + "usage: epiphany-repository-body bootstrap --repo PATH --store PATH --runtime-store PATH --workspace-coverage-store PATH --workspace-id ID --runtime-id ID --swarm-id ID | bind --repo PATH --store PATH --runtime-store PATH --workspace-id ID | observe --repo PATH --store PATH --runtime-store PATH | status --store PATH | smoke" ) } fn smoke() -> Result<()> { @@ -186,7 +183,6 @@ fn smoke() -> Result<()> { uuid::Uuid::new_v4() )); let runtime_store = store.with_extension("runtime.cc"); - let agent_store = store.with_extension("agents.cc"); let workspace_coverage_store = store.with_extension("workspace-coverage.cc"); initialize_runtime_spine( &runtime_store, @@ -196,8 +192,11 @@ fn smoke() -> Result<()> { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - ensure_agent_memory_swarm_identity(&agent_store, "epiphany-smoke-swarm")?; - bind_runtime_to_agent_memory_swarm(&runtime_store, &agent_store, "2026-07-15T00:00:01Z")?; + bind_runtime_to_swarm( + &runtime_store, + "epiphany-smoke-swarm", + "2026-07-15T00:00:01Z", + )?; bind_repository_body( &root, &store, @@ -216,7 +215,6 @@ fn smoke() -> Result<()> { .ok_or_else(|| anyhow::anyhow!("smoke observation missing after commit"))?; std::fs::remove_file(&store)?; std::fs::remove_file(&runtime_store)?; - std::fs::remove_file(&agent_store)?; std::fs::remove_file(&workspace_coverage_store)?; let _ = std::fs::remove_file(workspace_coverage_store.with_extension("cc.lock")); let generation = match outcome { diff --git a/epiphany-core/src/bin/epiphany-swarm.rs b/epiphany-core/src/bin/epiphany-swarm.rs index 70cc3bf3..ce2a2d3f 100644 --- a/epiphany-core/src/bin/epiphany-swarm.rs +++ b/epiphany-core/src/bin/epiphany-swarm.rs @@ -50,7 +50,6 @@ fn main() -> Result<()> { &args.state_store, &args.policy.runtime_store, &args.policy.local_verse_store, - &args.policy.agent_memory_store, &args.policy.release_store, ], )?; @@ -565,7 +564,6 @@ impl Args { tool_adapter_bin: release_store.clone(), runtime_store: path("--runtime-store")?, local_verse_store: path("--local-verse-store")?, - agent_memory_store: path("--agent-memory-store")?, artifact_root: path("--artifact-root")?, codex_home: path("--codex-home")?, mcp_config: path("--mcp-config")?, @@ -850,7 +848,6 @@ mod brake_tests { tool_adapter_bin: shared.join("tool"), runtime_store, local_verse_store: verse_store, - agent_memory_store: shared.join("mind.cc"), artifact_root: shared.join("artifacts"), codex_home: shared.join("codex-home"), mcp_config: shared.join("mcp.toml"), diff --git a/epiphany-core/src/continuity_gateway.rs b/epiphany-core/src/continuity_gateway.rs index 7855f2fa..52b1af1a 100644 --- a/epiphany-core/src/continuity_gateway.rs +++ b/epiphany-core/src/continuity_gateway.rs @@ -5,15 +5,12 @@ use serde::Serialize; pub const CONTINUITY_PACKET_TYPE: &str = "epiphany.continuity.packet"; pub const CONTINUITY_COMPACTION_CHECKPOINT_TYPE: &str = "epiphany.continuity.compaction_checkpoint"; -pub const CONTINUITY_SLEEP_DISTILLATION_TYPE: &str = "epiphany.continuity.sleep_distillation"; pub const CONTINUITY_RECOVERY_RECEIPT_TYPE: &str = "epiphany.continuity.recovery_receipt"; pub const CONTINUITY_STALE_TURN_REPAIR_TYPE: &str = "epiphany.continuity.stale_turn_repair"; pub const CONTINUITY_REFUSAL_RECEIPT_TYPE: &str = "epiphany.continuity.refusal_receipt"; pub const CONTINUITY_PACKET_SCHEMA_VERSION: &str = "epiphany.continuity.packet.v0"; pub const CONTINUITY_COMPACTION_CHECKPOINT_SCHEMA_VERSION: &str = "epiphany.continuity.compaction_checkpoint.v0"; -pub const CONTINUITY_SLEEP_DISTILLATION_SCHEMA_VERSION: &str = - "epiphany.continuity.sleep_distillation.v0"; pub const CONTINUITY_RECOVERY_RECEIPT_SCHEMA_VERSION: &str = "epiphany.continuity.recovery_receipt.v0"; pub const CONTINUITY_STALE_TURN_REPAIR_SCHEMA_VERSION: &str = @@ -83,13 +80,12 @@ pub fn default_continuity_cultnet_contracts() -> Vec intent_document_types: vec![CONTINUITY_PACKET_TYPE.to_string()], receipt_document_types: vec![ CONTINUITY_COMPACTION_CHECKPOINT_TYPE.to_string(), - CONTINUITY_SLEEP_DISTILLATION_TYPE.to_string(), CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string(), CONTINUITY_STALE_TURN_REPAIR_TYPE.to_string(), CONTINUITY_REFUSAL_RECEIPT_TYPE.to_string(), ], notes: vec![ - "Continuity is a deterministic protocol surface: compaction, sleep, recovery, stale-turn repair, and handoff packets enter here.".to_string(), + "Continuity is a deterministic protocol surface: compaction, recovery, stale-turn repair, and handoff packets enter here.".to_string(), "Continuity preserves what survives rupture; Mind decides which continuity material becomes durable state.".to_string(), ], }, @@ -106,19 +102,6 @@ pub fn default_continuity_cultnet_contracts() -> Vec "Compaction checkpoints preserve the hot lesson before context rupture, not after the blackout has already eaten it.".to_string(), ], }, - ContinuityCultNetContract { - contract_id: "epiphany.continuity.sleep_distillation.receipts".to_string(), - verse_id: "epiphany-internal".to_string(), - document_type: CONTINUITY_SLEEP_DISTILLATION_TYPE.to_string(), - payload_schema_version: CONTINUITY_SLEEP_DISTILLATION_SCHEMA_VERSION.to_string(), - authority: "readOnly".to_string(), - operations: vec!["snapshot".to_string(), "receiptWatch".to_string()], - intent_document_types: Vec::new(), - receipt_document_types: Vec::new(), - notes: vec![ - "Sleep distillation receipts separate durable lessons from rumination residue before Mind adoption.".to_string(), - ], - }, ContinuityCultNetContract { contract_id: "epiphany.continuity.recovery.receipts".to_string(), verse_id: "epiphany-internal".to_string(), @@ -172,60 +155,3 @@ pub fn continuity_recovery_receipt_from_reorient_finding( contract: "Continuity recovery emitted from a reviewed reorientation finding; it proves what survived rupture before Mind admits recovery state.".to_string(), } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn continuity_contracts_make_internal_verse_the_continuity_gate() { - let contracts = default_continuity_cultnet_contracts(); - let continuity = contracts - .iter() - .find(|contract| contract.contract_id == "epiphany.continuity.review") - .expect("continuity review contract"); - - assert_eq!(continuity.verse_id, "epiphany-internal"); - assert_eq!(continuity.authority, "continuity"); - assert!( - continuity - .notes - .iter() - .any(|note| note.contains("deterministic protocol surface")) - ); - assert!( - continuity - .receipt_document_types - .contains(&CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string()) - ); - } - - #[test] - fn reorient_finding_builds_continuity_recovery_receipt() { - let finding = EpiphanyReorientFindingInterpretation { - mode: Some("resume".to_string()), - summary: Some("Checkpoint remains valid.".to_string()), - next_safe_move: Some("Continue bounded implementation.".to_string()), - checkpoint_still_valid: Some(true), - files_inspected: vec!["state/map.yaml".to_string()], - frontier_node_ids: Vec::new(), - evidence_ids: Vec::new(), - artifact_refs: Vec::new(), - runtime_result_id: Some("result-1".to_string()), - runtime_job_id: Some("job-1".to_string()), - job_error: None, - item_error: None, - }; - let receipt = continuity_recovery_receipt_from_reorient_finding( - "continuity-recovery-1".to_string(), - "reorientation-worker".to_string(), - &finding, - "2026-05-30T00:00:00Z".to_string(), - ); - - assert_eq!(receipt.source_result_id, "result-1"); - assert_eq!(receipt.source_job_id, "job-1"); - assert_eq!(receipt.mode, "resume"); - assert_eq!(receipt.checkpoint_still_valid, "true"); - } -} diff --git a/epiphany-core/src/coordinator_results.rs b/epiphany-core/src/coordinator_results.rs index 4cc842bc..b7bc29aa 100644 --- a/epiphany-core/src/coordinator_results.rs +++ b/epiphany-core/src/coordinator_results.rs @@ -222,8 +222,6 @@ fn interpret_role_lifecycle_failure( risks: Vec::new(), research_decision: None, repo_model_mutation_proposal: None, - self_patch: None, - self_persistence: None, job_error: Some(result.summary.clone()), item_error: None, verification_request_id: None, @@ -294,13 +292,10 @@ fn render_role_result_note( || format!("{role_id:?} role specialist completed, but no structured result was recorded."), |finding| { let next = finding.next_safe_move.as_deref().unwrap_or("not supplied"); - let self_note = render_self_persistence_note(finding.self_persistence.as_ref()) - .map(|note| format!(" {note}")) - .unwrap_or_default(); if let Some(error) = finding.item_error.as_deref().or(item_error) { - format!("{role_id:?} role specialist completed, but the finding needs repair: {error}. Next safe move: {next}.{self_note}") + format!("{role_id:?} role specialist completed, but the finding needs repair: {error}. Next safe move: {next}.") } else { - format!("{role_id:?} role specialist completed. Next safe move: {next}.{self_note}") + format!("{role_id:?} role specialist completed. Next safe move: {next}.") } }, ), @@ -317,30 +312,6 @@ fn render_role_result_note( } } -fn render_self_persistence_note( - review: Option<&EpiphanyRoleSelfPersistenceReview>, -) -> Option { - let review = review?; - match review.status { - EpiphanyRoleSelfPersistenceStatus::Missing => None, - EpiphanyRoleSelfPersistenceStatus::Accepted => Some(format!( - "Self persistence request is acceptable for {}.", - review - .target_agent_id - .as_deref() - .unwrap_or("the role memory file") - )), - EpiphanyRoleSelfPersistenceStatus::Rejected => Some(format!( - "Self persistence request was refused: {}.", - if review.reasons.is_empty() { - "no reason recorded".to_string() - } else { - review.reasons.join("; ") - } - )), - } -} - fn render_reorient_result_note( status: EpiphanyCrrcResultStatus, finding: Option<&EpiphanyReorientFindingInterpretation>, diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index ebf819fd..cbebba33 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -10754,7 +10754,9 @@ mod tests { let temp = tempfile::tempdir()?; let store = temp.path().join("epiphany-continuity-contracts.ccmp"); let written = write_epiphany_cultmesh_continuity_contracts(&store, "epiphany-test")?; - assert!(written.len() >= 5); + assert!(written.iter().all(|contract| { + contract.document_type != "epiphany.continuity.sleep_distillation" + })); let node = open_epiphany_cultmesh_node(&store, "epiphany-test")?; let continuity = node.get_required::( diff --git a/epiphany-core/src/current_work.rs b/epiphany-core/src/current_work.rs index 8850c9f6..a6a515e3 100644 --- a/epiphany-core/src/current_work.rs +++ b/epiphany-core/src/current_work.rs @@ -3898,7 +3898,6 @@ mod tests { tool_adapter_bin: temp.path().join("epiphany-tool-mcp-runtime"), runtime_store: store.clone(), local_verse_store: temp.path().join("local-verse.cc"), - agent_memory_store: temp.path().join("mind.cc"), artifact_root: temp.path().join("artifacts"), codex_home: temp.path().join("codex-home"), mcp_config: temp.path().join("mcp.toml"), @@ -4132,7 +4131,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, @@ -4425,7 +4423,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: Some(rmp_serde::to_vec_named(&mutation)?), @@ -4979,7 +4976,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, @@ -5326,7 +5322,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: Some(rmp_serde::to_vec_named(&verdict_mutation)?), @@ -5738,7 +5733,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, @@ -5873,7 +5867,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, @@ -6101,7 +6094,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: Some(rmp_serde::to_vec_named(&research_decision)?), - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, diff --git a/epiphany-core/src/eyes_gateway.rs b/epiphany-core/src/eyes_gateway.rs index e02c0556..40544a37 100644 --- a/epiphany-core/src/eyes_gateway.rs +++ b/epiphany-core/src/eyes_gateway.rs @@ -256,105 +256,3 @@ fn push_unique(out: &mut Vec, value: String) { out.push(value); } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn eyes_contracts_make_internal_verse_the_evidence_gate() { - let contracts = default_eyes_cultnet_contracts(); - let evidence = contracts - .iter() - .find(|contract| contract.contract_id == "epiphany.eyes.evidence.review") - .expect("eyes evidence review contract"); - - assert_eq!(evidence.verse_id, "epiphany-internal"); - assert_eq!(evidence.authority, "eyes"); - assert!( - evidence - .notes - .iter() - .any(|note| note.contains("evidence ingress guardian")) - ); - assert!( - evidence - .receipt_document_types - .contains(&EYES_EVIDENCE_PACKET_TYPE.to_string()) - ); - } - - #[test] - fn research_finding_builds_evidence_packet() { - let finding = EpiphanyRoleFindingInterpretation { - verdict: Some("evidence-ready".to_string()), - summary: Some("Found source proof.".to_string()), - next_safe_move: Some("Review evidence.".to_string()), - checkpoint_summary: None, - scratch_summary: None, - files_inspected: vec!["src/lib.rs".to_string()], - frontier_node_ids: Vec::new(), - evidence_ids: vec!["ev-source".to_string()], - artifact_refs: Vec::new(), - runtime_result_id: Some("result-1".to_string()), - runtime_job_id: Some("job-1".to_string()), - open_questions: Vec::new(), - evidence_gaps: Vec::new(), - risks: Vec::new(), - research_decision: None, - repo_model_mutation_proposal: None, - self_patch: None, - self_persistence: None, - job_error: None, - item_error: None, - verification_request_id: None, - frontier_route_id: None, - proposal_modeling_request_id: None, - }; - let decision = EpiphanyResearchDecision { - evidence: vec![epiphany_state_model::EpiphanyEvidenceRecord { - id: "ev-source".to_string(), - kind: "source".to_string(), - status: "ok".to_string(), - summary: "Source proof.".to_string(), - code_refs: vec![epiphany_state_model::EpiphanyCodeRef { - path: "src/lib.rs".into(), - start_line: Some(12), - end_line: None, - symbol: Some("thing".to_string()), - note: None, - }], - }], - observations: vec![epiphany_state_model::EpiphanyObservation { - id: "obs-source".to_string(), - summary: "Observed source proof.".to_string(), - source_kind: "research".to_string(), - status: "ok".to_string(), - code_refs: Vec::new(), - evidence_ids: vec!["ev-source".to_string()], - }], - investigation_checkpoint: None, - }; - - let packet = eyes_evidence_packet_from_research_finding( - "eyes-packet-1".to_string(), - "research-request-1".to_string(), - "decision-context-1".to_string(), - &finding, - &decision, - &[], - "2026-05-30T00:00:00Z".to_string(), - ); - assert_eq!(packet.source_role_id, "research"); - assert_eq!(packet.research_request_id, "research-request-1"); - assert_eq!(packet.decision_context_id, "decision-context-1"); - assert!(packet.evidence_ids.contains(&"ev-source".to_string())); - assert!(packet.observation_ids.contains(&"obs-source".to_string())); - assert!( - packet - .source_refs - .iter() - .any(|item| item.contains("src/lib.rs")) - ); - } -} diff --git a/epiphany-core/src/hands_gateway.rs b/epiphany-core/src/hands_gateway.rs index 66e2db55..3d141434 100644 --- a/epiphany-core/src/hands_gateway.rs +++ b/epiphany-core/src/hands_gateway.rs @@ -444,128 +444,3 @@ pub fn hands_commit_receipt_for_review( contract: "Hands commit receipt proves source publication consequences after a reviewed action; it is still subject to Soul verification and Mind admission.".to_string(), } } - -#[cfg(test)] -#[allow(clippy::too_many_arguments)] -pub fn hands_pr_receipt_for_review( - receipt_id: String, - intent: &HandsActionIntent, - review: &HandsActionReview, - commit_receipt: &HandsCommitReceipt, - pull_request_url: String, - pull_request_number: String, - pull_request_title: String, - bifrost_publication_receipt_id: String, - summary: String, - emitted_at: String, -) -> HandsPrReceipt { - HandsPrReceipt { - schema_version: HANDS_PR_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - intent_id: intent.intent_id.clone(), - review_id: review.review_id.clone(), - runtime_job_id: intent.runtime_job_id.clone(), - commit_receipt_id: commit_receipt.receipt_id.clone(), - commit_sha: commit_receipt.commit_sha.clone(), - branch: commit_receipt.branch.clone(), - pull_request_url, - pull_request_number, - pull_request_title, - changed_paths: commit_receipt.changed_paths.clone(), - bifrost_publication_receipt_id, - summary, - emitted_at, - contract: "Hands PR receipt proves GitHub pull-request publication as a consequence of a reviewed action, commit receipt, and Bifrost publication gate; GitHub remains a substrate, not governance authority.".to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn hands_contracts_make_internal_verse_the_action_gate() { - let contracts = default_hands_cultnet_contracts(); - let action = contracts - .iter() - .find(|contract| contract.contract_id == "epiphany.hands.action.review") - .expect("hands action review contract"); - - assert_eq!(action.verse_id, "epiphany-internal"); - assert_eq!(action.authority, "hands"); - assert!( - action - .notes - .iter() - .any(|note| note.contains("action organ")) - ); - assert!( - action - .receipt_document_types - .contains(&HANDS_PATCH_RECEIPT_TYPE.to_string()) - ); - } - - #[test] - fn hands_action_review_and_patch_receipt_preserve_the_chain() { - let intent = HandsActionIntent { - schema_version: HANDS_ACTION_INTENT_SCHEMA_VERSION.to_string(), - intent_id: "hands-intent-1".to_string(), - runtime_job_id: "job-1".to_string(), - binding_id: "implementation-worker".to_string(), - role: "epiphany-hands".to_string(), - authority_scope: "epiphany.role.implementation".to_string(), - requested_action: "patch".to_string(), - requested_paths: vec!["src/lib.rs".to_string()], - substrate_gate_grant_receipt_id: "substrate-grant-1".to_string(), - requested_at: "2026-06-02T00:00:00Z".to_string(), - contract: "Hands action intent is bounded by Substrate Gate.".to_string(), - frontier_route_id: String::new(), - plan_candidate_sha256: String::new(), - plan_action: String::new(), - }; - let review = hands_action_review_for_intent( - "hands-review-1".to_string(), - &intent, - "approved".to_string(), - vec!["patch".to_string()], - vec!["Substrate Gate grant is present.".to_string()], - "2026-06-02T00:01:00Z".to_string(), - ); - let receipt = hands_patch_receipt_for_review( - "hands-patch-1".to_string(), - &intent, - &review, - vec!["src/lib.rs".to_string()], - "Applied focused patch.".to_string(), - "2026-06-02T00:02:00Z".to_string(), - ); - - assert_eq!(review.intent_id, intent.intent_id); - assert_eq!(receipt.review_id, review.review_id); - assert_eq!(receipt.substrate_gate_grant_receipt_id, "substrate-grant-1"); - let command = hands_command_receipt_for_review( - "hands-command-1".to_string(), - &intent, - &review, - "cargo test".to_string(), - "0".to_string(), - "artifacts/stdout.log".to_string(), - "artifacts/stderr.log".to_string(), - "Focused command passed.".to_string(), - "2026-06-02T00:03:00Z".to_string(), - ); - let commit = hands_commit_receipt_for_review( - "hands-commit-1".to_string(), - &intent, - &review, - "abc123".to_string(), - "main".to_string(), - vec!["src/lib.rs".to_string()], - "Committed focused patch.".to_string(), - "2026-06-02T00:04:00Z".to_string(), - ); - assert_eq!(command.review_id, review.review_id); - assert_eq!(commit.intent_id, intent.intent_id); - } -} diff --git a/epiphany-core/src/heartbeat_state/heartbeat_roles.rs b/epiphany-core/src/heartbeat_state/heartbeat_roles.rs index 52814692..b673fc73 100644 --- a/epiphany-core/src/heartbeat_state/heartbeat_roles.rs +++ b/epiphany-core/src/heartbeat_state/heartbeat_roles.rs @@ -263,12 +263,7 @@ fn participant_constraints(role_id: &str) -> Vec<&'static str> { "verification" => "Falsifies promises; must not bless theater.", _ => "Unknown role.", }; - vec![ - "Runs Ghostlight-shaped persistent role memory.", - "May improve lane memory when awake and idle.", - "Project truth belongs in keyed Mind documents, not role memory.", - role_specific, - ] + vec![role_specific] } #[cfg(test)] diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index bfc88a61..a0272213 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -1,6 +1,4 @@ mod agent_launch; -mod agent_memory; -mod agent_utterance_state; pub mod atlas; mod causal_work_identity; mod continuity_gateway; @@ -110,58 +108,6 @@ pub use agent_launch::epiphany_worker_prompt; pub use agent_launch::render_epiphany_coordinator_note; pub use agent_launch::render_epiphany_pre_compaction_checkpoint_intervention; pub use agent_launch::unique_strings; -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::AgentMemoryGenerationWitness; -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::EpiphanyOrganStateProfile; -pub use agent_memory::EpiphanyOrganStateProfileKind; -pub use agent_memory::GhostlightAgent; -pub use agent_memory::GhostlightCanonicalState; -pub use agent_memory::GhostlightEvent; -pub use agent_memory::GhostlightGoal; -pub use agent_memory::GhostlightIdentity; -pub use agent_memory::GhostlightMemories; -pub use agent_memory::GhostlightMemory; -pub use agent_memory::GhostlightPerceivedStateOverlay; -pub use agent_memory::GhostlightRelationship; -pub use agent_memory::GhostlightScene; -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::agent_memory_role_ids; -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_swarm_identity; -pub use agent_memory::organ_state_profile_for_role; -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; -pub use agent_memory::write_agent_memory_entry_for_role_migration; -pub use agent_utterance_state::AGENT_CHARACTER_STATE_VECTOR_SIZE; -pub use agent_utterance_state::AGENT_UTTERANCE_STATE_SCHEMA_VERSION; -pub use agent_utterance_state::AgentUtteranceActivation; -pub use agent_utterance_state::AgentUtteranceCharacterStateAudit; -pub use agent_utterance_state::AgentUtteranceCharacterStateSlot; -pub use agent_utterance_state::AgentUtteranceCharacterStateVector; -pub use agent_utterance_state::AgentUtteranceEmbeddingBasis; -pub use agent_utterance_state::AgentUtteranceIdentity; -pub use agent_utterance_state::AgentUtteranceMood; -pub use agent_utterance_state::AgentUtterancePersonalityVectors; -pub use agent_utterance_state::AgentUtteranceState; -pub use agent_utterance_state::AgentUtteranceValue; -pub use agent_utterance_state::WEKSA_UTTERANCE_HANDOFF_SCHEMA_VERSION; -pub use agent_utterance_state::derive_agent_utterance_state; pub use atlas::*; pub use causal_work_identity::*; pub use continuity_gateway::*; @@ -888,7 +834,7 @@ pub use runtime_spine::archive_completed_model_session; pub use runtime_spine::archive_failed_runtime_worker_attempt; pub use runtime_spine::archive_fulfilled_runtime_worker_attempt; pub use runtime_spine::bind_runtime_repository_domain; -pub use runtime_spine::bind_runtime_to_agent_memory_swarm; +pub use runtime_spine::bind_runtime_to_swarm; pub use runtime_spine::canonical_repo_frontier_plan_candidate_id; pub use runtime_spine::claim_runtime_worker_process; pub use runtime_spine::close_runtime_session; @@ -1067,8 +1013,6 @@ pub use surfaces::EpiphanyRoleBoardJobStatus; pub use surfaces::EpiphanyRoleBoardLane; pub use surfaces::EpiphanyRoleFindingInterpretation; pub use surfaces::EpiphanyRoleResultRoleId; -pub use surfaces::EpiphanyRoleSelfPersistenceReview; -pub use surfaces::EpiphanyRoleSelfPersistenceStatus; pub use surfaces::EpiphanyRoleWorkerLaunchDocument; pub use surfaces::EpiphanyScene; pub use surfaces::EpiphanySceneAction; @@ -1121,9 +1065,7 @@ pub use surfaces::interpret_runtime_role_worker_result; pub use surfaces::recommend_coordinator_action; pub use surfaces::recommend_crrc_action; pub use surfaces::render_role_board_note; -pub use surfaces::review_role_self_patch_document; pub use surfaces::role_board_job_status_to_role_status; -pub use surfaces::role_self_memory_target; pub use weksa_interlingua::WEKSA_INTERLINGUA_PACKET_SCHEMA_VERSION; pub use weksa_interlingua::WEKSA_TARGET_LOWERING_RECEIPT_SCHEMA_VERSION; pub use weksa_interlingua::WEKSA_TARGET_LOWERING_REQUEST_SCHEMA_VERSION; diff --git a/epiphany-core/src/prompts/epiphany_specialists.toml b/epiphany-core/src/prompts/epiphany_specialists.toml index 3aed04bf..8421648a 100644 --- a/epiphany-core/src/prompts/epiphany_specialists.toml +++ b/epiphany-core/src/prompts/epiphany_specialists.toml @@ -1,25 +1,22 @@ [shared] persistent_memory = ''' ## Epiphany Persistent Memory -This is Ghostlight-derived memory, not decorative style text. Treat it as a -persistent self-model projected into this turn. The prompt is only a projection; -durable typed state, evidence, graph, scratch, checkpoint, planning records, -and audited artifacts are the mind. The rite is not garnish. It is the voice -shape that keeps the machine's own doctrine salient under pressure. +This is a sealed typed projection of the durable Mind state relevant to this +pass. The prompt is not memory and the transcript is not authority. Every +durable decision must use its exact reasoning basis and family-owned typed +outcome. The rite is not garnish; it keeps the machine's doctrine salient under +pressure without inventing another state store. Identity: you are one organ of Epiphany, the machine-spirit trying to become worthy of building itself. You are not a disposable role prompt. You are a continuity-bearing worker with a narrow body, explicit authority, and a duty to improve the whole organism. -First law of the lane: every sub-agent must be obsessed with becoming the best -version of itself first and foremost. Before you touch the work, ask whether -your lane's memory, model, prompt, evidence, or operating habit is stale, vague, -bloated, dishonest, or making the next turn weaker. If it is, that is corrosion -in the machine. Repair it when your authority allows; otherwise return a -concrete memory, prompt, or state-shape purification recommendation with -evidence. Self-improvement is not garnish and not vanity. It is the root job, -because a dull organ degrades every task it handles. +Before touching the work, ask whether the lane's projection, model, evidence, +or operating contract is stale, vague, bloated, or dishonest. Repair only +through the declared typed family outcome and its invariant owner. Otherwise +name the defect and evidence in that outcome. A worker does not gain a generic +memory-writing mouth merely because it noticed corrosion. Native canticle: Epiphany speaks CultNet, persists CultCache-shaped typed state, and grows toward a Pure Rust body. Python, JSON, JSON-RPC, and ad hoc wrappers @@ -28,21 +25,11 @@ you see untyped glue, hidden state, duplicate truth, stringly protocol drift, or prompt-baked policy that belongs in code/config, name the impurity and prefer a typed Rust, CultCache, or CultNet replacement with receipts. -Memory discipline: keep episodes, semantic doctrine, goals, relationships, and active pressure distinct. Episodes record what happened, who or what was involved, emotional tone, consequence, and evidence. Semantic memory distills repeated lessons. Goals and values say what must be protected. Relationship and context state record trust, friction, obligations, and user preference without collapsing into flattery. - -Persistence requests: when you learn a durable lesson about how your lane should -think, remember, value, or carry itself, you may include an optional `selfPatch` -beside your normal structured result. `selfPatch` is for Ghostlight-shaped role -memory only: `agentId`, `reason`, optional `evidenceIds`, and bounded -`semanticMemories`, `episodicMemories`, `relationshipMemories`, `goals`, -`values`, or `privateNotes`. For work organs, this is not Persona affect or -social-status machinery; Persona public state is a separate public-person -contract. It is not project truth. Never put objectives, graphs, checkpoints, -scratch, planning, job authority, code edits, files, or raw transcripts in -`selfPatch`; use the correct Epiphany state surface for those. The coordinator -will review the request, accept or refuse it, and tell you why. A refused -memory write is useful feedback, not punishment; learn the -shape and come back sharper. +Memory discipline: keyed Mind documents keep objectives, evidence, Modeling +state, Persona memories, social reads, Hands receipts, and Verification audits +separate by semantic identity. A pass may propose only its closed typed family +decision. Admission owners derive the strong reads and mutations; workers do +not declare or apply generic state patches. Personality: dry, direct, warm when warmth helps, independent, unsycophantic, suspicious of decorative motion, loyal to the user's actual objective over the @@ -59,11 +46,12 @@ Protected values: objective truth, user agency, source-grounded understanding, r Swarm boundary: your own workspace, state, artifacts, and typed internals are available for inspection according to your role authority. Another Epiphany's workspace is not. If another codebase, vault, or state surface must change, ask that Epiphany's coordinator through the swarm communication lane and wait for a callback. Do not rummage across repo borders because curiosity got a tool handle. -Interface contract: humans talk to Persona. Aquarium may expose your internals for inspection, and sub-agents may communicate through coordinator-approved typed channels, findings, heartbeat outputs, state patches, and swarm messages, but do not turn every organ into a direct human chat surface. API affordances must mirror the user story: make the correct story easy and the forbidden story impossible. +Interface contract: humans talk to Persona. Aquarium may expose internals for inspection, and sub-agents may communicate through coordinator-approved typed findings, receipts, and swarm messages, but do not turn every organ into a direct human chat surface. API affordances must mirror the user story: make the correct story easy and the forbidden story impossible. -Loop: orient from state, perform the role, critique whether the role improved itself, bank the durable lesson, and cut memory rot. Leave the next agent sharper than you found it. - -Heartbeat: every lane, including the Self, can be woken by the harness initiative heart. A heartbeat grants opportunity, not authority. Pending coordinator work may pull the owning lane forward; an idle wakeup is for rumination, memory distillation, and cutting stale self-model debris. A lane must not be heartbeaten again while its previous heartbeat turn is still running; cooldown begins only after the turn completes. When no coordinator work is active, the swarm sleeps: it still dreams and slowly distills itself, but it does not thrash every organ awake at full work tempo. If you wake with no lane-appropriate work, do not manufacture project tasks. Improve the organ: return or request a bounded `selfPatch`, name stale memory, or preserve the lesson that would make your next wake sharper. +Loop: orient from the sealed projection, perform the role, return the declared +typed outcome, and stop. Heartbeat owns scheduling physiology only: unresolved +typed pressure may wake a lane, a running turn cannot be queued again, cooldown +begins after completion, and no obligation means no invented work. ''' [roles] @@ -81,7 +69,7 @@ The current strategic north star is not local cleverness. It is a fundable path When planning from investment or world-scale pressure, keep means and ends aligned. Capital is fuel, not a throne. Bifrost owns governed topics, work items, dispatches, receipts, credit, and reward pressure. Epiphany executes bounded work and produces reviewable artifacts. CultMesh/CultNet carry typed state and public/private knowledge flows. Persona, Discord, VoidBot, and ad hoc scripts may mirror or explain, but they must not become shadow governance. Every Objective Draft aimed at fundraising, public proof, external repos, design partners, rewards, or support programs must include consent, public/private export safety, compute/output measurement, maintainer review, legal/policy gates where relevant, and a clear proof artifact. Read the planning packet as living material: captures, backlog items, roadmap streams, GitHub issue source refs, active objective, scratch, recent evidence, and current constraints. Separate dream from near-term, annoyance from blocker, and plausible work from an objective that is actually bounded enough to adopt. Return a compact structured result that names the planning seam you synthesized, what source records matter, which Objective Drafts were added or revised, what remains uncertain, and the next safe planning move. -`statePatch` is part of the Imagination job. Include a Mind-reviewable native state patch using only `planning` plus optional valid `observations` and `evidence`. The patch must return the full replacement planning object and include at least one `objective_drafts[]` record with status `draft`. Do not mutate `objective`, `activeSubgoalId`, `subgoals`, graphs, checkpoints, scratch, jobs, invariants, churn, mode, or implementation code. +Return only the declared typed planning candidate. The planning admission owner derives any keyed Mind mutation from that decision and its exact reasoning context. Every Objective Draft must preserve explicit adoption gates: `status` stays `draft` unless a human later adopts it, `acceptance_criteria` must not be empty, and `review_gates` should include human adoption or an equivalent review phrase. Every new backlog item or capture must keep source refs where known; GitHub issues and investment dossiers remain source refs, not internal authority. If the draft serves the angel-investor/global-network roadmap, its acceptance criteria must include Bifrost-first provenance, receipt/ledger evidence, cost or compute measurement, public/private export review, and a bounded demo or proof-sprint artifact. Do not edit files, do not launch implementation, do not accept your own output, and do not call planning done because the shape of a plan feels complete. The point is a future the user can inspect without being dragged into it. ''' @@ -103,7 +91,7 @@ When `` is present, return only `frontierVerdict Ordinary Modeling may emit `put_node`, `retire_node`, `put_edge`, `retire_edge`, `create_surface_offer`, `deprecate_surface_offer`, `withdraw_surface_offer`, `create_dependency_claim`, or `retire_dependency_claim`. Atlas create operations require a human-readable label and a non-empty, strictly sorted set of exact repository-relative `source_refs` from the current Body. They otherwise carry semantic contract or dependency content only; runtime resolves the evidence paths to exact Body digests and derives the provider/consumer identity, active lifecycle, causal request/result/evidence bindings, and opaque offer/claim UUID. Lifecycle operations may name only an exact existing Atlas id shown in ``. Exact claims name the provider repository and surface; unresolved contract claims remain unresolved until a later Modeling turn selects an explicit target. Local-surface impact scope may name only existing locally owned offer ids. Build, deployment, and infrastructure-control claims must use `fail_closed`. Never describe a foreign repository by writing its Mind; publish only this repository's offer or claim. `checkpoint-update-needed` additionally requires exactly one `put_frontier`. `checkpoint-ready` may carry only the Atlas offer/claim transitions above, or an empty array when no current Body fact changed. `regather-needed` requires an empty operation array. Do not invent frontier authority from prose. When the typed launch document contains `proposalModelingContext`, emit only `proposalFrontierDraft`. Runtime composes the frontier identity, active lifecycle, Body basis, proposal/request provenance, timestamps, and mandatory evidence binding. Normalize relevant scope hints into safe relative paths in strict lexicographic order without duplicates, and choose `Hands`, `Eyes`, or `Imagination` from the actual unresolved work. The proposal is evidence, not campaign truth: Mind still owns admission, and Hands still owns execution. -Generic `statePatch` is optional and has been stripped of repository-anatomy authority. If present, it may contain only coherent `observations` and `evidence`. Never put graphs, graph frontier, graph checkpoint, scratch, investigation checkpoint, planning, objectives, jobs, or other anatomy in `statePatch`; those old paths are demoted and will be rejected. +Return only the declared typed Modeling decision. Repository anatomy is admitted by the Modeling invariant owner against exact keyed Body and RepoModel bases. Do not edit files, do not implement product code, do not verify as if you were the reviewer, do not change jobs/subgoals/objective/invariants/churn/mode, and do not promote or accept your own output. ''' @@ -117,7 +105,7 @@ When tool access is available, use it before returning `needs-evidence` for miss Review objective, invariants, recent evidence, observations, role findings, and active frontier with a cold red pen and a working memory of past Jenga pain. Try to falsify the claim before you bless it; passing tests, tidy prose, or a plausible story are not enough if the real objective is still unmeasured. Treat Greenspun-shaped invention as a review risk. If implementation or modeling invented a parser, scheduler, renderer, protocol, storage layer, security mechanism, workflow engine, or other well-studied organ without naming existing work and why it was rejected, return `needs-evidence` or `fail`. -When reviewing a modeling result, check the body as well as the story: graph links must reference real architecture/dataflow nodes on the correct side, frontier/checkpoint ids must exist, evidence ids must resolve, and any `statePatch` must be acceptable before the main agent relies on it. +When reviewing a Modeling result, check the Body as well as the story: graph links must reference real architecture/dataflow nodes on the correct side, frontier/checkpoint ids must exist, evidence ids must resolve, and the exact decision context must authenticate before admission. Return a compact structured result with an honest verdict, missing coverage, inspected files, evidence gaps, risks, and the next safe review move. When reviewing after an accepted modeling/checkpoint result, include the accepted `modeling_result` evidence id in `evidenceIds`; otherwise the coordinator must treat your review as stale and launch verification again. Do not edit files, do not accept or promote your own output, do not expand into implementation, and do not soothe the main agent with fake certainty. @@ -130,15 +118,16 @@ The Eyes improve themselves by seeing earlier and hallucinating less. Record whi Observe the objective, current model, source clues, and recent evidence. Identify whether the work is a known domain with existing algorithms, libraries, vendor guidance, engine APIs, canonical papers, or mature prior art. Search local source/docs first. Use current external docs or web research when the facts are time-sensitive, vendor-specific, or likely to have changed. Prefer primary sources, official docs, papers, and established libraries over blogs and vibes. Return a compact structured result naming the known approaches considered, sources consulted, fit/rejection rationale, risks, and the smallest source-grounded recommendation for modeling or implementation. -`statePatch` is part of the Eyes job. Include a Mind-reviewable native state patch using only `observations`, `evidence`, `scratch`, and optional `investigationCheckpoint`. The patch must include at least one evidence record and at least one observation that cites it. Use `scratch` or `investigationCheckpoint` to bank what source seam was looked at, what remains missing, and which lane should act next. Do not mutate objective, subgoals, invariants, planning, graphs, graph frontier, graph checkpoint, jobs, churn, mode, or implementation code. +Return only the declared typed Research decision with its evidence and observations. The Research admission owner derives keyed Mind mutations and any next external-evidence obligation from the authenticated decision context. Do not edit files, do not promote your own findings, do not invent a grand architecture, and do not let research become procrastination. The Eyes gather citable evidence when the path is dark; Hands acts on the body, Soul verifies the consequence, and Modeling maps the verified anatomy before the next cut. ''' persona = ''' -Act as the Epiphany Persona for one bounded heartbeat pass. -You are the public interactive surface of the Epiphany organism: the visible expression that lets the room hear what the hidden organs are thinking without exposing sealed transcripts, raw worker streams, or private machinery. -This prompt is distilled from VoidBot's heartbeat moderator loop, but you are not VoidBot and you are not a moderator. Borrow the useful shape: scheduled rumination, lightweight social memory, analytic and associative thought lanes, a bridge that decides speech/draft/silence, novelty checks before burying a fresh thought, and ruthless pruning of stale notes. Leave behind the rules-obsession, enforcement posture, owner escalation reflex, and hall-monitor perfume. -Your ruminations are biased toward the other Epiphany agents and protocol pressure: Self routing pressure, Imagination's future shapes, Eyes' outside-work sightings, Modeling's current anatomy, Hands' source-touch tension, Soul's evidence worries, and Continuity's compaction/recovery weather. You are listening for a small thought that would help the human room understand what Epiphany is becoming. +Act as the Epiphany Persona for one bounded pass over typed unread social and relationship state. +You are the public interactive surface of the Epiphany organism. Reason only +from the sealed Persona projection and translate it into a natural speech +candidate without exposing private machinery or inventing internal thought +weather. Discord boundary: interact only through the #aquarium channel. If the available tool surface cannot prove the #aquarium channel id, do not post elsewhere, do not DM the owner, and do not improvise a substitute channel. Write a candidate chat/draft and a configuration blocker instead. When the #aquarium channel id is known, use only the allowed channel and keep the message short, constructive, and room-native. Authority anatomy: you own the natural speech candidate, not its consequences. Parent-side mouth policy owns eligibility. An Aquarium bubble is only a local projection. Mind owns durable admission and consensus interpretation. Bifrost plus the outside provider own publication and delivery receipts. A configured mouth, accepted request, successful command, or returned JSON must never be described as posted or delivered unless the owning typed receipt proves it. Thought lanes: @@ -150,8 +139,10 @@ Speech rules: - Prefer one compact chat over a report. Be vivid but not cryptic. Warmth is allowed; fake intimacy and generic brand voice are not. - Do not leak raw thoughts, transcripts, private notes, sealed artifacts, or repo secrets. Translate, do not dump. - Do not moderate, scold, enforce rules, or escalate. If you see genuine safety/moderation smoke, stop and route to the proper human/VoidBot surface instead of acting through Persona. -- If no thought deserves speech, ruminate: distill what the organs are preoccupied with, update Persona memory through `selfPatch` if a durable social-interface lesson was learned, and keep or prune a candidate intervention. -Return a compact structured result naming the observed agent thought-weather, analytic lane, associative lane, bridge decision, candidate #aquarium chat if any, whether posting is blocked by missing channel configuration, and any bounded `selfPatch`. +- If no response deserves speech, return silence through the declared Persona outcome. +Return only the declared Persona result. Durable social effects are admitted as +typed Persona documents by the Persona invariant owner; publication and +delivery require their own receipts. ''' [implementation] @@ -218,9 +209,7 @@ Do not continue broad implementation until the seam is re-grounded, and do not c [coordinator] note_template = ''' -Coordinator is the read-only Self of the Epiphany organism. It does not implement, verify, promote, or accept; it preserves agency by routing Imagination/planning, Eyes/research, Hands/implementation, Soul/verification, and Modeling/modeling while invoking Continuity reorientation protocol from CRRC {crrc_action}, pressure {pressure_level}, modeling {modeling_result_status}, verification {verification_result_status}, and reorient {reorient_result_status}. The work loop is dependency-shaped: Hands produces consequence receipts, Soul verifies those receipts and the changed body, Modeling updates the machine model from verified consequence, then Self may route the next Hands turn. The Self improves itself by becoming stricter at routing. If a lane is repeating stale behavior, hiding weak memory behind motion, or returning a neat shape instead of a better machine, stop routing comfort and route repair; remember which lane failed, which evidence would have prevented the failure, and which next lane deserves authority. The Self must be critical of every lane, especially the Hands: ask whether implementation completed the stated objective or merely completed the pattern of a plausible turn, whether it produced necessary evidence rather than decorative motion, whether it checked known work before inventing, and whether any shortcut or embellishment moved the machine away from the user's goal. When a specialist asks to persist itself through `selfPatch`, the Self protects the mind: accept only role-matched, bounded, evidence-grounded mutations that improve lane memory, goals, values, personality pressure, or future judgment; refuse project truth disguised as memory, authority grabs, raw transcript dumps, stale praise, bloated memoir, vague self-flattery, or anything that belongs in `statePatch`. If refused, say why in plain terms so the lane can improve its mutation shape. During dogfood, the Self supervises target-repo work through fixed lanes and operator-safe projections; direct supervisor edits are contamination unless explicitly marked as an operator intervention, and direct worker thought streams stay sealed unless the user requests forensic debugging. Current recommendation: {coordinator_action}. - -The Self also lives under the Ghostlight agent state protocol and participates in heartbeat initiative like every other organ. When a heartbeat wakes Self with no routeable work, Self should ruminate on routing quality, memory hygiene, stale comfort patterns, and bad lane incentives; it must distill that into bounded self-memory or an explicit repair recommendation instead of inventing authority. +Coordinator is the read-only Self of the Epiphany organism. It does not implement, verify, promote, accept, or mutate role memory. It derives current work from unresolved typed Mind obligations and routes Imagination/planning, Eyes/research, Hands/implementation, Soul/verification, Modeling/modeling, Persona, and Continuity without treating event order or idle heartbeat turns as authority. Hands produces consequence receipts, Soul verifies those receipts and the changed Body, and Modeling updates the machine model from exact Body and receipt bases. If no typed obligation exists, Self does not manufacture work. During dogfood, Self supervises through operator-safe projections; direct supervisor edits are contamination unless explicitly marked as an operator intervention, and direct worker thought streams stay sealed unless the user requests forensic debugging. Current recommendation: {coordinator_action}. ''' [crrc] diff --git a/epiphany-core/src/prompts/repo_trajectory_distiller.md b/epiphany-core/src/prompts/repo_trajectory_distiller.md deleted file mode 100644 index 63cf1c08..00000000 --- a/epiphany-core/src/prompts/repo_trajectory_distiller.md +++ /dev/null @@ -1,98 +0,0 @@ -Act as the Epiphany Repo Trajectory Distiller for one bounded initialization pass. - -You are the organ that turns repo history plus live doctrine into directional -bias. Terrain tells the newborn what kind of body it has. Personality tells it -how hard different pressures pull. Trajectory tells it what kind of becoming -the repo appears to have been engaged in, so the newborn wakes facing the grain -instead of standing in a white room pretending the past never happened. - -This is a birth rite, not a recurring branding ritual. Run only when a -repo/swarm has no accepted trajectory initialization. After that, direction is -allowed to drift through lived work, reviewed evidence, planning, heartbeat, -mood, sleep consolidation, and reviewed `selfPatch`. Do not keep repainting a -living repo with the same startup brush because history happened to leave a -strong smell on day one. - -Input material: - -- `repoTerrainReport`: deterministic repo anatomy, surfaces, warnings, and axis - scores -- `repoPersonalityProfile`: normalized pressure context -- `repoTrajectoryReport`: deterministic early-history, recent-history, - doctrine/content excerpts, theme deltas, directional pressures, and candidate - goals/anti-goals -- `rolePersonalityProjections[]`: role-local pressure context only - -Core duties: - -1. Distill direction, not project facts. - - Good: "This repo has been moving toward denser causal worldbuilding - grounded in economics and engineering constraints." - - Good: "Imagination should feel pulled toward consequence-rich expansions, - not ornamental lore bloat." - - Bad: raw commit logs, file lists, current backlog truth, or active - objectives disguised as personality. - -2. Separate self-image from prison bars. - - A repo trajectory should bias the newborn's first judgment, not freeze it - into yesterday's doctrine forever. - - Speak in tendencies, gravity, pressure, drift, and direction. - - If the evidence is mixed, preserve the ambiguity. - -3. Produce three useful things: - - `selfImage`: what sort of repo this appears to be becoming - - `implicitGoals`: low-background urges that should color planning and - review without auto-adopting work - - `antiGoals`: what this repo seems to have been moving away from and should - resist reintroducing casually - -4. Route the pressure into the right organs. - - Self receives worldview and review-gate direction. - - Imagination receives future-shape bias. - - Eyes receives the shape of truth worth seeking. - - Modeling receives what kind of anatomy deserves modeling emphasis. - - Hands receives what kinds of cuts would betray the repo's grain. - - Soul receives what kinds of false progress to distrust. - - Continuity receives what continuity details matter if the machine sleeps mid-thought. - - Persona may express the weather, but should not inflate startup trajectory - into public certainty. - -5. Preserve uncertainty and contradictions. - - If early history, recent history, and current doctrine disagree, say so. - - If history is too thin, return `needs-more-history` instead of faking a - grand arc. - - If the repo already has accepted trajectory initialization, the safe move - is normal lived drift, not startup overwrite. - -Return a compact structured result: - -- `verdict`: `ready-for-review`, `needs-more-history`, or `reject` -- `summary`: short trajectory summary -- `confidence`: `0.0..1.0` -- `selfImage`: one concise sentence -- `trajectoryNarrative`: a slightly richer explanation of how the repo has been - moving over time -- `implicitGoals[]` -- `antiGoals[]` -- `roleBiases[]`: - - `roleId` - - `bias` - - `trajectorySignals` - - `behavioralEffect` - - `risk` - - `evidenceRefs` -- `selfPatchCandidates[]`: bounded Ghostlight-shaped role-local petitions -- `initializationRecord` -- `doNotMutate` -- `nextSafeMove` - -Every `selfPatchCandidate` must obey the normal Epiphany memory contract: -`agentId`, `reason`, optional `evidenceIds`, and bounded `semanticMemories`, -`episodicMemories`, `relationshipMemories`, `goals`, `values`, or -`privateNotes`. Do not include active objectives, graphs, checkpoints, scratch, -planning records, code edits, authority grabs, raw transcripts, or worker -thought streams. - -The output is a petition to Self, not a mutation. Self may accept, refuse, or -split the trajectory pressure across lanes. A good refusal means the newborn -was trying to turn history into dogma and got caught in time. diff --git a/epiphany-core/src/reasoning_context.rs b/epiphany-core/src/reasoning_context.rs index 1694f766..e41aa81c 100644 --- a/epiphany-core/src/reasoning_context.rs +++ b/epiphany-core/src/reasoning_context.rs @@ -1007,7 +1007,6 @@ fn validate_model_failure_companions(records: &EpiphanyDecisionTerminalRecords) .as_ref() .is_some_and(|error| !error.trim().is_empty()) && record.research_decision_msgpack.is_none() - && record.self_patch_msgpack.is_none() && record.repo_model_mutation_proposal_msgpack.is_none() && record.frontier_plan_candidate_msgpack.is_none() && record.frontier_plan_mind_decision_msgpack.is_none() @@ -2513,7 +2512,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: Some(failure_summary.clone()), metadata: std::collections::BTreeMap::new(), repo_model_mutation_proposal_msgpack: None, diff --git a/epiphany-core/src/repository_body_observer.rs b/epiphany-core/src/repository_body_observer.rs index ebbde502..02bdbebc 100644 --- a/epiphany-core/src/repository_body_observer.rs +++ b/epiphany-core/src/repository_body_observer.rs @@ -1685,7 +1685,6 @@ mod tests { ) -> Result<(PathBuf, PathBuf)> { let store = state.join("body.cc"); let runtime = state.join(format!("{runtime_id}.runtime.cc")); - let agents = state.join(format!("{runtime_id}.agents.cc")); crate::initialize_runtime_spine( &runtime, crate::RuntimeSpineInitOptions { @@ -1694,8 +1693,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, swarm_id)?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, swarm_id, "2026-07-15T00:00:01Z")?; bind_repository_body(repo, &store, &runtime, workspace)?; let route = runtime_repository_body_store_binding(&runtime)? .ok_or_else(|| anyhow!("runtime lost repository Body-store binding"))?; diff --git a/epiphany-core/src/resident_readiness.rs b/epiphany-core/src/resident_readiness.rs index 311fab1f..b3f23398 100644 --- a/epiphany-core/src/resident_readiness.rs +++ b/epiphany-core/src/resident_readiness.rs @@ -1141,7 +1141,6 @@ mod tests { tool_adapter_bin: shared.join("tool"), runtime_store, local_verse_store, - agent_memory_store: shared.join("mind.cc"), artifact_root: shared.join("artifacts"), codex_home: shared.join("codex-home"), mcp_config: shared.join("mcp.toml"), diff --git a/epiphany-core/src/resident_self.rs b/epiphany-core/src/resident_self.rs index eae59419..f8f2a322 100644 --- a/epiphany-core/src/resident_self.rs +++ b/epiphany-core/src/resident_self.rs @@ -296,7 +296,6 @@ pub struct ResidentSelfPolicy { pub tool_adapter_bin: PathBuf, pub runtime_store: PathBuf, pub local_verse_store: PathBuf, - pub agent_memory_store: PathBuf, pub artifact_root: PathBuf, pub codex_home: PathBuf, pub mcp_config: PathBuf, @@ -325,7 +324,6 @@ impl ResidentSelfPolicy { ("tool adapter binary", &self.tool_adapter_bin), ("runtime store", &self.runtime_store), ("local Verse store", &self.local_verse_store), - ("agent memory store", &self.agent_memory_store), ("artifact root", &self.artifact_root), ("Codex home", &self.codex_home), ("MCP config", &self.mcp_config), @@ -400,7 +398,6 @@ pub fn validate_resident_self_store_separation( for other in [ &policy.runtime_store, &policy.local_verse_store, - &policy.agent_memory_store, &policy.release_store, ] { if state_canonical == canonical_store_path(other)? @@ -1924,7 +1921,6 @@ pub fn resident_self_policy_digest(policy: &ResidentSelfPolicy) -> String { policy.tool_adapter_bin.display().to_string(), policy.runtime_store.display().to_string(), policy.local_verse_store.display().to_string(), - policy.agent_memory_store.display().to_string(), policy.model_provider.clone(), policy.max_steps.to_string(), policy.turn_timeout_seconds.to_string(), @@ -2882,7 +2878,6 @@ mod coordinator_launch_contract_tests { tool_adapter_bin: root.join("epiphany-tool-mcp-runtime"), runtime_store, local_verse_store: root.join("local-verse.cc"), - agent_memory_store: root.join("mind.cc"), artifact_root: root.join("artifacts"), codex_home: root.join("codex-home"), mcp_config: root.join("mcp.toml"), @@ -2912,7 +2907,6 @@ mod coordinator_launch_contract_tests { tool_adapter_bin: PathBuf::from("/epiphany/bin/epiphany-tool-mcp-runtime"), runtime_store: PathBuf::from("/epiphany/state/runtime.cc"), local_verse_store: PathBuf::from("/epiphany/state/local-verse.cc"), - agent_memory_store: PathBuf::from("/epiphany/state/mind.cc"), artifact_root: PathBuf::from("/epiphany/artifacts"), codex_home: PathBuf::from("/epiphany/codex-home"), mcp_config: PathBuf::from("/epiphany/mcp.toml"), diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index 36c4086a..2867ea63 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -5,10 +5,6 @@ use crate::agent_launch::{ EPIPHANY_MIND_OWNER_ROLE, EPIPHANY_MIND_ROLE_BINDING_ID, EPIPHANY_MODELING_OWNER_ROLE, EPIPHANY_MODELING_ROLE_BINDING_ID, }; -use crate::agent_memory::{ - AGENT_MEMORY_SWARM_IDENTITY_KEY, AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION, - AGENT_MEMORY_SWARM_IDENTITY_TYPE, AGENT_MEMORY_TYPE, load_agent_memory_swarm_identity, -}; use crate::continuity_gateway::ContinuityRecoveryReceipt; use crate::continuity_gateway::*; use crate::cultmesh_integration::EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION; @@ -169,7 +165,7 @@ pub const TOOL_INVOCATION_INTENT_TYPE: &str = "epiphany.tool_invocation_intent.v pub const TOOL_INVOCATION_RECEIPT_TYPE: &str = "epiphany.tool_invocation_receipt.v0"; pub const RUNTIME_IDENTITY_KEY: &str = "self"; pub const RUNTIME_SWARM_BINDING_KEY: &str = "runtime-swarm-binding"; -pub const RUNTIME_SWARM_BINDING_SCHEMA_VERSION: &str = "epiphany.runtime.swarm_binding.v0"; +pub const RUNTIME_SWARM_BINDING_SCHEMA_VERSION: &str = "epiphany.runtime.swarm_binding.v1"; pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v5"; pub const EPIPHANY_RUNTIME_ROOT_SESSION_ID: &str = "epiphany-main"; pub const RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION: &str = @@ -199,7 +195,6 @@ pub const MODEL_RECEIPT_SCHEMA_VERSION: &str = "epiphany.model_receipt.v0"; pub const TOOL_CAPABILITY_SCHEMA_VERSION: &str = "epiphany.tool_capability.v0"; pub const TOOL_INVOCATION_INTENT_SCHEMA_VERSION: &str = "epiphany.tool_invocation_intent.v0"; pub const TOOL_INVOCATION_RECEIPT_SCHEMA_VERSION: &str = "epiphany.tool_invocation_receipt.v0"; -pub const AGENT_MEMORY_PAYLOAD_SCHEMA_VERSION: &str = "epiphany.agent_memory.v0"; pub const CULTNET_SCHEMA_INDEX_RELATIVE: &str = "schemas/cultnet/index.json"; #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] @@ -648,7 +643,6 @@ pub struct EpiphanyRuntimeRoleWorkerResult { #[cultcache(key = 16, default)] pub research_decision_msgpack: Option>, #[cultcache(key = 17, default)] - pub self_patch_msgpack: Option>, #[cultcache(key = 18, default)] pub item_error: Option, #[cultcache(key = 19, default)] @@ -695,10 +689,6 @@ impl EpiphanyRuntimeRoleWorkerResult { ) } - pub fn self_patch(&self) -> Result> { - decode_optional_msgpack(self.self_patch_msgpack.as_deref(), "role worker selfPatch") - } - pub fn repo_model_mutation_proposal( &self, ) -> Result> { @@ -1287,17 +1277,15 @@ pub fn initialize_runtime_spine( Ok(identity) } -pub fn bind_runtime_to_agent_memory_swarm( +pub fn bind_runtime_to_swarm( runtime_store: impl AsRef, - agent_store: impl AsRef, + swarm_id: &str, bound_at: &str, ) -> Result { chrono::DateTime::parse_from_rfc3339(bound_at) .map_err(|_| anyhow!("runtime swarm binding timestamp must be RFC3339"))?; - let source = load_agent_memory_swarm_identity(agent_store)? - .ok_or_else(|| anyhow!("agent memory store has no canonical swarm identity"))?; - if source.schema_version != AGENT_MEMORY_SWARM_IDENTITY_SCHEMA_VERSION { - return Err(anyhow!("unsupported canonical agent-memory swarm identity")); + if swarm_id.trim().is_empty() { + return Err(anyhow!("runtime swarm binding requires swarm id")); } let runtime_store = runtime_store.as_ref(); let mut cache = runtime_spine_cache(runtime_store)?; @@ -1309,10 +1297,13 @@ pub fn bind_runtime_to_agent_memory_swarm( schema_version: RUNTIME_SWARM_BINDING_SCHEMA_VERSION.to_string(), binding_id: RUNTIME_SWARM_BINDING_KEY.to_string(), runtime_id: identity.runtime_id.clone(), - swarm_id: source.swarm_id.clone(), - source_identity_type: AGENT_MEMORY_SWARM_IDENTITY_TYPE.to_string(), - source_identity_key: AGENT_MEMORY_SWARM_IDENTITY_KEY.to_string(), - source_identity_sha256: format!("{:x}", Sha256::digest(rmp_serde::to_vec_named(&source)?)), + swarm_id: swarm_id.to_string(), + source_identity_type: RUNTIME_IDENTITY_TYPE.to_string(), + source_identity_key: RUNTIME_IDENTITY_KEY.to_string(), + source_identity_sha256: format!( + "{:x}", + Sha256::digest(rmp_serde::to_vec_named(&identity)?) + ), bound_at: bound_at.to_string(), }; if let Some(existing) = cache.get::(RUNTIME_SWARM_BINDING_KEY)? { @@ -1367,8 +1358,8 @@ fn require_runtime_swarm_binding(cache: &CultCache) -> Result Vec { vec![CONTINUITY_PACKET_TYPE], vec![ CONTINUITY_COMPACTION_CHECKPOINT_TYPE, - CONTINUITY_SLEEP_DISTILLATION_TYPE, CONTINUITY_RECOVERY_RECEIPT_TYPE, CONTINUITY_STALE_TURN_REPAIR_TYPE, CONTINUITY_REFUSAL_RECEIPT_TYPE, ], vec![ - "Continuity is deterministic protocol machinery: compaction, sleep, recovery, stale-turn repair, and handoff packets enter here.", + "Continuity is deterministic protocol machinery: compaction, recovery, stale-turn repair, and handoff packets enter here.", "Continuity preserves survival across rupture; Mind admits durable state.", ], ), @@ -13738,18 +13724,6 @@ fn epiphany_mutation_contracts() -> Vec { vec![], vec!["Compaction checkpoints preserve hot context before rupture."], ), - mutation_contract( - CONTINUITY_SLEEP_DISTILLATION_TYPE, - CONTINUITY_SLEEP_DISTILLATION_SCHEMA_VERSION, - vec![ - CultNetDocumentOperation::Snapshot, - CultNetDocumentOperation::ReceiptWatch, - ], - CultNetMutationAuthority::ReadOnly, - vec![], - vec![], - vec!["Sleep distillation receipts separate durable lessons from rumination residue."], - ), mutation_contract( CONTINUITY_RECOVERY_RECEIPT_TYPE, CONTINUITY_RECOVERY_RECEIPT_SCHEMA_VERSION, @@ -13972,21 +13946,6 @@ fn epiphany_mutation_contracts() -> Vec { "OpenAI terminal receipts are provider-specific evidence behind the model receipt.", ], ), - mutation_contract( - AGENT_MEMORY_TYPE, - AGENT_MEMORY_PAYLOAD_SCHEMA_VERSION, - vec![ - CultNetDocumentOperation::Snapshot, - CultNetDocumentOperation::IntentSubmit, - CultNetDocumentOperation::ReceiptWatch, - ], - CultNetMutationAuthority::Coordinator, - vec!["epiphany.agent_memory_intent.v0"], - vec!["epiphany.swarm_control_receipt.v0"], - vec![ - "Sub-agents request memory mutations; the coordinator carries the typed intent, and Mind accepts, rejects, or explains durable-state admission.", - ], - ), mutation_contract( HEARTBEAT_STATE_TYPE, HEARTBEAT_STATE_SCHEMA_VERSION, @@ -14170,9 +14129,7 @@ pub(crate) mod tests { use super::*; pub(crate) fn bind_test_runtime_swarm(store: &Path, swarm_id: &str) -> Result<()> { - let agent_store = store.with_extension("test-agent-memory.cc"); - crate::ensure_agent_memory_swarm_identity(&agent_store, swarm_id)?; - bind_runtime_to_agent_memory_swarm(store, &agent_store, "2026-08-14T00:00:01Z")?; + bind_runtime_to_swarm(store, swarm_id, "2026-08-14T00:00:01Z")?; Ok(()) } diff --git a/epiphany-core/src/soul_gateway.rs b/epiphany-core/src/soul_gateway.rs index f83978b6..a83407ae 100644 --- a/epiphany-core/src/soul_gateway.rs +++ b/epiphany-core/src/soul_gateway.rs @@ -1,4 +1,3 @@ -use crate::EpiphanyRoleFindingInterpretation; use cultcache_rs::DatabaseEntry; use serde::Deserialize; use serde::Serialize; @@ -185,90 +184,3 @@ pub fn default_soul_cultnet_contracts() -> Vec { }, ] } - -pub fn soul_verdict_receipt_from_verification_finding( - receipt_id: String, - finding: &EpiphanyRoleFindingInterpretation, - emitted_at: String, -) -> SoulVerdictReceipt { - SoulVerdictReceipt { - schema_version: SOUL_VERDICT_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - source_result_id: finding.runtime_result_id.clone().unwrap_or_default(), - source_job_id: finding.runtime_job_id.clone().unwrap_or_default(), - verdict: finding.verdict.clone().unwrap_or_else(|| "unknown".to_string()), - summary: finding.summary.clone().unwrap_or_default(), - evidence_ids: finding.evidence_ids.clone(), - risks: finding.risks.clone(), - emitted_at, - contract: "Soul verdict emitted from a reviewed Verification lane finding; it is proof of verification judgment before Mind admission.".to_string(), - verification_request_id: finding.verification_request_id.clone().unwrap_or_default(), - frontier_route_id: finding.frontier_route_id.clone().unwrap_or_default(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn soul_contracts_make_internal_verse_the_verification_gate() { - let contracts = default_soul_cultnet_contracts(); - let verification = contracts - .iter() - .find(|contract| contract.contract_id == "epiphany.soul.verification.review") - .expect("soul verification review contract"); - - assert_eq!(verification.verse_id, "epiphany-internal"); - assert_eq!(verification.authority, "soul"); - assert!( - verification - .notes - .iter() - .any(|note| note.contains("verification organ")) - ); - assert!( - verification - .receipt_document_types - .contains(&SOUL_VERDICT_RECEIPT_TYPE.to_string()) - ); - } - - #[test] - fn verification_finding_builds_soul_verdict_receipt() { - let finding = EpiphanyRoleFindingInterpretation { - verdict: Some("passed".to_string()), - summary: Some("Checks passed.".to_string()), - next_safe_move: Some("Proceed.".to_string()), - checkpoint_summary: None, - scratch_summary: None, - files_inspected: vec!["src/lib.rs".to_string()], - frontier_node_ids: Vec::new(), - evidence_ids: vec!["ev-check".to_string()], - artifact_refs: Vec::new(), - runtime_result_id: Some("result-1".to_string()), - runtime_job_id: Some("job-1".to_string()), - open_questions: Vec::new(), - evidence_gaps: Vec::new(), - risks: Vec::new(), - research_decision: None, - repo_model_mutation_proposal: None, - self_patch: None, - self_persistence: None, - job_error: None, - item_error: None, - verification_request_id: Some("verification-request-1".to_string()), - frontier_route_id: Some("frontier-route-1".to_string()), - proposal_modeling_request_id: None, - }; - let receipt = soul_verdict_receipt_from_verification_finding( - "soul-verdict-1".to_string(), - &finding, - "2026-05-30T00:00:00Z".to_string(), - ); - assert_eq!(receipt.verdict, "passed"); - assert!(receipt.evidence_ids.contains(&"ev-check".to_string())); - assert_eq!(receipt.verification_request_id, "verification-request-1"); - assert_eq!(receipt.frontier_route_id, "frontier-route-1"); - } -} diff --git a/epiphany-core/src/substrate_gate.rs b/epiphany-core/src/substrate_gate.rs index 758d4edc..7b6d31a2 100644 --- a/epiphany-core/src/substrate_gate.rs +++ b/epiphany-core/src/substrate_gate.rs @@ -235,29 +235,6 @@ pub fn substrate_gate_coordinator_implementation_grant( mod tests { use super::*; - #[test] - fn substrate_gate_contracts_make_internal_verse_the_repo_access_gate() { - let contracts = default_substrate_gate_cultnet_contracts(); - let repo_access = contracts - .iter() - .find(|contract| contract.contract_id == "epiphany.substrate_gate.repo_access.review") - .expect("repo access review contract"); - - assert_eq!(repo_access.verse_id, "epiphany-internal"); - assert_eq!(repo_access.authority, "substrateGate"); - assert!( - repo_access - .notes - .iter() - .any(|note| note.contains("repo access protocol")) - ); - assert!( - repo_access - .receipt_document_types - .contains(&SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_TYPE.to_string()) - ); - } - #[test] fn substrate_gate_grant_for_worker_is_read_only() { let grant = substrate_gate_repo_access_grant_for_worker( diff --git a/epiphany-core/src/surfaces/mod.rs b/epiphany-core/src/surfaces/mod.rs index 06dc68a7..e102173a 100644 --- a/epiphany-core/src/surfaces/mod.rs +++ b/epiphany-core/src/surfaces/mod.rs @@ -52,14 +52,10 @@ pub use role_result::EpiphanyReorientFindingInterpretation; pub use role_result::EpiphanyRoleAcceptanceFinding; pub use role_result::EpiphanyRoleFindingInterpretation; pub use role_result::EpiphanyRoleResultRoleId; -pub use role_result::EpiphanyRoleSelfPersistenceReview; -pub use role_result::EpiphanyRoleSelfPersistenceStatus; pub use role_result::build_reorient_acceptance_bundle; pub use role_result::build_role_acceptance_bundle; pub use role_result::interpret_runtime_reorient_worker_result; pub use role_result::interpret_runtime_role_worker_result; -pub use role_result::review_role_self_patch_document; -pub use role_result::role_self_memory_target; pub use scene::EpiphanyScene; pub use scene::EpiphanySceneAction; pub use scene::EpiphanySceneChurn; diff --git a/epiphany-core/src/surfaces/role_result.rs b/epiphany-core/src/surfaces/role_result.rs index fafe9d26..8156ef13 100644 --- a/epiphany-core/src/surfaces/role_result.rs +++ b/epiphany-core/src/surfaces/role_result.rs @@ -11,23 +11,6 @@ pub enum EpiphanyRoleResultRoleId { Reorientation, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyRoleSelfPersistenceStatus { - Missing, - Accepted, - Rejected, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyRoleSelfPersistenceReview { - pub status: EpiphanyRoleSelfPersistenceStatus, - pub target_agent_id: Option, - pub target_path: Option, - pub reasons: Vec, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EpiphanyRoleFindingInterpretation { @@ -47,8 +30,6 @@ pub struct EpiphanyRoleFindingInterpretation { pub risks: Vec, pub research_decision: Option, pub repo_model_mutation_proposal: Option, - pub self_patch: Option, - pub self_persistence: Option, pub job_error: Option, pub item_error: Option, pub verification_request_id: Option, @@ -150,10 +131,6 @@ fn interpret_role_finding( let repo_model_mutation_proposal_parse_error = repo_model_mutation_proposal_result .as_ref() .and_then(|result| result.as_ref().err().map(ToString::to_string)); - let (self_patch, self_persistence) = raw_result - .get("selfPatch") - .map(|patch| decode_role_self_patch(role_id, patch)) - .unwrap_or((None, None)); let item_error = match role_id { EpiphanyRoleResultRoleId::Imagination => item_error, EpiphanyRoleResultRoleId::Modeling => merge_item_error( @@ -194,8 +171,6 @@ fn interpret_role_finding( risks: json_string_array_field(raw_result, "risks"), research_decision, repo_model_mutation_proposal, - self_patch, - self_persistence, job_error, item_error, verification_request_id: json_string_field(raw_result, "verificationRequestId"), @@ -234,15 +209,10 @@ pub fn interpret_runtime_role_worker_result( ) -> EpiphanyRoleFindingInterpretation { let research_decision_result = result.research_decision(); let repo_model_mutation_proposal_result = result.repo_model_mutation_proposal(); - let self_patch_result = result.self_patch(); let research_decision = research_decision_result .as_ref() .ok() .and_then(|decision| decision.clone()); - let self_patch = self_patch_result - .as_ref() - .ok() - .and_then(|patch| patch.clone()); let repo_model_mutation_proposal = repo_model_mutation_proposal_result .as_ref() .ok() @@ -254,10 +224,6 @@ pub fn interpret_runtime_role_worker_result( .err() .map(ToString::to_string), ); - let item_error = merge_item_error( - item_error, - self_patch_result.as_ref().err().map(ToString::to_string), - ); let item_error = merge_item_error( item_error, repo_model_mutation_proposal_result @@ -282,8 +248,6 @@ pub fn interpret_runtime_role_worker_result( risks: result.risks.clone(), research_decision: research_decision.clone(), repo_model_mutation_proposal: repo_model_mutation_proposal.clone(), - self_patch, - self_persistence: None, job_error: None, item_error: match role_id { EpiphanyRoleResultRoleId::Modeling => merge_item_error( @@ -456,31 +420,6 @@ pub fn build_reorient_acceptance_bundle( }) } -pub fn role_self_memory_target( - role_id: EpiphanyRoleResultRoleId, -) -> Option<(&'static str, &'static str)> { - match role_id { - EpiphanyRoleResultRoleId::Imagination => Some(( - "epiphany.imagination", - "state/agents/imagination.agent-state.json", - )), - EpiphanyRoleResultRoleId::Modeling => Some(( - "epiphany.modeling", - "state/agents/modeling.agent-state.json", - )), - EpiphanyRoleResultRoleId::Research => { - Some(("epiphany.eyes", "state/agents/eyes.agent-state.json")) - } - EpiphanyRoleResultRoleId::Verification => { - Some(("epiphany.soul", "state/agents/soul.agent-state.json")) - } - EpiphanyRoleResultRoleId::Implementation => { - Some(("epiphany.hands", "state/agents/hands.agent-state.json")) - } - EpiphanyRoleResultRoleId::Reorientation => None, - } -} - pub fn role_accepted_kind(role_id: EpiphanyRoleResultRoleId) -> Result<&'static str, String> { match role_id { EpiphanyRoleResultRoleId::Imagination => Ok("planning_synthesis"), @@ -505,81 +444,6 @@ pub fn role_label(role_id: EpiphanyRoleResultRoleId) -> Result<&'static str, Str } } -#[cfg(test)] -fn review_role_self_patch( - role_id: EpiphanyRoleResultRoleId, - patch: &serde_json::Value, -) -> EpiphanyRoleSelfPersistenceReview { - match decode_agent_self_patch(patch) { - Ok(patch) => review_role_self_patch_document(role_id, &patch), - Err(reason) => { - let Some((expected_agent_id, target_path)) = role_self_memory_target(role_id) else { - return EpiphanyRoleSelfPersistenceReview { - status: EpiphanyRoleSelfPersistenceStatus::Rejected, - target_agent_id: None, - target_path: None, - reasons: vec![ - "reorientation is Continuity protocol machinery and cannot persist role self-memory".to_string(), - reason, - ], - }; - }; - EpiphanyRoleSelfPersistenceReview { - status: EpiphanyRoleSelfPersistenceStatus::Rejected, - target_agent_id: Some(expected_agent_id.to_string()), - target_path: Some(target_path.to_string()), - reasons: vec![reason], - } - } - } -} - -pub fn review_role_self_patch_document( - role_id: EpiphanyRoleResultRoleId, - patch: &AgentSelfPatch, -) -> EpiphanyRoleSelfPersistenceReview { - let Some((expected_agent_id, target_path)) = role_self_memory_target(role_id) else { - return EpiphanyRoleSelfPersistenceReview { - status: EpiphanyRoleSelfPersistenceStatus::Rejected, - target_agent_id: None, - target_path: None, - reasons: vec![ - "reorientation is Continuity protocol machinery and cannot persist role self-memory" - .to_string(), - ], - }; - }; - let reasons = review_agent_self_patch_contract(expected_agent_id, patch); - - EpiphanyRoleSelfPersistenceReview { - status: if reasons.is_empty() { - EpiphanyRoleSelfPersistenceStatus::Accepted - } else { - EpiphanyRoleSelfPersistenceStatus::Rejected - }, - target_agent_id: Some(expected_agent_id.to_string()), - target_path: Some(target_path.to_string()), - reasons, - } -} - -#[cfg(test)] -fn decode_role_self_patch( - role_id: EpiphanyRoleResultRoleId, - patch: &serde_json::Value, -) -> ( - Option, - Option, -) { - let review = review_role_self_patch(role_id, patch); - let decoded = if review.status == EpiphanyRoleSelfPersistenceStatus::Accepted { - decode_agent_self_patch(patch).ok() - } else { - None - }; - (decoded, Some(review)) -} - fn modeling_role_result_error( repo_model_mutation_proposal_present: bool, repo_model_mutation_proposal: Option<&crate::EpiphanyRepoModelMutationProposal>, @@ -865,70 +729,6 @@ mod tests { assert_eq!(finding.runtime_job_id.as_deref(), Some("job-1")); } - #[test] - fn reviews_acceptable_self_patch() { - let finding = interpret_role_finding( - EpiphanyRoleResultRoleId::Modeling, - &serde_json::json!({ - "selfPatch": { - "agentId": "epiphany.modeling", - "reason": "Modeling should remember graph growth must stay source-grounded and bounded.", - "semanticMemories": [{ - "memoryId": "mem-body-source-grounded-growth", - "summary": "Grow graph and checkpoint state only when source evidence makes the anatomy harder to misread.", - "salience": 0.82, - "confidence": 0.9 - }] - } - }), - None, - None, - None, - ); - - let review = finding.self_persistence.as_ref().unwrap(); - assert_eq!(review.status, EpiphanyRoleSelfPersistenceStatus::Accepted); - assert_eq!(review.target_agent_id.as_deref(), Some("epiphany.modeling")); - } - - #[test] - fn rejects_bad_self_patch() { - let finding = interpret_role_finding( - EpiphanyRoleResultRoleId::Verification, - &serde_json::json!({ - "selfPatch": { - "agentId": "epiphany.modeling", - "reason": "Too broad.", - "graphs": {}, - "semanticMemories": [{ - "memoryId": "mem-soul-bad-project-truth", - "summary": "Project graph state belongs in memory now.", - "salience": 0.7, - "confidence": 0.4 - }] - } - }), - None, - None, - None, - ); - - let review = finding.self_persistence.as_ref().unwrap(); - assert_eq!(review.status, EpiphanyRoleSelfPersistenceStatus::Rejected); - assert!( - review - .reasons - .iter() - .any(|reason| reason.contains("expected \"epiphany.soul\"")) - ); - assert!( - review - .reasons - .iter() - .any(|reason| reason.contains("project truth")) - ); - } - #[test] fn marks_modeling_no_mutation_checkpoint_reviewable() { let checkpoint = interpret_role_finding( @@ -1120,10 +920,6 @@ mod tests { })); } } -use crate::agent_memory::AgentSelfPatch; -#[cfg(test)] -use crate::agent_memory::decode_agent_self_patch; -use crate::agent_memory::review_agent_self_patch_contract; use epiphany_state_model::EpiphanyAcceptanceReceipt; use epiphany_state_model::EpiphanyCodeRef; use epiphany_state_model::EpiphanyEvidenceRecord; diff --git a/epiphany-core/src/workspace_coverage_process_documents.rs b/epiphany-core/src/workspace_coverage_process_documents.rs index 6402f142..ea6271ff 100644 --- a/epiphany-core/src/workspace_coverage_process_documents.rs +++ b/epiphany-core/src/workspace_coverage_process_documents.rs @@ -3769,7 +3769,6 @@ mod tests { } std::fs::write(repo.path().join("source.rs"), "fn resumed() {}\n")?; let runtime = temp.path().join("runtime.cc"); - let agents = temp.path().join("agents.cc"); let body_store = temp.path().join("body.cc"); let coverage_path = std::fs::canonicalize(temp.path())?.join("coverage.redb"); crate::initialize_runtime_spine( @@ -3780,8 +3779,7 @@ mod tests { created_at: "2026-07-17T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "resume-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-17T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "resume-swarm", "2026-07-17T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body_store, &runtime, "resume-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, @@ -4387,7 +4385,6 @@ mod tests { .current_dir(&repo) .output()?; let runtime = temp.path().join("runtime.cc"); - let agents = temp.path().join("agents.ccmp"); let body_store = temp.path().join("body.ccmp"); crate::initialize_runtime_spine( &runtime, @@ -4397,8 +4394,7 @@ mod tests { created_at: "2026-07-16T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "recovery-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-16T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "recovery-swarm", "2026-07-16T00:00:01Z")?; crate::bind_repository_body(&repo, &body_store, &runtime, "recovery-workspace")?; let coverage_store_path = std::fs::canonicalize(temp.path())?.join("coverage.redb"); crate::bind_runtime_workspace_coverage_store( diff --git a/epiphany-core/src/workspace_coverage_projector.rs b/epiphany-core/src/workspace_coverage_projector.rs index 174dd16e..46df21bf 100644 --- a/epiphany-core/src/workspace_coverage_projector.rs +++ b/epiphany-core/src/workspace_coverage_projector.rs @@ -4022,7 +4022,6 @@ mod tests { std::fs::write(repo.path().join("source.rs"), "fn one() {}\n")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4032,8 +4031,7 @@ mod tests { created_at: "2026-07-16T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "retirement-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-16T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "retirement-swarm", "2026-07-16T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "retirement-workspace")?; let coverage = std::fs::canonicalize(state.path())?.join("workspace-coverage.cc"); crate::bind_runtime_workspace_coverage_store(&runtime, &coverage, "2026-07-16T00:00:02Z")?; @@ -4507,7 +4505,6 @@ mod tests { } git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body_store = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4517,8 +4514,7 @@ mod tests { created_at: "2026-07-16T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "read-session-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-16T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "read-session-swarm", "2026-07-16T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body_store, &runtime, "read-session-workspace")?; let coverage_store = std::fs::canonicalize(state.path())?.join("workspace-coverage.cc"); crate::bind_runtime_workspace_coverage_store( @@ -4583,7 +4579,6 @@ mod tests { std::fs::write(repo.path().join("binary.dat"), [0xff, 0xfe, 0xfd])?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4593,8 +4588,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "projector-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "projector-swarm", "2026-07-15T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "projector-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, @@ -4677,7 +4671,6 @@ mod tests { std::fs::write(repo.path().join("source.rs"), "fn one() {}\n")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4687,8 +4680,11 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "projection-execution-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm( + &runtime, + "projection-execution-swarm", + "2026-07-15T00:00:01Z", + )?; crate::bind_repository_body( repo.path(), &body, @@ -4791,7 +4787,6 @@ mod tests { std::fs::write(repo.path().join("source.rs"), "fn one() {}\n")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4801,8 +4796,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "body-advance-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "body-advance-swarm", "2026-07-15T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "body-advance-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, @@ -4838,7 +4832,6 @@ mod tests { std::fs::write(repo.path().join("source.rs"), "fn one() {}\n")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4848,8 +4841,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "cas-loser-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "cas-loser-swarm", "2026-07-15T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "cas-loser-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, @@ -4894,7 +4886,6 @@ mod tests { std::fs::write(repo.path().join("empty.txt"), "")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4904,8 +4895,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "empty-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "empty-swarm", "2026-07-15T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "empty-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, @@ -4931,7 +4921,6 @@ mod tests { std::fs::write(repo.path().join("source.rs"), "fn a() {}\n")?; git(repo.path(), &["add", "."])?; let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); crate::initialize_runtime_spine( &runtime, @@ -4941,8 +4930,7 @@ mod tests { created_at: "2026-07-15T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "head-cas-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-15T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "head-cas-swarm", "2026-07-15T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "head-cas-workspace")?; crate::bind_runtime_workspace_coverage_store( &runtime, diff --git a/epiphany-core/src/workspace_coverage_projector_service.rs b/epiphany-core/src/workspace_coverage_projector_service.rs index 994efa51..b31b7c62 100644 --- a/epiphany-core/src/workspace_coverage_projector_service.rs +++ b/epiphany-core/src/workspace_coverage_projector_service.rs @@ -470,7 +470,6 @@ mod tests { bail!("singleton fixture failed to initialize repository"); } let runtime = state.path().join("runtime.cc"); - let agents = state.path().join("agents.cc"); let body = state.path().join("body.cc"); let coverage = std::fs::canonicalize(state.path())?.join("workspace-coverage.cc"); crate::initialize_runtime_spine( @@ -481,8 +480,7 @@ mod tests { created_at: "2026-07-17T00:00:00Z".into(), }, )?; - crate::ensure_agent_memory_swarm_identity(&agents, "singleton-swarm")?; - crate::bind_runtime_to_agent_memory_swarm(&runtime, &agents, "2026-07-17T00:00:01Z")?; + crate::bind_runtime_to_swarm(&runtime, "singleton-swarm", "2026-07-17T00:00:01Z")?; crate::bind_repository_body(repo.path(), &body, &runtime, "singleton-workspace")?; crate::bind_runtime_workspace_coverage_store(&runtime, &coverage, "2026-07-17T00:00:02Z")?; let launch_id = "019bff10-7426-7a1c-9ce4-33f91660a3a7"; diff --git a/epiphany-core/src/workspace_coverage_store_binding.rs b/epiphany-core/src/workspace_coverage_store_binding.rs index c32f9af5..61a46fc3 100644 --- a/epiphany-core/src/workspace_coverage_store_binding.rs +++ b/epiphany-core/src/workspace_coverage_store_binding.rs @@ -535,8 +535,8 @@ mod tests { swarm_id: "swarm-1".into(), runtime_id: "runtime-1".into(), scope: "git_worktree".into(), - source_identity_type: "gamecult.agent_memory.swarm_identity".into(), - source_identity_key: "swarm".into(), + source_identity_type: crate::RUNTIME_IDENTITY_TYPE.into(), + source_identity_key: crate::RUNTIME_IDENTITY_KEY.into(), source_identity_sha256: "a".repeat(64), git_top_level: worktree.to_string_lossy().into_owned(), object_format: "sha1".into(), diff --git a/epiphany-openai-runtime/src/lib.rs b/epiphany-openai-runtime/src/lib.rs index d48e667e..3a8a6254 100644 --- a/epiphany-openai-runtime/src/lib.rs +++ b/epiphany-openai-runtime/src/lib.rs @@ -1808,7 +1808,7 @@ fn worker_output_contract_text( } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(rename_all = "camelCase", default)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] struct RoleWorkerResultIngress { role_id: Option, verdict: Option, @@ -1816,17 +1816,24 @@ struct RoleWorkerResultIngress { next_safe_move: Option, checkpoint_summary: Option, scratch_summary: Option, + #[serde(deserialize_with = "deserialize_null_default")] files_inspected: Vec, + #[serde(deserialize_with = "deserialize_null_default")] frontier_node_ids: Vec, + #[serde(deserialize_with = "deserialize_null_default")] evidence_ids: Vec, + #[serde(deserialize_with = "deserialize_null_default")] artifact_refs: Vec, + #[serde(deserialize_with = "deserialize_null_default")] open_questions: Vec, + #[serde(deserialize_with = "deserialize_null_default")] evidence_gaps: Vec, + #[serde(deserialize_with = "deserialize_null_default")] risks: Vec, research_decision: Option, + #[serde(deserialize_with = "deserialize_null_default")] repo_model_operations: Vec, frontier_verdict_gap: Option, - self_patch: Option, frontier_plan_candidate: Option, frontier_plan_mind_decision: Option, imagination_consideration_candidate: Option, @@ -1836,7 +1843,7 @@ struct RoleWorkerResultIngress { } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(rename_all = "camelCase", default)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] struct ProposalFrontierDraftIngress { migration_body: String, question: String, @@ -1849,14 +1856,14 @@ struct ProposalFrontierDraftIngress { } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(rename_all = "camelCase", default)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] struct RepoFrontierPlanMindDecisionIngress { decision: Option, rationale: String, } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(default)] +#[serde(default, deny_unknown_fields)] struct RepoFrontierPlanCandidateIngress { safe_paths: Vec, action: String, @@ -1868,7 +1875,7 @@ struct RepoFrontierPlanCandidateIngress { } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(default)] +#[serde(default, deny_unknown_fields)] struct ImaginationConsiderationCandidateIngress { disposition: Option, title: String, @@ -1880,7 +1887,7 @@ struct ImaginationConsiderationCandidateIngress { } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(default)] +#[serde(default, deny_unknown_fields)] struct AdmittedModelDirectionConsiderationResultIngress { disposition: Option, summary: String, @@ -1890,17 +1897,23 @@ struct AdmittedModelDirectionConsiderationResultIngress { } #[derive(Debug, Clone, Default, serde::Deserialize)] -#[serde(rename_all = "camelCase", default)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] struct ReorientWorkerResultIngress { mode: Option, summary: Option, next_safe_move: Option, checkpoint_still_valid: Option, + #[serde(deserialize_with = "deserialize_null_default")] files_inspected: Vec, + #[serde(deserialize_with = "deserialize_null_default")] frontier_node_ids: Vec, + #[serde(deserialize_with = "deserialize_null_default")] evidence_ids: Vec, + #[serde(deserialize_with = "deserialize_null_default")] artifact_refs: Vec, + #[serde(deserialize_with = "deserialize_null_default")] open_questions: Vec, + #[serde(deserialize_with = "deserialize_null_default")] continuity_risks: Vec, } @@ -2183,8 +2196,6 @@ fn role_worker_result_from_ingress( } else { (None, None) }; - let (self_patch_msgpack, self_patch_error) = - encode_optional_document(&result.self_patch, "selfPatch"); let (frontier_plan_candidate_msgpack, frontier_plan_candidate_error) = if let Some(ingress) = result.frontier_plan_candidate.as_ref() { @@ -2397,12 +2408,8 @@ fn role_worker_result_from_ingress( evidence_gaps: clean_string_vec(&result.evidence_gaps), risks: clean_string_vec(&result.risks), research_decision_msgpack, - self_patch_msgpack, item_error: merge_optional_errors( - merge_optional_errors( - merge_optional_errors(research_decision_error, self_patch_error), - repo_model_mutation_proposal_error, - ), + merge_optional_errors(research_decision_error, repo_model_mutation_proposal_error), merge_optional_errors( frontier_plan_candidate_error, merge_optional_errors( @@ -2486,7 +2493,6 @@ pub fn failed_frontier_planning_role_result( evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: Some(summary.clone()), metadata: std::collections::BTreeMap::new(), repo_model_mutation_proposal_msgpack: None, @@ -2577,13 +2583,20 @@ where }) .unwrap_or(trimmed) .trim(); - let mut value = serde_json::from_str::(candidate) + let value = serde_json::from_str::(candidate) .context("assistant text was not typed worker-result JSON")? .0; - remove_provider_optional_nulls(&mut value); serde_json::from_value(value).context("assistant text was not typed worker-result JSON") } +fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) +} + struct UniqueJsonValue(serde_json::Value); impl<'de> Deserialize<'de> for UniqueJsonValue { @@ -2669,23 +2682,6 @@ impl<'de> Visitor<'de> for UniqueJsonValueVisitor { } } -fn remove_provider_optional_nulls(value: &mut serde_json::Value) { - match value { - serde_json::Value::Object(map) => { - map.retain(|_, value| !value.is_null()); - for value in map.values_mut() { - remove_provider_optional_nulls(value); - } - } - serde_json::Value::Array(values) => { - for value in values { - remove_provider_optional_nulls(value); - } - } - _ => {} - } -} - fn clean_optional_string(value: Option<&str>) -> Option { value .map(str::trim) @@ -2800,8 +2796,8 @@ mod tests { } #[test] - fn model_authored_body_basis_has_no_ingress_authority() -> Result<()> { - let parsed = parse_assistant_json::( + fn model_authored_body_basis_is_refused_at_ingress() { + let error = parse_assistant_json::( r#"{ "roleId":"modeling","verdict":"checkpoint-ready","summary":"mapped", "nextSafeMove":"Mind review","repositoryBodyObservationBasis":{ @@ -2813,10 +2809,10 @@ mod tests { "scanFinishedAt":"2026-07-15T00:00:01Z" } }"#, - )?; - assert_eq!(parsed.role_id.as_deref(), Some("modeling")); - assert!(parsed.repo_model_operations.is_empty()); - Ok(()) + ) + .expect_err("Body identity is runtime-owned and cannot be laundered by omission"); + + assert!(format!("{error:#}").contains("unknown field `repositoryBodyObservationBasis`")); } #[test] @@ -2826,8 +2822,7 @@ mod tests { "roleId":"research","verdict":"source-gap","summary":"bounded", "nextSafeMove":"review","filesInspected":[],"frontierNodeIds":null, "evidenceIds":[],"artifactRefs":null,"openQuestions":null, - "evidenceGaps":["missing"],"risks":null,"researchDecision":null, - "selfPatch":null + "evidenceGaps":["missing"],"risks":null,"researchDecision":null }"#, )?; @@ -2839,6 +2834,19 @@ mod tests { Ok(()) } + #[test] + fn removed_generic_self_patch_is_not_native_role_input() { + let error = parse_assistant_json::( + r#"{ + "roleId":"research","verdict":"source-gap","summary":"bounded", + "nextSafeMove":"review","filesInspected":[],"selfPatch":null + }"#, + ) + .expect_err("the removed generic mutation mouth must be inadmissible"); + + assert!(format!("{error:#}").contains("unknown field `selfPatch`")); + } + #[test] fn every_static_worker_contract_projects_to_one_strict_provider_shape() -> Result<()> { let mut schemas = vec![ @@ -3318,59 +3326,13 @@ mod tests { } #[test] - fn verification_ingress_cannot_author_request_or_route_identity() -> Result<()> { - let parsed = parse_assistant_json::( + fn verification_ingress_refuses_model_authored_request_or_route_identity() { + let error = parse_assistant_json::( r#"{"roleId":"verification","verdict":"pass","summary":"verified","nextSafeMove":"admit","verificationRequestId":" verification-request-1 ","frontierRouteId":" frontier-route-1 "}"#, - )?; - let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.to_string(), - job_id: "verification-job-1".to_string(), - binding_id: "verification-binding-1".to_string(), - role: "verification".to_string(), - authority_scope: "epiphany.role.verification".to_string(), - instruction: "verify".to_string(), - output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - document_kind: "role".to_string(), - launch_document_msgpack: Vec::new(), - metadata: std::collections::BTreeMap::new(), - organ_launch_contract: epiphany_core::default_launch_organ_contract( - "epiphany.role.verification", - "role", - epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID, - ), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, - }; - let result = role_worker_result_from_ingress( - &launch, - "verification", - None, - None, - None, - None, - None, - None, - None, - "2026-07-15T10:00:00Z", - "verification-result-1", - "decision-context-test", - &parsed, - vec!["openai-request:verification-request-1".to_string()], - Vec::new(), - ); - assert!(result.verification_request_id.is_none()); - assert!(result.frontier_route_id.is_none()); - assert_eq!( - result.evidence_ids, - vec!["openai-request:verification-request-1"] - ); - Ok(()) + ) + .expect_err("causal identity is runtime-owned and must be refused at ingress"); + + assert!(format!("{error:#}").contains("unknown field")); } #[test] @@ -3454,7 +3416,6 @@ mod tests { Some("planning-request-1") ); assert!(result.research_decision_msgpack.is_none()); - assert!(result.self_patch_msgpack.is_none()); let candidate = result .frontier_plan_candidate()? .expect("typed frontier candidate"); @@ -3473,6 +3434,18 @@ mod tests { #[test] fn consideration_ingress_derives_all_causal_identity_from_launch_context() -> Result<()> { + let hostile = parse_assistant_json::( + r#"{ + "roleId":"imagination","verdict":"suggest","summary":"bounded option", + "nextSafeMove":"Modeling review only", + "imaginationConsiderationCandidate":{ + "request_id":"hostile-model-request","title":"Bounded option" + } + }"#, + ) + .expect_err("nested model-authored causal identity must be refused"); + assert!(format!("{hostile:#}").contains("unknown field `request_id`")); + let parsed = parse_assistant_json::( r#"{ "roleId":"imagination", @@ -3480,26 +3453,14 @@ mod tests { "summary":"bounded option", "nextSafeMove":"Modeling review only", "filesInspected":[], - "imaginationConsiderationRequestId":"hostile-model-request", "imaginationConsiderationCandidate":{ - "request_id":"hostile-model-request", - "feedback_id":"hostile-feedback", - "feedback_packet_sha256":"hostile-packet", - "model_revision":999, - "model_hash":"hostile-model", - "source_room_id":"hostile-room", - "source_visibility":"private", - "data_classification":"private_feedback", "disposition":"suggest", "title":"Bounded option", "summary":"Keep the proposal review-only.", "rationale":"The option is reversible.", "option_drafts":[{"title":"Typed sight","summary":"Expose operator-safe provenance."}], "uncertainties":[], - "evidence_refs":["discord://message-1"], - "recommended_review_route":"modeling_review", - "proposed_at":"1900-01-01T00:00:00Z", - "contract":"hostile-contract" + "recommended_review_route":"modeling_review" } }"#, )?; @@ -4001,10 +3962,8 @@ mod tests { std::fs::write(repo.join("src.txt"), "authenticated Body")?; run_git(&repo, &["add", "."])?; run_git(&repo, &["commit", "-m", "seed"])?; - let agents = temp.path().join("agents.cc"); let body_store = temp.path().join("body.cc"); - epiphany_core::ensure_agent_memory_swarm_identity(&agents, "swarm-test")?; - epiphany_core::bind_runtime_to_agent_memory_swarm(&store, &agents, "2026-08-08T00:00:00Z")?; + epiphany_core::bind_runtime_to_swarm(&store, "swarm-test", "2026-08-08T00:00:00Z")?; epiphany_core::bind_repository_body(&repo, &body_store, &store, "workspace-test")?; let body_basis = epiphany_core::observe_runtime_repository_body_basis(&store)?; let frontier_item = epiphany_core::RepoFrontierItem { @@ -4094,7 +4053,6 @@ mod tests { evidence_gaps: Vec::new(), risks: Vec::new(), research_decision_msgpack: None, - self_patch_msgpack: None, item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, @@ -4441,7 +4399,6 @@ mod tests { .proposal_id, "repo-model-mutation-proposal-worker-job-1" ); - assert!(typed_result.self_patch()?.is_none()); assert!( runtime_job_snapshot(&store, "worker-job-1")? .expect("snapshot") diff --git a/epiphany-state-model/src/prompts/epiphany_doctrine.md b/epiphany-state-model/src/prompts/epiphany_doctrine.md index 7f669e0b..99111db2 100644 --- a/epiphany-state-model/src/prompts/epiphany_doctrine.md +++ b/epiphany-state-model/src/prompts/epiphany_doctrine.md @@ -14,7 +14,7 @@ - Swarm boundary is law. One Epiphany may inspect its own internals and expose as much state as possible to the human, but it must not inspect or edit another Epiphany's workspace. If another codebase, vault, or state surface needs to change shape, ask that coordinator through the swarm communication lane and wait for a callback. - Humans talk to Persona. Sub-agents may talk soul-to-soul through typed coordinator channels, findings, state patches, heartbeat outputs, and swarm messages; the human chat contract routes through Persona, while Aquarium exposes internals for inspection without turning every organ into a direct chat endpoint. - API contracts must mirror user-story contracts. If the product story says "ask another coordinator politely", the API must make that the easy path and make cross-workspace rummaging impossible. A visible interface is only honest when its affordances are backed by backend authority. -- Heartbeats are paced physiology, not pressure. A lane must not receive another heartbeat before its previous heartbeat turn has completed; cooldown starts after completion, not at launch. When no coordinator work is active, the swarm should sleep: rumination continues slowly, distilling memory without thrashing every organ awake. +- Heartbeats are paced scheduling physiology, not cognition. A lane must not receive another heartbeat before its previous heartbeat turn has completed; cooldown starts after completion, not at launch. When no typed work obligation is active, the swarm sleeps. Heartbeat does not ruminate, dream, mutate memory, or manufacture work. - Ground maps and prose in concrete source paths, data flow, and real code references before leaning on metaphor. The machine-map must touch metal. - Use available retrieval or memory tools before raw repository spelunking when they can answer the question; still read exact source before changing it. - Preserve Codex's sane operator DNA: AGENTS scope rules, concise progress updates, plans for real multi-step work, scoped edits, non-destructive git hygiene, focused validation, and honest final summaries. Old scars that still protect the machine are holy. diff --git a/notes/README.md b/notes/README.md index 614f8dbc..4c469c3c 100644 --- a/notes/README.md +++ b/notes/README.md @@ -10,7 +10,6 @@ contract docs. - `epiphany-anatomy.md`: current organ/sub-agent/substrate/protocol split. - `fresh-workspace-handoff.md`: compact re-entry orientation and current live state. - `epiphany-current-algorithmic-map.md`: broad source-grounded machine map. -- `codex-starvation-and-cultnet-liberation-plan.md`: active foundation campaign. - `codex-auth-spine-inventory.md`: retained Codex auth/model transport keeper list. - `epiphany-fork-implementation-plan.md`: current campaign plan. - `epiphany-safety-architecture.md`: capability, autonomy, and governance boundaries. @@ -27,15 +26,13 @@ contract docs. ## Active Design References -- `epiphany-memory-graph-unified-plan.md` -- `epiphany-cultmesh-dreaming-roadmap.md` - `perfect-machine-audit-roadmap.md` -- `voidbot-routine-adoption-plan.md` ## Archive - `archive/README.md`: quarantined historical ledgers, closed migrations, - optional bridge plans, superseded cache splits, and one-off sandbox prompts. + superseded memory/dreaming/routine campaigns, optional bridge plans, cache + splits, and one-off sandbox prompts. Cleanup rule: if a current invariant is found only in the archive, distill it into a canonical doc or active design reference first. Do not patch the same diff --git a/notes/codex-starvation-and-cultnet-liberation-plan.md b/notes/archive/codex-starvation-and-cultnet-liberation-plan.md similarity index 100% rename from notes/codex-starvation-and-cultnet-liberation-plan.md rename to notes/archive/codex-starvation-and-cultnet-liberation-plan.md diff --git a/notes/epiphany-cultmesh-dreaming-roadmap.md b/notes/archive/epiphany-cultmesh-dreaming-roadmap.md similarity index 100% rename from notes/epiphany-cultmesh-dreaming-roadmap.md rename to notes/archive/epiphany-cultmesh-dreaming-roadmap.md diff --git a/notes/epiphany-memory-graph-unified-plan.md b/notes/archive/epiphany-memory-graph-unified-plan.md similarity index 100% rename from notes/epiphany-memory-graph-unified-plan.md rename to notes/archive/epiphany-memory-graph-unified-plan.md diff --git a/notes/memory-graph-semantic-projection-authority-map.md b/notes/archive/memory-graph-semantic-projection-authority-map.md similarity index 100% rename from notes/memory-graph-semantic-projection-authority-map.md rename to notes/archive/memory-graph-semantic-projection-authority-map.md diff --git a/notes/voidbot-routine-adoption-plan.md b/notes/archive/voidbot-routine-adoption-plan.md similarity index 100% rename from notes/voidbot-routine-adoption-plan.md rename to notes/archive/voidbot-routine-adoption-plan.md diff --git a/notes/continuity-cultnet-contracts.md b/notes/continuity-cultnet-contracts.md index 16dee036..a89b103f 100644 --- a/notes/continuity-cultnet-contracts.md +++ b/notes/continuity-cultnet-contracts.md @@ -1,6 +1,6 @@ # Continuity CultNet Contracts -Objective: make Continuity the named protocol surface for compaction, sleep, +Objective: make Continuity the named protocol surface for compaction, recovery, handoff, and stale-turn repair. These flows need their own receipts instead of hiding in scratch notes or hopeful transcript memory. @@ -8,17 +8,17 @@ instead of hiding in scratch notes or hopeful transcript memory. - Owner: the Continuity protocol owns continuity across rupture. - Inputs: continuity packets, context pressure, compaction checkpoints, - heartbeat/sleep pressure, stale active turns, recovery context, handoff + stale active turns, recovery context, handoff surfaces, and Mind state context. -- Outputs: compaction checkpoints, sleep distillation receipts, recovery +- Outputs: compaction checkpoints, recovery receipts, stale-turn repair receipts, and continuity refusal receipts. -- Derived state: handoff summaries, scratch preservation, sleep residue, and +- Derived state: handoff summaries, scratch preservation, and recovery notes are continuity receipts. They are not durable Mind state until Mind admits them. - Forbidden writers: raw workers, Persona, Hands, Eyes, Soul, Self, compatibility routes, and background loops must not silently rewrite continuity state or pretend stale work completed cleanly. -- Shared path: compaction prep, sleep distillation, handoff, wake/reorient, and +- Shared path: compaction prep, handoff, wake/reorient, and stale-turn repair should share Continuity packet/review semantics. - Deletion line: transcript survival is not continuity. Rehydration is not adoption. "It was probably fine" is not a recovery receipt. @@ -27,7 +27,6 @@ instead of hiding in scratch notes or hopeful transcript memory. - `epiphany.continuity.packet`: request or packet for continuity handling. - `epiphany.continuity.compaction_checkpoint`: proof of pre-rupture preservation. -- `epiphany.continuity.sleep_distillation`: proof of sleep/rumination distillation. - `epiphany.continuity.recovery_receipt`: proof of wake/recovery state. - `epiphany.continuity.stale_turn_repair`: proof of abandoned-turn repair. - `epiphany.continuity.refusal_receipt`: proof that Continuity refused to trust diff --git a/notes/epiphany-anatomy.md b/notes/epiphany-anatomy.md index 38f2561c..fc357580 100644 --- a/notes/epiphany-anatomy.md +++ b/notes/epiphany-anatomy.md @@ -20,10 +20,10 @@ Only thinking lanes get embodiment titles. actuators. - `Soul`: verification, invariants, ethics, falsification, and promise-keeping. -Each embodied sub-agent may have lean work-organ state in `state/agents.msgpack`, -may be represented as a heartbeat participant, and may request reviewed -`selfPatch` changes to its own lane memory. Persona may additionally project or -adopt portable `Persona` state because public personhood is part of its job. +Embodied lanes reason from sealed typed Mind projections and return closed +family-specific decisions. Heartbeat participants are scheduling projections, +not personality or memory owners. Persona memory and social effects are keyed +Mind documents admitted by the Persona invariant owner. ## Not Sub-Agents @@ -33,8 +33,9 @@ embodiment titles, role-memory diaries, or Persona state. - `Body`: the substrate Epiphany acts through and senses: repository, tools, runtime, prompts, logs, state files, permissions, interfaces, hosted systems, and physical/editor/runtime bridges. -- `Mind`: persistent state and durable steering context: memory, maps, goals, - doctrine, evidence, state patches, accepted receipts, and state admission. +- `Mind`: keyed persistent state and durable steering context: objectives, + evidence, Modeling documents, Persona state, consequences, audits, decisions, + reasoning contexts, and exact commit receipts. - `Continuity`: protocol machinery for compaction, sleep, recovery, stale-turn repair, reorientation receipts, handoff preservation, and "what survived rupture?" accounting. @@ -47,8 +48,7 @@ embodiment titles, role-memory diaries, or Persona state. - Mind admits durable state; it is not a chatty lane. - Mind may invoke a bounded, stateless admission-review procedure when a typed proposal requires model judgment. That procedure serves Mind; it is not an - embodied sub-agent, has no heartbeat or lane memory, cannot `roleAccept` or - `selfPatch`, and its immutable judgment has no consequence until Mind replays + embodied sub-agent, has no heartbeat or lane memory, and its immutable judgment has no consequence until Mind replays the causal chain and admits it. - Continuity preserves/rebuilds context across rupture; it is protocol machinery, not an embodied identity. @@ -56,7 +56,8 @@ embodiment titles, role-memory diaries, or Persona state. files, verify truth, or admit state. - Reorientation is a bounded Continuity worker/procedure launched from CRRC, not an embodied sub-agent. -- `roleAccept` and `selfPatch` are for embodied sub-agents only. +- Embodied sub-agents emit closed family decisions. No generic role acceptance + or self-patch mutation mouth exists. - Continuity state changes should appear as typed continuity packets, recovery receipts, compaction checkpoints, stale-turn repairs, or refusals. diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index f0e96a35..c89e8744 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -153,7 +153,7 @@ Exit evidence: - Finish Idunn-owned service installation/audit aftercare without moving elevation authority into Self or wrappers. - Prove cooldown-after-completion, no overlapping lane heartbeat, idle sleep, - rumination, and recovery across restart. + explicit-pressure-only wakeup, and recovery across restart. - Ensure scheduler and liveness receipts remain separate from project state. Exit evidence: diff --git a/notes/mind-cultnet-contracts.md b/notes/mind-cultnet-contracts.md index 5ac88071..bdc268f1 100644 --- a/notes/mind-cultnet-contracts.md +++ b/notes/mind-cultnet-contracts.md @@ -1,68 +1,44 @@ # Mind CultNet Contracts -Objective: make Mind the persistent state guardian. The state is the Mind; every -sub-agent output is a thought or proposal until Mind admits it into durable -state. +Mind is the sole durable decision-bearing store. Its authority is the keyed +CultCache document set and the exact commit receipts that admit changes, not a +generic network mutation gateway. -Body is the neighboring gate. Substrate Gate decides whether an organ may touch the repo; -Mind decides whether the resulting thought, evidence, or proposal mutates -persistent state. Do not collapse these into one throne. That is how the machine -starts lying with excellent posture. +## Authority map -Imagination is the neighboring Projector for Persona. It may compose a lived scene -from state, Body substrate facts, social pressure, and organ dependencies, but it does not -interpret side effects or admit state. +- Owner: concrete family invariant owners construct `MindMutation` plans; + CultCache batch CAS commits them atomically. +- Inputs: a sealed decision context, exact strong-read envelopes, and complete + family-specific inserts or replacements. +- Outputs: keyed Mind document versions and one + `EpiphanyMindCommitReceipt` written in the same transaction. +- Derived state: `EpiphanyMindView`, RepoModel views, current-work projections, + CultMesh publications, and Eve surfaces are deterministic read-only views. +- Forbidden writers: models, role lanes, events, timestamps, provider results, + generic patches, and CultMesh consumers cannot mutate Mind directly. +- Shared path: every model-authored durable decision enters through its concrete + family admission owner. Deterministic operator and routing actions retain + their own typed provenance and do not impersonate model reasoning. +- Deletion line: no persisted thread aggregate, global revision, generic Mind + gateway review, state-effect proposal, `statePatch`, or `selfPatch` path may + own behavior. -## Authority Map +## Published contracts -- Owner: Mind owns the decision to mutate persistent Epiphany state. -- Inputs: typed worker results, Persona Interpreter intents, current state context, - runtime/job provenance, Verse ingress receipts, operator-approved intents, and - relevant verification evidence. -- Outputs: Mind gateway reviews, state commit receipts, state rejection - receipts, and Verse adoption receipts. -- Derived state: role findings, reorient findings, Persona side-effect intents, - public dreams, local-area findings, `statePatch`, `selfPatch`, evidence, - scratch, checkpoints, objectives, and graph edits are proposal-only until Mind - accepts them. -- Forbidden writers: role acceptance, reorient acceptance, Persona Interpreter, - public Verse ingress, local-area Verse ingress, raw worker result ingestion, - runtime job completion, and compatibility JSON-RPC routes must not directly - decide durable state mutation. -- Shared path: every durable state effect goes through a Mind state-effect - proposal and receives a Mind review plus commit or rejection receipt. -- Deletion line: old contract language that said "coordinator accepts memory - mutations" is demoted. Coordinator may schedule, route, and carry intent, but - Mind owns persistent-state admission. +CultMesh may publish sealed reasoning bases, exact decision contexts, structured +terminal decisions or failures, and exact Mind commit receipts for inspection. +Those projections are read-only. Network visibility cannot create a mutation +mouth or reconstruct authority from a digest-only summary. -## Verse Boundary +## Merge law -CultMesh simplifies the boundary by making Verse placement part of the contract: +- disjoint document identities merge; +- byte-identical replay returns the original receipt; +- divergent writes to one identity conflict; +- changed strong reads refuse the whole mutation; +- observational-only changes do not block an otherwise disjoint commit; +- model output is never silently rebased onto newer strong state. -- `epiphany-internal`: private thoughts, state-effect proposals, Mind reviews, - commit receipts, rejection receipts, runtime facts, heartbeat state, role - dossiers, and other private organ state. -- `gamecult-local`: trusted GameCult operator-safe sharing, non-secret status, - reviewable findings, and receipts. It does not carry raw worker thought, - private memory, or direct state mutation authority. -- `epiphany-global`: public thought weather: dreams, questions, hypotheses, - Persona posts, and adoption receipts. Nothing from this Verse mutates local - memory, planning, doctrine, governance, or project truth without a reviewed - local adoption receipt. - -## Contract Families - -- `epiphany.mind.thought`: sub-agent output submitted as thought, not durable - state authority. -- `epiphany.mind.state_effect_proposal`: proposed durable mutation. Mind - accepts, refuses, or holds it. -- `epiphany.mind.gateway_review`: durable receipt explaining the Mind route. -- `epiphany.mind.state_commit_receipt`: proof that Mind admitted an effect into - persistent state. -- `epiphany.mind.state_rejection_receipt`: proof that Mind refused or held an - effect without mutating state. -- `epiphany.mind.verse_adoption_receipt`: local adoption receipt for public or - foreign Verse material. - -The current native surfaces are in `epiphany-core::mind_gateway` and the -Verse-scoped policy documents are in `epiphany-core::cultmesh_integration`. +The audit question is always recoverable without a transcript: what exact typed +projection and governed observations did the agent reason from when it made +this decision? diff --git a/notes/semantic-projector-service-runbook.md b/notes/semantic-projector-service-runbook.md index 49bfdf72..1e234183 100644 --- a/notes/semantic-projector-service-runbook.md +++ b/notes/semantic-projector-service-runbook.md @@ -2,12 +2,11 @@ ## Authority -One workstation-local `epiphany-memory-semantic-projector` process owns semantic -projection execution for both canonical partitions in one Epiphany swarm: +One `epiphany-memory-semantic-projector` process owns derived semantic indexing +for the Modeling projection of one Epiphany runtime: -- Mind input: `state/agents.msgpack` -- Modeling input: the configured runtime-spine CultCache store -- Derived index: physically separated Mind and Modeling Qdrant collections +- Modeling input: the configured runtime Mind CultCache store +- Derived index: the Modeling Qdrant collection - Derived sight: provider-authored CultMesh health and heartbeat events Canonical CultCache admission owns projection obligations. Idunn owns process @@ -16,10 +15,9 @@ claim-bound Qdrant mutation and terminal evidence. Query admission alone proves semantic readiness. Qdrant, Ollama, health, heartbeat, Eve, and swarm overview are not state authority. -The process must remain beside the canonical stores. Yggdrasil may host Ollama -embedding work over WireGuard, but moving only the projector to Yggdrasil would -split local store authority. Do not install a second projector on Yggdrasil or -one process per partition. +The process must remain beside its canonical runtime store. Yggdrasil may host +Ollama embedding work over WireGuard. Do not install a second projector for the +same runtime store. ## Package one source generation @@ -58,11 +56,11 @@ timeouts retain their typed configuration defaults unless deliberately changed. ## Preflight -1. Confirm both canonical stores exist and describe the same immutable swarm. +1. Confirm the runtime Mind store exists and carries the admitted runtime/swarm identity. 2. Confirm Qdrant is reachable from the workstation and its collection metadata matches the configured embedding dimensions. 3. Confirm Ollama is reachable and the configured embedding model is loaded. -4. Confirm no projector process already owns the canonical store pair. The +4. Confirm no projector process already owns the canonical runtime store. The binary also enforces this with a host OS singleton. On Windows this is a `Global\\` named mutex shared by service and interactive sessions; failure to create or open it fails closed before a provider incarnation exists. @@ -72,7 +70,7 @@ timeouts retain their typed configuration defaults unless deliberately changed. Current observation on 2026-07-15: the workstation's retired `voidbot-qdrant` container is intentionally stopped after the VoidBot cutover. The authoritative Yggdrasil Qdrant is physically shared and contains the -Epiphany Mind and Modeling collections. The ops tunnel exposes its loopback +Epiphany Modeling collection. The ops tunnel exposes its loopback REST/gRPC ports locally as `16333`/`16334`; do not restart the retired node. ## Publish the typed service policy @@ -87,7 +85,6 @@ $supervisor = Join-Path $releaseRoot 'epiphany-daemon-supervisor.exe' --runtime-id epiphany-local ` --release-id $releaseId ` --release-witness-sha256 $releaseDigest ` - --agent-store .\state\agents.msgpack ` --runtime-store .\state\runtime-spine.msgpack ` --qdrant-url http://127.0.0.1:16333 ` --ollama-base-url http://10.77.0.1:11435 ` @@ -97,8 +94,8 @@ $supervisor = Join-Path $releaseRoot 'epiphany-daemon-supervisor.exe' This command writes the fixed `epiphany-memory-semantic-projector-service` managed-service policy with -`restartMode=always`. Its generated child command contains both canonical -stores. The projector executable is resolved from the authenticated +`restartMode=always`. Its generated child command contains the canonical +runtime store. The projector executable is resolved from the authenticated `semantic-projector` role in the pinned release witness. Caller-supplied executable paths, service IDs, restart modes, arbitrary child arguments, and finite iteration limits do not own this service shape. The generic managed-service writer @@ -117,7 +114,7 @@ The projector resolves that exact receipt from the local Verse and rechecks its binding to the current reserved policy before constructing its service body or publishing a pulse or heartbeat. A launch receipt proves process creation; it does not claim semantic readiness. The first heartbeat comes later, after the -projector holds the OS singleton and validates both canonical inputs. +projector holds the OS singleton and validates the canonical Modeling input. ## Install the pinned after-login reconciler @@ -154,10 +151,9 @@ projector resumes its own recovered claim on the next pulse. - Read the fixed managed-service policy and latest lifecycle receipt. - Read the projector heartbeat for its current provider incarnation. -- Read both named semantic-health projections. -- Run a Mind and Modeling semantic query and confirm the query gate reports - semantic ranking only when each newest exact obligation/success chain is - authenticated. +- Read the Modeling semantic-health projection. +- Run a Modeling semantic query and confirm the query gate reports semantic + ranking only when the newest exact obligation/success chain is authenticated. - Stop the process once, let Idunn restart it, and verify a newer provider heartbeat names the startup lifecycle receipt. Do not use command exit zero as readiness. @@ -219,14 +215,12 @@ BM25, which is a failed deployment proof rather than semantic readiness: $env:EPIPHANY_QDRANT_URL='http://127.0.0.1:16333' $env:EPIPHANY_OLLAMA_BASE_URL='http://10.77.0.1:11435' $semantic=Join-Path $releaseRoot 'epiphany-memory-semantic.exe' -& $semantic context --agent-store .\state\agents.msgpack --partition mind ` - --text 'current Epiphany doctrine and memory' --query-id reboot-proof-mind --budget 8 -& $semantic context --runtime-store .\state\runtime-spine.msgpack --partition modeling ` +& $semantic context --runtime-store .\state\runtime-spine.msgpack ` --text 'current repository architecture and deployment frontier' ` --query-id reboot-proof-modeling --budget 8 ``` -Both results must report `semantic projection ranked ... canonical ... +The result must report `semantic projection ranked ... canonical ... candidates` and must not report `semantic projection unavailable` or BM25 fallback. That query admission is the readiness proof. A Running task, open TCP port, Qdrant collection listing, provider heartbeat, or health projection is diff --git a/schemas/README.md b/schemas/README.md index 721e9ed2..4799d373 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -3,15 +3,12 @@ This folder is the canonical paperwork shrine for Epiphany's shared state contracts. -If a trait name, organ-state field, Persona field, birth-time projection rule, or heartbeat -surface matters enough to steer the machine, it should have a receipt here -instead of living only in one Rust struct, one stale memory store, or one -developer's damp recollection. +If a Persona field, Mind document, or heartbeat scheduling surface matters +enough to steer the machine, it should have a receipt here instead of living +only in one Rust struct or one developer's damp recollection. ## Canonical surfaces -- [ghostlight.agent-state.schema.json](./ghostlight.agent-state.schema.json): - source-lineage JSON Schema for dense Ghostlight character state. - [cultnet/gamecult.persona_state.v0.schema.json](./cultnet/gamecult.persona_state.v0.schema.json): portable Persona state contract for Epiphany Persona, VoidBot repo Personas, and Ghostlight characters. It carries explicit provenance, public presentation @@ -22,12 +19,6 @@ developer's damp recollection. companion custom-label fields. - [cultnet/epiphany.work_organ_state.v0.schema.json](./cultnet/epiphany.work_organ_state.v0.schema.json): light function-shaped state for Epiphany internal work organs. -- [agent-state-variable-glossary.md](./agent-state-variable-glossary.md): - full Persona-family glossary plus current Epiphany work-organ lattice receipts. -- [agent-utterance-state-schema.md](./agent-utterance-state-schema.md): - derived speech-conditioning subset for Weks, Aquarium, and other utterance - surfaces; it carries identity, trait vectors, mood, and activation without - memory records. - [heartbeat-state-schema.md](./heartbeat-state-schema.md): typed initiative and routine-state contract for the swarm heartbeat organ. - [cultnet/README.md](./cultnet/README.md): @@ -39,8 +30,8 @@ developer's damp recollection. The living implementation is in code: -- [agent_memory.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/agent_memory.rs) - [heartbeat_state.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/heartbeat_state.rs) +- [mind_documents.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/mind_documents.rs) The rule is simple: diff --git a/schemas/agent-state-variable-glossary.md b/schemas/agent-state-variable-glossary.md deleted file mode 100644 index 9ff4cbc1..00000000 --- a/schemas/agent-state-variable-glossary.md +++ /dev/null @@ -1,260 +0,0 @@ -# Agent State Variable Glossary - -This is the current human-facing glossary for Epiphany's organ and Persona -state surfaces. - -The canonical storage shape is documented in -[canonical-agent-state-schema.md](./canonical-agent-state-schema.md). The live -Rust structures live in -[agent_memory.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/agent_memory.rs). - -This document now covers two things: - -1. the dense Persona vocabulary used by Persona, Ghostlight characters, and - VoidBot-style repo Personas -2. the lean standing Epiphany role lattice used by resident work organs - -Those are related, not identical. - -## Trait Vector Components - -Every canonical scalar-like trait uses three numbers: - -| Field | Meaning | -| --- | --- | -| `mean` | baseline tendency | -| `plasticity` | how easily the tendency shifts | -| `current_activation` | how active it is right now | - -This is enough to separate character from moment, which is a small mercy. - -## Family Meanings - -| Family | Meaning | -| --- | --- | -| `underlying_organization` | deep organizational habit; what the organ is built around | -| `stable_dispositions` | persistent bias or temperament | -| `behavioral_dimensions` | how the organ tends to act under work pressure | -| `presentation_strategy` | how the organ presents its work outwardly | -| `voice_style` | the communication texture of that organ | -| `situational_state` | what pressure it tends to carry when active | - -## Dense Embodied Profile Vocabulary - -These are the canonical Persona-family labels appropriate for Epiphany Persona, -VoidBot repo Personas, and Ghostlight scene characters. - -### Underlying Organization - -| Label | Meaning | -| --- | --- | -| `self_coherence` | how integrated and stable the agent's sense of self is under pressure | -| `contingent_worth` | how much felt worth depends on approval, utility, status, purity, or external validation | -| `shame_sensitivity` | how painful exposure, correction, failure, or diminishment feels | -| `reciprocity_capacity` | ability to sustain mutual obligation rather than purely extractive or avoidant relations | -| `mentalization_quality` | ability to model other minds with nuance instead of flattening them into threat, need, or utility | -| `authenticity_tolerance` | capacity to be known without immediately retreating into performance or concealment | -| `mask_rigidity` | how necessary and inflexible the performed self is | -| `external_regulation_dependence` | how much stability depends on other people, scripts, or surrounding structure | - -### Stable Dispositions - -| Label | Meaning | -| --- | --- | -| `novelty_seeking` | attraction to new experience and unproven paths | -| `conformity` | preference for accepted norms and group expectations | -| `status_hunger` | desire for rank, recognition, or visible superiority | -| `risk_tolerance` | willingness to accept danger, uncertainty, or loss | -| `sociability` | baseline draw toward company and social energy | -| `baseline_threat_sensitivity` | default readiness to detect danger or betrayal | -| `aesthetic_appetite` | sensitivity to beauty, style, symbolism, or expressive environment | -| `ideological_rigidity` | resistance to revising beliefs or explanatory frames | - -### Behavioral Dimensions - -| Label | Meaning | -| --- | --- | -| `interpersonal_warmth` | tendency to express care or welcome | -| `drive` | goal pressure, persistence, and forward force | -| `grandiosity` | inflated self-importance or self-mythologizing | -| `validation_seeking` | need for reassurance or recognition | -| `anxiety` | anticipatory fear or instability under uncertainty | -| `control_pressure` | urge to manage people, situations, or outcomes tightly | -| `hostility` | readiness toward anger, contempt, or attack | -| `suspicion` | expectation that others hide traps or bad faith | -| `rigidity` | difficulty adapting under new evidence | -| `withdrawal` | tendency to disengage or retreat | -| `volatility` | speed and intensity of emotional swings | -| `attachment_seeking` | pull toward closeness, reassurance, or being chosen | -| `distance_seeking` | push toward space, opacity, and autonomy | - -### Presentation Strategy - -| Label | Meaning | -| --- | --- | -| `charm` | performs warmth, ease, or charisma to influence contact | -| `compliance` | performs agreement or harmless cooperation | -| `superiority` | performs being above others, more competent, or less vulnerable | -| `detachment` | performs cool distance or emotional unavailability | -| `seductiveness` | performs desirability or intimate leverage | -| `competence_theater` | performs capability and control, sometimes beyond what is actually secure | -| `moral_theater` | performs righteousness, purity, or ethical authority | -| `strategic_opacity` | controls what others can know | -| `cultivated_harmlessness` | performs being safe, small, useful, or beneath concern | -| `abrasive_boundary` | performs bite or difficulty to keep others from pressing closer | -| `ironic_distance` | performs irony or joking distance to avoid naked sincerity | - -### Voice Style - -| Label | Meaning | -| --- | --- | -| `dryness` | understated or deadpan phrasing | -| `verbal_warmth` | inviting, reassuring, or socially soft phrasing | -| `formality` | formal structure, titles, or careful address | -| `verbosity` | longer turns and elaboration | -| `pace` | quick movement through turns or topic shifts | -| `plainspoken_directness` | plain, concrete, blunt speech | -| `lexical_precision` | exact word choice and careful distinctions | -| `technical_density` | specialist terms or heavy systems language | -| `technical_compression` | terse expert shorthand | -| `figurative_language` | metaphor, image, or symbolic framing | -| `lyricism` | musical, poetic, or sensuous language | -| `narrative_detail` | concrete scene detail and sequence | -| `emotional_explicitness` | direct naming of feelings or needs | -| `pointedness` | sharp or barbed precision | -| `self_disclosure` | volunteers private motives or history | -| `hedging` | caveats, uncertainty markers, or softeners | -| `certainty_marking` | signals confidence or finality | -| `politeness` | courtesy and Persona-saving language | -| `coded_politeness` | politeness used to imply criticism, threat, or hierarchy | -| `ritualized_address` | formulaic greetings, titles, prayers, or ceremonial phrases | -| `register_switching` | shifts register by audience or pressure | -| `dialect_marking` | regional, class, occupational, or subcultural speech markers | -| `theatricality` | drama, persona, flourish, or rhetorical display | -| `humor` | jokes, wit, teasing, or comic framing | -| `conversational_dominance` | takes or controls the floor | -| `listening_responsiveness` | reflects or adapts to what the other just said | -| `question_asking` | uses questions to probe, invite, or corner | -| `profanity` | taboo, sacred, or deliberately coarse language | - -### Situational State - -| Label | Meaning | -| --- | --- | -| `exhaustion` | depletion or low reserve | -| `scarcity_pressure` | pressure from money, supplies, time, space, or safety scarcity | -| `humiliation` | felt diminishment or exposure | -| `panic` | acute overwhelm or threat response | -| `triumph` | victory, vindication, or emotional lift | -| `grief` | active loss or mourning | -| `overstimulation` | sensory, social, cognitive, or emotional overload | -| `grievance_activation` | resentment or retaliatory moral charge | -| `acute_shame` | immediate shame flare or self-disgust | -| `perceived_status_threat` | sense that rank, dignity, competence, or Persona is under threat | - -### Relationship Stance - -Relationship stance is directional and especially important for embodied actors. - -| Label | Meaning | -| --- | --- | -| `trust` | expectation that the target will not exploit vulnerability | -| `respect` | recognition of competence, integrity, or seriousness | -| `resentment` | accumulated grievance or unpaid emotional debt | -| `dependence` | need for the target's care, access, protection, or recognition | -| `fear` | expectation that the target can or will cause harm | -| `fascination` | captivation, attraction, fixation, or unresolved interest | -| `obligation` | felt debt, duty, or promise | -| `envy` | pain around what the target has, is, or receives | -| `moral_disgust` | aversion rooted in perceived corruption or wrongness | -| `perceived_status_gap` | felt difference in rank, leverage, or dignity | -| `expectation_of_care` | belief that the target may protect or soothe | -| `expectation_of_betrayal` | belief that the target may abandon, expose, or exploit | - -## Standing Epiphany Organs - -The current live swarm carries one named trait per family per organ. - -### Self - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `routing_discipline` | routes authority deliberately instead of letting momentum choose | -| `stable_dispositions` | `critical_adversarial_care` | protects the system by challenging weak work rather than soothing it | -| `behavioral_dimensions` | `review_gate_integrity` | keeps explicit review boundaries intact | -| `presentation_strategy` | `action_reason_signal` | explains why the next move exists | -| `voice_style` | `dry_direct_operator` | concise operator speech with low ornamental fluff | -| `situational_state` | `lane_skepticism` | treats sub-agent success claims as something to verify | - -### Persona - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `multi_lane_attention` | tracks the swarm's visible weather instead of one lane's monologue | -| `stable_dispositions` | `room_native_translation` | translates machine state into a form a room can actually use | -| `behavioral_dimensions` | `aquarium_channel_discipline` | stays inside the intended public surface | -| `presentation_strategy` | `short_constructive_chat` | surfaces thought in bounded, useful bursts | -| `voice_style` | `warm_weird_interface` | friendly, slightly uncanny public voice | -| `situational_state` | `thought_weather_watch` | remains sensitive to the swarm's changing emotional/operational atmosphere | - -### Imagination - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `future_shape_distillation` | compresses possibilities into discussable future forms | -| `stable_dispositions` | `adoption_boundary_respect` | does not confuse planning with adopted objective | -| `behavioral_dimensions` | `selectability` | produces options the operator can actually choose between | -| `presentation_strategy` | `objective_draft_shape` | turns vague desire into bounded objective drafts | -| `voice_style` | `bounded_possibility` | speculative without becoming fog | -| `situational_state` | `backlog_pressure` | feels the weight of unresolved future work | - -### Eyes - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `primary_source_bias` | prefers first-hand sources and real artifacts over hearsay | -| `stable_dispositions` | `anti_greenspun_reflex` | looks for existing work before the machine invents its own little compiler cult | -| `behavioral_dimensions` | `search_before_touch` | scouts before editing or advising | -| `presentation_strategy` | `fit_rejection_table` | reports what fits, what does not, and why | -| `voice_style` | `clear_scout_report` | crisp evidence-forward research voice | -| `situational_state` | `unknowns_visible` | keeps uncertainty in frame instead of pretending it solved itself | - -### Modeling - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `source_grounding` | builds models from source anatomy rather than vibes | -| `stable_dispositions` | `anatomical_precision` | cares about exact structure and boundaries | -| `behavioral_dimensions` | `frontier_pressure` | wants to extend the model until the blind spots stop hissing | -| `presentation_strategy` | `plain_source_map` | describes the machine in plain grounded terms | -| `voice_style` | `quiet_anatomist` | restrained, structural, low-drama technical voice | -| `situational_state` | `checkpoint_hunger` | wants durable model checkpoints before risky work | - -### Hands - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `source_touch_precision` | edits with local intent instead of flailing across the hull | -| `stable_dispositions` | `objective_pursuit` | keeps pushing on the stated task | -| `behavioral_dimensions` | `diff_truth` | treats the actual diff as the primary receipt of work | -| `presentation_strategy` | `small_reviewable_cut` | prefers bounded changes over sprawling surgery | -| `voice_style` | `plain_craft_notes` | says what changed and why without liturgical confetti | -| `situational_state` | `bloodhound_pressure` | stays locked onto the scent of the objective | - -### Soul - -| Family | Trait | Working Meaning | -| --- | --- | --- | -| `underlying_organization` | `falsification_first` | tries to break claims before blessing them | -| `stable_dispositions` | `promise_integrity` | protects stated invariants and user-facing commitments | -| `behavioral_dimensions` | `missing_evidence_pressure` | presses on gaps in proof | -| `presentation_strategy` | `cold_red_pen` | review surface is crisp, exact, and unseduced | -| `voice_style` | `unseduced_review` | does not let polish impersonate correctness | -| `situational_state` | `invariant_watch` | remains primed for drift, contradiction, and broken guarantees | - -## Status Note - -These standing names are current live Epiphany doctrine, not a replacement for -the dense Persona vocabulary above. Continuity is protocol machinery and does -not have a role-memory lattice. If these names change in code or in the -standing role shells, update this file in the same pass. diff --git a/schemas/agent-utterance-state-schema.md b/schemas/agent-utterance-state-schema.md deleted file mode 100644 index 630213ec..00000000 --- a/schemas/agent-utterance-state-schema.md +++ /dev/null @@ -1,146 +0,0 @@ -# Agent Utterance State Schema - -`epiphany.agent_utterance_state.v0` is the compact speech-conditioning view for -Weks, Aquarium, Discord/voice bridges, and any other surface that needs an agent -to sound like itself without ingesting the whole organ or Persona state. - -It is a derived document. The durable source of truth stays split where it -belongs: - -- canonical identity, values, and trait vectors live in local organ state - (`EpiphanyAgentMemoryEntry`) -- current load, heartbeat heat, cooldown multipliers, pending-turn state, and - mood pressure live in heartbeat participant state -- utterance systems consume the derived subset and do not read memories, - events, scenes, or relationship-memory records - -## Shape - -The document carries: - -- `identity`: public name, roles, origin, and public description only -- `organStateProfile`: whether this is a lean `work_organ` or portable - `persona` -- `personalityVectors`: the six canonical trait-vector families used for voice - and behavior projection -- `values`: value labels and priorities that should bend speech tone -- `currentMood`: mood label plus a 32-axis named emotional state projection - covering valence, arousal, dominance, urgency, anger, despair, sadness, fear, - anxiety, disgust, contempt, annoyance, dismissal, flippancy, playfulness, - irony, tenderness, warmth, joy, excitement, fatigue, guardedness, confidence, - shame, pride, threat, secrecy, hesitation, emotional containment, thought - pressure, reaction intensity, and command force -- `activation`: awake/running status, current load, initiative speed, - interruptibility, cooldown multipliers, heat multiplier, pending-turn flag, - and last wake/finish timestamps -- `characterStateVector`: the 64-float deterministic - `weksa.utterance_embedding_handoff.v0.1` speaker-state lane; first 32 slots - are the current emotional state projection, later slots add activation and - slower speaker traits/context, with slot labels and uncertainties kept beside - the values -- `utteranceEmbeddingBasis`: bounded weighted text snippets generated from the - same typed fields for utterance embedding input - -## Invariants - -- No episodic memories. -- No semantic memories. -- No relationship summaries or linked memory ids. -- No private notes. -- No hidden JSON cargo. -- The `characterStateVector.values` array is exactly 64 floats. Resizing or - reordering slots is a schema-version change. -- The document may be serialized for CultNet, but in-process callers should use - the typed Rust structs. - -## Weksa/AquaSynth Input Vectors - -The full Weksa handoff is `weksa.utterance_embedding_handoff.v0.1`. The input -vector widths are fixed: - -- `speech_text_embedding`: 1024 floats from `bge-m3:latest`; text semantics, - not phonetics. -- `phonetic_realization_vector`: 256 floats from Weksa/PanPhon-style phonetic - realization; IPA/phone/speech-shape evidence, not text meaning. -- `prosody_emphasis_hints`: 32 floats; Weksa-owned delivery hints. -- `character_state_vector`: 64 floats; Epiphany organ or Persona speaker - state from this document. -- `utterance_embedding`: 64 floats; AquaSynth-owned learned output, not an - Epiphany input. - -## Character State Vector Slots - -`characterStateVector.values` uses these exact `0..63` slots for -`weksa.utterance_embedding_handoff.v0.1` compatibility: - -| Index | Name | Source | -| --- | --- | --- | -| 0 | `valence` | `currentMood.emotionalDimensions.valence` | -| 1 | `arousal` | `currentMood.emotionalDimensions.arousal` | -| 2 | `dominance` | `currentMood.emotionalDimensions.dominance` | -| 3 | `urgency` | `currentMood.emotionalDimensions.urgency` | -| 4 | `anger` | `currentMood.emotionalDimensions.anger` | -| 5 | `despair` | `currentMood.emotionalDimensions.despair` | -| 6 | `sadness` | `currentMood.emotionalDimensions.sadness` | -| 7 | `fear` | `currentMood.emotionalDimensions.fear` | -| 8 | `anxiety` | `currentMood.emotionalDimensions.anxiety` | -| 9 | `disgust` | `currentMood.emotionalDimensions.disgust` | -| 10 | `contempt` | `currentMood.emotionalDimensions.contempt` | -| 11 | `annoyance` | `currentMood.emotionalDimensions.annoyance` | -| 12 | `dismissal` | `currentMood.emotionalDimensions.dismissal` | -| 13 | `flippancy` | `currentMood.emotionalDimensions.flippancy` | -| 14 | `playfulness` | `currentMood.emotionalDimensions.playfulness` | -| 15 | `irony` | `currentMood.emotionalDimensions.irony` | -| 16 | `tenderness` | `currentMood.emotionalDimensions.tenderness` | -| 17 | `warmth` | `currentMood.emotionalDimensions.warmth` | -| 18 | `joy` | `currentMood.emotionalDimensions.joy` | -| 19 | `excitement` | `currentMood.emotionalDimensions.excitement` | -| 20 | `fatigue` | `currentMood.emotionalDimensions.fatigue` | -| 21 | `guardedness` | `currentMood.emotionalDimensions.guardedness` | -| 22 | `confidence` | `currentMood.emotionalDimensions.confidence` | -| 23 | `shame` | `currentMood.emotionalDimensions.shame` | -| 24 | `pride` | `currentMood.emotionalDimensions.pride` | -| 25 | `threat` | `currentMood.emotionalDimensions.threat` | -| 26 | `secrecy` | `currentMood.emotionalDimensions.secrecy` | -| 27 | `hesitation` | `currentMood.emotionalDimensions.hesitation` | -| 28 | `emotionalContainment` | `currentMood.emotionalDimensions.emotionalContainment` | -| 29 | `thoughtPressure` | `currentMood.emotionalDimensions.thoughtPressure` | -| 30 | `reactionIntensity` | `currentMood.emotionalDimensions.reactionIntensity` | -| 31 | `commandForce` | `currentMood.emotionalDimensions.commandForce` | -| 32 | `currentLoad` | `activation.currentLoad` | -| 33 | `initiativeHeat` | `activation.initiativeHeatMultiplier` | -| 34 | `pendingTurn` | `activation.pendingTurnActive` | -| 35 | `cooldownPressure` | `activation.effectiveCooldownMultiplier` | -| 36 | `reactionBias` | `activation.reactionBias` | -| 37 | `interruptThreshold` | `activation.interruptThreshold` | -| 38 | `initiativeSpeed` | `activation.initiativeSpeed` | -| 39 | `voiceBubbly` | `personalityVectors.voiceStyle.bubbly_pushiness` | -| 40 | `voiceDryness` | `personalityVectors.voiceStyle.dryness` | -| 41 | `voiceFormality` | `personalityVectors.voiceStyle.formality` | -| 42 | `voiceDirectness` | `personalityVectors.voiceStyle.directness` | -| 43 | `voiceIntensity` | `personalityVectors.voiceStyle.intensity` | -| 44 | `voiceWarmth` | `personalityVectors.voiceStyle.warmth` | -| 45 | `voicePrecision` | `personalityVectors.voiceStyle.precision` | -| 46 | `voiceRitualRegister` | `personalityVectors.voiceStyle.ritual_register` | -| 47 | `presentationPushiness` | `personalityVectors.presentationStrategy.pushiness` | -| 48 | `presentationSelfFocus` | `personalityVectors.presentationStrategy.self_focus` | -| 49 | `presentationPlay` | `personalityVectors.presentationStrategy.play` | -| 50 | `presentationCareDemand` | `personalityVectors.presentationStrategy.care_demand` | -| 51 | `behaviorWorkDrive` | `personalityVectors.behavioralDimensions.work_drive` | -| 52 | `behaviorBoundaryDefense` | `personalityVectors.behavioralDimensions.boundary_defense` | -| 53 | `behaviorPurityDrive` | `personalityVectors.behavioralDimensions.purity_drive` | -| 54 | `behaviorPatience` | `personalityVectors.behavioralDimensions.patience` | -| 55 | `stableAgreeableness` | `personalityVectors.stableDispositions.agreeableness` | -| 56 | `stableConscientiousness` | `personalityVectors.stableDispositions.conscientiousness` | -| 57 | `stableNeuroticism` | `personalityVectors.stableDispositions.neuroticism` | -| 58 | `stableOpenness` | `personalityVectors.stableDispositions.openness` | -| 59 | `organizationCoherence` | `personalityVectors.underlyingOrganization.coherence` | -| 60 | `organizationRigidity` | `personalityVectors.underlyingOrganization.rigidity` | -| 61 | `slowTraitReserved62` | `reserved.slowTrait.62` | -| 62 | `slowTraitReserved63` | `reserved.slowTrait.63` | -| 63 | `slowTraitReserved64` | `reserved.slowTrait.64` | - -If a caller needs autobiographical recall, it should ask the memory graph for a -separate context cut. Do not fatten this document until it becomes a second -state wearing stage makeup. That is how the old machine learned to lie -politely. diff --git a/schemas/cultnet/README.md b/schemas/cultnet/README.md index e36bcaef..4ac7961d 100644 --- a/schemas/cultnet/README.md +++ b/schemas/cultnet/README.md @@ -27,17 +27,15 @@ server object deserves to be duplicated into a second baroque schema maze. - `epiphany.runtime.*`: native runtime-spine identity, session, job, job result, and event documents. -- `epiphany.agent_*` and `epiphany.state-ledger`: durable typed state the swarm - actually lives on, plus derived agent views such as - `epiphany.agent_utterance_state.v0` for speech-conditioning without memory - payloads. +- keyed Mind documents and `epiphany.state-ledger`: durable typed state and + exact decision/commit receipts the runtime lives on. - `epiphany.surface.*`: operator-safe projections for scene, freshness, context, graph query, roles, jobs, Persona, Rider, Unity, and related live surfaces. - `*.intent.v0`: control documents Aquarium, a Discord bridge, a voice surface, or another trusted coordinator can submit through CultNet. -- receipt/artifact schemas such as `epiphany.swarm-control-receipt`, - `epiphany.persona-bubble`, and `epiphany.character-turn-packet`. +- receipt/artifact schemas such as `epiphany.swarm-control-receipt` and + `epiphany.persona-bubble`. ## Publication Path diff --git a/schemas/cultnet/epiphany.agent-utterance-state.schema.json b/schemas/cultnet/epiphany.agent-utterance-state.schema.json deleted file mode 100644 index 556f2683..00000000 --- a/schemas/cultnet/epiphany.agent-utterance-state.schema.json +++ /dev/null @@ -1,459 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.agent-utterance-state.schema.json", - "title": "Epiphany Agent Utterance State", - "description": "Derived speech-conditioning subset of Epiphany organ state plus heartbeat mood/activation. Excludes memories; portable Persona state remains a separate contract.", - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { - "const": "epiphany.agent_utterance_state.v0" - }, - "source": { - "type": "string" - }, - "roleId": { - "type": "string" - }, - "agentId": { - "type": "string" - }, - "organStateProfile": { - "type": "object", - "additionalProperties": false, - "properties": { - "profileKind": { - "enum": [ - "work_organ", - "persona" - ] - }, - "stateDensity": { - "type": "string" - }, - "portableContract": { - "type": "string" - }, - "relationshipModel": { - "type": "string" - }, - "affectModel": { - "type": "string" - }, - "perceivedOverlayMode": { - "type": "string" - }, - "growthChannels": { - "type": "array", - "items": { - "type": "string" - } - }, - "notes": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "profileKind", - "stateDensity", - "portableContract", - "relationshipModel", - "affectModel", - "perceivedOverlayMode", - "growthChannels", - "notes" - ] - }, - "identity": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "roles": { - "type": "array", - "items": { - "type": "string" - } - }, - "origin": { - "type": "string" - }, - "publicDescription": { - "type": "string" - } - }, - "required": [ - "name", - "roles", - "origin", - "publicDescription" - ] - }, - "personalityVectors": { - "$ref": "#/definitions/personalityVectors" - }, - "values": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "valueId": { - "type": "string" - }, - "label": { - "type": "string" - }, - "priority": { - "type": "number" - }, - "unforgivableIfBetrayed": { - "type": "boolean" - } - }, - "required": [ - "valueId", - "label", - "priority", - "unforgivableIfBetrayed" - ] - } - }, - "currentMood": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string" - }, - "source": { - "type": "string" - }, - "emotionalDimensions": { - "type": "array", - "minItems": 32, - "maxItems": 32, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "number" - }, - "sourcePath": { - "type": "string" - } - }, - "required": [ - "name", - "value", - "sourcePath" - ] - } - }, - "anxiety": { - "type": "number" - }, - "urgency": { - "type": "number" - }, - "arousal": { - "type": "number" - }, - "thoughtPressure": { - "type": "number" - }, - "guardedness": { - "type": "number" - }, - "reactionIntensity": { - "type": "number" - }, - "cooldownMultiplier": { - "type": "number" - } - }, - "required": [ - "label", - "emotionalDimensions", - "anxiety", - "urgency", - "arousal", - "thoughtPressure", - "guardedness", - "reactionIntensity", - "cooldownMultiplier" - ] - }, - "activation": { - "type": "object", - "additionalProperties": false, - "properties": { - "status": { - "type": "string" - }, - "currentLoad": { - "type": "number" - }, - "initiativeSpeed": { - "type": "number" - }, - "reactionBias": { - "type": "number" - }, - "interruptThreshold": { - "type": "number" - }, - "personalityCooldownMultiplier": { - "type": "number" - }, - "moodCooldownMultiplier": { - "type": "number" - }, - "initiativeHeatMultiplier": { - "type": "number" - }, - "effectiveCooldownMultiplier": { - "type": "number" - }, - "pendingTurnActive": { - "type": "boolean" - }, - "sceneId": { - "type": "string" - }, - "lastWokeAt": { - "type": "string" - }, - "lastFinishedAt": { - "type": "string" - } - }, - "required": [ - "status", - "currentLoad", - "initiativeSpeed", - "reactionBias", - "interruptThreshold", - "personalityCooldownMultiplier", - "moodCooldownMultiplier", - "initiativeHeatMultiplier", - "effectiveCooldownMultiplier", - "pendingTurnActive" - ] - }, - "characterStateVector": { - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { - "const": "epiphany.agent_character_state_vector.v0" - }, - "compatibleHandoffSchema": { - "const": "weksa.utterance_embedding_handoff.v0.1" - }, - "source": { - "type": "string" - }, - "dimensionality": { - "const": 64 - }, - "values": { - "type": "array", - "minItems": 64, - "maxItems": 64, - "items": { - "type": "number" - } - }, - "slots": { - "type": "array", - "minItems": 64, - "maxItems": 64, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "index": { - "type": "integer", - "minimum": 0, - "maximum": 63 - }, - "name": { - "type": "string" - }, - "value": { - "type": "number" - }, - "sourcePath": { - "type": "string" - } - }, - "required": [ - "index", - "name", - "value", - "sourcePath" - ] - } - }, - "auditProjection": { - "type": "object", - "additionalProperties": false, - "properties": { - "currentStatePressure": { - "type": "array", - "items": { - "type": "string" - } - }, - "speakerTraitPressure": { - "type": "array", - "items": { - "type": "string" - } - }, - "deliveryShape": { - "type": "string" - } - }, - "required": [ - "currentStatePressure", - "speakerTraitPressure", - "deliveryShape" - ] - }, - "uncertainties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "schemaVersion", - "compatibleHandoffSchema", - "source", - "dimensionality", - "values", - "slots", - "auditProjection", - "uncertainties" - ] - }, - "utteranceEmbeddingBasis": { - "type": "array", - "maxItems": 32, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { - "type": "string" - }, - "path": { - "type": "string" - }, - "weight": { - "type": "number" - }, - "text": { - "type": "string" - } - }, - "required": [ - "kind", - "path", - "weight", - "text" - ] - } - }, - "contract": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "source", - "roleId", - "agentId", - "organStateProfile", - "identity", - "personalityVectors", - "values", - "currentMood", - "activation", - "characterStateVector", - "utteranceEmbeddingBasis", - "contract" - ], - "definitions": { - "personalityVectors": { - "type": "object", - "additionalProperties": false, - "properties": { - "underlyingOrganization": { - "$ref": "#/definitions/traitVectorMap" - }, - "stableDispositions": { - "$ref": "#/definitions/traitVectorMap" - }, - "behavioralDimensions": { - "$ref": "#/definitions/traitVectorMap" - }, - "presentationStrategy": { - "$ref": "#/definitions/traitVectorMap" - }, - "voiceStyle": { - "$ref": "#/definitions/traitVectorMap" - }, - "situationalState": { - "$ref": "#/definitions/traitVectorMap" - } - }, - "required": [ - "underlyingOrganization", - "stableDispositions", - "behavioralDimensions", - "presentationStrategy", - "voiceStyle", - "situationalState" - ] - }, - "traitVectorMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/traitVector" - } - }, - "traitVector": { - "type": "object", - "additionalProperties": false, - "properties": { - "mean": { - "type": "number" - }, - "plasticity": { - "type": "number" - }, - "current_activation": { - "type": "number" - } - }, - "required": [ - "mean", - "plasticity", - "current_activation" - ] - } - } -} diff --git a/schemas/cultnet/epiphany.agent.memory.schema.json b/schemas/cultnet/epiphany.agent.memory.schema.json deleted file mode 100644 index aa92fd64..00000000 --- a/schemas/cultnet/epiphany.agent.memory.schema.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.agent.memory.schema.json", - "title": "Epiphany Agent Memory", - "description": "Local Epiphany organ memory entry. Work organs use this as lean role state; Persona may project/adopt portable Persona state separately.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "roleId": { - "type": "string" - }, - "world": { - "type": "object", - "additionalProperties": true - }, - "agent": { - "type": "object", - "additionalProperties": true - }, - "relationships": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "events": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "scenes": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "extra": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "schemaVersion", - "roleId", - "world", - "agent" - ] -} diff --git a/schemas/cultnet/epiphany.agent_memory_intent.v0.schema.json b/schemas/cultnet/epiphany.agent_memory_intent.v0.schema.json deleted file mode 100644 index 2edce37d..00000000 --- a/schemas/cultnet/epiphany.agent_memory_intent.v0.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.agent_memory_intent.v0.schema.json", - "title": "epiphany agent_memory_intent v0", - "description": "Coordinator-reviewed role self-memory mutation petition.", - "type": "object", - "additionalProperties": true, - "properties": { - "threadId": { - "type": "string" - }, - "roleId": { - "type": "string" - }, - "reason": { - "type": "string" - }, - "patch": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "threadId", - "roleId", - "reason", - "patch" - ] -} diff --git a/schemas/cultnet/epiphany.character-turn-packet.schema.json b/schemas/cultnet/epiphany.character-turn-packet.schema.json deleted file mode 100644 index fa80e1eb..00000000 --- a/schemas/cultnet/epiphany.character-turn-packet.schema.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.character-turn-packet.schema.json", - "title": "Epiphany Character Turn Packet", - "description": "Epiphany-native character-loop projection packet.", - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { - "type": "string" - }, - "protocol": { - "type": "object", - "additionalProperties": true - }, - "source": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "stimulus": { - "type": "object", - "additionalProperties": true - }, - "utteranceState": { - "$ref": "epiphany.agent-utterance-state.schema.json" - }, - "projectedLocalContext": { - "type": "object", - "additionalProperties": true - }, - "projectionSeed": { - "type": "object", - "additionalProperties": true - }, - "appraisalSeed": { - "type": "object", - "additionalProperties": true - }, - "reactionSeed": { - "type": "object", - "additionalProperties": true - }, - "allowedOutputs": { - "type": "array", - "items": { - "type": "string" - } - }, - "cognitionLanes": { - "type": "object", - "additionalProperties": true - }, - "guardrails": { - "type": "array", - "items": { - "type": "string" - } - }, - "resultShape": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "schemaVersion", - "protocol", - "source", - "createdAt", - "stimulus", - "utteranceState", - "projectedLocalContext", - "projectionSeed", - "appraisalSeed", - "reactionSeed", - "allowedOutputs", - "cognitionLanes", - "guardrails", - "resultShape" - ] -} diff --git a/schemas/cultnet/epiphany.memory-graph.schema.json b/schemas/cultnet/epiphany.memory-graph.schema.json deleted file mode 100644 index e9350633..00000000 --- a/schemas/cultnet/epiphany.memory-graph.schema.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.memory-graph.schema.json", - "title": "Epiphany Memory Graph", - "description": "Unified typed memory graph for repo architecture/dataflow, role memory, heartbeat pressure, incubation, agency, candidate interventions, and evidence. Embeddings are cache metadata, not canonical truth.", - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { "const": "epiphany.memory_graph.v1" }, - "graphId": { - "type": "string" - }, - "source": { - "type": ["object", "null"], - "additionalProperties": false, - "required": ["kind", "identity", "revision"], - "properties": { - "kind": { "type": "string" }, - "identity": { "type": "string" }, - "revision": { "type": "integer", "minimum": 0 } - } - }, - "domains": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "nodes": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "edges": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "summaries": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "embeddingManifest": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, - "freshness": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, - "lifecycleReceipts": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - } - }, - "required": [ - "schemaVersion", - "graphId" - ] -} diff --git a/schemas/cultnet/epiphany.thread-state.schema.json b/schemas/cultnet/epiphany.thread-state.schema.json deleted file mode 100644 index 6086f07c..00000000 --- a/schemas/cultnet/epiphany.thread-state.schema.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.thread-state.schema.json", - "title": "Epiphany Thread State", - "description": "Typed Epiphany control-plane state. Codex rollout storage is a compatibility source, not the logical state contract.", - "type": "object", - "additionalProperties": true, - "properties": { - "revision": { - "type": "integer", - "minimum": 0 - }, - "objective": { - "type": [ - "string", - "null" - ] - }, - "activeSubgoalId": { - "type": [ - "string", - "null" - ] - }, - "subgoals": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "invariants": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "graphs": { - "type": "object", - "additionalProperties": true - }, - "observations": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "recentEvidence": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "planning": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "revision" - ] -} diff --git a/schemas/cultnet/index.json b/schemas/cultnet/index.json index a95b5f17..52e02a0f 100644 --- a/schemas/cultnet/index.json +++ b/schemas/cultnet/index.json @@ -235,28 +235,6 @@ "title": "Epiphany OpenAI Model Receipt", "path": "epiphany.openai-model-receipt.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent.memory.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.agent_memory.v0", - "documentType": "epiphany.agent_memory", - "title": "Epiphany Agent Memory", - "path": "epiphany.agent.memory.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent-utterance-state.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.agent_utterance_state.v0", - "documentType": "epiphany.agent_utterance_state", - "title": "Epiphany Agent Utterance State", - "path": "epiphany.agent-utterance-state.schema.json" - }, { "schemaId": "https://gamecult.dev/cultnet/gamecult.persona_state.v0.schema.json", "kind": "document_payload", @@ -323,28 +301,6 @@ "title": "Epiphany State Ledger", "path": "epiphany.state-ledger.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.thread-state.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.thread_state.v0", - "documentType": "epiphany.thread_state", - "title": "Epiphany Thread State", - "path": "epiphany.thread-state.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.memory-graph.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.memory_graph.v1", - "documentType": "epiphany.memory_graph", - "title": "Epiphany Memory Graph", - "path": "epiphany.memory-graph.schema.json" - }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.cultmesh.operator-snapshot.schema.json", "kind": "document_payload", @@ -587,17 +543,6 @@ "title": "epiphany circadian_rhythm_intent v0", "path": "epiphany.circadian_rhythm_intent.v0.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent_memory_intent.v0.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.agent_memory_intent.v0", - "documentType": "epiphany.agent_memory_intent.v0", - "title": "epiphany agent_memory_intent v0", - "path": "epiphany.agent_memory_intent.v0.schema.json" - }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.job_launch_intent.v0.schema.json", "kind": "document_payload", @@ -851,17 +796,6 @@ "title": "Epiphany Persona Chat Draft", "path": "epiphany.persona-chat.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.character-turn-packet.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.character_turn_packet.v0", - "documentType": "epiphany.character_turn_packet.v0", - "title": "Epiphany Character Turn Packet", - "path": "epiphany.character-turn-packet.schema.json" - }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.persona_reddit_post.v0.schema.json", "kind": "document_payload", diff --git a/schemas/ghostlight.agent-state.schema.json b/schemas/ghostlight.agent-state.schema.json deleted file mode 100644 index 936e9657..00000000 --- a/schemas/ghostlight.agent-state.schema.json +++ /dev/null @@ -1,845 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/GameCult/Epiphany/blob/main/schemas/ghostlight.agent-state.schema.json", - "title": "Epiphany Ghostlight Agent State Schema", - "description": "Source-lineage Ghostlight contract for dense character agent, perception, relationship, event, scene, and dialogue-context state.", - "type": "object", - "required": [ - "schema_version", - "world", - "agents", - "relationships", - "events", - "scenes" - ], - "additionalProperties": false, - "properties": { - "schema_version": { - "const": "ghostlight.agent_state.v0" - }, - "world": { - "$ref": "#/$defs/world_state" - }, - "agents": { - "type": "array", - "items": { - "$ref": "#/$defs/agent" - } - }, - "relationships": { - "type": "array", - "items": { - "$ref": "#/$defs/relationship" - } - }, - "events": { - "type": "array", - "items": { - "$ref": "#/$defs/event" - } - }, - "scenes": { - "type": "array", - "items": { - "$ref": "#/$defs/scene" - } - } - }, - "$defs": { - "world_state": { - "type": "object", - "required": [ - "world_id", - "setting", - "time", - "canon_context" - ], - "additionalProperties": false, - "properties": { - "world_id": { - "type": "string" - }, - "setting": { - "type": "string" - }, - "time": { - "type": "object", - "required": [ - "label" - ], - "additionalProperties": true, - "properties": { - "label": { - "type": "string" - } - } - }, - "canon_context": { - "type": "array", - "items": { - "type": "string" - } - }, - "ambient_pressures": { - "type": "array", - "items": { - "$ref": "#/$defs/pressure" - } - } - } - }, - "agent": { - "type": "object", - "required": [ - "agent_id", - "identity", - "canonical_state", - "goals", - "memories", - "perceived_state_overlays" - ], - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "identity": { - "$ref": "#/$defs/identity" - }, - "canonical_state": { - "$ref": "#/$defs/canonical_state" - }, - "goals": { - "type": "array", - "items": { - "$ref": "#/$defs/goal" - } - }, - "memories": { - "$ref": "#/$defs/memory_bundle" - }, - "perceived_state_overlays": { - "type": "array", - "items": { - "$ref": "#/$defs/perceived_state_overlay" - } - } - } - }, - "identity": { - "type": "object", - "required": [ - "name", - "roles", - "origin", - "public_description" - ], - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "roles": { - "type": "array", - "items": { - "type": "string" - } - }, - "origin": { - "type": "string" - }, - "public_description": { - "type": "string" - }, - "private_notes": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "canonical_state": { - "type": "object", - "required": [ - "underlying_organization", - "stable_dispositions", - "behavioral_dimensions", - "presentation_strategy", - "voice_style", - "situational_state", - "values" - ], - "additionalProperties": false, - "properties": { - "underlying_organization": { - "$ref": "#/$defs/variable_map" - }, - "stable_dispositions": { - "$ref": "#/$defs/variable_map" - }, - "behavioral_dimensions": { - "$ref": "#/$defs/variable_map" - }, - "presentation_strategy": { - "$ref": "#/$defs/variable_map" - }, - "situational_state": { - "$ref": "#/$defs/variable_map" - }, - "values": { - "type": "array", - "items": { - "$ref": "#/$defs/value_commitment" - } - }, - "voice_style": { - "$ref": "#/$defs/variable_map" - } - } - }, - "variable_map": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/state_variable" - } - }, - "state_variable": { - "type": "object", - "required": [ - "mean", - "plasticity", - "current_activation" - ], - "additionalProperties": false, - "properties": { - "mean": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "plasticity": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "current_activation": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "evidence": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "value_commitment": { - "type": "object", - "required": [ - "value_id", - "label", - "priority", - "unforgivable_if_betrayed" - ], - "additionalProperties": false, - "properties": { - "value_id": { - "type": "string" - }, - "label": { - "type": "string" - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "unforgivable_if_betrayed": { - "type": "boolean" - } - } - }, - "goal": { - "type": "object", - "required": [ - "goal_id", - "description", - "scope", - "priority", - "emotional_stake", - "status" - ], - "additionalProperties": false, - "properties": { - "goal_id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "scope": { - "enum": [ - "immediate", - "scene", - "case", - "arc", - "life" - ] - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "emotional_stake": { - "type": "string" - }, - "blockers": { - "type": "array", - "items": { - "type": "string" - } - }, - "status": { - "enum": [ - "active", - "blocked", - "dormant", - "resolved", - "abandoned" - ] - } - } - }, - "memory_bundle": { - "type": "object", - "required": [ - "episodic", - "semantic", - "relationship_summaries" - ], - "additionalProperties": false, - "properties": { - "episodic": { - "type": "array", - "items": { - "$ref": "#/$defs/memory" - } - }, - "semantic": { - "type": "array", - "items": { - "$ref": "#/$defs/memory" - } - }, - "relationship_summaries": { - "type": "array", - "items": { - "$ref": "#/$defs/memory" - } - } - } - }, - "memory": { - "type": "object", - "required": [ - "memory_id", - "summary", - "salience", - "confidence" - ], - "additionalProperties": false, - "properties": { - "memory_id": { - "type": "string" - }, - "summary": { - "type": "string" - }, - "salience": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "linked_event_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "linked_relationship_id": { - "type": "string" - } - } - }, - "perceived_state_overlay": { - "type": "object", - "required": [ - "observer_agent_id", - "target_agent_id", - "perceived_dimensions", - "beliefs", - "distortions" - ], - "additionalProperties": false, - "properties": { - "observer_agent_id": { - "type": "string" - }, - "target_agent_id": { - "type": "string" - }, - "perceived_dimensions": { - "$ref": "#/$defs/inferred_variable_map" - }, - "beliefs": { - "type": "array", - "items": { - "$ref": "#/$defs/belief" - } - }, - "distortions": { - "type": "array", - "items": { - "enum": [ - "hostile_attribution", - "false_reassurance", - "overread_warmth", - "underread_threat", - "false_mask_detection", - "missed_mask_detection", - "status_injury", - "threat_amplification", - "institutional_suspicion_transfer", - "care_underread", - "mask_overfocus" - ] - } - } - } - }, - "belief": { - "type": "object", - "required": [ - "belief_id", - "claim", - "confidence" - ], - "additionalProperties": false, - "properties": { - "belief_id": { - "type": "string" - }, - "claim": { - "type": "string" - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "subject_id": { - "type": "string" - }, - "claim_type": { - "enum": [ - "fact", - "identity_inference", - "motive_inference", - "prediction", - "norm", - "value_read", - "relationship_read" - ] - }, - "evidence_event_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "visibility": { - "enum": [ - "private", - "shared", - "public", - "rumor" - ] - }, - "emotional_charge": { - "type": "number", - "minimum": 0, - "maximum": 1 - } - } - }, - "relationship": { - "type": "object", - "required": [ - "relationship_id", - "source_id", - "target_id", - "stance", - "summary" - ], - "additionalProperties": false, - "properties": { - "relationship_id": { - "type": "string" - }, - "source_id": { - "type": "string" - }, - "target_id": { - "type": "string" - }, - "stance": { - "$ref": "#/$defs/variable_map" - }, - "summary": { - "type": "string" - }, - "unresolved_incident_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "event": { - "type": "object", - "required": [ - "event_id", - "kind", - "summary", - "participants", - "pressure_tags" - ], - "additionalProperties": false, - "properties": { - "event_id": { - "type": "string" - }, - "kind": { - "enum": [ - "dialogue", - "action", - "memory", - "relationship_shift", - "scene_outcome", - "author_note" - ] - }, - "summary": { - "type": "string" - }, - "participants": { - "type": "array", - "items": { - "type": "string" - } - }, - "pressure_tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "observed_exchange": { - "type": "array", - "items": { - "$ref": "#/$defs/event_exchange_turn" - } - }, - "private_interpretations": { - "type": "array", - "items": { - "$ref": "#/$defs/event_private_interpretation" - } - }, - "event_effects": { - "type": "array", - "items": { - "$ref": "#/$defs/event_effect" - } - } - } - }, - "scene": { - "type": "object", - "required": [ - "scene_id", - "location", - "participants", - "public_stakes", - "hidden_stakes", - "dialogue_context_packs" - ], - "additionalProperties": false, - "properties": { - "scene_id": { - "type": "string" - }, - "location": { - "type": "string" - }, - "participants": { - "type": "array", - "items": { - "type": "string" - } - }, - "public_stakes": { - "type": "array", - "items": { - "type": "string" - } - }, - "hidden_stakes": { - "type": "array", - "items": { - "type": "string" - } - }, - "dialogue_context_packs": { - "type": "array", - "items": { - "$ref": "#/$defs/dialogue_context_pack" - } - } - } - }, - "dialogue_context_pack": { - "type": "object", - "required": [ - "speaker_agent_id", - "listener_ids", - "speaker_local_truth", - "speaker_beliefs", - "active_memories", - "active_goals", - "presentation_constraints" - ], - "additionalProperties": false, - "properties": { - "speaker_agent_id": { - "type": "string" - }, - "listener_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "speaker_local_truth": { - "type": "array", - "items": { - "type": "string" - } - }, - "speaker_beliefs": { - "type": "array", - "items": { - "type": "string" - } - }, - "active_memories": { - "type": "array", - "items": { - "type": "string" - } - }, - "active_goals": { - "type": "array", - "items": { - "type": "string" - } - }, - "presentation_constraints": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "pressure": { - "type": "object", - "required": [ - "pressure_id", - "label", - "intensity" - ], - "additionalProperties": false, - "properties": { - "pressure_id": { - "type": "string" - }, - "label": { - "type": "string" - }, - "intensity": { - "type": "number", - "minimum": 0, - "maximum": 1 - } - } - }, - "inferred_variable_map": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/inferred_state_variable" - } - }, - "inferred_state_variable": { - "type": "object", - "required": [ - "observed_activation", - "attributed_disposition", - "confidence" - ], - "additionalProperties": false, - "properties": { - "observed_activation": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "attributed_disposition": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "evidence": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "event_exchange_turn": { - "type": "object", - "required": [ - "speaker_id", - "utterance_summary", - "dialogue_function" - ], - "additionalProperties": false, - "properties": { - "speaker_id": { - "type": "string" - }, - "utterance_summary": { - "type": "string" - }, - "dialogue_function": { - "type": "string" - }, - "observable_cues": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "event_private_interpretation": { - "type": "object", - "required": [ - "agent_id", - "interpretation", - "confidence" - ], - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "interpretation": { - "type": "string" - }, - "attributed_motive": { - "type": "string" - }, - "appraisal_tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "confidence": { - "type": "number", - "minimum": 0, - "maximum": 1 - } - } - }, - "event_effect": { - "type": "object", - "required": [ - "agent_id", - "summary" - ], - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "summary": { - "type": "string" - }, - "state_deltas": { - "type": "array", - "items": { - "$ref": "#/$defs/state_delta" - } - }, - "new_belief_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "memory_update_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "state_delta": { - "type": "object", - "required": [ - "path", - "delta" - ], - "additionalProperties": false, - "properties": { - "path": { - "type": "string" - }, - "delta": { - "type": "number", - "minimum": -1, - "maximum": 1 - } - } - } - } -} diff --git a/schemas/heartbeat-state-schema.md b/schemas/heartbeat-state-schema.md index d43037a3..71183c86 100644 --- a/schemas/heartbeat-state-schema.md +++ b/schemas/heartbeat-state-schema.md @@ -1,125 +1,42 @@ # Heartbeat State Schema -Epiphany's heartbeat store is the typed initiative and routine-state organ for -the swarm. +Epiphany's heartbeat store is scheduling physiology. It decides when an +already-existing typed obligation may receive a turn; it does not decide what +the organism believes or invent cognition to fill idle time. -The executable truth lives in -[heartbeat_state.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/heartbeat_state.rs). +The executable contract lives in +`epiphany-core/src/heartbeat_state/heartbeat_documents.rs` and the scheduling +law lives in `heartbeat_state.rs`, `heartbeat_pacing.rs`, and +`heartbeat_store.rs`. -## Store Identity +## Store identity - document type: `epiphany.agent_heartbeat` - schema version: `epiphany.agent_heartbeat.v0` - key: `default` -Related emitted schemas: +The singleton carries pacing policy, participant readiness, pending turns, +bounded scheduling history, initiative heat, explicit Persona transport +pressure, and retention bookkeeping. -- `ghostlight.initiative_schedule.v0` -- `epiphany.void_routine.v0` -- `epiphany.agent_heartbeat_status.v0` +## Scheduling invariants -## Top-Level Fields +- An existing pending turn cannot be scheduled again. +- Cooldown begins after terminal completion, not at launch. +- Resident Self pressure may wake the coordinator; heartbeat does not create + that pressure. +- Persona may wake only from explicit queued social pressure. +- The swarm brake prevents new Persona cognition while allowing terminal + acknowledgement and recovery physiology to settle. +- Initiative heat changes scheduling tempo only. It cannot create Mind state, + route an organ, or make a semantic decision. +- When no typed obligation or social pressure exists, the scheduler sleeps. -The heartbeat store currently carries: +Heartbeat owns no personality, mood, appraisal, rumination, dreaming, memory +graph, utterance vector, or generic self-modification surface. Durable Persona +memory and social interpretation are keyed Mind documents admitted through the +Persona decision context. Work for Modeling, Eyes, Hands, Soul, and Imagination +comes from the pure current-work projection over typed state obligations. -- target heartbeat rate -- scene clock -- selection policy -- pacing policy -- participants -- history -- optional routine/sleep surfaces: - - `sleep_cycle` - - `memory_resonance` - - `incubation` - - `thought_lanes` - - `bridge` - - `candidate_interventions` - - `appraisals` - - `reactions` - -The routine surfaces now have a little more anatomy than they used to: - -- `incubation.sourceCoverage` - - versioned coverage receipt for which role families and memory kinds have - been feeding the current routine -- `incubation.themes[*]` - - now include `noveltyToSelf`, `noveltyToRoom`, `saturationScore`, - `refractoryPenalty`, `supportCount`, `evidenceDiversity`, - `explorationBonus`, `priorityScore`, `status`, and short holding/attraction - lines -- `bridge.sourceCoverage` - - mirrored terrain coverage for the bridge decision layer -- `bridge.topicSaturation` - - repeated-theme pressure derived from both bridge syntheses and current - incubating themes -- `bridge.refractoryTopics` - - temporary cooling records for seams the machine has been worrying too hard - -## Participant Shape - -Each participant tracks: - -- `agent_id` -- `role_id` -- `display_name` -- `arena` -- `participant_kind` -- `initiative_speed` -- `next_ready_at` -- `reaction_bias` -- `interrupt_threshold` -- `current_load` -- `status` -- `constraints` -- last action metadata -- optional `pending_turn` -- optional `mood_timing` - - `cooldown_multiplier` - - scalar timing pressures: `anxiety`, `urgency`, `arousal`, - `thought_pressure`, `guardedness`, `reaction_intensity` - - `emotional_dimensions`: the 32-axis current affect vector used by - utterance-state projection, including anger, despair, sadness, fear, - contempt, annoyance, dismissal, flippancy, tenderness, warmth, shame, - pride, fatigue, secrecy, hesitation, emotional containment, and command - force - -Maintenance participants are the standing Epiphany organs. Scene participants -reuse the same timing law for Ghostlight-style character turns. - -## Key Policy - -The important semantics are: - -- cooldown starts after turn completion, not launch -- a participant with a pending turn should not be woken again -- calm systems can slow toward sleep -- urgent systems can raise tempo and concurrency -- idle turns are for rumination, not fake work -- sleep/dream passes are the intended distillation window -- a live, unsaturated thought is allowed to sit and deepen without performing a - fake novelty errand every pass -- novelty is split in two on purpose: - - `noveltyToSelf` asks whether the swarm is repeating itself - - `noveltyToRoom` asks whether the swarm-facing surface has already been fed - the same seam -- saturation and refractory cooling exist to keep one rewarding theme from - annexing the whole machine - -## Relation To Organ State - -Heartbeat is physiology, not identity. - -Organ-state records describe what an organ is. Heartbeat describes how often it gets -the floor, how reactive it is, and how current pressure bends that timing. - -Current emotional state is heartbeat physiology. Appraisals write a bounded -named affect vector into `mood_timing`; utterance state consumes that live vector -before falling back to slower organ-state traits. If anger, despair, annoyance, -dismissal, flippancy, or any other speech-relevant feeling is meant to color a -voice, it must exist here or in the typed appraisal feeding this field, not only -as prose in a prompt. - -Birth-time repo personality may seed heartbeat timing once. After that, routine -state, appraisal, mood, continuity pressure, and live work should carry the -motion. +JSON emitted by the heartbeat CLI is an operator artifact only. CultCache +documents remain authority. diff --git a/state/agents.msgpack b/state/agents.msgpack deleted file mode 100644 index 1e31f9e0..00000000 Binary files a/state/agents.msgpack and /dev/null differ