Build reorient launches from typed decisions

This commit is contained in:
Meta Vens 2026-05-19 17:05:26 +01:00
commit bc57becdba
4 changed files with 87 additions and 66 deletions

View file

@ -69,6 +69,7 @@ use crate::pressure::derive_epiphany_pressure;
use crate::pressure::map_epiphany_pressure;
use crate::reorient::EpiphanyFreshnessWatcherSnapshot;
use crate::reorient::derive_epiphany_freshness_view;
use crate::reorient::derive_epiphany_reorient;
use crate::reorient::map_epiphany_reorient;
use crate::runtime_results::load_epiphany_reorient_result_snapshot;
use crate::runtime_results::load_epiphany_role_result_snapshot;
@ -418,6 +419,13 @@ pub async fn select_epiphany_coordinator_automation(
&core_freshness.graph,
&core_freshness.watcher,
);
let (_core_state_status, core_reorient_decision) = derive_epiphany_reorient(
Some(input.state),
&core_pressure,
&core_freshness.retrieval,
&core_freshness.graph,
&core_freshness.watcher,
);
if state_status != ThreadEpiphanyReorientStateStatus::Ready {
return EpiphanyCoordinatorAutomationVerdict {
action: EpiphanyCoordinatorAutomationAction::None,
@ -488,7 +496,7 @@ pub async fn select_epiphany_coordinator_automation(
None,
input.state,
checkpoint,
&reorient_decision,
&core_reorient_decision,
)
}),
EpiphanyCoordinatorAutomationAction::None

View file

