Move coordinator judgment into core

This commit is contained in:
Meta Vens 2026-05-20 08:39:56 +01:00
commit 3d4ed26b0f
9 changed files with 461 additions and 285 deletions

View file

@ -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<EpiphanyRoleBoardLane>,
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,

View file

@ -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<St
.map(|frontier| frontier.active_node_ids.clone())
.unwrap_or_default()
}
fn path_to_display_string(path: impl AsRef<Path>) -> 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",
}
}

View file

@ -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;

View file

@ -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<EpiphanyRoleBoardLane>,
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<EpiphanyRoleBoardLane>,
}
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 {

View file

@ -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;

View file

@ -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<String>,
}
pub struct EpiphanyReorientLaunchRequestInput<'a> {
pub thread_id: &'a str,
pub expected_revision: Option<u64>,
pub max_runtime_seconds: Option<u64>,
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<String>,
linked_graph_node_ids: Vec<String>,
) -> 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<String> {
state
.active_subgoal_id
.clone()
.map(|id| vec![id])
.unwrap_or_default()
}
fn active_graph_node_ids(state: &EpiphanyThreadState) -> Vec<String> {
state
.graph_frontier
.as_ref()
.map(|frontier| frontier.active_node_ids.clone())
.unwrap_or_default()
}
fn unique_strings(values: impl IntoIterator<Item = String>) -> Vec<String> {
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<Path>) -> 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");
}
}

View file

@ -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.

Binary file not shown.

View file

@ -371,7 +371,15 @@ current_status:
result state instead of protocol `ThreadEpiphanyReorientResultStatus`.
Coordinator view response assembly now receives core reorient state and
role-board status, projecting `ThreadEpiphanyReorientStateStatus` /
`ThreadEpiphanyRoleLane` only while building the legacy response.
`ThreadEpiphanyRoleLane` only while building the legacy response. The next
rude cut landed: coordinator status derivation is a typed `epiphany-core`
seam (`EpiphanyCoordinatorStatusInput` -> `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.