Migrate Reorientation to keyed Mind authority

This commit is contained in:
Meta Vens 2026-08-18 03:21:21 +02:00
commit d5df53ae40
17 changed files with 1542 additions and 1664 deletions

View file

@ -8,14 +8,10 @@ use crate::EpiphanyJobLaunchRequest;
use crate::EpiphanyPressure;
use crate::EpiphanyPressureLevel as CoreEpiphanyPressureLevel;
use crate::EpiphanyReorientAction as CoreEpiphanyReorientAction;
use crate::EpiphanyReorientDecision as CoreEpiphanyReorientDecision;
use crate::EpiphanyReorientLaunchRequestInput;
use crate::EpiphanyRoleResultRoleId;
use crate::EpiphanyRoleWorkerLaunchDocument;
use crate::EpiphanyWorkerLaunchDocument;
use crate::build_reorient_job_launch_request;
use crate::default_launch_organ_contract;
use epiphany_state_model::EpiphanyInvestigationCheckpoint;
use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind;
use epiphany_state_model::EpiphanyThreadState;
@ -1238,49 +1234,6 @@ fn build_epiphany_role_launch_instruction(role_id: EpiphanyRoleResultRoleId) ->
epiphany_worker_prompt(body)
}
pub fn build_epiphany_reorient_launch_request(
thread_id: &str,
expected_revision: Option<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
checkpoint: &EpiphanyInvestigationCheckpoint,
decision: &CoreEpiphanyReorientDecision,
) -> EpiphanyJobLaunchRequest {
build_epiphany_reorient_launch_request_with_dynamic_context(
thread_id,
expected_revision,
max_runtime_seconds,
state,
checkpoint,
decision,
None,
)
}
pub fn build_epiphany_reorient_launch_request_with_dynamic_context(
thread_id: &str,
expected_revision: Option<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
checkpoint: &EpiphanyInvestigationCheckpoint,
decision: &CoreEpiphanyReorientDecision,
dynamic_prompt_context: Option<String>,
) -> EpiphanyJobLaunchRequest {
let instruction = build_epiphany_reorient_launch_instruction(decision.action);
build_reorient_job_launch_request(EpiphanyReorientLaunchRequestInput {
thread_id,
expected_revision,
max_runtime_seconds,
binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
owner_role: EPIPHANY_REORIENT_OWNER_ROLE,
instruction,
state,
checkpoint,
decision,
dynamic_prompt_context,
})
}
pub fn build_epiphany_reorient_launch_instruction(action: CoreEpiphanyReorientAction) -> String {
let prompts = &epiphany_specialist_prompt_config().reorientation;
let body = match action {

View file

@ -1072,15 +1072,20 @@ fn run_coordinator(args: &Args) -> Result<Value> {
steps.push(step);
break;
}
let can_accept = args.auto_review
&& reorient_result_auto_acceptable(&result)
&& revision.is_some();
if result["status"].as_str() == Some("failed") {
let recorded = accept_reorient(&runtime_store)?;
push_event(
&mut step,
json!({"type": "reorientFailureRecorded", "recorded": status_cli::sanitize_for_operator(recorded)}),
);
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
append_operator_step_jsonl(&steps_path, &step)?;
steps.push(step);
continue;
}
let can_accept = args.auto_review && reorient_result_auto_acceptable(&result);
if can_accept {
let accepted = accept_reorient(
&runtime_store,
&thread_id,
revision.and_then(|value| u64::try_from(value).ok()),
)?;
let accepted = accept_reorient(&runtime_store)?;
push_event(
&mut step,
json!({"type": "reorientAccept", "accepted": status_cli::sanitize_for_operator(accepted)}),
@ -1858,29 +1863,6 @@ fn launch_role(
}))
}
fn launch_job_value(
service: &epiphany_core::EpiphanyCoordinatorService,
thread_id: &str,
state: &epiphany_state_model::EpiphanyThreadState,
request: &epiphany_core::EpiphanyJobLaunchRequest,
) -> Result<Value> {
let launched = service.launch_job(
thread_id,
state,
request,
format!("epiphany-heartbeat-launch-{}", Uuid::new_v4()),
Uuid::new_v4().to_string(),
now(),
)?;
Ok(json!({
"bindingId": launched.binding_id,
"launcherJobId": launched.launcher_job_id,
"backendJobId": launched.backend_job_id,
"revision": launched.epiphany_state.revision,
"state": launched.epiphany_state,
}))
}
fn accept_role(
runtime_store: &Path,
thread_id: &str,
@ -1919,52 +1901,30 @@ fn accept_role(
}))
}
fn accept_reorient(
runtime_store: &Path,
thread_id: &str,
expected_revision: Option<u64>,
) -> Result<Value> {
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store);
let state = service
.state()?
.ok_or_else(|| anyhow!("cannot accept reorientation without native coordinator state"))?;
let snapshot = service.reorient_result(epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID)?;
if let Some(result_id) = snapshot
.finding
.as_ref()
.and_then(|finding| finding.runtime_result_id.as_deref())
&& let Some(existing) = state.acceptance_receipts.iter().find(|receipt| {
receipt.result_id == result_id
&& receipt.binding_id == epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID
&& receipt.surface == "reorientAccept"
&& receipt.status == "accepted"
})
{
return Ok(json!({
"revision": state.revision,
"state": state,
"acceptedReceiptId": existing.id,
"changed": false,
}));
fn accept_reorient(runtime_store: &Path) -> Result<Value> {
let work = epiphany_core::project_current_work(runtime_store)?
.reorientation
.ok_or_else(|| anyhow!("Mind has no reviewable reorientation work"))?;
if work.action != epiphany_core::EpiphanyAgentPassContinuationAction::Review {
return Err(anyhow!("reorientation work is not reviewable"));
}
let accepted = service.accept_reorient(
thread_id,
&state,
epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
expected_revision,
None,
now(),
&Uuid::new_v4().to_string(),
true,
true,
)?;
let job_id = work
.job_id
.ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?;
let snapshot = epiphany_core::runtime_job_snapshot(runtime_store, &job_id)?
.ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?;
let accepted = match snapshot.job.status {
epiphany_core::EpiphanyRuntimeJobStatus::Completed => {
epiphany_core::accept_reorientation_result(runtime_store, &job_id, &now())?
}
epiphany_core::EpiphanyRuntimeJobStatus::Failed => {
epiphany_core::record_reorientation_pass_failure(runtime_store, &job_id)?
}
_ => return Err(anyhow!("reorientation work is not terminally reviewable")),
};
Ok(json!({
"revision": accepted.state.revision,
"state": accepted.state,
"acceptedReceiptId": accepted.update.accepted_receipt_id,
"acceptedObservationId": accepted.update.accepted_observation_id,
"acceptedEvidenceId": accepted.update.accepted_evidence_id,
"finding": accepted.finding,
"mindCommitReceipt": accepted,
"changed": true,
}))
}
@ -2151,64 +2111,18 @@ fn default_binding_id_for_role(role_id: &str) -> String {
fn launch_reorient(
runtime_store: &Path,
local_verse_store: &Path,
thread_id: &str,
expected_revision: Option<i64>,
max_runtime_seconds: u64,
_local_verse_store: &Path,
_thread_id: &str,
_expected_revision: Option<i64>,
_max_runtime_seconds: u64,
) -> Result<Value> {
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store);
let state = service
.state()?
.ok_or_else(|| anyhow!("cannot launch reorientation without native coordinator state"))?;
let checkpoint = match state.investigation_checkpoint.clone() {
Some(checkpoint) => checkpoint,
None => {
let projection = epiphany_core::runtime_modeling_semantic_projection_input(
runtime_store,
)
.context("cannot launch reorientation without a valid admitted RepoModel checkpoint")?;
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
projection.snapshot(),
&projection.obligation().obligation_id,
)
}
};
let status = collect_coordinator_status(runtime_store, thread_id)?;
let decision: epiphany_core::EpiphanyReorientDecision =
serde_json::from_value(status["reorient"]["decision"].clone())
.context("native reorientation status did not contain a typed decision")?;
let context = epiphany_core::render_launch_dynamic_prompt_context(
runtime_store,
local_verse_store,
&state,
epiphany_core::reorient_launch_context_focus(&state, &decision.next_action),
)
.map_err(anyhow::Error::msg)?;
let expected_revision = expected_revision.and_then(|value| u64::try_from(value).ok());
let request = epiphany_core::build_epiphany_reorient_launch_request_with_dynamic_context(
thread_id,
expected_revision,
Some(max_runtime_seconds),
&state,
&checkpoint,
&decision,
Some(context),
);
let launched = service.launch_job(
thread_id,
&state,
&request,
format!("epiphany-heartbeat-launch-{}", Uuid::new_v4()),
Uuid::new_v4().to_string(),
now(),
)?;
let requested_at = now();
let request = epiphany_core::request_current_reorientation(runtime_store, &requested_at)?;
let job_id = epiphany_core::launch_current_reorientation_work(runtime_store, &requested_at)?;
Ok(json!({
"bindingId": launched.binding_id,
"launcherJobId": launched.launcher_job_id,
"backendJobId": launched.backend_job_id,
"revision": launched.epiphany_state.revision,
"state": launched.epiphany_state,
"decision": decision,
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
"backendJobId": job_id,
"request": request,
}))
}
@ -2344,8 +2258,35 @@ fn read_role_result(runtime_store: &Path, thread_id: &str, role_id: &str) -> Res
Ok(collect_coordinator_status(runtime_store, thread_id)?["roleResults"][role_id].clone())
}
fn read_reorient_result(runtime_store: &Path, thread_id: &str) -> Result<Value> {
Ok(collect_coordinator_status(runtime_store, thread_id)?["reorientResult"].clone())
fn read_reorient_result(runtime_store: &Path, _thread_id: &str) -> Result<Value> {
let Some(work) = epiphany_core::project_current_work(runtime_store)?.reorientation else {
return Ok(json!({
"source": "native",
"status": "backendMissing",
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
"note": "No keyed reorientation request is active."
}));
};
let Some(job_id) = work.job_id else {
return Ok(json!({
"source": "native",
"status": "pending",
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
"note": "The keyed reorientation request is ready to launch."
}));
};
let snapshot = epiphany_core::read_runtime_reorient_result(Some(runtime_store), &job_id);
let mut value = json!({
"source": "native",
"status": snapshot.status,
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
"runtimeJobId": job_id,
"note": snapshot.note,
});
if let Some(finding) = snapshot.finding {
value["finding"] = json!(finding);
}
Ok(value)
}
fn maybe_apply_role_self_patch(accepted: &Value, agent_memory_dir: &Path) -> Result<Option<Value>> {

View file

@ -4,20 +4,14 @@ use anyhow::anyhow;
use epiphany_core::EpiphanyCoordinatorRoleResultStatus;
use epiphany_core::EpiphanyCoordinatorStatus;
use epiphany_core::EpiphanyCoordinatorStatusInput;
use epiphany_core::EpiphanyCrrcInput;
use epiphany_core::EpiphanyCrrcReorientAction;
use epiphany_core::EpiphanyCrrcAction;
use epiphany_core::EpiphanyCrrcRecommendation;
use epiphany_core::EpiphanyCrrcResultStatus;
use epiphany_core::EpiphanyCrrcSceneAction;
use epiphany_core::EpiphanyCrrcStateStatus;
use epiphany_core::EpiphanyFreshnessInput;
use epiphany_core::EpiphanyGraphFreshnessStatus;
use epiphany_core::EpiphanyInvalidationStatus;
use epiphany_core::EpiphanyJobStatus;
use epiphany_core::EpiphanyJobsInput;
use epiphany_core::EpiphanyReorientAction;
use epiphany_core::EpiphanyReorientFreshnessStatus;
use epiphany_core::EpiphanyReorientInput;
use epiphany_core::EpiphanyReorientPressureLevel;
use epiphany_core::EpiphanyRetrievalFreshnessStatus;
use epiphany_core::EpiphanyRoleBoardCheckpointSummary;
use epiphany_core::EpiphanyRoleBoardInput;
use epiphany_core::EpiphanyRoleBoardJob;
@ -29,20 +23,16 @@ use epiphany_core::EpiphanyRuntimeJobSnapshot;
use epiphany_core::EpiphanyRuntimeJobStatus;
use epiphany_core::EpiphanySceneInput;
use epiphany_core::EpiphanyTokenUsageSnapshot;
use epiphany_core::derive_freshness;
use epiphany_core::derive_jobs;
use epiphany_core::derive_planning_view;
use epiphany_core::derive_pressure_view;
use epiphany_core::derive_role_board;
use epiphany_core::derive_scene;
use epiphany_core::read_accepted_coordinator_state;
use epiphany_core::recommend_crrc_action;
use epiphany_core::recommend_reorientation;
use epiphany_core::runtime_job_snapshot;
use epiphany_self_policy::crrc_scene_action_to_coordinator_scene_action;
use epiphany_self_policy::derive_coordinator_finding_signals;
use epiphany_self_policy::derive_coordinator_status;
use epiphany_self_policy::reorient_finding_already_accepted;
use epiphany_state_model::EpiphanyThreadState;
use serde_json::Value;
use serde_json::json;
@ -279,7 +269,7 @@ pub fn native_coordinator_json(runtime_store: &Path, thread_id: &str) -> Result<
}
fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Value> {
let cwd = absolute_path(&args.cwd)?;
let _cwd = absolute_path(&args.cwd)?;
let store_path = absolute_path(&args.store)?;
let runtime_store_path = store_path.clone();
let state = if store_path.exists() {
@ -303,53 +293,33 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
reorient_binding_id: REORIENT_BINDING_ID,
});
let pressure = derive_pressure_view(None::<&EpiphanyTokenUsageSnapshot>);
let freshness = derive_freshness(EpiphanyFreshnessInput {
state: state_ref,
retrieval_override: None,
watcher: None,
});
let reorient_pressure_level = match pressure.level {
epiphany_core::EpiphanyPressureLevel::Unknown => EpiphanyReorientPressureLevel::Unknown,
epiphany_core::EpiphanyPressureLevel::Low => EpiphanyReorientPressureLevel::Low,
epiphany_core::EpiphanyPressureLevel::Elevated => EpiphanyReorientPressureLevel::Medium,
epiphany_core::EpiphanyPressureLevel::High => EpiphanyReorientPressureLevel::High,
epiphany_core::EpiphanyPressureLevel::Critical => EpiphanyReorientPressureLevel::Critical,
};
let reorient_checkpoint = match state_ref
.and_then(|state| state.investigation_checkpoint.clone())
{
Some(checkpoint) => Some(checkpoint),
None => match epiphany_core::runtime_current_repo_model(&runtime_store_path)? {
None => None,
Some(_) => {
let projection =
epiphany_core::runtime_modeling_semantic_projection_input(&runtime_store_path)
.context("failed to validate current RepoModel continuity projection")?;
Some(
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
projection.snapshot(),
&projection.obligation().obligation_id,
),
)
let mind = epiphany_core::assemble_mind_view(&runtime_store_path)?;
let durable_checkpoint_present = mind.investigation_checkpoint.is_some();
let latest_reorientation_decision = mind.reorientation_decisions.last();
let reorientation_work = current_work.reorientation.as_ref();
let reorient_action = latest_reorientation_decision
.map(|decision| {
if decision.mode == "regather" {
EpiphanyReorientAction::Regather
} else {
EpiphanyReorientAction::Resume
}
})
.unwrap_or(EpiphanyReorientAction::Resume);
let reorient_next_action = latest_reorientation_decision
.map(|decision| decision.next_safe_move.clone())
.unwrap_or_else(|| "No unresolved keyed reorientation obligation.".into());
let reorient_state_status = "ready";
let reorient_decision = json!({
"action": match reorient_action {
EpiphanyReorientAction::Resume => "resume",
EpiphanyReorientAction::Regather => "regather",
},
};
let durable_checkpoint_present = reorient_checkpoint.is_some();
let (reorient_state_status, reorient_decision) =
recommend_reorientation(EpiphanyReorientInput {
checkpoint: reorient_checkpoint.as_ref(),
state_present: state_ref.is_some(),
pressure_level: reorient_pressure_level,
retrieval_status: reorient_retrieval_status(freshness.retrieval.status),
retrieval_dirty_paths: freshness.retrieval.dirty_paths.clone(),
graph_status: reorient_graph_status(freshness.graph.status),
graph_dirty_paths: freshness.graph.dirty_paths.clone(),
watcher_status: reorient_watcher_status(freshness.watcher.status),
watcher_changed_paths: freshness.watcher.changed_paths.clone(),
watcher_graph_node_ids: freshness.watcher.graph_node_ids.clone(),
active_frontier_node_ids: freshness.watcher.active_frontier_node_ids.clone(),
watched_root: freshness.watcher.watched_root.clone().or(Some(cwd.clone())),
});
"nextAction": reorient_next_action,
"requestId": reorientation_work.map(|work| work.request.request_id.as_str()),
"basisDigest": reorientation_work.map(|work| work.request.projection.projection_digest.as_str()),
"authority": "keyedMindCurrentWork",
});
let planning = derive_planning_view(state_ref);
let mut jobs = derive_jobs(EpiphanyJobsInput {
state: state_ref,
@ -361,35 +331,23 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
} else {
EpiphanyCrrcStateStatus::Missing
};
let reorient_action = match reorient_decision.action {
EpiphanyReorientAction::Resume => EpiphanyCrrcReorientAction::Resume,
EpiphanyReorientAction::Regather => EpiphanyCrrcReorientAction::Regather,
};
let result_status =
native_reorient_result_status(state_ref, &runtime_store_path, REORIENT_BINDING_ID);
let reorient_finding =
native_reorient_finding(state_ref, &runtime_store_path, REORIENT_BINDING_ID);
let reorient_finding_accepted = state_ref.is_some_and(|state| {
reorient_finding
.as_ref()
.is_some_and(|finding| reorient_finding_already_accepted(state, finding))
});
let result_status = keyed_reorientation_result_status(
&runtime_store_path,
reorientation_work,
latest_reorientation_decision.is_some(),
)?;
let reorient_finding_accepted = latest_reorientation_decision.is_some();
let modeling_result_status =
native_role_result_status(state_ref, &runtime_store_path, MODELING_BINDING_ID);
let research_result_status =
native_role_result_status(state_ref, &runtime_store_path, RESEARCH_BINDING_ID);
let verification_result_status =
native_role_result_status(state_ref, &runtime_store_path, VERIFICATION_BINDING_ID);
let recommendation = recommend_crrc_action(EpiphanyCrrcInput {
loaded,
state_status,
should_prepare_compaction: pressure.should_prepare_compaction,
reorient_action,
result_status,
checkpoint_present: durable_checkpoint_present,
finding_present: reorient_finding.is_some(),
finding_accepted: reorient_finding_accepted,
});
let recommendation = keyed_reorientation_recommendation(
reorientation_work,
latest_reorientation_decision,
pressure.should_prepare_compaction,
);
let role_jobs = jobs
.iter()
.map(|job| EpiphanyRoleBoardJob {
@ -416,14 +374,14 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
}
})
}),
reorient_next_action: reorient_decision.next_action.clone(),
reorient_next_action: reorient_next_action.clone(),
jobs: role_jobs.clone(),
crrc_action: recommendation.action,
crrc_recommended_scene_action: recommendation
.recommended_scene_action
.map(crrc_scene_action_to_coordinator_scene_action),
crrc_reason: recommendation.reason.clone(),
reorient_decision_action: format!("{:?}", reorient_decision.action),
reorient_decision_action: format!("{:?}", reorient_action),
pressure_level: format!("{:?}", pressure.level),
reorient_result_status: result_status,
reorient_job: role_jobs
@ -461,7 +419,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
research_finding.as_ref(),
modeling_finding.as_ref(),
verification_finding.as_ref(),
reorient_finding.as_ref(),
None,
);
let modeling_result_proposal_bound =
latest_runtime_job_id_for_binding(state_ref, MODELING_BINDING_ID)
@ -493,20 +451,14 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
let frontier_relinquishment =
epiphany_core::runtime_latest_repo_frontier_relinquishment(&runtime_store_path)
.context("failed to derive latest frontier relinquishment from runtime-spine state")?;
let crrc_regather_current = crrc_regather_current_after_relinquishment(
state_ref,
&runtime_store_path,
frontier_relinquishment.as_ref(),
)?;
let proposal_modeling_work = current_work.proposal_modeling.as_ref();
let coordinator = derive_coordinator_status(EpiphanyCoordinatorStatusInput {
state_status,
checkpoint_present: durable_checkpoint_present,
pressure: pressure.clone(),
recommendation: recommendation.clone(),
crrc_regather_current,
roles: roles.clone(),
reorient_action: reorient_decision.action,
reorient_action,
research_result_status,
modeling_result_status,
verification_result_status,
@ -534,7 +486,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
verification_result_allows_implementation: finding_signals
.verification_result_allows_implementation,
verification_result_needs_evidence: finding_signals.verification_result_needs_evidence,
reorient_finding_accepted: finding_signals.reorient_finding_accepted,
reorient_finding_accepted,
hands_frontier_ready,
research_continuation_action,
frontier_planning_stage: frontier_planning.stage,
@ -645,7 +597,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
"verificationResultAccepted": finding_signals.verification_result_accepted,
"verificationResultFailureReviewed": finding_signals.verification_result_failure_reviewed,
},
"reorientResult": native_reorient_result(state_ref, &runtime_store_path, REORIENT_BINDING_ID),
"reorientResult": keyed_reorientation_result(&runtime_store_path, reorientation_work, latest_reorientation_decision)?,
"crrc": {
"threadId": thread_id,
"source": "native",
@ -907,33 +859,6 @@ fn native_role_result(
result
}
fn native_reorient_result(
state: Option<&EpiphanyThreadState>,
runtime_store: &Path,
binding_id: &str,
) -> Value {
let Some(job_id) = latest_runtime_job_id_for_binding(state, binding_id) else {
return json!({
"source": "native",
"status": "backendMissing",
"bindingId": binding_id,
"note": "No runtime-spine job is linked to this native reorientation binding.",
});
};
let snapshot = epiphany_core::read_runtime_reorient_result(Some(runtime_store), job_id);
let mut result = json!({
"source": "native",
"status": snapshot.status,
"bindingId": binding_id,
"runtimeJobId": job_id,
"note": snapshot.note,
});
if let Some(finding) = snapshot.finding {
result["finding"] = json!(finding);
}
result
}
fn native_role_result_status(
state: Option<&EpiphanyThreadState>,
runtime_store: &Path,
@ -959,51 +884,6 @@ fn native_role_finding(
epiphany_core::read_runtime_role_result(Some(runtime_store), job_id, role_id).finding
}
fn native_reorient_finding(
state: Option<&EpiphanyThreadState>,
runtime_store: &Path,
binding_id: &str,
) -> Option<epiphany_core::EpiphanyReorientFindingInterpretation> {
let job_id = latest_runtime_job_id_for_binding(state, binding_id)?;
epiphany_core::read_runtime_reorient_result(Some(runtime_store), job_id).finding
}
fn native_reorient_result_status(
state: Option<&EpiphanyThreadState>,
runtime_store: &Path,
binding_id: &str,
) -> EpiphanyCrrcResultStatus {
let Some(job_id) = latest_runtime_job_id_for_binding(state, binding_id) else {
return EpiphanyCrrcResultStatus::BackendMissing;
};
match runtime_job_snapshot(runtime_store, job_id) {
Ok(Some(snapshot)) => map_runtime_reorient_result_status(&snapshot),
Ok(None) => EpiphanyCrrcResultStatus::BackendMissing,
Err(_) => EpiphanyCrrcResultStatus::BackendUnavailable,
}
}
fn crrc_regather_current_after_relinquishment(
state: Option<&EpiphanyThreadState>,
runtime_store: &Path,
relinquishment: Option<&epiphany_core::RepoFrontierRelinquishmentReceipt>,
) -> Result<bool> {
let Some(relinquishment) = relinquishment else {
return Ok(true);
};
let Some(job_id) = latest_runtime_job_id_for_binding(state, REORIENT_BINDING_ID) else {
return Ok(false);
};
let Some(snapshot) = runtime_job_snapshot(runtime_store, job_id)? else {
return Ok(false);
};
let reorient_updated_at = chrono::DateTime::parse_from_rfc3339(&snapshot.job.updated_at)
.context("reorientation job updated_at is not RFC3339")?;
let relinquished_at = chrono::DateTime::parse_from_rfc3339(&relinquishment.relinquished_at)
.context("frontier relinquishment timestamp is not RFC3339")?;
Ok(reorient_updated_at > relinquished_at)
}
fn reconcile_native_runtime_jobs(
jobs: &mut [epiphany_core::EpiphanyJobView],
state: Option<&EpiphanyThreadState>,
@ -1037,33 +917,6 @@ fn latest_runtime_job_id_for_binding<'a>(
.map(|link| link.runtime_job_id.as_str())
}
fn reorient_retrieval_status(
status: EpiphanyRetrievalFreshnessStatus,
) -> EpiphanyReorientFreshnessStatus {
match status {
EpiphanyRetrievalFreshnessStatus::Missing
| EpiphanyRetrievalFreshnessStatus::Unavailable => EpiphanyReorientFreshnessStatus::Unknown,
EpiphanyRetrievalFreshnessStatus::Ready => EpiphanyReorientFreshnessStatus::Clean,
EpiphanyRetrievalFreshnessStatus::Stale => EpiphanyReorientFreshnessStatus::Stale,
EpiphanyRetrievalFreshnessStatus::Indexing => EpiphanyReorientFreshnessStatus::Dirty,
}
}
fn reorient_graph_status(status: EpiphanyGraphFreshnessStatus) -> EpiphanyReorientFreshnessStatus {
match status {
EpiphanyGraphFreshnessStatus::Missing => EpiphanyReorientFreshnessStatus::Unknown,
EpiphanyGraphFreshnessStatus::Stale => EpiphanyReorientFreshnessStatus::Stale,
}
}
fn reorient_watcher_status(status: EpiphanyInvalidationStatus) -> EpiphanyReorientFreshnessStatus {
match status {
EpiphanyInvalidationStatus::Unavailable => EpiphanyReorientFreshnessStatus::Unknown,
EpiphanyInvalidationStatus::Clean => EpiphanyReorientFreshnessStatus::Clean,
EpiphanyInvalidationStatus::Changed => EpiphanyReorientFreshnessStatus::Changed,
}
}
fn map_runtime_role_result_status(
snapshot: &EpiphanyRuntimeJobSnapshot,
) -> EpiphanyCoordinatorRoleResultStatus {
@ -1104,6 +957,103 @@ fn map_runtime_reorient_result_status(
}
}
fn keyed_reorientation_result_status(
runtime_store: &Path,
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
accepted_decision_present: bool,
) -> Result<EpiphanyCrrcResultStatus> {
let Some(work) = work else {
return Ok(if accepted_decision_present {
EpiphanyCrrcResultStatus::Completed
} else {
EpiphanyCrrcResultStatus::MissingBinding
});
};
let Some(job_id) = work.job_id.as_deref() else {
return Ok(EpiphanyCrrcResultStatus::MissingBinding);
};
let snapshot = runtime_job_snapshot(runtime_store, job_id)?
.ok_or_else(|| anyhow!("keyed reorientation work lost its runtime job"))?;
Ok(map_runtime_reorient_result_status(&snapshot))
}
fn keyed_reorientation_recommendation(
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
accepted: Option<&epiphany_core::EpiphanyMindReorientationDecisionDocument>,
should_prepare_compaction: bool,
) -> EpiphanyCrrcRecommendation {
let build = |action, scene, reason: &str| EpiphanyCrrcRecommendation {
action,
recommended_scene_action: scene,
reason: reason.into(),
};
if let Some(work) = work {
return match work.action {
epiphany_core::EpiphanyAgentPassContinuationAction::Launch => build(
EpiphanyCrrcAction::LaunchReorientWorker,
Some(EpiphanyCrrcSceneAction::ReorientLaunch),
"A keyed Mind continuity request has no live attempt; launch its exact sealed projection.",
),
epiphany_core::EpiphanyAgentPassContinuationAction::Wait => build(
EpiphanyCrrcAction::WaitForReorientWorker,
Some(EpiphanyCrrcSceneAction::ReorientResult),
"The exact keyed reorientation attempt is still live.",
),
epiphany_core::EpiphanyAgentPassContinuationAction::Review => build(
EpiphanyCrrcAction::ReviewReorientResult,
Some(EpiphanyCrrcSceneAction::ReorientResult),
"The exact keyed reorientation pass has a terminal decision or model-backed failure to review.",
),
};
}
if should_prepare_compaction {
return build(
EpiphanyCrrcAction::LaunchReorientWorker,
Some(EpiphanyCrrcSceneAction::ReorientLaunch),
"Context pressure requires a new keyed continuity request over the current Mind projection.",
);
}
if accepted.is_some_and(|decision| decision.mode == "regather") {
return build(
EpiphanyCrrcAction::RegatherManually,
Some(EpiphanyCrrcSceneAction::Reorient),
"The accepted keyed continuity decision requires explicit regather work.",
);
}
build(
EpiphanyCrrcAction::Continue,
Some(EpiphanyCrrcSceneAction::Reorient),
"No unresolved keyed reorientation obligation exists.",
)
}
fn keyed_reorientation_result(
runtime_store: &Path,
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
accepted: Option<&epiphany_core::EpiphanyMindReorientationDecisionDocument>,
) -> Result<Value> {
let Some(work) = work else {
return Ok(accepted
.map(|decision| json!({"status": "accepted", "decision": decision}))
.unwrap_or(Value::Null));
};
let Some(job_id) = work.job_id.as_deref() else {
return Ok(json!({
"status": "unlaunched",
"requestId": work.request.request_id,
}));
};
let snapshot = runtime_job_snapshot(runtime_store, job_id)?
.ok_or_else(|| anyhow!("keyed reorientation work lost its runtime job"))?;
let result = epiphany_core::runtime_reorient_worker_result(runtime_store, job_id)?;
Ok(json!({
"status": format!("{:?}", snapshot.job.status).to_ascii_lowercase(),
"requestId": work.request.request_id,
"jobId": job_id,
"result": result,
}))
}
fn map_runtime_job_status(status: EpiphanyRuntimeJobStatus) -> EpiphanyJobStatus {
match status {
EpiphanyRuntimeJobStatus::Queued => EpiphanyJobStatus::Pending,

View file

@ -1,7 +1,6 @@
use crate::coordinator_results::latest_runtime_link;
use crate::coordinator_state::changed_fields;
use crate::*;
use anyhow::Context;
use epiphany_state_model::EpiphanyThreadState;
use std::path::Path;
@ -37,16 +36,6 @@ pub struct EpiphanyRoleAcceptanceUpdate {
pub mind_review: MindGatewayReview,
}
#[derive(Debug, Clone)]
pub struct EpiphanyReorientAcceptanceUpdate {
pub state_update: EpiphanyStateUpdate,
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
pub accepted_receipt_id: String,
pub accepted_observation_id: String,
pub accepted_evidence_id: String,
pub mind_review: MindGatewayReview,
}
#[derive(Debug, Clone)]
pub struct EpiphanyNativeRoleAcceptance {
pub state: EpiphanyThreadState,
@ -54,118 +43,6 @@ pub struct EpiphanyNativeRoleAcceptance {
pub update: EpiphanyRoleAcceptanceUpdate,
}
#[derive(Debug, Clone)]
pub struct EpiphanyNativeReorientAcceptance {
pub state: EpiphanyThreadState,
pub finding: EpiphanyReorientFindingInterpretation,
pub update: EpiphanyReorientAcceptanceUpdate,
}
pub fn accept_coordinator_reorient_finding(
store: &Path,
thread_id: &str,
state: &EpiphanyThreadState,
binding_id: &str,
expected_revision: Option<u64>,
reference_turn_id: Option<String>,
accepted_at: String,
nonce: &str,
update_scratch: bool,
update_investigation_checkpoint: bool,
) -> anyhow::Result<EpiphanyNativeReorientAcceptance> {
let investigation_checkpoint = if update_investigation_checkpoint {
match state.investigation_checkpoint.clone() {
Some(checkpoint) => Some(checkpoint),
None => {
let model = crate::assemble_repo_model_view(store).context(
"cannot accept reorientation without a current keyed RepoModel checkpoint",
)?;
Some(
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
&model.memory_context_projection(),
&model.projection_digest,
),
)
}
}
} else {
None
};
let snapshot = read_reorient_result_snapshot(Some(state), Some(store), binding_id);
if snapshot.status != EpiphanyCrrcResultStatus::Completed {
return Err(anyhow::anyhow!(
"reorientation finding is not completed: {}",
snapshot.note
));
}
let finding = snapshot
.finding
.ok_or_else(|| anyhow::anyhow!("completed reorientation result has no typed finding"))?;
let update = build_native_reorient_acceptance_update(
expected_revision,
binding_id,
&finding,
format!("ev-reorient-{nonce}"),
format!("obs-reorient-{nonce}"),
accepted_at.clone(),
update_scratch,
update_investigation_checkpoint,
investigation_checkpoint,
)
.map_err(anyhow::Error::msg)?;
let contract = acceptance_launch_contract_for_binding(store, state, binding_id, "reorient")
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
enforce_acceptance_receipt_proofs(
&contract,
&reorient_acceptance_claimed_effects(),
&[
MIND_GATEWAY_REVIEW_TYPE.to_string(),
CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string(),
],
&[
MIND_GATEWAY_REVIEW_TYPE.to_string(),
CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string(),
],
)
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
let recovery = continuity_recovery_receipt_from_reorient_finding(
format!("continuity-recovery-{}", update.accepted_receipt_id),
binding_id.to_string(),
&finding,
accepted_at.clone(),
);
let next_state = apply_coordinator_state_update_to_state(
state,
update.state_update.clone(),
reference_turn_id,
)?;
let commit = mind_state_commit_receipt(
format!("mind-commit-{}", update.accepted_receipt_id),
&update.mind_review,
next_state.revision,
update
.changed_fields
.iter()
.map(|field| format!("{field:?}"))
.collect(),
accepted_at,
);
commit_state_with_mind_witness(
store,
thread_id,
state,
&next_state,
&update.mind_review,
&commit,
&[EpiphanyAcceptancePrerequisite::Continuity(recovery)],
)?;
Ok(EpiphanyNativeReorientAcceptance {
state: next_state,
finding,
update,
})
}
pub fn accept_coordinator_role_finding(
store: &Path,
thread_id: &str,
@ -332,48 +209,6 @@ pub fn completed_role_finding(
})
}
pub fn completed_reorient_finding(
runtime_store_path: Option<&Path>,
state: &EpiphanyThreadState,
binding_id: &str,
) -> Result<EpiphanyReorientFindingInterpretation, EpiphanyCoordinatorAdmissionError> {
let Some(link) = latest_runtime_link(state, binding_id) else {
return if state
.job_bindings
.iter()
.any(|binding| binding.id == binding_id)
{
Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(
"reorientation findings without runtime-spine results are unsupported; accept only typed runtime-spine results".to_string(),
))
} else {
Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(format!(
"epiphany reorientation binding {binding_id:?} was not found"
)))
};
};
let snapshot = read_runtime_reorient_result(runtime_store_path, &link.runtime_job_id);
if snapshot.status != EpiphanyCrrcResultStatus::Completed {
return Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(format!(
"cannot accept reorientation result while worker status is {:?}",
snapshot.status
)));
}
let store = runtime_store_path.ok_or_else(|| {
EpiphanyCoordinatorAdmissionError::InvalidRequest(
"cannot accept completed reorientation worker without a loaded runtime-spine store"
.to_string(),
)
})?;
load_launch_organ_contract(store, &link.runtime_job_id, "reorient")?;
snapshot.finding.ok_or_else(|| {
EpiphanyCoordinatorAdmissionError::InvalidRequest(
"cannot accept completed reorientation worker because no typed runtime-spine result was recorded"
.to_string(),
)
})
}
pub fn load_launch_organ_contract(
runtime_store_path: &Path,
job_id: &str,
@ -495,13 +330,6 @@ pub fn role_acceptance_enforceable_receipts(role_id: EpiphanyRoleResultRoleId) -
receipts
}
pub fn reorient_acceptance_claimed_effects() -> Vec<EpiphanyReceiptEffectKind> {
vec![
EpiphanyReceiptEffectKind::StateAdmission,
EpiphanyReceiptEffectKind::ContinuityRecovery,
]
}
pub fn coordinator_acceptance_cache(store_path: &Path) -> anyhow::Result<CultCache> {
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
@ -649,76 +477,6 @@ pub fn build_native_role_acceptance_update(
})
}
pub fn build_native_reorient_acceptance_update(
expected_revision: Option<u64>,
binding_id: &str,
finding: &EpiphanyReorientFindingInterpretation,
accepted_evidence_id: String,
accepted_observation_id: String,
accepted_at: String,
update_scratch: bool,
update_investigation_checkpoint: bool,
checkpoint: Option<epiphany_state_model::EpiphanyInvestigationCheckpoint>,
) -> Result<EpiphanyReorientAcceptanceUpdate, String> {
let mind_finding = EpiphanyReorientAcceptanceFinding {
mode: finding.mode.clone(),
summary: finding.summary.clone(),
next_safe_move: finding.next_safe_move.clone(),
checkpoint_still_valid: finding.checkpoint_still_valid,
files_inspected: finding.files_inspected.clone(),
runtime_result_id: finding.runtime_result_id.clone(),
runtime_job_id: finding.runtime_job_id.clone(),
};
let mind_review = mind_review_reorient_acceptance(
binding_id,
&mind_finding,
update_scratch,
update_investigation_checkpoint,
);
mind_review_allows_state(&mind_review)?;
let bundle = build_reorient_acceptance_bundle(
binding_id,
mind_finding,
accepted_evidence_id,
accepted_observation_id,
accepted_at,
update_scratch,
update_investigation_checkpoint
.then_some(checkpoint)
.flatten(),
)?;
let accepted_receipt_id = bundle.accepted_receipt_id.clone();
let accepted_observation_id = bundle.accepted_observation_id.clone();
let accepted_evidence_id = bundle.accepted_evidence_id.clone();
let mut changed_fields = vec![
EpiphanyStateUpdatedField::AcceptanceReceipts,
EpiphanyStateUpdatedField::Observations,
EpiphanyStateUpdatedField::Evidence,
];
if bundle.scratch.is_some() {
changed_fields.push(EpiphanyStateUpdatedField::Scratch);
}
if bundle.investigation_checkpoint.is_some() {
changed_fields.push(EpiphanyStateUpdatedField::InvestigationCheckpoint);
}
Ok(EpiphanyReorientAcceptanceUpdate {
state_update: EpiphanyStateUpdate {
expected_revision,
scratch: bundle.scratch,
investigation_checkpoint: bundle.investigation_checkpoint,
acceptance_receipts: vec![bundle.receipt],
observations: vec![bundle.observation],
evidence: vec![bundle.evidence],
..Default::default()
},
changed_fields,
accepted_receipt_id,
accepted_observation_id,
accepted_evidence_id,
mind_review,
})
}
fn state_update_from_patch(
expected_revision: Option<u64>,
patch: EpiphanyRoleStatePatchDocument,

View file

@ -5,11 +5,9 @@ use crate::EpiphanyJobInterruptRequest;
use crate::EpiphanyJobInterruptResult;
use crate::EpiphanyJobLaunchRequest;
use crate::EpiphanyJobLaunchResult;
use crate::EpiphanyNativeReorientAcceptance;
use crate::EpiphanyNativeRoleAcceptance;
use crate::EpiphanyRoleResultRoleId;
use crate::EpiphanyStateUpdate;
use crate::accept_coordinator_reorient_finding;
use crate::accept_coordinator_role_finding;
use crate::apply_coordinator_state_update;
use crate::apply_coordinator_state_update_from_state;
@ -162,32 +160,6 @@ impl EpiphanyCoordinatorService {
)
}
pub fn accept_reorient(
&self,
thread_id: &str,
state: &EpiphanyThreadState,
binding_id: &str,
expected_revision: Option<u64>,
reference_turn_id: Option<String>,
accepted_at: String,
nonce: &str,
update_scratch: bool,
update_investigation_checkpoint: bool,
) -> Result<EpiphanyNativeReorientAcceptance> {
accept_coordinator_reorient_finding(
&self.store,
thread_id,
state,
binding_id,
expected_revision,
reference_turn_id,
accepted_at,
nonce,
update_scratch,
update_investigation_checkpoint,
)
}
pub fn interrupt_job(
&self,
thread_id: &str,

View file

@ -25,6 +25,7 @@ pub struct EpiphanyCurrentWorkProjection {
pub proposal_modeling: Option<EpiphanyProposalModelingWorkProjection>,
pub frontier_verdict_modeling: Option<EpiphanyFrontierVerdictModelingWorkProjection>,
pub verification: Option<EpiphanyVerificationWorkProjection>,
pub reorientation: Option<crate::EpiphanyReorientationWorkProjection>,
pub hands_frontier_ready: bool,
}
@ -339,6 +340,7 @@ pub fn project_current_work(store_path: impl AsRef<Path>) -> Result<EpiphanyCurr
proposal_modeling: current_proposal_modeling_work(&cache)?,
frontier_verdict_modeling: current_frontier_verdict_modeling_work(&cache)?,
verification: current_verification_work(&cache)?,
reorientation: crate::reorientation_work::current_reorientation_work(&cache)?,
hands_frontier_ready: crate::runtime_has_actionable_hands_frontier(store_path)?,
})
}
@ -2119,7 +2121,7 @@ pub fn accept_frontier_verdict_modeling_result(
}
}
fn commit_current_work_launch(
pub(crate) fn commit_current_work_launch(
store_path: &Path,
cache: &CultCache,
mut expected: Vec<CultCacheEnvelope>,

View file

@ -41,6 +41,7 @@ mod prompt_context;
mod proposal;
mod public_source_identity;
mod reasoning_context;
mod reorientation_work;
mod repo_model_documents;
mod repo_model_gateway;
mod repository_body_observer;
@ -106,8 +107,6 @@ pub use agent_launch::build_epiphany_admitted_model_direction_consideration_laun
pub use agent_launch::build_epiphany_imagination_consideration_launch_request;
pub use agent_launch::build_epiphany_job_launch_request;
pub use agent_launch::build_epiphany_reorient_launch_instruction;
pub use agent_launch::build_epiphany_reorient_launch_request;
pub use agent_launch::build_epiphany_reorient_launch_request_with_dynamic_context;
pub use agent_launch::build_epiphany_role_launch_request;
pub use agent_launch::build_epiphany_role_launch_request_with_dynamic_context;
pub use agent_launch::epiphany_admitted_model_direction_consideration_output_schema;
@ -203,22 +202,16 @@ pub use causal_work_identity::*;
pub use continuity_gateway::*;
pub use coordinator_acceptance::EpiphanyAcceptancePrerequisite;
pub use coordinator_acceptance::EpiphanyCoordinatorAdmissionError;
pub use coordinator_acceptance::EpiphanyNativeReorientAcceptance;
pub use coordinator_acceptance::EpiphanyNativeRoleAcceptance;
pub use coordinator_acceptance::EpiphanyReorientAcceptanceUpdate;
pub use coordinator_acceptance::EpiphanyRoleAcceptanceUpdate;
pub use coordinator_acceptance::accept_coordinator_reorient_finding;
pub use coordinator_acceptance::accept_coordinator_role_finding;
pub use coordinator_acceptance::acceptance_launch_contract_for_binding;
pub use coordinator_acceptance::build_native_reorient_acceptance_update;
pub use coordinator_acceptance::build_native_role_acceptance_update;
pub use coordinator_acceptance::completed_reorient_finding;
pub use coordinator_acceptance::completed_role_finding;
pub use coordinator_acceptance::coordinator_acceptance_cache;
pub use coordinator_acceptance::enforce_acceptance_receipt_proofs;
pub use coordinator_acceptance::load_launch_organ_contract;
pub use coordinator_acceptance::read_accepted_coordinator_state;
pub use coordinator_acceptance::reorient_acceptance_claimed_effects;
pub use coordinator_acceptance::role_acceptance_claimed_effects;
pub use coordinator_acceptance::role_acceptance_enforceable_receipts;
pub use coordinator_acceptance::state_update_from_role_patch;
@ -974,6 +967,7 @@ pub use proposal::EpiphanyMapProposalInput;
pub use proposal::propose_map_update;
pub use public_source_identity::ImmutableGithubSource;
pub use reasoning_context::*;
pub use reorientation_work::*;
pub use repo_model_documents::*;
pub use repo_model_gateway::*;
pub use repository_body_observer::*;
@ -1267,15 +1261,7 @@ pub use surfaces::EpiphanyPressureStatus;
pub use surfaces::EpiphanyReorientAcceptanceBundle;
pub use surfaces::EpiphanyReorientAcceptanceFinding;
pub use surfaces::EpiphanyReorientAction;
pub use surfaces::EpiphanyReorientCheckpointStatus;
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;
pub use surfaces::EpiphanyReorientWorkerLaunchDocument;
pub use surfaces::EpiphanyRetrievalFreshness;
pub use surfaces::EpiphanyRetrievalFreshnessStatus;
@ -1327,7 +1313,6 @@ pub use surfaces::RepoFrontierProposalModelingContextProjection;
pub use surfaces::RepoFrontierResearchContextProjection;
pub use surfaces::RepoFrontierVerificationContextProjection;
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::default_epiphany_view_lenses;
pub use surfaces::derive_context;
@ -1347,7 +1332,6 @@ pub use surfaces::interpret_runtime_reorient_worker_result;
pub use surfaces::interpret_runtime_role_worker_result;
pub use surfaces::modeling_role_state_patch_policy_errors;
pub use surfaces::recommend_crrc_action;
pub use surfaces::recommend_reorientation;
pub use surfaces::render_role_board_note;
pub use surfaces::reorientation_role_status;
pub use surfaces::research_role_state_patch_policy_errors;

View file

@ -263,6 +263,8 @@ pub struct EpiphanyMindView {
pub observations: Vec<EpiphanyObservation>,
pub evidence: Vec<EpiphanyEvidenceRecord>,
pub verification_audits: Vec<EpiphanyMindVerificationAuditDocument>,
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
pub mode: Option<EpiphanyModeState>,
pub planning: EpiphanyPlanningState,
@ -280,6 +282,8 @@ pub(crate) fn register_mind_document_types(cache: &mut CultCache) -> Result<()>
cache.register_entry_type::<EpiphanyMindObservationDocument>()?;
cache.register_entry_type::<EpiphanyMindEvidenceDocument>()?;
cache.register_entry_type::<EpiphanyMindVerificationAuditDocument>()?;
cache.register_entry_type::<crate::EpiphanyMindReorientationDecisionDocument>()?;
cache.register_entry_type::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
cache.register_entry_type::<EpiphanyMindInvestigationCheckpointDocument>()?;
cache.register_entry_type::<EpiphanyMindPlanningCaptureDocument>()?;
cache.register_entry_type::<EpiphanyMindBacklogItemDocument>()?;
@ -398,6 +402,28 @@ pub(crate) fn validate_mind_write_envelope(envelope: &CultCacheEnvelope) -> Resu
rmp_serde::from_slice(&envelope.payload)?;
value.validate()?;
value.audit_id
} else if envelope.r#type == crate::EpiphanyMindReorientationDecisionDocument::TYPE {
let value: crate::EpiphanyMindReorientationDecisionDocument =
rmp_serde::from_slice(&envelope.payload)?;
if value.schema_version != crate::MIND_REORIENTATION_DECISION_SCHEMA_VERSION
|| value.decision_id.trim().is_empty()
|| value.request_id.trim().is_empty()
|| value.decision_context_id.trim().is_empty()
{
return Err(anyhow!("Mind reorientation decision is invalid"));
}
value.decision_id
} else if envelope.r#type == crate::EpiphanyMindReorientationPassFailureDocument::TYPE {
let value: crate::EpiphanyMindReorientationPassFailureDocument =
rmp_serde::from_slice(&envelope.payload)?;
if value.schema_version != crate::MIND_REORIENTATION_PASS_FAILURE_SCHEMA_VERSION
|| value.failure_id.trim().is_empty()
|| value.request_id.trim().is_empty()
|| value.decision_context_id.trim().is_empty()
{
return Err(anyhow!("Mind reorientation failure is invalid"));
}
value.failure_id
} else if envelope.r#type == EpiphanyMindInvestigationCheckpointDocument::TYPE {
rmp_serde::from_slice::<EpiphanyMindInvestigationCheckpointDocument>(&envelope.payload)?
.value
@ -462,6 +488,10 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
values::<EpiphanyMindObservationDocument, _>(&cache, |value| value.value)?;
let mut evidence = values::<EpiphanyMindEvidenceDocument, _>(&cache, |value| value.value)?;
let mut verification_audits = cache.get_all::<EpiphanyMindVerificationAuditDocument>()?;
let mut reorientation_decisions =
cache.get_all::<crate::EpiphanyMindReorientationDecisionDocument>()?;
let mut reorientation_failures =
cache.get_all::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
let checkpoints =
values::<EpiphanyMindInvestigationCheckpointDocument, _>(&cache, |value| value.value)?;
let mut captures =
@ -503,6 +533,8 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
observations.sort_by(|left, right| left.id.cmp(&right.id));
evidence.sort_by(|left, right| left.id.cmp(&right.id));
verification_audits.sort_by(|left, right| left.audit_id.cmp(&right.audit_id));
reorientation_decisions.sort_by(|left, right| left.decision_id.cmp(&right.decision_id));
reorientation_failures.sort_by(|left, right| left.failure_id.cmp(&right.failure_id));
captures.sort_by(|left, right| left.id.cmp(&right.id));
backlog_items.sort_by(|left, right| left.id.cmp(&right.id));
roadmap_streams.sort_by(|left, right| left.id.cmp(&right.id));
@ -545,6 +577,8 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
observations,
evidence,
verification_audits,
reorientation_decisions,
reorientation_failures,
investigation_checkpoint,
mode,
planning: EpiphanyPlanningState {
@ -583,6 +617,8 @@ fn canonical_mind_versions(
| EpiphanyMindObservationDocument::TYPE
| EpiphanyMindEvidenceDocument::TYPE
| EpiphanyMindVerificationAuditDocument::TYPE
| crate::EpiphanyMindReorientationDecisionDocument::TYPE
| crate::EpiphanyMindReorientationPassFailureDocument::TYPE
| EpiphanyMindInvestigationCheckpointDocument::TYPE
| EpiphanyMindPlanningCaptureDocument::TYPE
| EpiphanyMindBacklogItemDocument::TYPE

View file

@ -110,6 +110,8 @@ pub struct EpiphanyMindPromptProjection {
pub observations: Vec<epiphany_state_model::EpiphanyObservation>,
pub evidence: Vec<epiphany_state_model::EpiphanyEvidenceRecord>,
pub verification_audits: Vec<crate::EpiphanyMindVerificationAuditDocument>,
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
pub investigation_checkpoint: Option<epiphany_state_model::EpiphanyInvestigationCheckpoint>,
pub mode: Option<epiphany_state_model::EpiphanyModeState>,
pub planning: epiphany_state_model::EpiphanyPlanningState,
@ -130,6 +132,8 @@ impl From<crate::EpiphanyMindView> for EpiphanyMindPromptProjection {
observations: value.observations,
evidence: value.evidence,
verification_audits: value.verification_audits,
reorientation_decisions: value.reorientation_decisions,
reorientation_failures: value.reorientation_failures,
investigation_checkpoint: value.investigation_checkpoint,
mode: value.mode,
planning: value.planning,
@ -145,6 +149,12 @@ pub struct EpiphanyRoleReasoningProjection {
pub mind: EpiphanyMindPromptProjection,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EpiphanyReorientationReasoningProjection {
pub authority: crate::EpiphanyReorientWorkerLaunchDocument,
pub request: crate::EpiphanyReorientationRequest,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EpiphanyMindDocumentVersion {
pub store_id: String,
@ -194,7 +204,7 @@ impl EpiphanyMindDocumentVersion {
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EpiphanyReasoningProjection {
RolePass(EpiphanyRoleReasoningProjection),
ReorientLaunch(crate::EpiphanyReorientWorkerLaunchDocument),
ReorientLaunch(EpiphanyReorientationReasoningProjection),
PersonaProjector(PersonaProjectorInput),
PersonaTurn(PersonaTurnInput),
PersonaInterpreter(PersonaInterpreterInput),
@ -462,7 +472,7 @@ pub fn worker_reasoning_basis(
) -> Result<EpiphanyReasoningBasis> {
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
let launch_envelope = cache
cache
.get_envelope::<EpiphanyRuntimeWorkerLaunchRequest>(&launch.job_id)?
.ok_or_else(|| anyhow!("worker reasoning basis lost its launch envelope"))?;
let launch_document = launch.launch_document()?;
@ -478,13 +488,21 @@ pub fn worker_reasoning_basis(
}),
)
}
EpiphanyWorkerLaunchDocument::Reorient(document) => (
vec![EpiphanyMindDocumentVersion::from_envelope(
"epiphany-mind",
&launch_envelope,
)?],
EpiphanyReasoningProjection::ReorientLaunch(document),
),
EpiphanyWorkerLaunchDocument::Reorient(document) => {
let request = cache
.get::<crate::EpiphanyReorientationRequest>(&document.request_id)?
.ok_or_else(|| anyhow!("reorientation reasoning basis lost its request"))?;
crate::reorientation_work::validate_reorientation_request_current(&cache, &request)?;
(
request.source_documents.clone(),
EpiphanyReasoningProjection::ReorientLaunch(
EpiphanyReorientationReasoningProjection {
authority: document,
request,
},
),
)
}
};
EpiphanyReasoningBasis::new(
&launch.job_id,
@ -545,29 +563,54 @@ pub fn put_reasoning_basis(
basis.validate()?;
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
if let EpiphanyReasoningProjection::RolePass(projection) = basis.projection()? {
let launch = cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
.ok_or_else(|| anyhow!("role reasoning basis lost its worker launch"))?;
let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else {
return Err(anyhow!("role reasoning basis cites a non-role launch"));
};
let current_mind = crate::assemble_mind_view(store_path)?;
let modeling_body_mismatch = projection
.authority
.role_id
.eq_ignore_ascii_case("modeling")
&& projection.authority.repository_body_observation_basis
!= current_mind.repository_body_observation;
if projection.authority != document.into()
|| projection.mind != current_mind.clone().into()
|| basis.source_documents != current_mind.source_documents
|| modeling_body_mismatch
{
return Err(anyhow!(
"role reasoning projection diverges from its exact launch or keyed Mind sources"
));
match basis.projection()? {
EpiphanyReasoningProjection::RolePass(projection) => {
let launch = cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
.ok_or_else(|| anyhow!("role reasoning basis lost its worker launch"))?;
let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else {
return Err(anyhow!("role reasoning basis cites a non-role launch"));
};
let current_mind = crate::assemble_mind_view(store_path)?;
let modeling_body_mismatch = projection
.authority
.role_id
.eq_ignore_ascii_case("modeling")
&& projection.authority.repository_body_observation_basis
!= current_mind.repository_body_observation;
if projection.authority != document.into()
|| projection.mind != current_mind.clone().into()
|| basis.source_documents != current_mind.source_documents
|| modeling_body_mismatch
{
return Err(anyhow!(
"role reasoning projection diverges from its exact launch or keyed Mind sources"
));
}
}
EpiphanyReasoningProjection::ReorientLaunch(projection) => {
let launch = cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
.ok_or_else(|| anyhow!("reorientation reasoning basis lost its worker launch"))?;
let EpiphanyWorkerLaunchDocument::Reorient(document) = launch.launch_document()? else {
return Err(anyhow!("reorientation basis cites a non-reorient launch"));
};
if projection.authority != document
|| projection.request.request_id != document.request_id
|| projection.request.source_documents != basis.source_documents
{
return Err(anyhow!(
"reorientation reasoning projection diverges from its request"
));
}
crate::reorientation_work::validate_reorientation_request_current(
&cache,
&projection.request,
)?;
}
EpiphanyReasoningProjection::PersonaProjector(_)
| EpiphanyReasoningProjection::PersonaTurn(_)
| EpiphanyReasoningProjection::PersonaInterpreter(_) => {}
}
for source in &basis.source_documents {
if source.store_id != "epiphany-mind" {
@ -1334,6 +1377,8 @@ mod tests {
observations: Vec::new(),
evidence: Vec::new(),
verification_audits: Vec::new(),
reorientation_decisions: Vec::new(),
reorientation_failures: Vec::new(),
investigation_checkpoint: None,
mode: None,
planning: Default::default(),

File diff suppressed because it is too large Load diff

View file

@ -1161,6 +1161,10 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
cache.register_entry_type::<crate::ImaginationConsiderationReviewRequest>()?;
cache.register_entry_type::<RepoFrontierPlanCandidate>()?;
cache.register_entry_type::<RepoFrontierPlanMindRequest>()?;
cache.register_entry_type::<crate::EpiphanyReorientationRequest>()?;
cache.register_entry_type::<crate::EpiphanyReorientationLaunchBinding>()?;
cache.register_entry_type::<crate::EpiphanyMindReorientationDecisionDocument>()?;
cache.register_entry_type::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
cache.register_entry_type::<RepoFrontierPlanMindLaunchBinding>()?;
cache.register_entry_type::<RepoFrontierVerificationRequest>()?;
cache.register_entry_type::<EpiphanyRuntimeReorientWorkerResult>()?;

View file

@ -57,8 +57,7 @@ pub fn derive_coordinator_status(
.iter()
.map(coordinator_role_lane_from_role_board)
.collect();
let routing_recommendation =
routing_crrc_recommendation(&input.recommendation, input.crrc_regather_current);
let routing_recommendation = routing_crrc_recommendation(&input.recommendation);
let decision = recommend_coordinator_action(EpiphanyCoordinatorInput {
state_status: input.state_status,
checkpoint_present: input.checkpoint_present,
@ -110,14 +109,7 @@ pub fn derive_coordinator_status(
fn routing_crrc_recommendation(
recommendation: &epiphany_core::EpiphanyCrrcRecommendation,
regather_current: bool,
) -> EpiphanyCoordinatorCrrcRecommendation {
if recommendation.action == EpiphanyCrrcAction::RegatherManually && !regather_current {
return EpiphanyCoordinatorCrrcRecommendation {
action: EpiphanyCrrcAction::Continue,
recommended_scene_action: None,
};
}
EpiphanyCoordinatorCrrcRecommendation {
action: recommendation.action,
recommended_scene_action: recommendation
@ -1057,26 +1049,6 @@ mod tests {
use epiphany_state_model::EpiphanyAcceptanceReceipt;
use epiphany_state_model::EpiphanyEvidenceRecord;
#[test]
fn relinquishment_demotes_older_manual_regather_to_observation_only() {
let recommendation = epiphany_core::EpiphanyCrrcRecommendation {
action: EpiphanyCrrcAction::RegatherManually,
recommended_scene_action: Some(EpiphanyCrrcSceneAction::ReorientResult),
reason: "historical failed reorientation".to_string(),
};
let stale = routing_crrc_recommendation(&recommendation, false);
assert_eq!(stale.action, EpiphanyCrrcAction::Continue);
assert_eq!(stale.recommended_scene_action, None);
let current = routing_crrc_recommendation(&recommendation, true);
assert_eq!(current.action, EpiphanyCrrcAction::RegatherManually);
assert_eq!(
current.recommended_scene_action,
Some(EpiphanyCoordinatorSceneAction::ReorientResult)
);
}
fn base_roles() -> Vec<EpiphanyCoordinatorRoleLane> {
vec![
role(

View file

@ -194,10 +194,6 @@ pub struct EpiphanyCoordinatorStatusInput {
pub checkpoint_present: bool,
pub pressure: EpiphanyPressure,
pub recommendation: EpiphanyCrrcRecommendation,
/// True when CRRC's manual-regather pressure is causally newer than the
/// latest typed frontier relinquishment. Raw CRRC remains observable when
/// false, but it cannot route Self from stale continuity history.
pub crrc_regather_current: bool,
pub roles: Vec<EpiphanyRoleBoardLane>,
pub reorient_action: EpiphanyReorientAction,
pub research_result_status: EpiphanyCoordinatorRoleResultStatus,

View file

@ -61,14 +61,6 @@ pub use pressure::EpiphanyPressureStatus;
pub use pressure::EpiphanyTokenUsageSnapshot;
pub use pressure::derive_pressure_view;
pub use reorient::EpiphanyReorientAction;
pub use reorient::EpiphanyReorientCheckpointStatus;
pub use reorient::EpiphanyReorientDecision;
pub use reorient::EpiphanyReorientFreshnessStatus;
pub use reorient::EpiphanyReorientInput;
pub use reorient::EpiphanyReorientPressureLevel;
pub use reorient::EpiphanyReorientReason;
pub use reorient::EpiphanyReorientStateStatus;
pub use reorient::recommend_reorientation;
pub use role_board::EpiphanyRoleBoardCheckpointSummary;
pub use role_board::EpiphanyRoleBoardInput;
pub use role_board::EpiphanyRoleBoardJob;
@ -119,7 +111,6 @@ pub use view::epiphany_view_needs_pressure;
pub use view::epiphany_view_needs_reorientation_inputs;
pub use view::epiphany_view_needs_runtime_store;
pub use worker_launch::AdmittedModelDirectionConsiderationContextProjection;
pub use worker_launch::EpiphanyReorientLaunchRequestInput;
pub use worker_launch::EpiphanyReorientWorkerLaunchDocument;
pub use worker_launch::EpiphanyRoleWorkerLaunchDocument;
pub use worker_launch::EpiphanyWorkerLaunchDocument;
@ -141,4 +132,3 @@ pub use worker_launch::RepoFrontierPlanningContextProjection;
pub use worker_launch::RepoFrontierProposalModelingContextProjection;
pub use worker_launch::RepoFrontierResearchContextProjection;
pub use worker_launch::RepoFrontierVerificationContextProjection;
pub use worker_launch::build_reorient_job_launch_request;

View file

@ -1,566 +1,13 @@
use epiphany_state_model::EpiphanyInvestigationCheckpoint;
use epiphany_state_model::EpiphanyInvestigationDisposition;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
/// Display projection of an accepted keyed continuity decision.
///
/// This enum does not decide whether Reorientation should run. The keyed
/// current-work owner in `reorientation_work` owns launch, wait, review, and
/// admission behavior.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientAction {
Resume,
Regather,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientStateStatus {
Missing,
Ready,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientCheckpointStatus {
Missing,
ResumeReady,
RegatherRequired,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientReason {
MissingState,
MissingCheckpoint,
CheckpointReady,
CheckpointRequestedRegather,
CheckpointPathsDirty,
CheckpointPathsChanged,
FrontierChanged,
UnanchoredCheckpointWhileStateStale,
RetrievalFreshnessBlocked,
GraphFreshnessBlocked,
WatcherFreshnessBlocked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientPressureLevel {
Unknown,
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum EpiphanyReorientFreshnessStatus {
Unknown,
Clean,
Dirty,
Stale,
Changed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EpiphanyReorientInput<'a> {
pub checkpoint: Option<&'a EpiphanyInvestigationCheckpoint>,
pub state_present: bool,
pub pressure_level: EpiphanyReorientPressureLevel,
pub retrieval_status: EpiphanyReorientFreshnessStatus,
pub retrieval_dirty_paths: Vec<PathBuf>,
pub graph_status: EpiphanyReorientFreshnessStatus,
pub graph_dirty_paths: Vec<PathBuf>,
pub watcher_status: EpiphanyReorientFreshnessStatus,
pub watcher_changed_paths: Vec<PathBuf>,
pub watcher_graph_node_ids: Vec<String>,
pub active_frontier_node_ids: Vec<String>,
pub watched_root: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EpiphanyReorientDecision {
pub action: EpiphanyReorientAction,
pub checkpoint_status: EpiphanyReorientCheckpointStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint_id: Option<String>,
pub pressure_level: EpiphanyReorientPressureLevel,
pub retrieval_status: EpiphanyReorientFreshnessStatus,
pub graph_status: EpiphanyReorientFreshnessStatus,
pub watcher_status: EpiphanyReorientFreshnessStatus,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reasons: Vec<EpiphanyReorientReason>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub checkpoint_dirty_paths: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub checkpoint_changed_paths: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub active_frontier_node_ids: Vec<String>,
pub next_action: String,
pub note: String,
}
pub fn recommend_reorientation(
input: EpiphanyReorientInput<'_>,
) -> (EpiphanyReorientStateStatus, EpiphanyReorientDecision) {
let build_decision = |action: EpiphanyReorientAction,
checkpoint_status: EpiphanyReorientCheckpointStatus,
checkpoint_id: Option<String>,
reasons: Vec<EpiphanyReorientReason>,
checkpoint_dirty_paths: Vec<PathBuf>,
checkpoint_changed_paths: Vec<PathBuf>,
active_frontier_node_ids: Vec<String>,
next_action: String,
note: String| EpiphanyReorientDecision {
action,
checkpoint_status,
checkpoint_id,
pressure_level: input.pressure_level,
retrieval_status: input.retrieval_status,
graph_status: input.graph_status,
watcher_status: input.watcher_status,
reasons,
checkpoint_dirty_paths,
checkpoint_changed_paths,
active_frontier_node_ids,
next_action,
note,
};
let high_pressure = matches!(
input.pressure_level,
EpiphanyReorientPressureLevel::High | EpiphanyReorientPressureLevel::Critical
);
if !input.state_present {
let note = if high_pressure {
"No Epiphany state survived, and the last recorded context pressure was high; re-gather before editing."
.to_string()
} else {
"No Epiphany state survived, so there is no authoritative checkpoint to resume."
.to_string()
};
return (
EpiphanyReorientStateStatus::Missing,
build_decision(
EpiphanyReorientAction::Regather,
EpiphanyReorientCheckpointStatus::Missing,
None,
vec![
EpiphanyReorientReason::MissingState,
EpiphanyReorientReason::MissingCheckpoint,
],
Vec::new(),
Vec::new(),
Vec::new(),
"Re-gather source context before editing.".to_string(),
note,
),
);
}
let Some(checkpoint) = input.checkpoint else {
let note = if high_pressure {
"Epiphany state survived, but no durable investigation checkpoint was banked before high context pressure; re-gather before editing."
.to_string()
} else {
"Epiphany state survived, but there is no durable investigation checkpoint to resume from."
.to_string()
};
return (
EpiphanyReorientStateStatus::Ready,
build_decision(
EpiphanyReorientAction::Regather,
EpiphanyReorientCheckpointStatus::Missing,
None,
vec![EpiphanyReorientReason::MissingCheckpoint],
Vec::new(),
Vec::new(),
Vec::new(),
"Re-gather source context before editing.".to_string(),
note,
),
);
};
let checkpoint_status = match checkpoint.disposition {
EpiphanyInvestigationDisposition::ResumeReady => {
EpiphanyReorientCheckpointStatus::ResumeReady
}
EpiphanyInvestigationDisposition::RegatherRequired => {
EpiphanyReorientCheckpointStatus::RegatherRequired
}
};
let workspace_root = input.watched_root.as_deref();
let checkpoint_dirty_paths = overlapping_checkpoint_paths(
checkpoint,
input
.retrieval_dirty_paths
.iter()
.chain(input.graph_dirty_paths.iter()),
workspace_root,
);
let checkpoint_changed_paths = overlapping_checkpoint_paths(
checkpoint,
input.watcher_changed_paths.iter(),
workspace_root,
);
let frontier_changed = !input.active_frontier_node_ids.is_empty();
let unanchored_checkpoint_while_state_stale = checkpoint.code_refs.is_empty()
&& (!input.retrieval_dirty_paths.is_empty()
|| !input.graph_dirty_paths.is_empty()
|| !input.watcher_graph_node_ids.is_empty()
|| frontier_changed);
let mut reasons = Vec::new();
if checkpoint.disposition == EpiphanyInvestigationDisposition::RegatherRequired {
reasons.push(EpiphanyReorientReason::CheckpointRequestedRegather);
}
if !checkpoint_dirty_paths.is_empty() {
reasons.push(EpiphanyReorientReason::CheckpointPathsDirty);
}
if !checkpoint_changed_paths.is_empty() {
reasons.push(EpiphanyReorientReason::CheckpointPathsChanged);
}
if frontier_changed {
reasons.push(EpiphanyReorientReason::FrontierChanged);
}
if unanchored_checkpoint_while_state_stale {
reasons.push(EpiphanyReorientReason::UnanchoredCheckpointWhileStateStale);
}
let retrieval_blocked = input.retrieval_status != EpiphanyReorientFreshnessStatus::Clean;
let graph_blocked = input.graph_status != EpiphanyReorientFreshnessStatus::Clean;
let watcher_blocked = matches!(
input.watcher_status,
EpiphanyReorientFreshnessStatus::Dirty
| EpiphanyReorientFreshnessStatus::Stale
| EpiphanyReorientFreshnessStatus::Changed
);
if retrieval_blocked {
reasons.push(EpiphanyReorientReason::RetrievalFreshnessBlocked);
}
if graph_blocked {
reasons.push(EpiphanyReorientReason::GraphFreshnessBlocked);
}
if watcher_blocked {
reasons.push(EpiphanyReorientReason::WatcherFreshnessBlocked);
}
let should_regather = checkpoint.disposition
== EpiphanyInvestigationDisposition::RegatherRequired
|| !checkpoint_dirty_paths.is_empty()
|| !checkpoint_changed_paths.is_empty()
|| frontier_changed
|| unanchored_checkpoint_while_state_stale
|| retrieval_blocked
|| graph_blocked
|| watcher_blocked;
if !should_regather {
reasons.push(EpiphanyReorientReason::CheckpointReady);
return (
EpiphanyReorientStateStatus::Ready,
build_decision(
EpiphanyReorientAction::Resume,
checkpoint_status,
Some(checkpoint.checkpoint_id.clone()),
reasons,
Vec::new(),
Vec::new(),
Vec::new(),
checkpoint.next_action.clone().unwrap_or_else(|| {
"Resume from the durable checkpoint focus and verify the seam before broad edits."
.to_string()
}),
"Resume-ready checkpoint remains aligned with current freshness and watcher signals."
.to_string(),
),
);
}
let mut note_fragments = Vec::new();
if checkpoint.disposition == EpiphanyInvestigationDisposition::RegatherRequired {
note_fragments.push("the checkpoint explicitly requests re-gather".to_string());
}
if !checkpoint_dirty_paths.is_empty() {
note_fragments.push(format!(
"{} checkpoint path(s) are already marked dirty",
checkpoint_dirty_paths.len()
));
}
if !checkpoint_changed_paths.is_empty() {
note_fragments.push(format!(
"watcher observed {} changed checkpoint path(s)",
checkpoint_changed_paths.len()
));
}
if frontier_changed {
note_fragments.push(format!(
"watcher hit {} active frontier node(s)",
input.active_frontier_node_ids.len()
));
}
if unanchored_checkpoint_while_state_stale {
note_fragments.push(
"the checkpoint has no code refs while freshness signals already show drift"
.to_string(),
);
}
if retrieval_blocked {
note_fragments.push(format!(
"retrieval freshness is {:?}, not clean",
input.retrieval_status
));
}
if graph_blocked {
note_fragments.push(format!(
"graph freshness is {:?}, not clean",
input.graph_status
));
}
if watcher_blocked {
note_fragments.push(format!(
"watcher freshness is {:?} and reports invalidation pressure",
input.watcher_status
));
}
(
EpiphanyReorientStateStatus::Ready,
build_decision(
EpiphanyReorientAction::Regather,
checkpoint_status,
Some(checkpoint.checkpoint_id.clone()),
reasons,
checkpoint_dirty_paths,
checkpoint_changed_paths,
input.active_frontier_node_ids,
checkpoint
.next_action
.clone()
.unwrap_or_else(|| "Re-gather source context before editing.".to_string()),
format!("Re-gather before editing: {}.", note_fragments.join("; ")),
),
)
}
fn overlapping_checkpoint_paths<'a>(
checkpoint: &EpiphanyInvestigationCheckpoint,
candidate_paths: impl IntoIterator<Item = &'a PathBuf>,
workspace_root: Option<&Path>,
) -> Vec<PathBuf> {
let checkpoint_path_keys: HashSet<String> = checkpoint
.code_refs
.iter()
.map(|code_ref| code_ref_path_key(code_ref.path.as_path(), workspace_root))
.collect();
if checkpoint_path_keys.is_empty() {
return Vec::new();
}
let mut overlaps = Vec::new();
let mut seen = HashSet::new();
for path in candidate_paths {
let path_key = code_ref_path_key(path.as_path(), workspace_root);
if checkpoint_path_keys.contains(&path_key) && seen.insert(path_key) {
overlaps.push(path.clone());
}
}
overlaps
}
fn code_ref_path_key(path: &Path, workspace_root: Option<&Path>) -> String {
if let Some(workspace_root) = workspace_root
&& let Ok(relative_path) = path.strip_prefix(workspace_root)
{
return epiphany_path_key(relative_path);
}
epiphany_path_key(path)
}
fn epiphany_path_key(path: &Path) -> String {
path.to_string_lossy()
.replace('\\', "/")
.trim_start_matches("./")
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use epiphany_state_model::EpiphanyCodeRef;
fn checkpoint() -> EpiphanyInvestigationCheckpoint {
EpiphanyInvestigationCheckpoint {
checkpoint_id: "ix-1".to_string(),
kind: "implementation".to_string(),
disposition: EpiphanyInvestigationDisposition::ResumeReady,
focus: "runtime seam".to_string(),
summary: Some("resume here".to_string()),
next_action: Some("Continue from the seam.".to_string()),
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()],
}
}
fn input(checkpoint: Option<&EpiphanyInvestigationCheckpoint>) -> EpiphanyReorientInput<'_> {
EpiphanyReorientInput {
checkpoint,
state_present: true,
pressure_level: EpiphanyReorientPressureLevel::Low,
retrieval_status: EpiphanyReorientFreshnessStatus::Clean,
retrieval_dirty_paths: Vec::new(),
graph_status: EpiphanyReorientFreshnessStatus::Clean,
graph_dirty_paths: Vec::new(),
watcher_status: EpiphanyReorientFreshnessStatus::Clean,
watcher_changed_paths: Vec::new(),
watcher_graph_node_ids: Vec::new(),
active_frontier_node_ids: Vec::new(),
watched_root: None,
}
}
#[test]
fn resumes_when_checkpoint_is_clean() {
let checkpoint = checkpoint();
let (state_status, decision) = recommend_reorientation(input(Some(&checkpoint)));
assert_eq!(state_status, EpiphanyReorientStateStatus::Ready);
assert_eq!(decision.action, EpiphanyReorientAction::Resume);
assert_eq!(decision.checkpoint_id.as_deref(), Some("ix-1"));
assert_eq!(
decision.reasons,
vec![EpiphanyReorientReason::CheckpointReady]
);
}
#[test]
fn regathers_when_checkpoint_path_changes() {
let checkpoint = checkpoint();
let mut input = input(Some(&checkpoint));
input.watcher_status = EpiphanyReorientFreshnessStatus::Changed;
input.watcher_changed_paths = vec![PathBuf::from("./src/lib.rs")];
let (_state_status, decision) = recommend_reorientation(input);
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
assert_eq!(
decision.reasons,
vec![
EpiphanyReorientReason::CheckpointPathsChanged,
EpiphanyReorientReason::WatcherFreshnessBlocked,
]
);
assert_eq!(
decision.checkpoint_changed_paths,
vec![PathBuf::from("./src/lib.rs")]
);
}
#[test]
fn freshness_authorities_block_resume_but_unknown_watcher_does_not() {
let checkpoint = checkpoint();
for (field, status, reason) in [
(
"retrieval",
EpiphanyReorientFreshnessStatus::Unknown,
EpiphanyReorientReason::RetrievalFreshnessBlocked,
),
(
"retrieval",
EpiphanyReorientFreshnessStatus::Dirty,
EpiphanyReorientReason::RetrievalFreshnessBlocked,
),
(
"retrieval",
EpiphanyReorientFreshnessStatus::Stale,
EpiphanyReorientReason::RetrievalFreshnessBlocked,
),
(
"retrieval",
EpiphanyReorientFreshnessStatus::Changed,
EpiphanyReorientReason::RetrievalFreshnessBlocked,
),
(
"graph",
EpiphanyReorientFreshnessStatus::Unknown,
EpiphanyReorientReason::GraphFreshnessBlocked,
),
(
"graph",
EpiphanyReorientFreshnessStatus::Dirty,
EpiphanyReorientReason::GraphFreshnessBlocked,
),
(
"graph",
EpiphanyReorientFreshnessStatus::Stale,
EpiphanyReorientReason::GraphFreshnessBlocked,
),
(
"graph",
EpiphanyReorientFreshnessStatus::Changed,
EpiphanyReorientReason::GraphFreshnessBlocked,
),
(
"watcher",
EpiphanyReorientFreshnessStatus::Dirty,
EpiphanyReorientReason::WatcherFreshnessBlocked,
),
(
"watcher",
EpiphanyReorientFreshnessStatus::Stale,
EpiphanyReorientReason::WatcherFreshnessBlocked,
),
(
"watcher",
EpiphanyReorientFreshnessStatus::Changed,
EpiphanyReorientReason::WatcherFreshnessBlocked,
),
] {
let mut candidate = input(Some(&checkpoint));
match field {
"retrieval" => candidate.retrieval_status = status,
"graph" => candidate.graph_status = status,
"watcher" => candidate.watcher_status = status,
_ => unreachable!(),
}
let (_, decision) = recommend_reorientation(candidate);
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
assert!(decision.reasons.contains(&reason));
}
let mut candidate = input(Some(&checkpoint));
candidate.watcher_status = EpiphanyReorientFreshnessStatus::Unknown;
let (_, decision) = recommend_reorientation(candidate);
assert_eq!(decision.action, EpiphanyReorientAction::Resume);
}
#[test]
fn missing_state_regathers() {
let mut input = input(None);
input.state_present = false;
input.pressure_level = EpiphanyReorientPressureLevel::High;
let (state_status, decision) = recommend_reorientation(input);
assert_eq!(state_status, EpiphanyReorientStateStatus::Missing);
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
assert!(
decision
.reasons
.contains(&EpiphanyReorientReason::MissingState)
);
assert!(decision.note.contains("high"));
}
}

View file

@ -1,28 +1,16 @@
use epiphany_state_model::EpiphanyChurnState;
use epiphany_state_model::EpiphanyCodeRef;
use epiphany_state_model::EpiphanyEvidenceRecord;
use epiphany_state_model::EpiphanyGraphCheckpoint;
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.v3";
pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0";
@ -80,14 +68,14 @@ impl EpiphanyWorkerLaunchDocument {
pub fn thread_id(&self) -> &str {
match self {
Self::Role(document) => &document.thread_id,
Self::Reorient(document) => &document.thread_id,
Self::Reorient(document) => &document.creation_thread_id,
}
}
pub fn dynamic_prompt_context(&self) -> Option<&str> {
match self {
Self::Role(document) => document.dynamic_prompt_context.as_deref(),
Self::Reorient(document) => document.dynamic_prompt_context.as_deref(),
Self::Reorient(_) => None,
}
.map(str::trim)
.filter(|context| !context.is_empty())
@ -188,62 +176,9 @@ impl From<&crate::RepoFrontierResearchRequest> for RepoFrontierResearchContextPr
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EpiphanyReorientWorkerLaunchDocument {
pub thread_id: String,
pub mode: String,
pub checkpoint_id: String,
pub checkpoint_kind: String,
pub checkpoint_disposition: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dynamic_prompt_context: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint_focus: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint_summary: Option<String>,
pub checkpoint_next_action: String,
#[serde(default)]
pub checkpoint_open_questions: Vec<String>,
#[serde(default)]
pub checkpoint_evidence_ids: Vec<String>,
#[serde(default)]
pub checkpoint_code_refs: Vec<EpiphanyCodeRef>,
#[serde(default)]
pub decision_reasons: Vec<String>,
pub decision_note: String,
pub pressure_level: String,
pub retrieval_status: String,
pub graph_status: String,
pub watcher_status: String,
#[serde(default)]
pub checkpoint_dirty_paths: Vec<String>,
#[serde(default)]
pub checkpoint_changed_paths: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scratch: Option<EpiphanyScratchPad>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub graphs: Option<EpiphanyGraphs>,
#[serde(default)]
pub recent_evidence: Vec<EpiphanyEvidenceRecord>,
#[serde(default)]
pub recent_observations: Vec<EpiphanyObservation>,
#[serde(default)]
pub active_frontier_node_ids: Vec<String>,
#[serde(default)]
pub linked_subgoal_ids: Vec<String>,
#[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 dynamic_prompt_context: Option<String>,
pub schema_version: String,
pub request_id: String,
pub creation_thread_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -402,301 +337,3 @@ impl RepoFrontierPlanMindContextProjection {
}
}
}
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(),
input.dynamic_prompt_context,
));
let output_contract_id = launch_document.output_contract_id().to_string();
let organ_launch_contract = crate::default_launch_organ_contract(
reorient_launch_authority_scope(input.decision.action),
launch_document.document_kind(),
&output_contract_id,
);
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,
organ_launch_contract,
max_runtime_seconds: input.max_runtime_seconds,
proposal_modeling_request_id: None,
frontier_planning_request_id: None,
frontier_plan_mind_request_id: None,
imagination_consideration_request_id: None,
admitted_model_direction_consideration_request_id: None,
repo_frontier_modeling_request_id: None,
repo_frontier_research_request_id: None,
repo_frontier_verification_request_id: None,
}
}
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>,
dynamic_prompt_context: Option<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(),
dynamic_prompt_context,
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::RetrievalFreshnessBlocked => "retrievalFreshnessBlocked",
EpiphanyReorientReason::GraphFreshnessBlocked => "graphFreshnessBlocked",
EpiphanyReorientReason::WatcherFreshnessBlocked => "watcherFreshnessBlocked",
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,
dynamic_prompt_context: Some(
"<epiphany_dynamic_context>\ncheckpoint weather\n</epiphany_dynamic_context>"
.to_string(),
),
});
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");
assert_eq!(
document.dynamic_prompt_context.as_deref(),
Some("<epiphany_dynamic_context>\ncheckpoint weather\n</epiphany_dynamic_context>")
);
}
}

View file

@ -630,7 +630,7 @@ pub fn build_worker_model_request(
(
EpiphanyWorkerLaunchDocument::Reorient(document),
epiphany_core::EpiphanyReasoningProjection::ReorientLaunch(projection),
) if projection == document => {}
) if projection.authority == *document => {}
_ => {
return Err(anyhow!(
"worker model request reasoning projection does not match its launch authority"
@ -2571,6 +2571,8 @@ mod tests {
observations: Vec::new(),
evidence: Vec::new(),
verification_audits: Vec::new(),
reorientation_decisions: Vec::new(),
reorientation_failures: Vec::new(),
investigation_checkpoint: None,
mode: None,
planning: Default::default(),