@ -1,18 +1,17 @@
use std::path::Path;
use std::sync::OnceLock;
use codex_app_server_protocol::ThreadEpiphanyGraphFreshnessStatus;
use codex_app_server_protocol::ThreadEpiphanyInvalidationStatus;
use codex_app_server_protocol::ThreadEpiphanyPressureLevel;
use codex_app_server_protocol::ThreadEpiphanyReorientAction;
use codex_app_server_protocol::ThreadEpiphanyReorientDecision;
use codex_app_server_protocol::ThreadEpiphanyReorientReason;
use codex_app_server_protocol::ThreadEpiphanyReorientWorkerLaunchDocument;
use codex_app_server_protocol::ThreadEpiphanyRetrievalFreshnessStatus;
use codex_app_server_protocol::ThreadEpiphanyRoleId;
use codex_app_server_protocol::ThreadEpiphanyRoleWorkerLaunchDocument;
use codex_app_server_protocol::ThreadEpiphanyWorkerLaunchDocument;
use epiphany_core::EpiphanyJobLaunchRequest;
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::EpiphanyReorientWorkerLaunchDocument;
use epiphany_core::EpiphanyRoleWorkerLaunchDocument;
use epiphany_core::EpiphanyWorkerLaunchDocument;
@ -480,7 +479,7 @@ pub fn build_epiphany_reorient_launch_request(
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
checkpoint: &EpiphanyInvestigationCheckpoint,
decision: &ThreadEpiphanyReorientDecision,
decision: &CoreEpiphanyReorientDecision,
) -> EpiphanyJobLaunchRequest {
let linked_subgoal_ids = epiphany_active_subgoal_ids(Some(state));
let linked_graph_node_ids = unique_strings(
@ -493,12 +492,12 @@ pub fn build_epiphany_reorient_launch_request(
.clone()
.unwrap_or_else(|| decision.next_action.clone());
let authority_scope = match decision.action {
ThreadEpiphanyReorientAction::Resume => "epiphany.reorient.resume",
ThreadEpiphanyReorientAction::Regather => "epiphany.reorient.regather",
CoreEpiphanyReorientAction::Resume => "epiphany.reorient.resume",
CoreEpiphanyReorientAction::Regather => "epiphany.reorient.regather",
};
let scope = match decision.action {
ThreadEpiphanyReorientAction::Resume => "reorient-guided checkpoint resume",
ThreadEpiphanyReorientAction::Regather => "reorient-guided checkpoint regather",
CoreEpiphanyReorientAction::Resume => "reorient-guided checkpoint resume",
CoreEpiphanyReorientAction::Regather => "reorient-guided checkpoint regather",
};
let instruction = build_epiphany_reorient_launch_instruction(decision.action);
let launch_document =
@ -521,11 +520,10 @@ pub fn build_epiphany_reorient_launch_request(
.map(|reason| reorient_reason_label(*reason).to_string())
.collect(),
decision_note: decision.note.clone(),
pressure_level: pressure_level_label(decision.pressure_level).to_string(),
retrieval_status: retrieval_freshness_status_label(decision.retrieval_status)
.to_string(),
graph_status: graph_freshness_status_label(decision.graph_status).to_string(),
watcher_status: invalidation_status_label(decision.watcher_status).to_string(),
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()
@ -566,11 +564,11 @@ pub fn build_epiphany_reorient_launch_request(
}
}
pub fn build_epiphany_reorient_launch_instruction(action: ThreadEpiphanyReorientAction) -> String {
pub fn build_epiphany_reorient_launch_instruction(action: CoreEpiphanyReorientAction) -> String {
let prompts = &epiphany_specialist_prompt_config().reorientation;
let body = match action {
ThreadEpiphanyReorientAction::Resume => prompts.resume.as_str(),
ThreadEpiphanyReorientAction::Regather => prompts.regather.as_str(),
CoreEpiphanyReorientAction::Resume => prompts.resume.as_str(),
CoreEpiphanyReorientAction::Regather => prompts.regather.as_str(),
};
epiphany_agent_prompt_with_memory(body)
}
@ -741,10 +739,10 @@ fn path_to_display_string(path: impl AsRef<Path>) -> String {
path.as_ref().to_string_lossy().to_string()
}
fn reorient_action_label(action: ThreadEpiphanyReorientAction) -> &'static str {
fn reorient_action_label(action: CoreEpiphanyReorientAction) -> &'static str {
match action {
ThreadEpiphanyReorientAction::Resume => "resume",
ThreadEpiphanyReorientAction::Regather => "regather",
CoreEpiphanyReorientAction::Resume => "resume",
CoreEpiphanyReorientAction::Regather => "regather",
}
}
@ -755,18 +753,38 @@ fn investigation_disposition_label(disposition: EpiphanyInvestigationDisposition
}
}
fn reorient_reason_label(reason: ThreadEpiphanyReorientReason) -> &'static str {
fn reorient_reason_label(reason: CoreEpiphanyReorientReason) -> &'static str {
match reason {
ThreadEpiphanyReorientReason::MissingState => "missingState",
ThreadEpiphanyReorientReason::MissingCheckpoint => "missingCheckpoint",
ThreadEpiphanyReorientReason::CheckpointRequestedRegather => "checkpointRequestedRegather",
ThreadEpiphanyReorientReason::CheckpointPathsDirty => "checkpointPathsDirty",
ThreadEpiphanyReorientReason::CheckpointPathsChanged => "checkpointPathsChanged",
ThreadEpiphanyReorientReason::FrontierChanged => "frontierChanged",
ThreadEpiphanyReorientReason::UnanchoredCheckpointWhileStateStale => {
CoreEpiphanyReorientReason::MissingState => "missingState",
CoreEpiphanyReorientReason::MissingCheckpoint => "missingCheckpoint",
CoreEpiphanyReorientReason::CheckpointRequestedRegather => "checkpointRequestedRegather",
CoreEpiphanyReorientReason::CheckpointPathsDirty => "checkpointPathsDirty",
CoreEpiphanyReorientReason::CheckpointPathsChanged => "checkpointPathsChanged",
CoreEpiphanyReorientReason::FrontierChanged => "frontierChanged",
CoreEpiphanyReorientReason::UnanchoredCheckpointWhileStateStale => {
"unanchoredCheckpointWhileStateStale"
}
ThreadEpiphanyReorientReason::CheckpointReady => "checkpointReady",
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",
}
}
@ -779,31 +797,3 @@ pub fn pressure_level_label(level: ThreadEpiphanyPressureLevel) -> &'static str
ThreadEpiphanyPressureLevel::Critical => "critical",
}
}
fn retrieval_freshness_status_label(
status: ThreadEpiphanyRetrievalFreshnessStatus,
) -> &'static str {
match status {
ThreadEpiphanyRetrievalFreshnessStatus::Missing => "missing",
ThreadEpiphanyRetrievalFreshnessStatus::Ready => "ready",
ThreadEpiphanyRetrievalFreshnessStatus::Stale => "stale",
ThreadEpiphanyRetrievalFreshnessStatus::Indexing => "indexing",
ThreadEpiphanyRetrievalFreshnessStatus::Unavailable => "unavailable",
}
}
fn graph_freshness_status_label(status: ThreadEpiphanyGraphFreshnessStatus) -> &'static str {
match status {
ThreadEpiphanyGraphFreshnessStatus::Missing => "missing",
ThreadEpiphanyGraphFreshnessStatus::Ready => "ready",
ThreadEpiphanyGraphFreshnessStatus::Stale => "stale",
}
}
fn invalidation_status_label(status: ThreadEpiphanyInvalidationStatus) -> &'static str {
match status {
ThreadEpiphanyInvalidationStatus::Unavailable => "unavailable",
ThreadEpiphanyInvalidationStatus::Clean => "clean",
ThreadEpiphanyInvalidationStatus::Changed => "changed",
}
}

