diff --git a/epiphany-codex-bridge/src/launch_context.rs b/epiphany-codex-bridge/src/launch_context.rs index 3d1d5c4f..66a0aa56 100644 --- a/epiphany-codex-bridge/src/launch_context.rs +++ b/epiphany-codex-bridge/src/launch_context.rs @@ -5,6 +5,7 @@ use epiphany_core::EpiphanyMemoryContextQuery; use epiphany_core::EpiphanyMemoryProfile; use epiphany_core::EpiphanyPromptContextInput; use epiphany_core::EpiphanyThreadState; +use epiphany_core::import_gjallar_daemon_affordances; use epiphany_core::load_memory_graph_snapshot; use epiphany_core::memory_graph_from_epiphany_graphs; use epiphany_core::plan_memory_graph_context_cut; @@ -12,6 +13,7 @@ use epiphany_core::query_epiphany_local_verse_context; use epiphany_core::render_epiphany_prompt_context; use epiphany_core::seed_epiphany_local_verse_context; use epiphany_core::write_memory_graph_snapshot; +use std::env; use std::path::Path; use std::path::PathBuf; @@ -69,6 +71,20 @@ pub fn render_launch_dynamic_prompt_context( local_verse_store.display() ) })?; + if let Some(gjallar_store) = gjallar_affordance_store_path() { + import_gjallar_daemon_affordances( + &local_verse_store, + &gjallar_store, + EPIPHANY_LOCAL_VERSE_RUNTIME_ID, + ) + .map_err(|error| { + format!( + "failed to import Gjallar daemon affordances from {} into {}: {error}", + gjallar_store.display(), + local_verse_store.display() + ) + })?; + } let local_verse = query_epiphany_local_verse_context(&local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) .map_err(|error| { @@ -93,6 +109,12 @@ pub fn render_launch_dynamic_prompt_context( )) } +fn gjallar_affordance_store_path() -> Option { + env::var_os("EPIPHANY_GJALLAR_AFFORDANCE_STORE") + .map(PathBuf::from) + .filter(|path| path.exists()) +} + fn launch_memory_context( runtime_store_path: &Path, state: &EpiphanyThreadState, diff --git a/epiphany-core/src/bin/epiphany-eve-surface.rs b/epiphany-core/src/bin/epiphany-eve-surface.rs index 1f82fb58..4c06b48a 100644 --- a/epiphany-core/src/bin/epiphany-eve-surface.rs +++ b/epiphany-core/src/bin/epiphany-eve-surface.rs @@ -1,9 +1,6 @@ use anyhow::Context; use anyhow::Result; use chrono::Utc; -use epiphany_core::query_epiphany_local_verse_context; -use epiphany_core::seed_epiphany_local_verse_context; -use epiphany_core::EpiphanyLocalVerseContext; use epiphany_core::EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_EYES_CONTRACT_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION; @@ -19,8 +16,12 @@ use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_SOUL_CONTRACT_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_SUBSTRATE_GATE_CONTRACT_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION; -use serde_json::json; +use epiphany_core::EpiphanyLocalVerseContext; +use epiphany_core::GJALLAR_AFFORDANCE_SCHEMA_VERSION; +use epiphany_core::query_epiphany_local_verse_context; +use epiphany_core::seed_epiphany_local_verse_context; use serde_json::Value; +use serde_json::json; use std::env; use std::fs; use std::path::Path; @@ -196,6 +197,7 @@ fn surface_document(args: &Args, context: Option<&EpiphanyLocalVerseContext>) -> "children": [ operator_status_panel(context), local_verse_panel(context), + daemon_affordance_panel(context), runtime_spine_panel(context), memory_graph_panel(context), persona_panel() @@ -262,6 +264,46 @@ fn local_verse_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value { }) } +fn daemon_affordance_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value { + let rows: Vec = context + .map(|context| { + context + .daemon_affordances + .iter() + .take(12) + .map(|affordance| { + json!({ + "label": affordance.affordance_id, + "value": format!( + "{}:{}; authority={}; status={}; source={}", + affordance.surface_kind, + affordance.action, + affordance.authority, + affordance.status, + affordance.source_record + ) + }) + }) + .collect() + }) + .unwrap_or_default(); + json!({ + "id": "epiphany.daemon.affordances", + "kind": "inspector.kv", + "props": { + "title": "Daemon Affordances", + "authority": "Gjallar affordance records derived from Odin-owned sight", + "freshness": context + .and_then(|context| context.daemon_affordances.first()) + .map(|affordance| affordance.observed_at.as_str()) + .unwrap_or("missing"), + "rows": rows, + "emptyState": "No Gjallar affordances loaded; import an Odin/Gjallar CultMesh store rather than probing sockets or calling MCP." + }, + "children": [] + }) +} + fn runtime_spine_panel(context: Option<&EpiphanyLocalVerseContext>) -> Value { let snapshot = context.and_then(|context| context.latest_operator_snapshot.as_ref()); let intent = context.and_then(|context| context.latest_operator_run_intent.as_ref()); @@ -444,6 +486,14 @@ fn schema_catalog(context: Option<&EpiphanyLocalVerseContext>) -> Vec { "cultmesh://huginn/persona/{personaId}", true, ), + schema_entry( + GJALLAR_AFFORDANCE_SCHEMA_VERSION, + "gjallar", + "imported-affordance", + "cultcache-cc", + "cultmesh://odin/gjallar/affordances", + true, + ), ]; if let Some(context) = context { for contract in &context.contract_summaries { @@ -487,7 +537,8 @@ fn witnesses(args: &Args, context: Option<&EpiphanyLocalVerseContext>) -> Vec Result { break; } let can_accept = args.auto_review - && result.pointer("/finding/statePatch").is_some() - && revision.is_some(); + && revision.is_some() + && role_result_auto_acceptable(role_id, &result); if !can_accept { final_action = json!({ "action": review_action_for_role(role_id), @@ -873,6 +873,27 @@ fn review_action_for_role(role_id: &str) -> &'static str { } } +fn role_result_auto_acceptable(role_id: &str, result: &Value) -> bool { + if result["status"].as_str() != Some("completed") { + return false; + } + let Some(finding) = result.get("finding").filter(|value| value.is_object()) else { + return false; + }; + if finding + .get("itemError") + .and_then(Value::as_str) + .is_some_and(|error| !error.trim().is_empty()) + { + return false; + } + match role_id { + "verification" => true, + "research" | "modeling" => finding.get("statePatch").is_some(), + _ => false, + } +} + fn launch_reorient( client: &mut status_cli::AppServerClient, thread_id: &str, @@ -1539,4 +1560,38 @@ mod tests { "reviewImplementationResult" ); } + + #[test] + fn auto_review_accepts_patchless_verification_but_not_patchless_modeling() { + let verification = json!({ + "status": "completed", + "finding": { + "verdict": "needs-evidence", + "summary": "Soul asks Hands for one bounded evidence-gathering turn.", + "nextSafeMove": "Continue implementation for evidence.", + "filesInspected": ["state/map.yaml"] + } + }); + assert!(role_result_auto_acceptable("verification", &verification)); + + let modeling = json!({ + "status": "completed", + "finding": { + "verdict": "checkpoint-ready", + "summary": "Looks plausible, but no statePatch was supplied." + } + }); + assert!(!role_result_auto_acceptable("modeling", &modeling)); + + let errored_verification = json!({ + "status": "completed", + "finding": { + "itemError": "verification result is malformed" + } + }); + assert!(!role_result_auto_acceptable( + "verification", + &errored_verification + )); + } } diff --git a/epiphany-core/src/bin/epiphany-verse-query.rs b/epiphany-core/src/bin/epiphany-verse-query.rs index 48577e96..5f2cf899 100644 --- a/epiphany-core/src/bin/epiphany-verse-query.rs +++ b/epiphany-core/src/bin/epiphany-verse-query.rs @@ -1,6 +1,7 @@ use anyhow::Context; use anyhow::Result; use chrono::Utc; +use epiphany_core::import_gjallar_daemon_affordances; use epiphany_core::query_epiphany_local_verse_context; use epiphany_core::seed_epiphany_local_verse_context; use serde_json::json; @@ -16,10 +17,24 @@ fn main() -> Result<()> { args.runtime_id.clone(), Utc::now().to_rfc3339(), )?; + if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() { + import_gjallar_daemon_affordances( + &args.store, + gjallar_store, + args.runtime_id.clone(), + )?; + } let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?; println!("{}", serde_json::to_string_pretty(&context)?); } "query" => { + if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() { + import_gjallar_daemon_affordances( + &args.store, + gjallar_store, + args.runtime_id.clone(), + )?; + } let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?; println!("{}", serde_json::to_string_pretty(&context)?); } @@ -29,6 +44,13 @@ fn main() -> Result<()> { args.runtime_id.clone(), "2026-06-02T00:00:00Z", )?; + if let Some(gjallar_store) = args.gjallar_affordance_store.as_ref() { + import_gjallar_daemon_affordances( + &args.store, + gjallar_store, + args.runtime_id.clone(), + )?; + } let context = query_epiphany_local_verse_context(&args.store, args.runtime_id)?; if context.verse_policies.len() != 3 { anyhow::bail!("local Verse query smoke expected three Verse policies"); @@ -41,6 +63,9 @@ fn main() -> Result<()> { if context.contract_summaries.len() < 6 { anyhow::bail!("local Verse query smoke expected organ contract summaries"); } + if args.gjallar_affordance_store.is_some() && context.daemon_affordances.is_empty() { + anyhow::bail!("local Verse query smoke expected imported daemon affordances"); + } println!( "{}", serde_json::to_string_pretty(&json!({ @@ -50,6 +75,7 @@ fn main() -> Result<()> { "verses": context.verse_policies.len(), "globalRooms": context.global_room_policies.len(), "contracts": context.contract_summaries.len(), + "daemonAffordances": context.daemon_affordances.len(), }))? ); } @@ -62,6 +88,7 @@ struct Args { command: String, store: PathBuf, runtime_id: String, + gjallar_affordance_store: Option, } impl Args { @@ -70,6 +97,7 @@ impl Args { let command = values.next().unwrap_or_else(|| "query".to_string()); let mut store = PathBuf::from(".epiphany-run/cultmesh/epiphany-local.ccmp"); let mut runtime_id = "epiphany-local".to_string(); + let mut gjallar_affordance_store = None; while let Some(arg) = values.next() { match arg.as_str() { @@ -79,6 +107,13 @@ impl Args { "--runtime-id" => { runtime_id = values.next().context("missing --runtime-id value")?; } + "--gjallar-affordance-store" => { + gjallar_affordance_store = Some(PathBuf::from( + values + .next() + .context("missing --gjallar-affordance-store value")?, + )); + } _ => anyhow::bail!("unknown argument {arg:?}"), } } @@ -91,6 +126,7 @@ impl Args { command, store, runtime_id, + gjallar_affordance_store, }) } } diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index 180255cd..602874a4 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -64,6 +64,7 @@ pub const EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_TYPE: &str = "epiphany.cultmesh.continuity_contract"; pub const EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION: &str = "epiphany.cultmesh.continuity_contract.v0"; +pub const GJALLAR_AFFORDANCE_SCHEMA_VERSION: &str = "gjallar.affordance.v1"; pub const EPIPHANY_CULTMESH_INTERNAL_VERSE_ID: &str = "epiphany-internal"; pub const EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID: &str = "gamecult-local"; pub const EPIPHANY_CULTMESH_GLOBAL_VERSE_ID: &str = "epiphany-global"; @@ -467,6 +468,29 @@ pub struct EpiphanyCultMeshContinuityContractEntry { pub notes: Vec, } +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "gjallar.affordance", schema = "gjallar.affordance.v1")] +pub struct GjallarAffordanceEntry { + #[cultcache(key = 0)] + pub affordance_id: String, + #[cultcache(key = 1)] + pub source_record: String, + #[cultcache(key = 2)] + pub verse_id: Option, + #[cultcache(key = 3)] + pub surface_kind: String, + #[cultcache(key = 4)] + pub action: String, + #[cultcache(key = 5)] + pub authority: String, + #[cultcache(key = 6)] + pub status: String, + #[cultcache(key = 7)] + pub provenance: String, + #[cultcache(key = 8)] + pub observed_at: String, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EpiphanyLocalVerseContext { @@ -484,6 +508,7 @@ pub struct EpiphanyLocalVerseContext { pub latest_operator_run_intent: Option, pub latest_operator_run_receipt: Option, pub contract_summaries: Vec, + pub daemon_affordances: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] @@ -497,6 +522,20 @@ pub struct EpiphanyLocalVerseContractSummary { pub receipt_document_types: Vec, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EpiphanyLocalDaemonAffordanceSummary { + pub affordance_id: String, + pub source_record: String, + pub verse_id: Option, + pub surface_kind: String, + pub action: String, + pub authority: String, + pub status: String, + pub provenance: String, + pub observed_at: String, +} + cultmesh_documents!(EpiphanyCultMeshDocuments { EpiphanyCultMeshStatusEntry => EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION, EpiphanyCultMeshOperatorStatusEntry => EPIPHANY_CULTMESH_OPERATOR_STATUS_SCHEMA_VERSION, @@ -511,6 +550,7 @@ cultmesh_documents!(EpiphanyCultMeshDocuments { EpiphanyCultMeshHandsContractEntry => EPIPHANY_CULTMESH_HANDS_CONTRACT_SCHEMA_VERSION, EpiphanyCultMeshSoulContractEntry => EPIPHANY_CULTMESH_SOUL_CONTRACT_SCHEMA_VERSION, EpiphanyCultMeshContinuityContractEntry => EPIPHANY_CULTMESH_CONTINUITY_CONTRACT_SCHEMA_VERSION, + GjallarAffordanceEntry => GJALLAR_AFFORDANCE_SCHEMA_VERSION, }); pub fn open_epiphany_cultmesh_node( @@ -832,9 +872,69 @@ pub fn query_epiphany_local_verse_context( latest_operator_run_intent: node.get(EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_LATEST_KEY)?, latest_operator_run_receipt: node.get(EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_LATEST_KEY)?, contract_summaries, + daemon_affordances: query_gjallar_daemon_affordances_from_node(&node)?, }) } +pub fn query_gjallar_daemon_affordances( + store_path: impl AsRef, + runtime_id: impl Into, +) -> Result> { + let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; + query_gjallar_daemon_affordances_from_node(&node) +} + +pub fn import_gjallar_daemon_affordances( + local_store_path: impl AsRef, + gjallar_store_path: impl AsRef, + runtime_id: impl Into, +) -> Result> { + let runtime_id = runtime_id.into(); + let gjallar_node = + open_epiphany_cultmesh_node(gjallar_store_path, format!("{runtime_id}-gjallar-import"))?; + let affordances = gjallar_node.cache().get_all::()?; + + let mut local_node = open_epiphany_cultmesh_node(local_store_path, runtime_id)?; + for affordance in &affordances { + local_node.put(affordance.affordance_id.clone(), affordance)?; + } + local_node.flush()?; + + Ok(affordances + .into_iter() + .map(EpiphanyLocalDaemonAffordanceSummary::from) + .collect()) +} + +fn query_gjallar_daemon_affordances_from_node( + node: &CultMeshNode, +) -> Result> { + let mut summaries: Vec<_> = node + .cache() + .get_all::()? + .into_iter() + .map(EpiphanyLocalDaemonAffordanceSummary::from) + .collect(); + summaries.sort_by(|left, right| left.affordance_id.cmp(&right.affordance_id)); + Ok(summaries) +} + +impl From for EpiphanyLocalDaemonAffordanceSummary { + fn from(value: GjallarAffordanceEntry) -> Self { + Self { + affordance_id: value.affordance_id, + source_record: value.source_record, + verse_id: value.verse_id, + surface_kind: value.surface_kind, + action: value.action, + authority: value.authority, + status: value.status, + provenance: value.provenance, + observed_at: value.observed_at, + } + } +} + pub trait EpiphanyCultMeshContractSummarySource: DatabaseEntry { fn contract_id(&self) -> &str; fn verse_id(&self) -> &str; @@ -1530,6 +1630,43 @@ mod tests { Ok(()) } + #[test] + fn local_verse_context_imports_gjallar_daemon_affordances() -> Result<()> { + let temp = tempfile::tempdir()?; + let local_store = temp.path().join("epiphany-local-verse.ccmp"); + let gjallar_store = temp.path().join("gjallar-affordances.cc"); + seed_epiphany_local_verse_context(&local_store, "epiphany-test", "2026-06-02T00:00:00Z")?; + + let mut gjallar_node = open_epiphany_cultmesh_node(&gjallar_store, "gjallar-test")?; + let affordance = GjallarAffordanceEntry { + affordance_id: "gjallar:odin.service:bifrost:0".to_string(), + source_record: "odin.service:bifrost".to_string(), + verse_id: Some(EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID.to_string()), + surface_kind: "service".to_string(), + action: "inspect-eve-tui".to_string(), + authority: "odin".to_string(), + status: "available".to_string(), + provenance: "cultmesh://odin/gjallar".to_string(), + observed_at: "2026-06-04T00:00:00Z".to_string(), + }; + gjallar_node.put(affordance.affordance_id.clone(), &affordance)?; + gjallar_node.flush()?; + + let imported = + import_gjallar_daemon_affordances(&local_store, &gjallar_store, "epiphany-test")?; + let context = query_epiphany_local_verse_context(&local_store, "epiphany-test")?; + + assert_eq!(imported.len(), 1); + assert_eq!(context.daemon_affordances.len(), 1); + assert_eq!( + context.daemon_affordances[0].source_record, + "odin.service:bifrost" + ); + assert_eq!(context.daemon_affordances[0].authority, "odin"); + assert_eq!(context.daemon_affordances[0].action, "inspect-eve-tui"); + Ok(()) + } + #[test] fn mind_contracts_use_verses_to_keep_state_guarded() -> Result<()> { let temp = tempfile::tempdir()?; diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 12200218..011a9e52 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -158,8 +158,11 @@ pub use cultmesh_integration::EpiphanyCultMeshSoulContractEntry; pub use cultmesh_integration::EpiphanyCultMeshStatusEntry; pub use cultmesh_integration::EpiphanyCultMeshSubstrateGateContractEntry; pub use cultmesh_integration::EpiphanyCultMeshVersePolicyEntry; +pub use cultmesh_integration::EpiphanyLocalDaemonAffordanceSummary; pub use cultmesh_integration::EpiphanyLocalVerseContext; pub use cultmesh_integration::EpiphanyLocalVerseContractSummary; +pub use cultmesh_integration::GJALLAR_AFFORDANCE_SCHEMA_VERSION; +pub use cultmesh_integration::GjallarAffordanceEntry; pub use cultmesh_integration::default_epiphany_cultmesh_operator_status; pub use cultmesh_integration::epiphany_cultmesh_continuity_contracts; pub use cultmesh_integration::epiphany_cultmesh_eyes_contracts; @@ -170,6 +173,7 @@ pub use cultmesh_integration::epiphany_cultmesh_operator_snapshot_from_status_js pub use cultmesh_integration::epiphany_cultmesh_soul_contracts; pub use cultmesh_integration::epiphany_cultmesh_substrate_gate_contracts; pub use cultmesh_integration::epiphany_cultmesh_verse_policies; +pub use cultmesh_integration::import_gjallar_daemon_affordances; pub use cultmesh_integration::load_epiphany_cultmesh_operator_snapshot; pub use cultmesh_integration::load_epiphany_cultmesh_operator_status; pub use cultmesh_integration::load_epiphany_cultmesh_status; @@ -178,6 +182,7 @@ pub use cultmesh_integration::load_latest_epiphany_cultmesh_operator_run_receipt pub use cultmesh_integration::load_latest_epiphany_cultmesh_operator_snapshot; pub use cultmesh_integration::open_epiphany_cultmesh_node; pub use cultmesh_integration::query_epiphany_local_verse_context; +pub use cultmesh_integration::query_gjallar_daemon_affordances; pub use cultmesh_integration::seed_epiphany_local_verse_context; pub use cultmesh_integration::write_epiphany_cultmesh_continuity_contracts; pub use cultmesh_integration::write_epiphany_cultmesh_eyes_contracts; diff --git a/epiphany-core/src/prompt_context.rs b/epiphany-core/src/prompt_context.rs index 714d17fa..b22cd487 100644 --- a/epiphany-core/src/prompt_context.rs +++ b/epiphany-core/src/prompt_context.rs @@ -71,6 +71,31 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str ); } + if !input.local_verse.daemon_affordances.is_empty() { + lines.push("## Odin/Gjallar Daemon Affordances".to_string()); + lines.push( + "- Native path: ask Odin/Gjallar for daemon affordances, inspect Eve/CultUI TUI surfaces, and send provider-owned CultMesh command intents; MCP is not the Epiphany control path.".to_string(), + ); + for affordance in input.local_verse.daemon_affordances.iter().take(8) { + lines.push(format!( + "- `{}` [{}:{}]: source={}, authority={}, status={}, provenance={}", + affordance.affordance_id, + affordance.surface_kind, + affordance.action, + compact_line(&affordance.source_record), + compact_line(&affordance.authority), + compact_line(&affordance.status), + compact_line(&affordance.provenance) + )); + } + push_omitted_count( + &mut lines, + input.local_verse.daemon_affordances.len(), + 8, + "daemon affordances", + ); + } + lines.push("## Semantic Memory Context".to_string()); lines.push(format!( "- Packet `{}` from query `{}`", diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index 6a37cf9a..9b3cc13f 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -86,7 +86,7 @@ The landed machine now has: - first auditable Phase 6 fixed-lane coordinator runner through native `epiphany-mvp-coordinator`, producing coordinator summary, JSONL steps, rendered snapshots, transcript, stderr, runtime-spine status, native runtime job/result receipts for launched work, and final next-action artifacts while keeping semantic findings review-gated by default - first Codex-independent native runtime vertebra through `epiphany-runtime-spine`, storing runtime identity, sessions, jobs, job results, and events as typed CultCache MessagePack documents, opening/completing native jobs, projecting job-result counts, and emitting a framed CultNet hello message for the native contract - first Rust CultMesh local-node integration: `vendor/cultmesh-rs` now provides the repo-contained CultMesh surface over vendored CultCache/CultNet, and `epiphany-core` depends on that surface for local `EpiphanyCultMeshStatusEntry`, `EpiphanyCultMeshVersePolicyEntry`, and `EpiphanyCultMeshGlobalRoomPolicyEntry` round-trip smokes instead of treating raw CultNet as the ergonomic app-facing database layer -- first local Verse query surface: `notes/epiphany-verse-architecture.md` names Verse/Odin/Yggdrasil ownership, `epiphany-verse-query` can seed/query a compact CultMesh-backed local Verse context bundle for operator inspection and future dynamic prompt assembly without granting semantic memory, public sharing, or state-admission authority, and `tools/epiphany_local_run.ps1 -Mode status` now writes that packet as `local-verse-context.json` beside native status/operator snapshot artifacts +- first local Verse query surface: `notes/epiphany-verse-architecture.md` names Verse/Odin/Gjallar/Yggdrasil ownership, `epiphany-verse-query` can seed/query a compact CultMesh-backed local Verse context bundle for operator inspection and future dynamic prompt assembly without granting semantic memory, public sharing, or state-admission authority, can import `gjallar.affordance.v1` daemon affordance summaries from Odin/Gjallar stores, and `tools/epiphany_local_run.ps1 -Mode status` now writes that packet as `local-verse-context.json` beside native status/operator snapshot artifacts while auto-detecting the sibling Odin Gjallar store when present - first dynamic prompt launch handoff: role/reorient worker launch documents carry optional `dynamicPromptContext`, runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves rendered Verse/memory context can ride a role launch document without admitting unrelated private-looking text, live bridge role/reorient launch paths now feed local Verse plus memory graph context from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, and the bridge launch-context test proves the dynamic packet survives in a persisted runtime-spine worker launch request - first Hands/Soul/Continuity contract catalogs: action, verification, and continuity now have native `epiphany-core` contract families, runtime-spine advertisement, CultMesh policy documents, and local CultMesh smoke coverage beside Mind/Substrate Gate/Eyes; Hands now also has typed runtime-spine document bodies/helpers for `HandsActionIntent`, `HandsActionReview`, `HandsPatchReceipt`, `HandsCommandReceipt`, and `HandsCommitReceipt`, with `epiphany-hands-action-smoke` proving the action receipt chain without live file execution; Implementation now launches as `implementation-branch-turn-worker` owned by `epiphany-hands`, receives dynamic Proprioception context, gets a Substrate Gate mutation grant, and must make at most one branch-turn commit before Proprioception refresh - live-specialist runner must be rebuilt around heartbeat/runtime-spine result receipts @@ -137,8 +137,11 @@ These boundaries are more important than the individual method names: - Codex prompt machinery must not tell Epiphany agents what to do. Epiphany prompts, typed state projections, organ contracts, scheduler decisions, and mutation law are Epiphany-owned; Codex may transport and stream turns. -- MCP may survive only as a separate CultNet-speaking adapter; it does not - justify preserving the Codex host brain. +- MCP may survive only as an external compatibility adapter. It does not + justify preserving the Codex host brain, and it is not the native GameCult + daemon-control path. Epiphany agents should discover daemons through + Odin/Gjallar, inspect provider surfaces through Eve/CultUI, and interact via + CultMesh/CultNet provider-owned command documents and receipts. - `thread/epiphany/retrieve` is read-only. - `thread/epiphany/distill` is read-only. - `thread/epiphany/propose` is read-only. @@ -238,8 +241,9 @@ The next unknowns are: - how to expose state read/update, view lenses, runtime launch/read/interrupt, result accept/refuse, retrieval, checkpoint, CRRC, coordinator, heartbeat, and role-memory actions as CultNet contracts instead of Codex JSON-RPC verbs -- how to split MCP into a CultNet-speaking adapter without preserving Codex - app-server/plugin organs +- how to quarantine MCP as an external compatibility adapter without preserving + Codex app-server/plugin organs or treating MCP as the native GameCult daemon + actuator - how to make Aquarium and CLI status use CultNet contracts instead of `thread/epiphany/*` - how to define `EpiphanyDream`, dream lineage, ingress receipts, and adoption @@ -402,7 +406,8 @@ Liberation candidates, in the intended order: 4. Replace worker launch/result/selfPatch cargo with typed document contracts. 5. Move Aquarium/CLI/operator reads and actions from `thread/epiphany/*` JSON-RPC to CultNet contracts. -6. Split MCP into a CultNet adapter if it remains useful. +6. Quarantine MCP as an external adapter if it remains useful; GameCult daemon + discovery and control belong to Odin/Gjallar/Eve/CultMesh/CultNet. 7. Starve `codex_message_processor.rs` until Epiphany no longer depends on it for state, scheduling, runtime, views, acceptance, notifications, or tests. 8. Delete or seal Codex apps, skills, marketplace, and plugin UX from the diff --git a/notes/epiphany-verse-architecture.md b/notes/epiphany-verse-architecture.md index 9edab0e1..e0c232a8 100644 --- a/notes/epiphany-verse-architecture.md +++ b/notes/epiphany-verse-architecture.md @@ -65,6 +65,7 @@ It should expose compact TUI/API packets for: - organ contract policy - local Verse policies - admissible tool/query affordances +- Odin/Gjallar daemon affordances for provider-owned GameCult services Local Verse context is prompt input, not durable truth by itself. Mind still owns durable adoption. @@ -80,6 +81,12 @@ access receipts, or private-state export rules. Odin sees the map. Odin does not get to steal the throne. +Gjallar is the herald path from Odin-owned sight into agent context. Epiphany +should consume Gjallar affordance records as compact context, not crawl sockets +or ask MCP what tools happen to exist. The affordance says what can be +inspected or requested, where the source record lives, and which provider owns +the real command/state boundary. + ### Yggdrasil Yggdrasil is the hosting spine for important trusted GameCult Verses. @@ -128,6 +135,7 @@ The first native query tool is: cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- seed cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- query +cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- query --gjallar-affordance-store E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke ``` @@ -141,10 +149,27 @@ This writes and reads a compact CultMesh-backed local Verse context bundle: - latest operator run/snapshot receipts when present - organ contract summaries for Mind, Substrate Gate, Eyes, Hands, Soul, and Continuity +- imported `gjallar.affordance.v1` daemon affordance summaries when a Gjallar + store is provided This is not semantic search yet. It is the first typed inspection packet the future prompt assembler can depend on. +Daemon affordance context follows the pure GameCult path: + +```text +Odin owns accepted Verse/service/interface discovery +-> Gjallar publishes compact `gjallar.affordance.v1` records +-> Epiphany imports/queries those records into the local Verse context packet +-> agents inspect Eve/CultUI TUI surfaces and request provider-owned CultMesh commands +-> provider daemons emit accepted/denied/reconciled receipts +``` + +MCP is not the native Epiphany control path for GameCult daemons. It may remain +as external compatibility scaffolding, but daemon discovery and interaction for +GameCult-owned services should travel through Odin, Gjallar, Eve, CultMesh, and +provider-owned command documents. + `epiphany-prompt-context-smoke` is the first prompt-context proof on top of that packet. It seeds local Verse context, builds a memory graph context cut, renders the combined dynamic prompt packet, asserts that Verse/Odin/ @@ -186,6 +211,9 @@ memory. action, repo, or public-speech authority. - Every tool exposed to agents should have a compact, inspectable TUI/API surface before it becomes ambient prompt power. +- Epiphany may cache/import Gjallar affordance records for prompt context, but + Odin remains the source of discovery truth and providers remain the owners of + command side effects. ## Migration Cut diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index df750d9a..fff6d739 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -48,7 +48,7 @@ remembers doctrine; the branch remembers the blade. - Native status now exposes that quarantined bridge spine without granting it authority. `runtime_spine_status` counts tool invocation intents, receipts, and pending calls; `runtime_tool_invocation_statuses` derives read-only intent/receipt status rows; `epiphany-runtime-spine status` reports the counts; and `epiphany-mvp-status --runtime-store ` renders the latest tool calls under `tools`. This is operator visibility only. The model-runtime feedback seam is also now typed: MCP-shaped model tool calls preserve the original provider `call_id` plus source model request id; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up `EpiphanyModelRequest` from completed typed tool receipts using the previous provider response id; and `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned operation. `epiphany-model-runtime run-worker --auto-tools --tool-adapter-bin ` now lets a launched worker automatically execute typed MCP intents through the quarantined Codex MCP adapter, feed receipts back through follow-up model turns, and only then parse/complete the worker result. Codex still executes the quarantined MCP edge; Epiphany owns the continuation request shape and invocation boundary. - The active foundation directive is now `notes/codex-starvation-and-cultnet-liberation-plan.md`. The previous Codex app-server control-plane rebuild made Epiphany less rotten inside Codex, but that is not the destination. Epiphany must become a native CultCache/CultMesh/CultNet runtime while remaining an honest modified Codex-derived backend for subscription auth/model use. Codex should remain relatively vanilla and keep doing Codex things, including useful app-server and streaming bridge affordances, but it must not own Epiphany state, processes, prompts, scheduler decisions, or policy. The bridge handles interop; Codex and Epiphany do not get to rummage around in each other's organs. - `notes/epiphany-cultmesh-dreaming-roadmap.md` is the new concrete design for distributed Epiphany dreaming. The invariant is hard: private state stays local; public dreams are separately authored typed documents distributed through CultMesh/CultNet; foreign dreams are thought weather until a reviewed local adoption receipt digests them. The first code slice should be dream schemas plus a local CultMesh-backed store, not public fanout. -- CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.msgpack` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, and local operator run intent/receipt round-trip through CultMesh. `notes/epiphany-verse-architecture.md` now names the local Verse/Odin/Yggdrasil architecture: Odin sees advertised Verse metadata without bypassing boundaries, Yggdrasil hosts important trusted GameCult Verses such as Bifrost, and prompt assembly should query compact local Verse plus semantic memory packets instead of carrying the whole archive. Role/reorient launch documents now have optional `dynamicPromptContext`; runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves a rendered Verse/memory packet is preserved on a role launch document, live bridge role/reorient launches now feed local Verse plus memory graph context from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, and the bridge launch-context test proves the packet survives into a persisted runtime-spine worker launch request. The memory graph refreshes from current typed thread-state repo graphs when absent, so thin state remains visibly thin. Local-run status now has an operator read proof too: `tools/epiphany_local_run.ps1 -Mode status` builds/calls `epiphany-verse-query` and writes `local-verse-context.json` from `.epiphany-run\cultmesh\local-verse.ccmp` beside `status.json` and the operator snapshot. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.ccmp --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.ccmp --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.ccmp --runtime-id epiphany-operator-run-smoke --run-id smoke-run`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke`, and `.\tools\epiphany_local_run.ps1 -Mode status` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. +- CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.msgpack` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, local operator run intent/receipt round-trip, and imported `gjallar.affordance.v1` daemon affordance summaries through CultMesh. `notes/epiphany-verse-architecture.md` now names the local Verse/Odin/Yggdrasil architecture: Odin sees advertised Verse metadata without bypassing boundaries, Gjallar turns Odin sight into agent-ready affordances, Yggdrasil hosts important trusted GameCult Verses such as Bifrost, and prompt assembly should query compact local Verse plus semantic memory packets instead of carrying the whole archive. Role/reorient launch documents now have optional `dynamicPromptContext`; runtime prompt assembly consumes that field into worker instructions, `epiphany-prompt-context-smoke` proves a rendered Verse/memory packet is preserved on a role launch document, live bridge role/reorient launches now feed local Verse plus memory graph context from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, and if `EPIPHANY_GJALLAR_AFFORDANCE_STORE` points at a Gjallar store they import daemon affordances before rendering. The memory graph refreshes from current typed thread-state repo graphs when absent, so thin state remains visibly thin. Local-run status now has an operator read proof too: `tools/epiphany_local_run.ps1 -Mode status` builds/calls `epiphany-verse-query` and writes `local-verse-context.json` from `.epiphany-run\cultmesh\local-verse.ccmp` beside `status.json` and the operator snapshot; it auto-detects `E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc` when present. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.ccmp --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.ccmp --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.ccmp --runtime-id epiphany-operator-run-smoke --run-id smoke-run`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-verse-query -- smoke --gjallar-affordance-store E:\Projects\Odin\scratch\gjallar\gjallar.affordances.cc`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-prompt-context-smoke`, and `.\tools\epiphany_local_run.ps1 -Mode status` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. MCP is not the native GameCult daemon control path; daemon discovery/inspection/control should travel through Odin, Gjallar, Eve/CultUI, CultMesh, CultNet, and provider-owned command receipts. - `notes/codex-auth-spine-inventory.md` is the source-grounded keeper list for that reliquary. Corrected compliance invariant: Codex-compatible auth identity stays anchored in vendored Codex-derived auth machinery. `epiphany-openai-auth-spine` is now a thin Epiphany-named boundary over `codex-login`, not a clean-room clone of credential storage or token refresh. Codex apps, skills, marketplace, plugin UX, broad app-server workflow, and JSON-RPC sprawl remain cuttable because they are not subscription-auth legitimacy. - Edge JSON is allowed for schema description, hostile ingress before immediate typed parsing, sealed forensic artifacts, or named quarantine experiments. When both subsystems are ours, runtime data must remain typed CultCache documents and move over CultNet typed contracts. Generic `serde_json::Value` in worker launch/result/selfPatch/runtime flow is contamination until classified and replaced. - Hands now has a first executable action receipt spine plus a launchable branch-turn lane. `epiphany-core::hands_gateway` owns `HandsActionIntent`, `HandsActionReview`, `HandsPatchReceipt`, `HandsCommandReceipt`, and `HandsCommitReceipt`; runtime-spine can persist/reread them; `runtime_spine_persists_mind_gateway_review_receipts` includes the Hands chain beside Mind/Eyes/Substrate/Soul/Continuity proofs; `epiphany-hands-action-smoke` emits intent/review/patch/command/commit receipt ids from `.epiphany-smoke\runtime-spine\hands-action-smoke.msgpack`; and the repo-action launch proof profile now requires action intent, action review, and patch receipt together. Implementation is no longer a main-agent-only dead launch path: `implementation-branch-turn-worker` is owned by `epiphany-hands`, receives dynamic Proprioception/Verse context, gets a Substrate Gate mutation grant from the bridge, and its prompt/output schema require one task branch, at most one commit, branch/commit/changed-path/command evidence, and a Proprioception branch-map refresh request. `roleAccept` still refuses Implementation findings. Runtime completion now emits Hands intent/review/patch receipts from implementation results with `changedPaths`, emits a Hands commit receipt when `branchName` plus `commitSha` are present, and emits `HandsCommandReceipt` only from matching typed command-like tool invocation receipts with command plus exit-code proof. Hands receipt ids are projected into the typed Implementation result, and coordinator policy routes a completed Hands commit to Proprioception/modeling refresh until an accepted modeling result is newer than the commit; both the Codex bridge coordinator path and native `epiphany-mvp-status` compute that refresh-debt signal. Coordinator JSON/protocol, rendered MVP status, status smoke, CultMesh operator snapshots, and coordinator-run `coordinationGuards` now expose the debt as a first-class operator signal, including whether Implementation launch was suppressed for modeling refresh. The coordinator runner now maps `continueImplementation` into the Implementation role-launch/runtime path instead of stopping at the recommendation. Next hardening slice: run the non-ephemeral Hands branch-turn smoke, prove bounded branch/commit evidence appears, then prove the next coordinator/status pass routes to Proprioception/modeling refresh before another Hands turn. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 3e7630b9..d3f6fadd 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 7bcbd547..4395eef5 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -72,8 +72,8 @@ data_flow: active_subgoals: - Use `notes/epiphany-cultmesh-dreaming-roadmap.md` as the design authority for distributed Epiphany dreaming. Public dreams are separately authored typed documents distributed through CultMesh/CultNet; private state remains local and cannot be shared by flipping a visibility flag. Foreign dreams are external thought weather until a reviewed local adoption receipt digests them into memory, planning, doctrine, or governance. - CultMesh is now the preferred ergonomic Rust abstraction over CultCache and CultNet for Epiphany-owned local database surfaces. The Rust substrate is repo-contained under `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`; the old `E:\Projects\CultLib\crates\*` path is dead in this checkout and must not be reintroduced as a compile-time dependency. Future local Verse/dream/status work should start at CultMesh unless it is explicitly polishing CultMesh internals. Preserve the three-Verse boundary: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult project sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. - - Use `notes/epiphany-verse-architecture.md` as the source note for local Verse/Odin/Yggdrasil prompt-context architecture. Odin is the all-seer discovery/coordinator surface for advertised Verse metadata, not a trust-boundary bypass. Yggdrasil is the hosted spine for important trusted GameCult Verses such as Bifrost. The first native local Verse query surface is `epiphany-verse-query`, which seeds/queries compact CultMesh policy/status/contract context for dynamic prompt assembly and operator inspection; `epiphany-prompt-context-smoke` proves that compact local Verse context plus a semantic memory graph context cut can render a bounded dynamic prompt packet, omit unrelated private-looking text, and preserve the packet inside a role worker launch document. Live bridge role/reorient launch paths now feed `dynamicPromptContext` from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, refreshing the memory graph from current typed thread-state repo graphs when absent; the bridge launch-context test proves that packet survives into a persisted runtime-spine worker launch request. `tools/epiphany_local_run.ps1 -Mode status` now builds/calls `epiphany-verse-query` and writes `local-verse-context.json` beside native status and operator snapshot artifacts from `.epiphany-run/cultmesh/local-verse.ccmp`. - - The active priority is Codex starvation and CultNet liberation. Use `notes/codex-starvation-and-cultnet-liberation-plan.md` as the directive and `notes/codex-auth-spine-inventory.md` as the source-grounded keeper list for the retained OpenAI subscription auth/model transport bridge. Classify all Epiphany-relevant JSON contamination, define the native CultNet runtime boundary, keep worker launch/result/selfPatch cargo as typed documents, evacuate Epiphany operator/state/process authority from Codex JSON-RPC, wrap MCP's unavoidable JSON protocol edge in typed Epiphany intent/result/receipt documents, and keep cutting until Epiphany can run its own state and processes without `codex_message_processor.rs` except for bridge compatibility. + - Use `notes/epiphany-verse-architecture.md` as the source note for local Verse/Odin/Yggdrasil prompt-context architecture. Odin is the all-seer discovery/coordinator surface for advertised Verse metadata, not a trust-boundary bypass. Yggdrasil is the hosted spine for important trusted GameCult Verses such as Bifrost. The first native local Verse query surface is `epiphany-verse-query`, which seeds/queries compact CultMesh policy/status/contract context for dynamic prompt assembly and operator inspection; it can also import `gjallar.affordance.v1` daemon affordances from an Odin/Gjallar CultMesh store so agents see provider-owned daemon inspection/action surfaces without MCP. `epiphany-prompt-context-smoke` proves that compact local Verse context plus a semantic memory graph context cut can render a bounded dynamic prompt packet, omit unrelated private-looking text, and preserve the packet inside a role worker launch document. Live bridge role/reorient launch paths now feed `dynamicPromptContext` from sibling `local-verse.ccmp` and `memory-graph.msgpack` stores beside runtime-spine, refreshing the memory graph from current typed thread-state repo graphs when absent; if `EPIPHANY_GJALLAR_AFFORDANCE_STORE` points at a Gjallar store, the launch context imports daemon affordances before rendering. The bridge launch-context test proves that packet survives into a persisted runtime-spine worker launch request. `tools/epiphany_local_run.ps1 -Mode status` now builds/calls `epiphany-verse-query` and writes `local-verse-context.json` beside native status and operator snapshot artifacts from `.epiphany-run/cultmesh/local-verse.ccmp`; it auto-detects the sibling Odin Gjallar store when present. + - The active priority is Codex starvation and CultNet liberation. Use `notes/codex-starvation-and-cultnet-liberation-plan.md` as the directive and `notes/codex-auth-spine-inventory.md` as the source-grounded keeper list for the retained OpenAI subscription auth/model transport bridge. Classify all Epiphany-relevant JSON contamination, define the native CultNet runtime boundary, keep worker launch/result/selfPatch cargo as typed documents, evacuate Epiphany operator/state/process authority from Codex JSON-RPC, and keep cutting until Epiphany can run its own state and processes without `codex_message_processor.rs` except for bridge compatibility. MCP remains an external compatibility edge only; GameCult daemon discovery/inspection/control should travel through Odin, Gjallar, Eve/CultUI, CultMesh, CultNet, and provider-owned command receipts. - Use `notes/epiphany-anatomy.md` as the canonical anatomy map. Embodied sub-agents are Self, Face, Imagination, Eyes, Proprioception, Hands, and Soul. Body is substrate, Mind is persistent state/state admission, Continuity is recovery/compaction/sleep protocol machinery, and Substrate Gate is substrate access protocol. - Organ state now has a hard profile split: resident Epiphany sub-agents use lean `work_organ` state, public/person-shaped agents use portable `persona` state, and heartbeat/swarm machinery owns scheduling/coordination physiology. Face is the Epiphany organ; Persona is the shared GameCult contract for Epiphany Face, VoidBot repo Faces, and Ghostlight characters. Persona documents carry provenance, required public presentation metadata, `date-time` timestamps, typed `candidateActions`, strict anchored thoughts with non-authoritative extensions, custom enum companion labels, and typed affect subrecords for bonds/status reads/doctrine stances. Work organs must not inherit Persona affect/social-bond/status-read machinery just because the old Ghostlight-derived shape had room for it. - The Eyes/source-gathering fork exposed by the Mom-ready MVP proof is now landed as a fixed Research lane owned by Eyes. The quarantined Codex MCP spine may execute typed tool intents and emit typed receipts; the model runtime can build and run provider-neutral follow-up turns from those receipts; coordinator run mode passes the quarantined tool adapter into worker runtime with auto-tools enabled by default; worker runtime now enforces max-runtime with one timeout close primitive for both tokio timeout and watchdog paths; inner `openai-worker-*` jobs are opened before async auth/stream waits and sealed with their outer worker on timeout; native status/roles/jobs/coordinator surfaces project failed and completed runtime-linked jobs as terminal without Codex prompt or scheduler authority; direct tiny model-turn transport succeeds; worker requests record request metrics, transport readiness, and sampled raw Responses frame kinds; worker prompts no longer prepend the full shared persistent-memory rite; stream deltas compact before durable storage; live MVP modeling workers complete instead of timing out; stale completed runtime links no longer block replacement launches; jobLaunch validates before opening runtime jobs; completed-but-unreviewable modeling findings now stop for review instead of relaunching the same blind lane; and completed unreviewable modeling findings with verdict `regather-needed` route to `LaunchResearch` instead of relaunching Proprioception. Research findings are accepted only when their statePatch is Eyes-shaped: observations, evidence, scratch, and optional investigationCheckpoint, with no graph/planning/objective authority. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index 7e0b79d0..4ee52e90 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -13,7 +13,8 @@ param( [switch]$SkipBuild, [switch]$AutoReview, [switch]$NoEphemeral, - [switch]$SkipSleep + [switch]$SkipSleep, + [string]$GjallarAffordanceStore = "" ) $ErrorActionPreference = "Stop" @@ -188,6 +189,21 @@ if (-not $SkipBuild) { } } +if ($GjallarAffordanceStore -eq "") { + if ($env:EPIPHANY_GJALLAR_AFFORDANCE_STORE) { + $GjallarAffordanceStore = $env:EPIPHANY_GJALLAR_AFFORDANCE_STORE + } else { + $candidateOdinStore = Join-Path (Split-Path -Parent $Root) "Odin\scratch\gjallar\gjallar.affordances.cc" + if (Test-Path -LiteralPath $candidateOdinStore) { + $GjallarAffordanceStore = $candidateOdinStore + } + } +} +if ($GjallarAffordanceStore -ne "") { + $GjallarAffordanceStore = (Resolve-Path -LiteralPath $GjallarAffordanceStore).Path + $env:EPIPHANY_GJALLAR_AFFORDANCE_STORE = $GjallarAffordanceStore +} + $requiredBinaries = @($statusExe, $operatorRunExe, $operatorSnapshotExe, $verseQueryExe) if ($Mode -ne "status") { $requiredBinaries += @($codexAppServer, $coordinatorExe) @@ -258,14 +274,18 @@ if ($Mode -eq "status") { -WorkingDirectory $Root ` -StdoutPath (Join-Path $artifactRoot "status.stdout.json") ` -StderrPath (Join-Path $artifactRoot "status.stderr.log") + $localVerseArgs = @( + "seed", + "--store", $localVerseStore, + "--runtime-id", "epiphany-local" + ) + if ($GjallarAffordanceStore -ne "") { + $localVerseArgs += @("--gjallar-affordance-store", $GjallarAffordanceStore) + } Invoke-Checked ` -Label "read local Verse context" ` -FilePath $verseQueryExe ` - -Arguments @( - "seed", - "--store", $localVerseStore, - "--runtime-id", "epiphany-local" - ) ` + -Arguments $localVerseArgs ` -WorkingDirectory $Root ` -StdoutPath $localVerseJson ` -StderrPath (Join-Path $artifactRoot "local-verse-context.stderr.log") @@ -464,6 +484,7 @@ $summary = @" - operatorSnapshotStore: $operatorSnapshotStore - localVerseBinary: $verseQueryExe - localVerseStore: $localVerseStore +- gjallarAffordanceStore: $GjallarAffordanceStore - coordinatorBinary: $coordinatorExe - runtimeStore: $runtimeStore - modelRuntimeBinary: $modelRuntimeExe