From 3d4ed26b0fda0e3a5a2cdbcc7056742a0d2e00d7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 20 May 2026 08:39:56 +0100 Subject: [PATCH] Move coordinator judgment into core --- epiphany-codex-bridge/src/coordinator.rs | 172 ++---------- epiphany-codex-bridge/src/launch.rs | 148 +--------- epiphany-core/src/lib.rs | 6 + epiphany-core/src/surfaces/coordinator.rs | 110 ++++++++ epiphany-core/src/surfaces/mod.rs | 6 + epiphany-core/src/surfaces/worker_launch.rs | 293 ++++++++++++++++++++ notes/fresh-workspace-handoff.md | 1 + state/ledgers.msgpack | Bin 140721 -> 141013 bytes state/map.yaml | 10 +- 9 files changed, 461 insertions(+), 285 deletions(-) diff --git a/epiphany-codex-bridge/src/coordinator.rs b/epiphany-codex-bridge/src/coordinator.rs index d0089f81..72ecdb41 100644 --- a/epiphany-codex-bridge/src/coordinator.rs +++ b/epiphany-codex-bridge/src/coordinator.rs @@ -15,15 +15,14 @@ use codex_app_server_protocol::ThreadEpiphanySceneAction; use codex_app_server_protocol::ThreadEpiphanyViewCoordinator; use epiphany_core::EpiphanyCoordinatorAction as CoreEpiphanyCoordinatorAction; use epiphany_core::EpiphanyCoordinatorAutomationAction as CoreEpiphanyCoordinatorAutomationAction; -use epiphany_core::EpiphanyCoordinatorCrrcRecommendation; use epiphany_core::EpiphanyCoordinatorDecision as CoreEpiphanyCoordinatorDecision; -use epiphany_core::EpiphanyCoordinatorInput; use epiphany_core::EpiphanyCoordinatorRoleId as CoreEpiphanyCoordinatorRoleId; -use epiphany_core::EpiphanyCoordinatorRoleLane as CoreEpiphanyCoordinatorRoleLane; use epiphany_core::EpiphanyCoordinatorRoleResultStatus as CoreEpiphanyCoordinatorRoleResultStatus; use epiphany_core::EpiphanyCoordinatorRoleStatus as CoreEpiphanyCoordinatorRoleStatus; use epiphany_core::EpiphanyCoordinatorSceneAction as CoreEpiphanyCoordinatorSceneAction; -use epiphany_core::EpiphanyCoordinatorSignals; +use epiphany_core::EpiphanyCoordinatorSourceSignals; +use epiphany_core::EpiphanyCoordinatorStatus as CoreEpiphanyCoordinatorStatus; +use epiphany_core::EpiphanyCoordinatorStatusInput; use epiphany_core::EpiphanyCrrcAction as CoreEpiphanyCrrcAction; use epiphany_core::EpiphanyCrrcInput; use epiphany_core::EpiphanyCrrcRecommendation as CoreEpiphanyCrrcRecommendation; @@ -47,10 +46,9 @@ use epiphany_core::EpiphanyRoleBoardPlanningSummary; use epiphany_core::EpiphanyRoleFindingInterpretation as CoreEpiphanyRoleFinding; use epiphany_core::EpiphanyRoleResultRoleId; use epiphany_core::EpiphanyTokenUsageSnapshot; -use epiphany_core::coordinator_automation_action; +use epiphany_core::derive_coordinator_status; use epiphany_core::derive_role_board; use epiphany_core::modeling_role_state_patch_policy_errors; -use epiphany_core::recommend_coordinator_action; use epiphany_core::recommend_crrc_action; use epiphany_core::render_role_board_note; use epiphany_core::select_coordinator_automation_action; @@ -236,24 +234,9 @@ pub fn map_protocol_crrc_recommendation( pub type EpiphanyCoordinatorDecision = CoreEpiphanyCoordinatorDecision; -pub struct EpiphanyCoordinatorSourceSignals { - pub pressure_level: epiphany_core::EpiphanyPressureLevel, - pub should_prepare_compaction: bool, - pub reorient_action: CoreEpiphanyReorientAction, - pub crrc_action: CoreEpiphanyCrrcAction, - pub modeling_result_status: CoreEpiphanyCoordinatorRoleResultStatus, - pub verification_result_status: CoreEpiphanyCoordinatorRoleResultStatus, - pub reorient_result_status: CoreEpiphanyCrrcResultStatus, -} - pub struct EpiphanyCoordinatorStatus { - pub decision: EpiphanyCoordinatorDecision, - pub source_signals: EpiphanyCoordinatorSourceSignals, - pub roles: Vec, + pub core: CoreEpiphanyCoordinatorStatus, pub note: String, - pub modeling_result_accepted: bool, - pub verification_result_accepted: bool, - pub reorient_finding_accepted: bool, } pub async fn derive_epiphany_coordinator_status( @@ -351,24 +334,18 @@ pub async fn derive_epiphany_coordinator_status( let reorient_finding_accepted = reorient_finding.is_some_and(|finding| { state.is_some_and(|state| epiphany_reorient_finding_already_accepted(state, finding)) }); - let source_signals = EpiphanyCoordinatorSourceSignals { - pressure_level: pressure.level, - should_prepare_compaction: pressure.should_prepare_compaction, + let core = derive_coordinator_status(EpiphanyCoordinatorStatusInput { + state_status: map_core_crrc_state_status_from_reorient(state_status), + checkpoint_present, + pressure: pressure.clone(), + recommendation: recommendation.clone(), + roles, reorient_action: reorient_decision .map(|decision| decision.action) .unwrap_or(CoreEpiphanyReorientAction::Resume), - crrc_action: recommendation.action, modeling_result_status, verification_result_status, reorient_result_status, - }; - let decision = map_epiphany_coordinator( - state_status, - checkpoint_present, - pressure, - recommendation, - &roles, - &source_signals, modeling_result_accepted, modeling_result_reviewable, modeling_result_accepted_after_verification, @@ -379,25 +356,17 @@ pub async fn derive_epiphany_coordinator_status( verification_result_allows_implementation, verification_result_needs_evidence, reorient_finding_accepted, - ); + }); let note = render_epiphany_coordinator_note( recommendation.action, pressure.level, modeling_result_status, verification_result_status, - source_signals.reorient_result_status, - decision.action, + core.source_signals.reorient_result_status, + core.decision.action, ); - EpiphanyCoordinatorStatus { - decision, - source_signals, - roles, - note, - modeling_result_accepted, - verification_result_accepted, - reorient_finding_accepted, - } + EpiphanyCoordinatorStatus { core, note } } pub fn map_epiphany_coordinator_view( @@ -417,46 +386,27 @@ pub fn map_epiphany_coordinator_view( }, state_status: map_protocol_reorient_state_status(state_status), state_revision, - action: map_protocol_coordinator_action(status.decision.action), + action: map_protocol_coordinator_action(status.core.decision.action), target_role: status + .core .decision .target_role .map(map_protocol_coordinator_role_id), recommended_scene_action: status + .core .decision .recommended_scene_action .map(map_protocol_coordinator_scene_action), - requires_review: status.decision.requires_review, - can_auto_run: status.decision.can_auto_run, - reason: status.decision.reason, - source_signals: map_protocol_coordinator_source_signals(status.source_signals), + requires_review: status.core.decision.requires_review, + can_auto_run: status.core.decision.can_auto_run, + reason: status.core.decision.reason, + source_signals: map_protocol_coordinator_source_signals(status.core.source_signals), roles: map_protocol_role_board_lanes(&roles), note: status.note, } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EpiphanyCoordinatorAutomationAction { - None, - CompactRehydrateReorient, - LaunchReorientWorker, -} - -pub fn map_epiphany_coordinator_automation_action( - decision: &EpiphanyCoordinatorDecision, -) -> EpiphanyCoordinatorAutomationAction { - map_protocol_coordinator_automation_action(coordinator_automation_action(decision)) -} - -pub fn select_epiphany_coordinator_automation_action( - decision: &EpiphanyCoordinatorDecision, - force_checkpoint_compaction: bool, -) -> EpiphanyCoordinatorAutomationAction { - map_protocol_coordinator_automation_action(select_coordinator_automation_action( - decision, - force_checkpoint_compaction, - )) -} +pub type EpiphanyCoordinatorAutomationAction = CoreEpiphanyCoordinatorAutomationAction; pub struct EpiphanyCoordinatorAutomationInput<'a> { pub thread_id: &'a str, @@ -544,8 +494,8 @@ pub async fn select_epiphany_coordinator_automation( ) .await; - let action = select_epiphany_coordinator_automation_action( - &coordinator.decision, + let action = select_coordinator_automation_action( + &coordinator.core.decision, input.force_checkpoint_compaction, ); let launch_request = match action { @@ -582,64 +532,6 @@ pub async fn select_epiphany_coordinator_automation( } } -pub fn map_epiphany_coordinator( - state_status: CoreEpiphanyReorientStateStatus, - checkpoint_present: bool, - pressure: &epiphany_core::EpiphanyPressure, - recommendation: &CoreEpiphanyCrrcRecommendation, - roles: &[EpiphanyRoleBoardLane], - signals: &EpiphanyCoordinatorSourceSignals, - modeling_result_accepted: bool, - modeling_result_reviewable: bool, - modeling_result_accepted_after_verification: bool, - implementation_evidence_after_verification: bool, - verification_result_cites_implementation_evidence: bool, - verification_result_covers_current_modeling: bool, - verification_result_accepted: bool, - verification_result_allows_implementation: bool, - verification_result_needs_evidence: bool, - reorient_finding_accepted: bool, -) -> EpiphanyCoordinatorDecision { - recommend_coordinator_action(EpiphanyCoordinatorInput { - state_status: map_core_crrc_state_status_from_reorient(state_status), - checkpoint_present, - should_prepare_compaction: pressure.should_prepare_compaction, - recommendation: EpiphanyCoordinatorCrrcRecommendation { - action: recommendation.action, - recommended_scene_action: recommendation - .recommended_scene_action - .map(epiphany_core::crrc_scene_action_to_coordinator_scene_action), - }, - roles: roles - .iter() - .map(map_core_coordinator_role_lane_from_role_board) - .collect(), - signals: EpiphanyCoordinatorSignals { - modeling_result_status: signals.modeling_result_status, - verification_result_status: signals.verification_result_status, - }, - modeling_result_accepted, - modeling_result_reviewable, - modeling_result_accepted_after_verification, - implementation_evidence_after_verification, - verification_result_cites_implementation_evidence, - verification_result_covers_current_modeling, - verification_result_accepted, - verification_result_allows_implementation, - verification_result_needs_evidence, - reorient_finding_accepted, - }) -} - -fn map_core_coordinator_role_lane_from_role_board( - role: &EpiphanyRoleBoardLane, -) -> CoreEpiphanyCoordinatorRoleLane { - CoreEpiphanyCoordinatorRoleLane { - id: role.id, - status: role.status, - } -} - fn map_protocol_coordinator_role_id( role_id: CoreEpiphanyCoordinatorRoleId, ) -> ThreadEpiphanyRoleId { @@ -740,20 +632,6 @@ fn map_protocol_coordinator_action( } } -fn map_protocol_coordinator_automation_action( - action: CoreEpiphanyCoordinatorAutomationAction, -) -> EpiphanyCoordinatorAutomationAction { - match action { - CoreEpiphanyCoordinatorAutomationAction::None => EpiphanyCoordinatorAutomationAction::None, - CoreEpiphanyCoordinatorAutomationAction::CompactRehydrateReorient => { - EpiphanyCoordinatorAutomationAction::CompactRehydrateReorient - } - CoreEpiphanyCoordinatorAutomationAction::LaunchReorientWorker => { - EpiphanyCoordinatorAutomationAction::LaunchReorientWorker - } - } -} - pub fn epiphany_reorient_finding_already_accepted( state: &EpiphanyThreadState, finding: &CoreEpiphanyReorientFinding, diff --git a/epiphany-codex-bridge/src/launch.rs b/epiphany-codex-bridge/src/launch.rs index 2bede36b..cb7688f5 100644 --- a/epiphany-codex-bridge/src/launch.rs +++ b/epiphany-codex-bridge/src/launch.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::sync::OnceLock; use codex_app_server_protocol::ThreadEpiphanyReorientWorkerLaunchDocument; @@ -12,15 +11,13 @@ use epiphany_core::EpiphanyJobLaunchRequest; use epiphany_core::EpiphanyPressureLevel as CoreEpiphanyPressureLevel; use epiphany_core::EpiphanyReorientAction as CoreEpiphanyReorientAction; use epiphany_core::EpiphanyReorientDecision as CoreEpiphanyReorientDecision; -use epiphany_core::EpiphanyReorientFreshnessStatus as CoreEpiphanyReorientFreshnessStatus; -use epiphany_core::EpiphanyReorientPressureLevel as CoreEpiphanyReorientPressureLevel; -use epiphany_core::EpiphanyReorientReason as CoreEpiphanyReorientReason; +use epiphany_core::EpiphanyReorientLaunchRequestInput; use epiphany_core::EpiphanyReorientWorkerLaunchDocument; use epiphany_core::EpiphanyRoleResultRoleId; use epiphany_core::EpiphanyRoleWorkerLaunchDocument; use epiphany_core::EpiphanyWorkerLaunchDocument; +use epiphany_core::build_reorient_job_launch_request; use epiphany_state_model::EpiphanyInvestigationCheckpoint; -use epiphany_state_model::EpiphanyInvestigationDisposition; use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind; use epiphany_state_model::EpiphanyThreadState; @@ -487,88 +484,18 @@ pub fn build_epiphany_reorient_launch_request( checkpoint: &EpiphanyInvestigationCheckpoint, decision: &CoreEpiphanyReorientDecision, ) -> EpiphanyJobLaunchRequest { - let linked_subgoal_ids = epiphany_active_subgoal_ids(Some(state)); - let linked_graph_node_ids = unique_strings( - epiphany_active_graph_node_ids(Some(state)) - .into_iter() - .chain(decision.active_frontier_node_ids.iter().cloned()), - ); - let checkpoint_next_action = checkpoint - .next_action - .clone() - .unwrap_or_else(|| decision.next_action.clone()); - let authority_scope = match decision.action { - CoreEpiphanyReorientAction::Resume => "epiphany.reorient.resume", - CoreEpiphanyReorientAction::Regather => "epiphany.reorient.regather", - }; - let scope = match decision.action { - CoreEpiphanyReorientAction::Resume => "reorient-guided checkpoint resume", - CoreEpiphanyReorientAction::Regather => "reorient-guided checkpoint regather", - }; let instruction = build_epiphany_reorient_launch_instruction(decision.action); - let launch_document = - EpiphanyWorkerLaunchDocument::Reorient(EpiphanyReorientWorkerLaunchDocument { - thread_id: thread_id.to_string(), - mode: reorient_action_label(decision.action).to_string(), - checkpoint_id: checkpoint.checkpoint_id.clone(), - checkpoint_kind: checkpoint.kind.clone(), - checkpoint_disposition: investigation_disposition_label(checkpoint.disposition) - .to_string(), - checkpoint_focus: Some(checkpoint.focus.clone()), - checkpoint_summary: checkpoint.summary.clone(), - checkpoint_next_action, - checkpoint_open_questions: checkpoint.open_questions.clone(), - checkpoint_evidence_ids: checkpoint.evidence_ids.clone(), - checkpoint_code_refs: checkpoint.code_refs.clone(), - decision_reasons: decision - .reasons - .iter() - .map(|reason| reorient_reason_label(*reason).to_string()) - .collect(), - decision_note: decision.note.clone(), - pressure_level: reorient_pressure_level_label(decision.pressure_level).to_string(), - retrieval_status: reorient_freshness_status_label(decision.retrieval_status) - .to_string(), - graph_status: reorient_freshness_status_label(decision.graph_status).to_string(), - watcher_status: reorient_freshness_status_label(decision.watcher_status).to_string(), - checkpoint_dirty_paths: decision - .checkpoint_dirty_paths - .iter() - .map(path_to_display_string) - .collect(), - checkpoint_changed_paths: decision - .checkpoint_changed_paths - .iter() - .map(path_to_display_string) - .collect(), - scratch: state.scratch.clone(), - graphs: Some(state.graphs.clone()), - recent_evidence: state.recent_evidence.iter().take(8).cloned().collect(), - recent_observations: state.observations.iter().take(8).cloned().collect(), - active_frontier_node_ids: decision.active_frontier_node_ids.clone(), - linked_subgoal_ids: linked_subgoal_ids.clone(), - linked_graph_node_ids: linked_graph_node_ids.clone(), - }); - let output_contract_id = launch_document.output_contract_id().to_string(); - - EpiphanyJobLaunchRequest { + build_reorient_job_launch_request(EpiphanyReorientLaunchRequestInput { + thread_id, expected_revision, - binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID.to_string(), - kind: CoreEpiphanyJobKind::Specialist, - scope: scope.to_string(), - owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(), - authority_scope: authority_scope.to_string(), - linked_subgoal_ids: epiphany_active_subgoal_ids(Some(state)), - linked_graph_node_ids: unique_strings( - epiphany_active_graph_node_ids(Some(state)) - .into_iter() - .chain(decision.active_frontier_node_ids.iter().cloned()), - ), - instruction, - launch_document, - output_contract_id, max_runtime_seconds, - } + binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID, + owner_role: EPIPHANY_REORIENT_OWNER_ROLE, + instruction, + state, + checkpoint, + decision, + }) } pub fn build_epiphany_reorient_launch_instruction(action: CoreEpiphanyReorientAction) -> String { @@ -741,56 +668,3 @@ fn epiphany_active_graph_node_ids(state: Option<&EpiphanyThreadState>) -> Vec) -> String { - path.as_ref().to_string_lossy().to_string() -} - -fn reorient_action_label(action: CoreEpiphanyReorientAction) -> &'static str { - match action { - CoreEpiphanyReorientAction::Resume => "resume", - CoreEpiphanyReorientAction::Regather => "regather", - } -} - -fn investigation_disposition_label(disposition: EpiphanyInvestigationDisposition) -> &'static str { - match disposition { - EpiphanyInvestigationDisposition::ResumeReady => "resume_ready", - EpiphanyInvestigationDisposition::RegatherRequired => "regather_required", - } -} - -fn reorient_reason_label(reason: CoreEpiphanyReorientReason) -> &'static str { - match reason { - CoreEpiphanyReorientReason::MissingState => "missingState", - CoreEpiphanyReorientReason::MissingCheckpoint => "missingCheckpoint", - CoreEpiphanyReorientReason::CheckpointRequestedRegather => "checkpointRequestedRegather", - CoreEpiphanyReorientReason::CheckpointPathsDirty => "checkpointPathsDirty", - CoreEpiphanyReorientReason::CheckpointPathsChanged => "checkpointPathsChanged", - CoreEpiphanyReorientReason::FrontierChanged => "frontierChanged", - CoreEpiphanyReorientReason::UnanchoredCheckpointWhileStateStale => { - "unanchoredCheckpointWhileStateStale" - } - CoreEpiphanyReorientReason::CheckpointReady => "checkpointReady", - } -} - -fn reorient_pressure_level_label(level: CoreEpiphanyReorientPressureLevel) -> &'static str { - match level { - CoreEpiphanyReorientPressureLevel::Unknown => "unknown", - CoreEpiphanyReorientPressureLevel::Low => "low", - CoreEpiphanyReorientPressureLevel::Medium => "medium", - CoreEpiphanyReorientPressureLevel::High => "high", - CoreEpiphanyReorientPressureLevel::Critical => "critical", - } -} - -fn reorient_freshness_status_label(status: CoreEpiphanyReorientFreshnessStatus) -> &'static str { - match status { - CoreEpiphanyReorientFreshnessStatus::Unknown => "unknown", - CoreEpiphanyReorientFreshnessStatus::Clean => "clean", - CoreEpiphanyReorientFreshnessStatus::Dirty => "dirty", - CoreEpiphanyReorientFreshnessStatus::Stale => "stale", - CoreEpiphanyReorientFreshnessStatus::Changed => "changed", - } -} diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 643a9969..0163e68f 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -236,6 +236,9 @@ pub use surfaces::EpiphanyCoordinatorRoleResultStatus; pub use surfaces::EpiphanyCoordinatorRoleStatus; pub use surfaces::EpiphanyCoordinatorSceneAction; pub use surfaces::EpiphanyCoordinatorSignals; +pub use surfaces::EpiphanyCoordinatorSourceSignals; +pub use surfaces::EpiphanyCoordinatorStatus; +pub use surfaces::EpiphanyCoordinatorStatusInput; pub use surfaces::EpiphanyCrrcAction; pub use surfaces::EpiphanyCrrcInput; pub use surfaces::EpiphanyCrrcRecommendation; @@ -275,6 +278,7 @@ pub use surfaces::EpiphanyReorientDecision; pub use surfaces::EpiphanyReorientFindingInterpretation; pub use surfaces::EpiphanyReorientFreshnessStatus; pub use surfaces::EpiphanyReorientInput; +pub use surfaces::EpiphanyReorientLaunchRequestInput; pub use surfaces::EpiphanyReorientPressureLevel; pub use surfaces::EpiphanyReorientReason; pub use surfaces::EpiphanyReorientStateStatus; @@ -312,10 +316,12 @@ pub use surfaces::EpiphanyWorkerLaunchDocument; pub use surfaces::REORIENT_WORKER_OUTPUT_CONTRACT_ID; pub use surfaces::ROLE_WORKER_OUTPUT_CONTRACT_ID; pub use surfaces::build_reorient_acceptance_bundle; +pub use surfaces::build_reorient_job_launch_request; pub use surfaces::build_role_acceptance_bundle; pub use surfaces::coordinator_automation_action; pub use surfaces::crrc_scene_action_to_coordinator_scene_action; pub use surfaces::derive_context; +pub use surfaces::derive_coordinator_status; pub use surfaces::derive_freshness; pub use surfaces::derive_graph_query; pub use surfaces::derive_jobs; diff --git a/epiphany-core/src/surfaces/coordinator.rs b/epiphany-core/src/surfaces/coordinator.rs index 53dc9ba2..590061d7 100644 --- a/epiphany-core/src/surfaces/coordinator.rs +++ b/epiphany-core/src/surfaces/coordinator.rs @@ -1,6 +1,10 @@ use super::EpiphanyCrrcAction; use super::EpiphanyCrrcSceneAction; use super::EpiphanyCrrcStateStatus; +use super::EpiphanyPressure; +use super::EpiphanyPressureLevel; +use super::EpiphanyReorientAction; +use super::EpiphanyRoleBoardLane; use serde::Deserialize; use serde::Serialize; @@ -91,6 +95,18 @@ pub struct EpiphanyCoordinatorSignals { pub verification_result_status: EpiphanyCoordinatorRoleResultStatus, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EpiphanyCoordinatorSourceSignals { + pub pressure_level: EpiphanyPressureLevel, + pub should_prepare_compaction: bool, + pub reorient_action: EpiphanyReorientAction, + pub crrc_action: EpiphanyCrrcAction, + pub modeling_result_status: EpiphanyCoordinatorRoleResultStatus, + pub verification_result_status: EpiphanyCoordinatorRoleResultStatus, + pub reorient_result_status: super::EpiphanyCrrcResultStatus, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EpiphanyCoordinatorRoleLane { @@ -129,6 +145,36 @@ pub struct EpiphanyCoordinatorDecision { pub reason: String, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyCoordinatorStatusInput { + pub state_status: EpiphanyCrrcStateStatus, + pub checkpoint_present: bool, + pub pressure: EpiphanyPressure, + pub recommendation: super::EpiphanyCrrcRecommendation, + pub roles: Vec, + pub reorient_action: EpiphanyReorientAction, + pub modeling_result_status: EpiphanyCoordinatorRoleResultStatus, + pub verification_result_status: EpiphanyCoordinatorRoleResultStatus, + pub reorient_result_status: super::EpiphanyCrrcResultStatus, + pub modeling_result_accepted: bool, + pub modeling_result_reviewable: bool, + pub modeling_result_accepted_after_verification: bool, + pub implementation_evidence_after_verification: bool, + pub verification_result_cites_implementation_evidence: bool, + pub verification_result_covers_current_modeling: bool, + pub verification_result_accepted: bool, + pub verification_result_allows_implementation: bool, + pub verification_result_needs_evidence: bool, + pub reorient_finding_accepted: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyCoordinatorStatus { + pub decision: EpiphanyCoordinatorDecision, + pub source_signals: EpiphanyCoordinatorSourceSignals, + pub roles: Vec, +} + pub fn crrc_scene_action_to_coordinator_scene_action( action: EpiphanyCrrcSceneAction, ) -> EpiphanyCoordinatorSceneAction { @@ -141,6 +187,70 @@ pub fn crrc_scene_action_to_coordinator_scene_action( } } +pub fn derive_coordinator_status( + input: EpiphanyCoordinatorStatusInput, +) -> EpiphanyCoordinatorStatus { + let source_signals = EpiphanyCoordinatorSourceSignals { + pressure_level: input.pressure.level, + should_prepare_compaction: input.pressure.should_prepare_compaction, + reorient_action: input.reorient_action, + crrc_action: input.recommendation.action, + modeling_result_status: input.modeling_result_status, + verification_result_status: input.verification_result_status, + reorient_result_status: input.reorient_result_status, + }; + let coordinator_roles = input + .roles + .iter() + .map(coordinator_role_lane_from_role_board) + .collect(); + let decision = recommend_coordinator_action(EpiphanyCoordinatorInput { + state_status: input.state_status, + checkpoint_present: input.checkpoint_present, + should_prepare_compaction: input.pressure.should_prepare_compaction, + recommendation: EpiphanyCoordinatorCrrcRecommendation { + action: input.recommendation.action, + recommended_scene_action: input + .recommendation + .recommended_scene_action + .map(crrc_scene_action_to_coordinator_scene_action), + }, + roles: coordinator_roles, + signals: EpiphanyCoordinatorSignals { + modeling_result_status: input.modeling_result_status, + verification_result_status: input.verification_result_status, + }, + modeling_result_accepted: input.modeling_result_accepted, + modeling_result_reviewable: input.modeling_result_reviewable, + modeling_result_accepted_after_verification: input + .modeling_result_accepted_after_verification, + implementation_evidence_after_verification: input + .implementation_evidence_after_verification, + verification_result_cites_implementation_evidence: input + .verification_result_cites_implementation_evidence, + verification_result_covers_current_modeling: input + .verification_result_covers_current_modeling, + verification_result_accepted: input.verification_result_accepted, + verification_result_allows_implementation: input.verification_result_allows_implementation, + verification_result_needs_evidence: input.verification_result_needs_evidence, + reorient_finding_accepted: input.reorient_finding_accepted, + }); + EpiphanyCoordinatorStatus { + decision, + source_signals, + roles: input.roles, + } +} + +fn coordinator_role_lane_from_role_board( + lane: &EpiphanyRoleBoardLane, +) -> EpiphanyCoordinatorRoleLane { + EpiphanyCoordinatorRoleLane { + id: lane.id, + status: lane.status, + } +} + pub fn recommend_coordinator_action( input: EpiphanyCoordinatorInput, ) -> EpiphanyCoordinatorDecision { diff --git a/epiphany-core/src/surfaces/mod.rs b/epiphany-core/src/surfaces/mod.rs index e43b1fa6..3b2a6d29 100644 --- a/epiphany-core/src/surfaces/mod.rs +++ b/epiphany-core/src/surfaces/mod.rs @@ -23,8 +23,12 @@ pub use coordinator::EpiphanyCoordinatorRoleResultStatus; pub use coordinator::EpiphanyCoordinatorRoleStatus; pub use coordinator::EpiphanyCoordinatorSceneAction; pub use coordinator::EpiphanyCoordinatorSignals; +pub use coordinator::EpiphanyCoordinatorSourceSignals; +pub use coordinator::EpiphanyCoordinatorStatus; +pub use coordinator::EpiphanyCoordinatorStatusInput; pub use coordinator::coordinator_automation_action; pub use coordinator::crrc_scene_action_to_coordinator_scene_action; +pub use coordinator::derive_coordinator_status; pub use coordinator::recommend_coordinator_action; pub use coordinator::select_coordinator_automation_action; pub use crrc::EpiphanyCrrcAction; @@ -124,8 +128,10 @@ pub use scene::EpiphanySceneStatusCount; pub use scene::EpiphanySceneSubgoal; pub use scene::derive_scene; pub use state_patch::EpiphanyRoleStatePatchDocument; +pub use worker_launch::EpiphanyReorientLaunchRequestInput; pub use worker_launch::EpiphanyReorientWorkerLaunchDocument; pub use worker_launch::EpiphanyRoleWorkerLaunchDocument; pub use worker_launch::EpiphanyWorkerLaunchDocument; pub use worker_launch::REORIENT_WORKER_OUTPUT_CONTRACT_ID; pub use worker_launch::ROLE_WORKER_OUTPUT_CONTRACT_ID; +pub use worker_launch::build_reorient_job_launch_request; diff --git a/epiphany-core/src/surfaces/worker_launch.rs b/epiphany-core/src/surfaces/worker_launch.rs index 09fe4020..353aee50 100644 --- a/epiphany-core/src/surfaces/worker_launch.rs +++ b/epiphany-core/src/surfaces/worker_launch.rs @@ -6,12 +6,23 @@ use epiphany_state_model::EpiphanyGraphFrontier; use epiphany_state_model::EpiphanyGraphs; use epiphany_state_model::EpiphanyInvariant; use epiphany_state_model::EpiphanyInvestigationCheckpoint; +use epiphany_state_model::EpiphanyInvestigationDisposition; +use epiphany_state_model::EpiphanyJobKind; use epiphany_state_model::EpiphanyObservation; use epiphany_state_model::EpiphanyPlanningState; use epiphany_state_model::EpiphanyScratchPad; use epiphany_state_model::EpiphanySubgoal; +use epiphany_state_model::EpiphanyThreadState; use serde::Deserialize; use serde::Serialize; +use std::path::Path; + +use crate::EpiphanyJobLaunchRequest; +use crate::EpiphanyReorientAction; +use crate::EpiphanyReorientDecision; +use crate::EpiphanyReorientFreshnessStatus; +use crate::EpiphanyReorientPressureLevel; +use crate::EpiphanyReorientReason; pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v0"; pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0"; @@ -120,3 +131,285 @@ pub struct EpiphanyReorientWorkerLaunchDocument { #[serde(default)] pub linked_graph_node_ids: Vec, } + +pub struct EpiphanyReorientLaunchRequestInput<'a> { + pub thread_id: &'a str, + pub expected_revision: Option, + pub max_runtime_seconds: Option, + pub binding_id: &'a str, + pub owner_role: &'a str, + pub instruction: String, + pub state: &'a EpiphanyThreadState, + pub checkpoint: &'a EpiphanyInvestigationCheckpoint, + pub decision: &'a EpiphanyReorientDecision, +} + +pub fn build_reorient_job_launch_request( + input: EpiphanyReorientLaunchRequestInput<'_>, +) -> EpiphanyJobLaunchRequest { + let linked_subgoal_ids = active_subgoal_ids(input.state); + let linked_graph_node_ids = unique_strings( + active_graph_node_ids(input.state) + .into_iter() + .chain(input.decision.active_frontier_node_ids.iter().cloned()), + ); + let launch_document = + EpiphanyWorkerLaunchDocument::Reorient(build_reorient_worker_launch_document( + input.thread_id, + input.state, + input.checkpoint, + input.decision, + linked_subgoal_ids.clone(), + linked_graph_node_ids.clone(), + )); + let output_contract_id = launch_document.output_contract_id().to_string(); + + EpiphanyJobLaunchRequest { + expected_revision: input.expected_revision, + binding_id: input.binding_id.to_string(), + kind: EpiphanyJobKind::Specialist, + scope: reorient_launch_scope(input.decision.action).to_string(), + owner_role: input.owner_role.to_string(), + authority_scope: reorient_launch_authority_scope(input.decision.action).to_string(), + linked_subgoal_ids, + linked_graph_node_ids, + instruction: input.instruction, + launch_document, + output_contract_id, + max_runtime_seconds: input.max_runtime_seconds, + } +} + +fn build_reorient_worker_launch_document( + thread_id: &str, + state: &EpiphanyThreadState, + checkpoint: &EpiphanyInvestigationCheckpoint, + decision: &EpiphanyReorientDecision, + linked_subgoal_ids: Vec, + linked_graph_node_ids: Vec, +) -> EpiphanyReorientWorkerLaunchDocument { + EpiphanyReorientWorkerLaunchDocument { + thread_id: thread_id.to_string(), + mode: reorient_action_label(decision.action).to_string(), + checkpoint_id: checkpoint.checkpoint_id.clone(), + checkpoint_kind: checkpoint.kind.clone(), + checkpoint_disposition: investigation_disposition_label(checkpoint.disposition).to_string(), + checkpoint_focus: Some(checkpoint.focus.clone()), + checkpoint_summary: checkpoint.summary.clone(), + checkpoint_next_action: checkpoint + .next_action + .clone() + .unwrap_or_else(|| decision.next_action.clone()), + checkpoint_open_questions: checkpoint.open_questions.clone(), + checkpoint_evidence_ids: checkpoint.evidence_ids.clone(), + checkpoint_code_refs: checkpoint.code_refs.clone(), + decision_reasons: decision + .reasons + .iter() + .map(|reason| reorient_reason_label(*reason).to_string()) + .collect(), + decision_note: decision.note.clone(), + pressure_level: reorient_pressure_level_label(decision.pressure_level).to_string(), + retrieval_status: reorient_freshness_status_label(decision.retrieval_status).to_string(), + graph_status: reorient_freshness_status_label(decision.graph_status).to_string(), + watcher_status: reorient_freshness_status_label(decision.watcher_status).to_string(), + checkpoint_dirty_paths: decision + .checkpoint_dirty_paths + .iter() + .map(path_to_display_string) + .collect(), + checkpoint_changed_paths: decision + .checkpoint_changed_paths + .iter() + .map(path_to_display_string) + .collect(), + scratch: state.scratch.clone(), + graphs: Some(state.graphs.clone()), + recent_evidence: state.recent_evidence.iter().take(8).cloned().collect(), + recent_observations: state.observations.iter().take(8).cloned().collect(), + active_frontier_node_ids: decision.active_frontier_node_ids.clone(), + linked_subgoal_ids, + linked_graph_node_ids, + } +} + +fn active_subgoal_ids(state: &EpiphanyThreadState) -> Vec { + state + .active_subgoal_id + .clone() + .map(|id| vec![id]) + .unwrap_or_default() +} + +fn active_graph_node_ids(state: &EpiphanyThreadState) -> Vec { + state + .graph_frontier + .as_ref() + .map(|frontier| frontier.active_node_ids.clone()) + .unwrap_or_default() +} + +fn unique_strings(values: impl IntoIterator) -> Vec { + let mut unique = Vec::new(); + for value in values { + if !unique.iter().any(|existing| existing == &value) { + unique.push(value); + } + } + unique +} + +fn path_to_display_string(path: impl AsRef) -> String { + path.as_ref().to_string_lossy().to_string() +} + +fn reorient_launch_authority_scope(action: EpiphanyReorientAction) -> &'static str { + match action { + EpiphanyReorientAction::Resume => "epiphany.reorient.resume", + EpiphanyReorientAction::Regather => "epiphany.reorient.regather", + } +} + +fn reorient_launch_scope(action: EpiphanyReorientAction) -> &'static str { + match action { + EpiphanyReorientAction::Resume => "reorient-guided checkpoint resume", + EpiphanyReorientAction::Regather => "reorient-guided checkpoint regather", + } +} + +fn reorient_action_label(action: EpiphanyReorientAction) -> &'static str { + match action { + EpiphanyReorientAction::Resume => "resume", + EpiphanyReorientAction::Regather => "regather", + } +} + +fn investigation_disposition_label(disposition: EpiphanyInvestigationDisposition) -> &'static str { + match disposition { + EpiphanyInvestigationDisposition::ResumeReady => "resume_ready", + EpiphanyInvestigationDisposition::RegatherRequired => "regather_required", + } +} + +fn reorient_reason_label(reason: EpiphanyReorientReason) -> &'static str { + match reason { + EpiphanyReorientReason::MissingState => "missingState", + EpiphanyReorientReason::MissingCheckpoint => "missingCheckpoint", + EpiphanyReorientReason::CheckpointRequestedRegather => "checkpointRequestedRegather", + EpiphanyReorientReason::CheckpointPathsDirty => "checkpointPathsDirty", + EpiphanyReorientReason::CheckpointPathsChanged => "checkpointPathsChanged", + EpiphanyReorientReason::FrontierChanged => "frontierChanged", + EpiphanyReorientReason::UnanchoredCheckpointWhileStateStale => { + "unanchoredCheckpointWhileStateStale" + } + EpiphanyReorientReason::CheckpointReady => "checkpointReady", + } +} + +fn reorient_pressure_level_label(level: EpiphanyReorientPressureLevel) -> &'static str { + match level { + EpiphanyReorientPressureLevel::Unknown => "unknown", + EpiphanyReorientPressureLevel::Low => "low", + EpiphanyReorientPressureLevel::Medium => "medium", + EpiphanyReorientPressureLevel::High => "high", + EpiphanyReorientPressureLevel::Critical => "critical", + } +} + +fn reorient_freshness_status_label(status: EpiphanyReorientFreshnessStatus) -> &'static str { + match status { + EpiphanyReorientFreshnessStatus::Unknown => "unknown", + EpiphanyReorientFreshnessStatus::Clean => "clean", + EpiphanyReorientFreshnessStatus::Dirty => "dirty", + EpiphanyReorientFreshnessStatus::Stale => "stale", + EpiphanyReorientFreshnessStatus::Changed => "changed", + } +} + +#[cfg(test)] +mod tests { + use super::*; + use epiphany_state_model::EpiphanyCodeRef; + use epiphany_state_model::EpiphanyGraphFrontier; + use std::path::PathBuf; + + #[test] + fn reorient_launch_request_is_composed_from_typed_state() { + let state = EpiphanyThreadState { + revision: 7, + active_subgoal_id: Some("sg-1".to_string()), + graph_frontier: Some(EpiphanyGraphFrontier { + active_node_ids: vec!["node-a".to_string()], + active_edge_ids: Vec::new(), + open_question_ids: Vec::new(), + open_gap_ids: Vec::new(), + dirty_paths: Vec::new(), + }), + ..Default::default() + }; + let checkpoint = EpiphanyInvestigationCheckpoint { + checkpoint_id: "ix-1".to_string(), + kind: "implementation".to_string(), + disposition: EpiphanyInvestigationDisposition::RegatherRequired, + focus: "runtime seam".to_string(), + summary: Some("resume here".to_string()), + next_action: None, + captured_at_turn_id: Some("turn-1".to_string()), + open_questions: Vec::new(), + code_refs: vec![EpiphanyCodeRef { + path: PathBuf::from("src/lib.rs"), + start_line: None, + end_line: None, + symbol: None, + note: None, + }], + evidence_ids: vec!["ev-1".to_string()], + }; + let decision = EpiphanyReorientDecision { + action: EpiphanyReorientAction::Regather, + checkpoint_status: crate::EpiphanyReorientCheckpointStatus::RegatherRequired, + checkpoint_id: Some("ix-1".to_string()), + pressure_level: EpiphanyReorientPressureLevel::High, + retrieval_status: EpiphanyReorientFreshnessStatus::Stale, + graph_status: EpiphanyReorientFreshnessStatus::Changed, + watcher_status: EpiphanyReorientFreshnessStatus::Dirty, + reasons: vec![EpiphanyReorientReason::FrontierChanged], + checkpoint_dirty_paths: vec![PathBuf::from("src/lib.rs")], + checkpoint_changed_paths: vec![PathBuf::from("src/main.rs")], + active_frontier_node_ids: vec!["node-b".to_string()], + next_action: "Regather the seam.".to_string(), + note: "continuity moved".to_string(), + }; + + let request = build_reorient_job_launch_request(EpiphanyReorientLaunchRequestInput { + thread_id: "thread-1", + expected_revision: Some(state.revision), + max_runtime_seconds: Some(60), + binding_id: "reorient-worker", + owner_role: "epiphany-reorient", + instruction: "do the reorientation".to_string(), + state: &state, + checkpoint: &checkpoint, + decision: &decision, + }); + + assert_eq!(request.binding_id, "reorient-worker"); + assert_eq!(request.scope, "reorient-guided checkpoint regather"); + assert_eq!(request.authority_scope, "epiphany.reorient.regather"); + assert_eq!(request.linked_subgoal_ids, vec!["sg-1"]); + assert_eq!( + request.linked_graph_node_ids, + vec!["node-a".to_string(), "node-b".to_string()] + ); + let EpiphanyWorkerLaunchDocument::Reorient(document) = request.launch_document else { + panic!("expected reorient launch document"); + }; + assert_eq!(document.mode, "regather"); + assert_eq!(document.checkpoint_next_action, "Regather the seam."); + assert_eq!(document.decision_reasons, vec!["frontierChanged"]); + assert_eq!(document.pressure_level, "high"); + assert_eq!(document.retrieval_status, "stale"); + assert_eq!(document.graph_status, "changed"); + assert_eq!(document.watcher_status, "dirty"); + } +} diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 07de4a4d..3514a04b 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -74,6 +74,7 @@ remembers doctrine; the branch remembers the blade. - View-lens orchestration now uses bridge-native `EpiphanyViewLens` selectors. App-server converts `ThreadEpiphanyViewLens` at the request wall, and the bridge maps back only for the legacy response echo. - Coordinator status derivation now receives core `EpiphanyCrrcResultStatus` for reorient result state; `ThreadEpiphanyReorientResultStatus` is projection-only for coordinator/view responses. - Coordinator view response assembly now receives core reorient state and role-board status; `ThreadEpiphanyReorientStateStatus` / `ThreadEpiphanyRoleLane` are projected inside the response builder. +- Coordinator judgment is no longer bridge-owned. `epiphany-core` now exposes `EpiphanyCoordinatorStatusInput` -> `EpiphanyCoordinatorStatus`; the bridge gathers runtime snapshots and accepted-finding facts, then core derives source signals and the coordinator decision. Reorient worker launch composition is also core-owned through `EpiphanyReorientLaunchRequestInput` -> `EpiphanyJobLaunchRequest`; bridge supplies the prompt instruction plus retained compatibility binding/owner constants and performs the host launch side effect. - MCP remains JSON because MCP is a JSON protocol. The latest processor cut only moved MCP refresh/OAuth/status/resource/tool-call route handling into `codex_message_processor/mcp_routes.rs`, reducing the root processor to about 8,977 lines; it did not claim MCP payloads are CultNet or make MCP an Epiphany-owned runtime contract. - Codex account auth route handling remains in vendored Codex but now lives in `codex_message_processor/auth_routes.rs`: API-key login, ChatGPT browser/device-code/external-token login, logout, auth status, account, rate limits, and add-credits nudge handling. This preserves Codex ownership of subscription auth while cutting the root processor to about 8,094 lines. - One-off command execution and command stream write/resize/terminate route handling now live in `codex_message_processor/command_routes.rs`; `CommandExecManager` remains the lifecycle owner, while the root processor is about 7,800 lines. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 84c52e6527e7a4383b5dc2999420804736c17199..ff18499c5ff0ea5d77faafe73e89ff87b2933a89 100644 GIT binary patch delta 326 zcmYk2KT88a5XISmkj6r=vKsJDWz3QI2V<$dO)9%}cP_Vbx4W60HC&a#!q!4K&`%K% z#Lpm>eiE^F7c5fFoA;Y}ywzv_`>X%7+dFt&?azgHdFaGrC&YUvdK-J&&mW^=bUG+b z2BTtHoK42(lX188BX*X*m(GzkRB2NIl@s+ggKsiFNZUJs#190n6gIa?E zOO63FlDU^`oCRq$5IQnw8EP4=s?t1<7 `EpiphanyCoordinatorStatus`), so + bridge code gathers runtime/acceptance facts but no longer owns the + coordinator decision assembly. Reorient worker launch-request/document + composition also moved behind a typed core seam + (`EpiphanyReorientLaunchRequestInput` -> `EpiphanyJobLaunchRequest`); + bridge supplies only the prompt instruction and retained compatibility + binding/owner constants before performing host side effects. The bridge still depends on vendored Codex for app-server protocol DTOs and absolute path validation, so the purification is not done. next_action: Continue Codex starvation and CultNet liberation by cutting bridge-internal `ThreadEpiphany*` authority. Prioritize role-result/reorient-result DTO coupling next: derive typed CultNet surfaces first, push JSON-RPC projection into app-server, and keep MCP/OpenAI JSON only at their true protocol/text edges. Do not add compatibility adapters unless they protect a named ownership invariant.