View file

@ -53,6 +53,7 @@ use crate::mutation::thread_epiphany_patch_has_state_replacements;
use crate::pressure::derive_epiphany_pressure;
use crate::reorient::EpiphanyFreshnessWatcherSnapshot;
use crate::reorient::derive_epiphany_freshness_view;
use crate::reorient::derive_epiphany_reorient;
use crate::reorient::map_epiphany_reorient;
use crate::runtime_results::load_completed_epiphany_reorient_finding;
use crate::runtime_results::load_completed_epiphany_role_finding;
@ -590,6 +591,13 @@ pub async fn launch_thread_epiphany_reorient(
&freshness.graph,
&freshness.watcher,
);
let (_core_state_status, launch_decision) = derive_epiphany_reorient(
state,
&pressure,
&freshness.retrieval,
&freshness.graph,
&freshness.watcher,
);
let state = state.ok_or_else(|| {
EpiphanyBridgeError::InvalidRequest(format!(
@ -610,7 +618,7 @@ pub async fn launch_thread_epiphany_reorient(
max_runtime_seconds,
state,
checkpoint,
&decision,
&launch_decision,
);
let changed_fields = epiphany_job_launch_changed_fields();
let launched = launch_epiphany_job_on_thread(thread, launch_request).await?;

View file

@ -21,6 +21,7 @@ use epiphany_core::EpiphanyPressure;
use epiphany_core::EpiphanyPressureLevel;
use epiphany_core::EpiphanyReorientAction as CoreEpiphanyReorientAction;
use epiphany_core::EpiphanyReorientCheckpointStatus as CoreEpiphanyReorientCheckpointStatus;
use epiphany_core::EpiphanyReorientDecision as CoreEpiphanyReorientDecision;
use epiphany_core::EpiphanyReorientFreshnessStatus as CoreEpiphanyReorientFreshnessStatus;
use epiphany_core::EpiphanyReorientInput;
use epiphany_core::EpiphanyReorientPressureLevel as CoreEpiphanyReorientPressureLevel;
@ -172,7 +173,25 @@ pub fn map_epiphany_reorient(
ThreadEpiphanyReorientStateStatus,
ThreadEpiphanyReorientDecision,
) {
let (state_status, decision) = recommend_reorientation(EpiphanyReorientInput {
let (state_status, decision) =
derive_epiphany_reorient(state, pressure, retrieval, graph, watcher);
(
map_protocol_reorient_state_status(state_status),
map_protocol_reorient_decision(decision),
)
}
pub fn derive_epiphany_reorient(
state: Option<&EpiphanyThreadState>,
pressure: &EpiphanyPressure,
retrieval: &CoreEpiphanyRetrievalFreshness,
graph: &CoreEpiphanyGraphFreshness,
watcher: &CoreEpiphanyInvalidationInput,
) -> (
CoreEpiphanyReorientStateStatus,
CoreEpiphanyReorientDecision,
) {
recommend_reorientation(EpiphanyReorientInput {
checkpoint: state.and_then(|state| state.investigation_checkpoint.as_ref()),
state_present: state.is_some(),
pressure_level: map_core_reorient_pressure_level(pressure.level),
@ -185,11 +204,7 @@ pub fn map_epiphany_reorient(
watcher_graph_node_ids: watcher.graph_node_ids.clone(),
active_frontier_node_ids: watcher.active_frontier_node_ids.clone(),
watched_root: watcher.watched_root.clone(),
});
(
map_protocol_reorient_state_status(state_status),
map_protocol_reorient_decision(decision),
)
})
}
fn map_core_reorient_pressure_level(