diff --git a/epiphany-core/src/heartbeat_state.rs b/epiphany-core/src/heartbeat_state.rs index 6860056d..f9918117 100644 --- a/epiphany-core/src/heartbeat_state.rs +++ b/epiphany-core/src/heartbeat_state.rs @@ -126,7 +126,7 @@ pub fn run_void_routine_store( "incubation": incubation, "thoughtLanes": thought_lanes, "bridge": bridge, - "candidateInterventions": candidate_interventions, + "candidateInterventions": &candidate_interventions, "appraisals": &appraisals, "reactions": &reactions, "reviewNotes": [ @@ -1451,7 +1451,7 @@ fn build_memory_resonance(records: &[RoleMemoryRecord]) -> HeartbeatMemoryResona fn build_incubation( previous: &Option, bridge: &Option, - candidate_interventions: &Option, + candidate_interventions: &Option, resonance: &HeartbeatMemoryResonance, records: &[RoleMemoryRecord], ) -> Value { @@ -1915,7 +1915,10 @@ fn build_thought_bridge( }) } -fn build_candidate_interventions(bridge: &Value, incubation: &Value) -> Value { +fn build_candidate_interventions( + bridge: &Value, + incubation: &Value, +) -> HeartbeatCandidateInterventions { let decision = bridge .pointer("/decision/speakDecision") .and_then(Value::as_str) @@ -1932,27 +1935,37 @@ fn build_candidate_interventions(bridge: &Value, incubation: &Value) -> Value { let items = if decision == "draft" && strongest_status == "ripe" { strongest_theme .map(|theme| { - vec![serde_json::json!({ - "interventionId": format!("candidate-{}", theme.get("themeId").and_then(Value::as_str).unwrap_or("theme")), - "summary": "Possible Aquarium-facing thought-weather note", - "draft": format!("I keep seeing {} rhyme across the swarm; this one finally has enough blood to inspect in the open.", theme.get("themeId").and_then(Value::as_str).unwrap_or("an unnamed seam")), - "decision": decision, - "requiresFace": true, - "requiresReview": true, - "noveltyToRoom": theme.get("noveltyToRoom").cloned().unwrap_or(Value::Null), - "saturationScore": theme.get("saturationScore").cloned().unwrap_or(Value::Null), - "createdAt": now_iso(), - })] + let theme_id = theme + .get("themeId") + .and_then(Value::as_str) + .unwrap_or("theme"); + vec![HeartbeatCandidateIntervention { + intervention_id: format!("candidate-{theme_id}"), + summary: "Possible Aquarium-facing thought-weather note".to_string(), + draft: format!("I keep seeing {} rhyme across the swarm; this one finally has enough blood to inspect in the open.", theme_id), + decision: decision.to_string(), + requires_face: true, + requires_review: true, + novelty_to_room: theme + .get("noveltyToRoom") + .and_then(Value::as_f64) + .unwrap_or(0.0), + saturation_score: theme + .get("saturationScore") + .and_then(Value::as_f64) + .unwrap_or(0.0), + created_at: now_iso(), + }] }) .unwrap_or_default() } else { Vec::new() }; - serde_json::json!({ - "schema_version": "epiphany.candidate_interventions.v0", - "updatedAt": now_iso(), - "items": items, - }) + HeartbeatCandidateInterventions { + schema_version: "epiphany.candidate_interventions.v0".to_string(), + updated_at: now_iso(), + items, + } } fn build_agent_appraisals( @@ -2467,29 +2480,21 @@ fn novelty_to_self( fn novelty_to_room( theme_id: &str, summary: &str, - previous_candidate_interventions: &Option, + previous_candidate_interventions: &Option, bridge: &Option, ) -> f64 { let mut score = 0.64_f64; for intervention in previous_candidate_interventions .as_ref() - .and_then(|value| value.get("items")) - .and_then(Value::as_array) .into_iter() - .flatten() + .flat_map(|value| value.items.iter()) .take(8) { let similarity = theme_similarity( theme_id, summary, - intervention - .get("interventionId") - .and_then(Value::as_str) - .unwrap_or(""), - intervention - .get("draft") - .and_then(Value::as_str) - .unwrap_or(""), + &intervention.intervention_id, + &intervention.draft, ); if similarity >= 0.42 { return 0.22; diff --git a/epiphany-core/src/heartbeat_state/heartbeat_documents.rs b/epiphany-core/src/heartbeat_state/heartbeat_documents.rs index d3330881..824e083f 100644 --- a/epiphany-core/src/heartbeat_state/heartbeat_documents.rs +++ b/epiphany-core/src/heartbeat_state/heartbeat_documents.rs @@ -69,7 +69,7 @@ pub struct EpiphanyHeartbeatCognitionEntry { #[cultcache(key = 9, default)] pub bridge: Option, #[cultcache(key = 10, default)] - pub candidate_interventions: Option, + pub candidate_interventions: Option, #[cultcache(key = 11, default)] pub appraisals: Option, #[cultcache(key = 12, default)] @@ -334,6 +334,31 @@ pub struct HeartbeatAgentReaction { pub recommended_use: String, } +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct HeartbeatCandidateInterventions { + #[serde(rename = "schema_version")] + pub schema_version: String, + pub updated_at: String, + pub items: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct HeartbeatCandidateIntervention { + pub intervention_id: String, + pub summary: String, + pub draft: String, + pub decision: String, + pub requires_face: bool, + pub requires_review: bool, + pub novelty_to_room: f64, + pub saturation_score: f64, + pub created_at: String, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct HeartbeatInitiativeHeatPolicy { #[serde(default = "default_heat_schema_version")] diff --git a/epiphany-core/src/heartbeat_state/heartbeat_store.rs b/epiphany-core/src/heartbeat_state/heartbeat_store.rs index 2e000eb1..9ffa8c45 100644 --- a/epiphany-core/src/heartbeat_state/heartbeat_store.rs +++ b/epiphany-core/src/heartbeat_state/heartbeat_store.rs @@ -130,7 +130,7 @@ fn legacy_heartbeat_cognition_entry( incubation: legacy.incubation, thought_lanes: legacy.thought_lanes, bridge: legacy.bridge, - candidate_interventions: legacy.candidate_interventions, + candidate_interventions: decode_legacy_document(legacy.candidate_interventions)?, appraisals: decode_legacy_document(legacy.appraisals)?, reactions: decode_legacy_document(legacy.reactions)?, })) diff --git a/epiphany-core/src/memory_graph/profiles/heartbeat.rs b/epiphany-core/src/memory_graph/profiles/heartbeat.rs index c84eda7a..db9d60e5 100644 --- a/epiphany-core/src/memory_graph/profiles/heartbeat.rs +++ b/epiphany-core/src/memory_graph/profiles/heartbeat.rs @@ -1,5 +1,9 @@ use crate::heartbeat_state::EpiphanyHeartbeatCognitionEntry; #[cfg(test)] +use crate::heartbeat_state::HeartbeatCandidateIntervention; +#[cfg(test)] +use crate::heartbeat_state::HeartbeatCandidateInterventions; +#[cfg(test)] use crate::heartbeat_state::HeartbeatMemoryResonance; #[cfg(test)] use crate::heartbeat_state::HeartbeatMemoryResonancePair; @@ -166,24 +170,21 @@ fn import_candidate_interventions( let Some(candidates) = cognition.candidate_interventions.as_ref() else { return; }; - let Some(items) = candidates.get("items").and_then(Value::as_array) else { - return; - }; let domain_id = push_domain( domains, EpiphanyMemoryProfile::CandidateIntervention, "heartbeat candidate interventions", ); let before = nodes.len(); - for item in items.iter().take(8) { - let intervention_id = string_at(item, "interventionId"); + for item in candidates.items.iter().take(8) { + let intervention_id = item.intervention_id.clone(); nodes.push(EpiphanyMemoryNode { id: memory_graph_node_id(&domain_id, "candidate", &intervention_id, None), domain_id: domain_id.clone(), profile: EpiphanyMemoryProfile::CandidateIntervention, kind: EpiphanyMemoryNodeKind::CandidateIntervention, - title: string_at(item, "summary"), - claim: string_at(item, "draft"), + title: item.summary.clone(), + claim: item.draft.clone(), question: "Should this candidate intervention be spoken, deferred, or retired?" .to_string(), tension: "Candidate speech is not authority and still requires review.".to_string(), @@ -191,7 +192,7 @@ fn import_candidate_interventions( .to_string(), source_hashes: vec!["anchor:missing".to_string()], lifecycle: EpiphanyMemoryLifecycle::Queued, - salience: score_to_u32(number_at(item, "noveltyToRoom")), + salience: score_to_u32(item.novelty_to_room), confidence: 55, ..Default::default() }); @@ -423,14 +424,21 @@ mod tests { "reason": "The thought is live but not ready for speech." } })), - candidate_interventions: Some(serde_json::json!({ - "items": [{ - "interventionId": "candidate-theme-body-soul", - "summary": "Possible note", - "draft": "Evidence boundaries are lighting up.", - "noveltyToRoom": 0.7 - }] - })), + candidate_interventions: Some(HeartbeatCandidateInterventions { + schema_version: "epiphany.candidate_interventions.v0".to_string(), + updated_at: "2026-05-18T00:00:00Z".to_string(), + items: vec![HeartbeatCandidateIntervention { + intervention_id: "candidate-theme-body-soul".to_string(), + summary: "Possible note".to_string(), + draft: "Evidence boundaries are lighting up.".to_string(), + decision: "draft".to_string(), + requires_face: true, + requires_review: true, + novelty_to_room: 0.7, + saturation_score: 0.1, + created_at: "2026-05-18T00:00:00Z".to_string(), + }], + }), appraisals: None, reactions: None, }; diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 78e742e9..2410b9ac 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -61,7 +61,7 @@ remembers doctrine; the branch remembers the blade. - Thread start route handling and its async lifecycle task now live in `codex_message_processor/thread_start_routes.rs`; root no longer owns `thread_start_task`. - Thread config helpers now live in `codex_message_processor/thread_config.rs`: instruction-source loading, thread config override construction, config-load error shaping, and dynamic-tool validation. The pure dynamic-tool validation contract has module-local unit tests. - Turn control routes now live in `codex_message_processor/turn_routes.rs`: turn start, thread item injection, app-server client info setting, turn steer, turn interrupt, mode normalization, and input-limit validation with module-local unit tests. -- Heartbeat durable document/schema types now live in `epiphany-core/src/heartbeat_state/heartbeat_documents.rs`, role/default construction lives in `epiphany-core/src/heartbeat_state/heartbeat_roles.rs`, persistence/validation lives in `epiphany-core/src/heartbeat_state/heartbeat_store.rs`, and status/artifact JSON projection lives in `epiphany-core/src/heartbeat_state/heartbeat_projection.rs`. Adaptive pacing, scene protocol, participant personality/mood timing, initiative heat projection, birth personality seed receipts, pending-turn freeze/multiplier facts, Void routine sleep-cycle physiology, Void routine memory-resonance pairs, role thought appraisals, and derived reactions are typed fields rather than `extra` maps or generic JSON cargo; only the sealed legacy combined-state reader still carries `extra` for old `voidRoutine` migration. The root `heartbeat_state.rs` keeps scheduling and the remaining Void cognition orchestration for the next cuts. +- Heartbeat durable document/schema types now live in `epiphany-core/src/heartbeat_state/heartbeat_documents.rs`, role/default construction lives in `epiphany-core/src/heartbeat_state/heartbeat_roles.rs`, persistence/validation lives in `epiphany-core/src/heartbeat_state/heartbeat_store.rs`, and status/artifact JSON projection lives in `epiphany-core/src/heartbeat_state/heartbeat_projection.rs`. Adaptive pacing, scene protocol, participant personality/mood timing, initiative heat projection, birth personality seed receipts, pending-turn freeze/multiplier facts, Void routine sleep-cycle physiology, Void routine memory-resonance pairs, candidate interventions, role thought appraisals, and derived reactions are typed fields rather than `extra` maps or generic JSON cargo; only the sealed legacy combined-state reader still carries `extra` for old `voidRoutine` migration. The root `heartbeat_state.rs` keeps scheduling and the remaining Void cognition orchestration for the next cuts. - Heartbeat role/default and store seams now have module-local unit tests: fixed lane catalog, Ghostlight scene role projection, and typed state/cognition CultCache round-trip. Keep future heartbeat cuts aligned to those seam tests instead of growing root integration-only coverage. - Heartbeat adaptive pacing now lives in `epiphany-core/src/heartbeat_state/heartbeat_pacing.rs`, including running-turn count and effective cooldown multipliers. It has a module-local unit test for pending-turn counting; root scheduling calls the pacing seam instead of owning pressure/concurrency math directly. - Thread projection now lives in `codex_message_processor/thread_projection.rs`: state-db/thread-store/rollout summary projection, `ConversationSummary` to API `Thread` mapping, thread-title attachment helpers, turn pagination, and rollout turn reconstruction. `codex_message_processor.rs` is about 1,009 lines after this extraction and mostly owns JSON-RPC dispatch, processor construction, shared thread lookup/submit/error helpers, and lifecycle shutdown. @@ -101,7 +101,7 @@ remembers doctrine; the branch remembers the blade. - Active heartbeat turns now freeze initiative explicitly. A running pending turn carries `initiativeFrozen` and `initiativeFreezeReason`; schedule/status/readiness projections expose `initiative_frozen`; the scheduler still rejects attempts to wake that lane again; and completion receipts mark `cooldownStartedAfterCompletion: true`. A focused high-heat test proves a 25x heated implementation lane cannot be queued again while it is still thinking. - The first Ghostlight-derived timing slice is landed in Epiphany, but Ghostlight is reference lineage rather than a sibling runtime to preserve. `epiphany-heartbeat-store init --profile ghostlight-scene --scene-id --scene-participant ` creates a typed CultCache MessagePack scene heartbeat store whose participants are `arena=scene`, `participantKind=character`; `tick` emits `ghostlight.initiative_schedule.v0` receipts with `scene_turn` actions and local-affordance basis. The generic Epiphany maintenance lanes are not auto-patched into scene stores. -- The first Void-derived routine slice is landed in Epiphany, but VoidBot is reference lineage rather than a runtime dependency. `epiphany-heartbeat-store routine --store .\state\agent-heartbeats.msgpack --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.msgpack` reads typed role dossiers, computes typed bounded memory resonance, maintains incubation themes, runs analytic and associative cognition lanes, writes bridge syntheses/saturation/tension, projects the active thought cluster through each role's Ghostlight-shaped personality vectors, derives typed participant-local reactions from typed appraisals, applies personality/mood timing, advances typed sleep/dream-cycle state, updates the typed heartbeat store, and emits an auditable `epiphany.void_routine.v0` receipt. The routine now also carries anti-loop physiology imported from Void's calmer brain: `noveltyToSelf`, `noveltyToRoom`, source coverage, saturation pressure, refractory cooling, and explicit permission to let a live unsaturated thought deepen without forcing novelty theater every pass. This mutates only heartbeat physiology fields: project truth and role memory mutation remain on their reviewed surfaces. +- The first Void-derived routine slice is landed in Epiphany, but VoidBot is reference lineage rather than a runtime dependency. `epiphany-heartbeat-store routine --store .\state\agent-heartbeats.msgpack --artifact-dir .\.epiphany-heartbeats --agent-store .\state\agents.msgpack` reads typed role dossiers, computes typed bounded memory resonance, maintains incubation themes, runs analytic and associative cognition lanes, writes bridge syntheses/saturation/tension, produces typed candidate interventions, projects the active thought cluster through each role's Ghostlight-shaped personality vectors, derives typed participant-local reactions from typed appraisals, applies personality/mood timing, advances typed sleep/dream-cycle state, updates the typed heartbeat store, and emits an auditable `epiphany.void_routine.v0` receipt. The routine now also carries anti-loop physiology imported from Void's calmer brain: `noveltyToSelf`, `noveltyToRoom`, source coverage, saturation pressure, refractory cooling, and explicit permission to let a live unsaturated thought deepen without forcing novelty theater every pass. This mutates only heartbeat physiology fields: project truth and role memory mutation remain on their reviewed surfaces. - The next memory foundation must not build separate engines for repo graph and agent mind. Heartbeat cognition, role self-memory, repo architecture/dataflow, evidence scars, incubation, agency pressure, and context packets should converge through `EpiphanyMemoryGraph` profiles so one typed graph owns anchors, lifecycle, summaries, freshness, context cuts, and Qdrant embedding manifests. Repo, agent, and heartbeat profile producers now map accepted graph truth, reviewed role dossier memories/goals/values, and provisional heartbeat short-term/incubation/agency/candidate pressure into memory graph documents without scanner/Qdrant/model work. Heartbeat import deliberately does not mark durable promotion. Compose now merges profile snapshots into one validated graph and rejects duplicate authority; `epiphany-memory-graph compose` writes a composed typed CultCache graph from typed source graph stores; `epiphany-memory-graph refresh` can invoke live agent-memory plus heartbeat-cognition producers from their typed stores into one graph store; `epiphany-core` has a native typed `EpiphanyThreadState` CultCache store seam; the bridge mirrors loaded Codex-thread Epiphany state into `state/thread-state.msgpack`; and CultNet now advertises logical `EpiphanyThreadState` plus `EpiphanyMemoryGraph` snapshot contracts. The next clean slice is local refresh/dogfood once a mirrored thread-state store exists, then a Qdrant embedding cache writer that treats embeddings as rebuildable manifests, not canonical memory. - Manual Codex-run rumination still has an explicit aftercare rule until Epiphany owns the full sleep consolidator. In the intended Epiphany cycle, sub-agents ruminate when idle and distill when they sleep; in this supervising Codex thread, a heartbeat/routine vigil is only physiology until a separate closing pass reviews the receipts and decides whether map, handoff, evidence, or role self-memory should change. - The Face public-surface pass is landed as a bounded lane. Face's role dossier lives in `state/agents.msgpack`; `epiphany_specialists.toml` gives it a VoidBot-heartbeat-derived prompt stripped of moderation authority; `state/face-discord.toml` and the native `epiphany-face-discord` binary enforce that Face may interact only through #aquarium. Missing channel id or token writes candidate chat artifacts instead of posting elsewhere. If a Face has a persona name/avatar, `post` uses the shared Discord webhook pipe so each Epiphany can speak with its own nickname and avatar without a separate bot identity. @@ -198,7 +198,7 @@ The current spine, blessed but not yet finished: - distilled shared and config-backed prompt doctrine through the base prompt, rendered Epiphany state prompt files, modeling/Body, implementation/Hands, verification/Soul, reorientation/Life, coordinator/Self, and CRRC intervention surfaces - shared Ghostlight-derived persistent memory projection across Imagination, Body, Soul, Life, Self, CRRC checkpoint steering, and the GUI-launched Hands implementation lane - Ghostlight-derived heartbeat initiative scheduling through the native `epiphany-heartbeat-store` binary, with Self and Face included as first-class Epiphany maintenance participants, Ghostlight scene characters supported through the same timing law, role-personality cooldowns, appraisal mood/anxiety cooldowns, effective recovery timing, adaptive pressure-based pump tempo/concurrency, idle rumination feeding candidate self-memory pressure, sleep/dream passes serving as the distillation window, and heartbeat state persisted through typed CultCache MessagePack stores -- Void-derived native routine physiology through `epiphany-heartbeat-store routine`, with typed sleep cycle, typed memory resonance, incubation, analytic/associative cognition lanes, bridge judgment, candidate interventions, typed Ghostlight-style personality appraisals, mood/anxiety timing, typed derived reactions, and dream maintenance stored in the typed heartbeat store +- Void-derived native routine physiology through `epiphany-heartbeat-store routine`, with typed sleep cycle, typed memory resonance, incubation, analytic/associative cognition lanes, bridge judgment, typed candidate interventions, typed Ghostlight-style personality appraisals, mood/anxiety timing, typed derived reactions, and dream maintenance stored in the typed heartbeat store - Epiphany-native character-loop packet projection through `epiphany-character-loop`, with Face as the first public-surface actor over its typed role dossier - Face as the public #aquarium-only surface for translating agent thought-weather into short chats or candidate drafts, with persona webhook presentation and Void memory/search access, without moderator authority - Ghostlight-shaped role dossiers in `state/agents.msgpack`, plus `selfPatch` review projection through `roleResult`/`roleAccept` and accepted memory application through the native `epiphany-agent-memory-store` binary diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 0a9145c3..9b74ff7e 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 45c0c387..9743c4be 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -101,7 +101,7 @@ accepted_design: - Agent self-memory review/application now has typed `AgentSelfPatch` core APIs. The old JSON-value selfPatch functions remain only as edge compatibility wrappers for CLI/artifact ingress; `epiphany-agent-memory-store`, heartbeat rumination application, and repo initialization selfPatch processing parse once at the edge and then operate on typed documents. Role-result self-persistence no longer exports a JSON-value reviewer; native review uses `review_role_self_patch_document`. - Durable Ghostlight role dossiers are typed through their relationship/event/scene/perceived-overlay surfaces now. `EpiphanyAgentMemoryEntry` no longer stores those arrays as `serde_json::Value`, and durable Ghostlight structs no longer carry flattened unknown `extra` bags. `AgentSelfPatch.extra` remains only as an ingress guard so unknown patch keys can be rejected with reasons. - The state ledger CultCache document is now a closed typed ledger shape. Branch and evidence records no longer carry flattened `extra` maps; evidence additions remain explicit records with `ts`, `type`, `status`, `note`, and optional branch. - - Heartbeat durable document/schema types live in `epiphany-core/src/heartbeat_state/heartbeat_documents.rs`; role/default construction lives in `epiphany-core/src/heartbeat_state/heartbeat_roles.rs`; heartbeat persistence/validation lives in `epiphany-core/src/heartbeat_state/heartbeat_store.rs`; heartbeat status/artifact JSON projection lives in `epiphany-core/src/heartbeat_state/heartbeat_projection.rs`; the root heartbeat module remains the scheduling/cognition orchestration surface until those responsibilities are split into their own tested seams. Heartbeat timing authority is now typed state: adaptive pacing, scene protocol, participant personality/mood timing, initiative heat projection, birth personality seed receipts, pending-turn freeze/multiplier facts, Void routine sleep-cycle physiology, Void routine memory-resonance pairs, role thought appraisals, and derived reactions are named fields rather than `extra` maps or generic JSON cargo. The only remaining heartbeat `extra` bag is the sealed legacy combined-state reader for old `voidRoutine` migration. + - Heartbeat durable document/schema types live in `epiphany-core/src/heartbeat_state/heartbeat_documents.rs`; role/default construction lives in `epiphany-core/src/heartbeat_state/heartbeat_roles.rs`; heartbeat persistence/validation lives in `epiphany-core/src/heartbeat_state/heartbeat_store.rs`; heartbeat status/artifact JSON projection lives in `epiphany-core/src/heartbeat_state/heartbeat_projection.rs`; the root heartbeat module remains the scheduling/cognition orchestration surface until those responsibilities are split into their own tested seams. Heartbeat timing authority is now typed state: adaptive pacing, scene protocol, participant personality/mood timing, initiative heat projection, birth personality seed receipts, pending-turn freeze/multiplier facts, Void routine sleep-cycle physiology, Void routine memory-resonance pairs, candidate interventions, role thought appraisals, and derived reactions are named fields rather than `extra` maps or generic JSON cargo. The only remaining heartbeat `extra` bag is the sealed legacy combined-state reader for old `voidRoutine` migration. - Heartbeat role/default and store seams are pinned by module-local unit tests for fixed lane catalog, Ghostlight scene role projection, and typed state/cognition CultCache round-trip. - Heartbeat adaptive pacing lives in `epiphany-core/src/heartbeat_state/heartbeat_pacing.rs`; it owns pressure/concurrency derivation, running-turn count, initiative heat projection, and effective cooldown multipliers for scheduler/projection callers. - `epiphany-openai-auth-spine` is now a thin Epiphany-named boundary over retained vendored `codex-login`, not a clean-room credential store or token-refresh clone. Keep this impurity sealed as the Codex-compatible subscription auth organ while Epiphany owns typed request/status/event/runtime documents around it. @@ -293,9 +293,10 @@ rejected_paths: current_status: summary: The Codex starvation pass has five typing receipts, fifty-six processor/foundation cuts, a real physical extraction in `epiphany-codex-bridge`, a root `epiphany-state-model` crate for durable Epiphany state plus prompt-state rendering, a source-grounded Codex auth spine inventory, the native `epiphany-openai-adapter` document boundary, a buildable Codex auth/model wrapper in `epiphany-openai-codex-spine`, the first native runtime caller in `epiphany-openai-runtime`, native heartbeat launch planning/job opening in `epiphany-core`, native state-update contract/validation/application in `epiphany-core`, native exact retrieval path matching in `epiphany-core`, and the route-facing mutation/view/coordinator projection services in `epiphany-codex-bridge`. `codex-protocol` no longer defines durable `Epiphany*` state structs; it re-exports the native model and renderer for legacy rollout/API compatibility. `epiphany-core` has no direct vendored Codex dependency, and `codex-core` no longer depends on `epiphany-core`: pressure derives from native `EpiphanyTokenUsageSnapshot`, Codex rollout replay stays in the real Codex rollout adapter, prompt rendering lives in `epiphany-state-model`, and `CodexThread` exposes only host state/path/persistence facts. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval/indexing execution and projection, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, state update/promote/role-accept/reorient-accept/role-launch/reorient-launch/job-launch/job-interrupt application, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, watcher invalidation adaptation, full `thread/epiphany/view` response construction, freshness/context/graph-query response construction, role/reorient result response construction, distill/propose patch-response construction, coordinator status/view construction, and acceptance/evidence signal helpers. Vendored app-server no longer retains root Epiphany modules; `codex_message_processor.rs` still contains route dispatch, JSON-RPC response shaping, watcher registration/snapshotting calls, and notification emission. Codex product surfaces for apps, skills, plugins, marketplace, external agent config import, SDK scaffolding, connectors, ChatGPT app discovery, plugin provenance, and skill mention plumbing have been cut from the live path. `codex_thread.rs` is now about 449 lines and owns Codex thread hosting, not Epiphany policy. `epiphany-openai-codex-spine` owns the native OpenAI model transport path while intentionally reaching retained vendored `codex-login` only through `epiphany-openai-auth-spine`; auth identity, credential storage, and ChatGPT token refresh remain owned by the Codex-compatible auth organ. `epiphany-openai-runtime` writes typed OpenAI/runtime documents, loads durable worker launch requests, persists typed role/reorient worker result documents, and completes heartbeat/specialist runtime jobs without Codex worker execution. MCP itself is correctly JSON at its protocol edge; the target remains typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON. latest_cut: > - The heartbeat memory-resonance, sleep-cycle, role thought appraisal, and - derived reaction routine documents are typed. Native routine construction, - cognition persistence, legacy decode, memory-graph import, mood timing, and - sleep/dream-cycle update now operate on named CultCache documents instead of - generic JSON; status still projects JSON only as operator glass. - next_action: Continue internal CultCache purification before outward feature growth. The remaining high-signal internal JSON is the rest of `EpiphanyHeartbeatCognitionEntry`: `incubation`, `thought_lanes`, `bridge`, and `candidate_interventions` still persist as `serde_json::Value`. Type those cognition documents one bounded organ at a time, keeping JSON only at artifact/status/wire projections and legacy decode seams; then return to app-server JSON-RPC route starvation toward CultNet contracts. + The heartbeat memory-resonance, sleep-cycle, candidate-intervention, role + thought appraisal, and derived reaction routine documents are typed. Native + routine construction, cognition persistence, legacy decode, memory-graph + import, novelty checks, mood timing, and sleep/dream-cycle update now + operate on named CultCache documents instead of generic JSON; status still + projects JSON only as operator glass. + next_action: Continue internal CultCache purification before outward feature growth. The remaining high-signal internal JSON is the rest of `EpiphanyHeartbeatCognitionEntry`: `incubation`, `thought_lanes`, and `bridge` still persist as `serde_json::Value`. Type those cognition documents one bounded organ at a time, keeping JSON only at artifact/status/wire projections and legacy decode seams; then return to app-server JSON-RPC route starvation toward CultNet contracts.