Extract Epiphany role board into core
This commit is contained in:
parent
c319d9f771
commit
f229731d7c
8 changed files with 677 additions and 301 deletions
|
|
@ -153,12 +153,22 @@ pub use surfaces::EpiphanyPressure;
|
|||
pub use surfaces::EpiphanyPressureBasis;
|
||||
pub use surfaces::EpiphanyPressureLevel;
|
||||
pub use surfaces::EpiphanyPressureStatus;
|
||||
pub use surfaces::EpiphanyRoleBoardCheckpointSummary;
|
||||
pub use surfaces::EpiphanyRoleBoardInput;
|
||||
pub use surfaces::EpiphanyRoleBoardJob;
|
||||
pub use surfaces::EpiphanyRoleBoardJobStatus;
|
||||
pub use surfaces::EpiphanyRoleBoardLane;
|
||||
pub use surfaces::EpiphanyRoleBoardPlanningSummary;
|
||||
pub use surfaces::EpiphanyViewInput;
|
||||
pub use surfaces::EpiphanyViewLens;
|
||||
pub use surfaces::EpiphanyViewOutput;
|
||||
pub use surfaces::coordinator_automation_action;
|
||||
pub use surfaces::crrc_scene_action_to_coordinator_scene_action;
|
||||
pub use surfaces::derive_pressure_view;
|
||||
pub use surfaces::derive_role_board;
|
||||
pub use surfaces::render_role_board_note;
|
||||
pub use surfaces::recommend_coordinator_action;
|
||||
pub use surfaces::recommend_crrc_action;
|
||||
pub use surfaces::reorientation_role_status;
|
||||
pub use surfaces::role_board_job_status_to_role_status;
|
||||
pub use surfaces::select_coordinator_automation_action;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod coordinator;
|
||||
mod crrc;
|
||||
mod pressure;
|
||||
mod role_board;
|
||||
|
||||
pub use coordinator::EpiphanyCoordinatorAction;
|
||||
pub use coordinator::EpiphanyCoordinatorAutomationAction;
|
||||
|
|
@ -30,6 +31,16 @@ pub use pressure::EpiphanyPressureBasis;
|
|||
pub use pressure::EpiphanyPressureLevel;
|
||||
pub use pressure::EpiphanyPressureStatus;
|
||||
pub use pressure::derive_pressure_view;
|
||||
pub use role_board::EpiphanyRoleBoardCheckpointSummary;
|
||||
pub use role_board::EpiphanyRoleBoardInput;
|
||||
pub use role_board::EpiphanyRoleBoardJob;
|
||||
pub use role_board::EpiphanyRoleBoardJobStatus;
|
||||
pub use role_board::EpiphanyRoleBoardLane;
|
||||
pub use role_board::EpiphanyRoleBoardPlanningSummary;
|
||||
pub use role_board::derive_role_board;
|
||||
pub use role_board::render_role_board_note;
|
||||
pub use role_board::reorientation_role_status;
|
||||
pub use role_board::role_board_job_status_to_role_status;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyViewInput {
|
||||
|
|
|
|||
484
epiphany-core/src/surfaces/role_board.rs
Normal file
484
epiphany-core/src/surfaces/role_board.rs
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
use super::EpiphanyCoordinatorRoleId;
|
||||
use super::EpiphanyCoordinatorRoleStatus;
|
||||
use super::EpiphanyCoordinatorSceneAction;
|
||||
use super::EpiphanyCrrcAction;
|
||||
use super::EpiphanyCrrcResultStatus;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EpiphanyRoleBoardJobStatus {
|
||||
Idle,
|
||||
Needed,
|
||||
Pending,
|
||||
Running,
|
||||
Completed,
|
||||
Failed,
|
||||
Cancelled,
|
||||
Blocked,
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyRoleBoardJob {
|
||||
pub id: String,
|
||||
pub owner_role: String,
|
||||
pub status: EpiphanyRoleBoardJobStatus,
|
||||
pub progress_note: Option<String>,
|
||||
pub blocking_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyRoleBoardPlanningSummary {
|
||||
pub capture_count: usize,
|
||||
pub backlog_item_count: usize,
|
||||
pub roadmap_stream_count: usize,
|
||||
pub objective_draft_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyRoleBoardCheckpointSummary {
|
||||
pub disposition: Option<String>,
|
||||
pub next_action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyRoleBoardInput {
|
||||
pub state_present: bool,
|
||||
pub planning: EpiphanyRoleBoardPlanningSummary,
|
||||
pub checkpoint: Option<EpiphanyRoleBoardCheckpointSummary>,
|
||||
pub reorient_next_action: String,
|
||||
pub jobs: Vec<EpiphanyRoleBoardJob>,
|
||||
pub crrc_action: EpiphanyCrrcAction,
|
||||
pub crrc_recommended_scene_action: Option<EpiphanyCoordinatorSceneAction>,
|
||||
pub crrc_reason: String,
|
||||
pub reorient_decision_action: String,
|
||||
pub pressure_level: String,
|
||||
pub reorient_result_status: EpiphanyCrrcResultStatus,
|
||||
pub reorient_job: Option<EpiphanyRoleBoardJob>,
|
||||
pub imagination_binding_id: String,
|
||||
pub modeling_binding_id: String,
|
||||
pub verification_binding_id: String,
|
||||
pub reorient_owner_role: String,
|
||||
pub imagination_owner_role: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyRoleBoardLane {
|
||||
pub id: EpiphanyCoordinatorRoleId,
|
||||
pub title: String,
|
||||
pub owner_role: String,
|
||||
pub status: EpiphanyCoordinatorRoleStatus,
|
||||
pub note: String,
|
||||
pub jobs: Vec<EpiphanyRoleBoardJob>,
|
||||
pub authority_scopes: Vec<String>,
|
||||
pub recommended_action: Option<EpiphanyCoordinatorSceneAction>,
|
||||
}
|
||||
|
||||
pub fn derive_role_board(input: EpiphanyRoleBoardInput) -> Vec<EpiphanyRoleBoardLane> {
|
||||
let state_present = input.state_present;
|
||||
let checkpoint_present = input.checkpoint.is_some();
|
||||
let imagination_jobs = input
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|job| job.id == input.imagination_binding_id)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let imagination_bound_job = imagination_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == input.imagination_binding_id);
|
||||
let imagination_has_bound_job = imagination_bound_job.is_some();
|
||||
let imagination_status = imagination_bound_job
|
||||
.map(|job| role_board_job_status_to_role_status(job.status))
|
||||
.unwrap_or_else(|| imagination_role_status(state_present, &input.planning));
|
||||
let imagination_note = imagination_bound_job
|
||||
.and_then(job_note)
|
||||
.unwrap_or_else(|| render_imagination_role_note(state_present, &input.planning));
|
||||
let imagination_recommended_action = if imagination_has_bound_job {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch)
|
||||
} else {
|
||||
Some(EpiphanyCoordinatorSceneAction::Update)
|
||||
};
|
||||
|
||||
let modeling_jobs = input
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|job| job.id == input.modeling_binding_id || job.id == "graph-remap")
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let modeling_bound_job = modeling_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == input.modeling_binding_id);
|
||||
let modeling_has_bound_job = modeling_bound_job.is_some();
|
||||
let modeling_status = modeling_role_status(
|
||||
checkpoint_present,
|
||||
modeling_bound_job.or_else(|| modeling_jobs.first()),
|
||||
);
|
||||
let modeling_recommended_action = if modeling_has_bound_job {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch)
|
||||
} else {
|
||||
Some(EpiphanyCoordinatorSceneAction::Update)
|
||||
};
|
||||
|
||||
let verification_jobs = input
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|job| job.id == input.verification_binding_id || job.id == "verification")
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let verification_bound_job = verification_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == input.verification_binding_id);
|
||||
let verification_has_bound_job = verification_bound_job.is_some();
|
||||
let verification_primary_job = verification_bound_job.or_else(|| verification_jobs.first());
|
||||
let verification_status = verification_primary_job
|
||||
.map(|job| role_board_job_status_to_role_status(job.status))
|
||||
.unwrap_or(EpiphanyCoordinatorRoleStatus::Unavailable);
|
||||
let verification_note = verification_primary_job
|
||||
.and_then(job_note)
|
||||
.unwrap_or_else(|| {
|
||||
"Review evidence, worker findings, and verifier results before promotion.".to_string()
|
||||
});
|
||||
let verification_recommended_action = if verification_has_bound_job {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let reorientation_jobs = input.reorient_job.clone().into_iter().collect::<Vec<_>>();
|
||||
|
||||
vec![
|
||||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Implementation,
|
||||
title: "Implementation".to_string(),
|
||||
owner_role: "coding-agent".to_string(),
|
||||
status: if input.crrc_action == EpiphanyCrrcAction::Continue {
|
||||
EpiphanyCoordinatorRoleStatus::Ready
|
||||
} else {
|
||||
EpiphanyCoordinatorRoleStatus::Blocked
|
||||
},
|
||||
note: if input.crrc_action == EpiphanyCrrcAction::Continue {
|
||||
"Continue the bounded coding task.".to_string()
|
||||
} else {
|
||||
format!("Wait for CRRC action: {:?}.", input.crrc_action)
|
||||
},
|
||||
jobs: input
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|job| {
|
||||
matches!(
|
||||
job.owner_role.as_str(),
|
||||
"coding-agent" | "implementation" | "epiphany-implementation"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
authority_scopes: Vec::new(),
|
||||
recommended_action: if input.crrc_action == EpiphanyCrrcAction::Continue {
|
||||
None
|
||||
} else {
|
||||
input.crrc_recommended_scene_action
|
||||
},
|
||||
},
|
||||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Imagination,
|
||||
title: "Imagination / Planning".to_string(),
|
||||
owner_role: input.imagination_owner_role,
|
||||
status: imagination_status,
|
||||
note: imagination_note,
|
||||
jobs: imagination_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/update".to_string(),
|
||||
],
|
||||
recommended_action: imagination_recommended_action,
|
||||
},
|
||||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Modeling,
|
||||
title: "Modeling / Checkpoint".to_string(),
|
||||
owner_role: "epiphany-modeler".to_string(),
|
||||
status: modeling_status,
|
||||
note: render_modeling_role_note(input.checkpoint.as_ref(), &input.reorient_next_action),
|
||||
jobs: modeling_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/update".to_string(),
|
||||
],
|
||||
recommended_action: modeling_recommended_action,
|
||||
},
|
||||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Verification,
|
||||
title: "Verification / Review".to_string(),
|
||||
owner_role: "epiphany-verifier".to_string(),
|
||||
status: verification_status,
|
||||
note: verification_note,
|
||||
jobs: verification_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/distill".to_string(),
|
||||
"thread/epiphany/propose".to_string(),
|
||||
"thread/epiphany/promote".to_string(),
|
||||
],
|
||||
recommended_action: verification_recommended_action,
|
||||
},
|
||||
EpiphanyRoleBoardLane {
|
||||
id: EpiphanyCoordinatorRoleId::Reorientation,
|
||||
title: "Reorientation".to_string(),
|
||||
owner_role: input.reorient_owner_role,
|
||||
status: reorientation_role_status(input.crrc_action, input.reorient_result_status),
|
||||
note: format!(
|
||||
"{} verdict, result {:?}, pressure {}. {}",
|
||||
input.reorient_decision_action,
|
||||
input.reorient_result_status,
|
||||
input.pressure_level,
|
||||
input.crrc_reason
|
||||
),
|
||||
jobs: reorientation_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/reorientLaunch".to_string(),
|
||||
"thread/epiphany/reorientResult".to_string(),
|
||||
"thread/epiphany/reorientAccept".to_string(),
|
||||
],
|
||||
recommended_action: input.crrc_recommended_scene_action,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn role_board_job_status_to_role_status(
|
||||
status: EpiphanyRoleBoardJobStatus,
|
||||
) -> EpiphanyCoordinatorRoleStatus {
|
||||
match status {
|
||||
EpiphanyRoleBoardJobStatus::Idle => EpiphanyCoordinatorRoleStatus::Ready,
|
||||
EpiphanyRoleBoardJobStatus::Needed => EpiphanyCoordinatorRoleStatus::Needed,
|
||||
EpiphanyRoleBoardJobStatus::Pending | EpiphanyRoleBoardJobStatus::Running => {
|
||||
EpiphanyCoordinatorRoleStatus::Running
|
||||
}
|
||||
EpiphanyRoleBoardJobStatus::Completed => EpiphanyCoordinatorRoleStatus::Completed,
|
||||
EpiphanyRoleBoardJobStatus::Failed
|
||||
| EpiphanyRoleBoardJobStatus::Cancelled
|
||||
| EpiphanyRoleBoardJobStatus::Blocked => EpiphanyCoordinatorRoleStatus::Blocked,
|
||||
EpiphanyRoleBoardJobStatus::Unavailable => EpiphanyCoordinatorRoleStatus::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reorientation_role_status(
|
||||
action: EpiphanyCrrcAction,
|
||||
result_status: EpiphanyCrrcResultStatus,
|
||||
) -> EpiphanyCoordinatorRoleStatus {
|
||||
match result_status {
|
||||
EpiphanyCrrcResultStatus::Pending | EpiphanyCrrcResultStatus::Running => {
|
||||
return EpiphanyCoordinatorRoleStatus::Waiting;
|
||||
}
|
||||
EpiphanyCrrcResultStatus::Failed
|
||||
| EpiphanyCrrcResultStatus::Cancelled
|
||||
| EpiphanyCrrcResultStatus::BackendUnavailable
|
||||
| EpiphanyCrrcResultStatus::BackendMissing => {
|
||||
return EpiphanyCoordinatorRoleStatus::Blocked;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match action {
|
||||
EpiphanyCrrcAction::Continue => EpiphanyCoordinatorRoleStatus::Ready,
|
||||
EpiphanyCrrcAction::PrepareCheckpoint
|
||||
| EpiphanyCrrcAction::LaunchReorientWorker
|
||||
| EpiphanyCrrcAction::RegatherManually => EpiphanyCoordinatorRoleStatus::Needed,
|
||||
EpiphanyCrrcAction::WaitForReorientWorker => EpiphanyCoordinatorRoleStatus::Waiting,
|
||||
EpiphanyCrrcAction::ReviewReorientResult | EpiphanyCrrcAction::AcceptReorientResult => {
|
||||
EpiphanyCoordinatorRoleStatus::Review
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_role_board_note(
|
||||
roles: &[EpiphanyRoleBoardLane],
|
||||
state_status: &str,
|
||||
recommendation: EpiphanyCrrcAction,
|
||||
) -> String {
|
||||
let blocked_count = roles
|
||||
.iter()
|
||||
.filter(|role| {
|
||||
matches!(
|
||||
role.status,
|
||||
EpiphanyCoordinatorRoleStatus::Blocked | EpiphanyCoordinatorRoleStatus::Needed
|
||||
)
|
||||
})
|
||||
.count();
|
||||
format!(
|
||||
"Role ownership is derived read-only from Epiphany state, jobs, and CRRC. State: {state_status}; recommendation: {recommendation:?}; blocked-or-needed lanes: {blocked_count}.",
|
||||
)
|
||||
}
|
||||
|
||||
fn imagination_role_status(
|
||||
state_present: bool,
|
||||
planning: &EpiphanyRoleBoardPlanningSummary,
|
||||
) -> EpiphanyCoordinatorRoleStatus {
|
||||
if !state_present {
|
||||
return EpiphanyCoordinatorRoleStatus::Blocked;
|
||||
}
|
||||
if planning.capture_count == 0
|
||||
&& planning.backlog_item_count == 0
|
||||
&& planning.roadmap_stream_count == 0
|
||||
&& planning.objective_draft_count == 0
|
||||
{
|
||||
EpiphanyCoordinatorRoleStatus::Needed
|
||||
} else {
|
||||
EpiphanyCoordinatorRoleStatus::Ready
|
||||
}
|
||||
}
|
||||
|
||||
fn render_imagination_role_note(
|
||||
state_present: bool,
|
||||
planning: &EpiphanyRoleBoardPlanningSummary,
|
||||
) -> String {
|
||||
if !state_present {
|
||||
return "No authoritative Epiphany state exists for planning synthesis.".to_string();
|
||||
}
|
||||
if planning.capture_count == 0
|
||||
&& planning.backlog_item_count == 0
|
||||
&& planning.roadmap_stream_count == 0
|
||||
&& planning.objective_draft_count == 0
|
||||
{
|
||||
return "Planning substrate is empty; capture or import backlog material before synthesis."
|
||||
.to_string();
|
||||
}
|
||||
format!(
|
||||
"Planning material ready: {} captures, {} backlog items, {} roadmap streams, {} objective drafts.",
|
||||
planning.capture_count,
|
||||
planning.backlog_item_count,
|
||||
planning.roadmap_stream_count,
|
||||
planning.objective_draft_count
|
||||
)
|
||||
}
|
||||
|
||||
fn modeling_role_status(
|
||||
checkpoint_present: bool,
|
||||
graph_remap_job: Option<&EpiphanyRoleBoardJob>,
|
||||
) -> EpiphanyCoordinatorRoleStatus {
|
||||
if let Some(job) = graph_remap_job
|
||||
&& matches!(
|
||||
job.status,
|
||||
EpiphanyRoleBoardJobStatus::Pending | EpiphanyRoleBoardJobStatus::Running
|
||||
)
|
||||
{
|
||||
return EpiphanyCoordinatorRoleStatus::Running;
|
||||
}
|
||||
if checkpoint_present {
|
||||
EpiphanyCoordinatorRoleStatus::Ready
|
||||
} else {
|
||||
EpiphanyCoordinatorRoleStatus::Needed
|
||||
}
|
||||
}
|
||||
|
||||
fn render_modeling_role_note(
|
||||
checkpoint: Option<&EpiphanyRoleBoardCheckpointSummary>,
|
||||
reorient_next_action: &str,
|
||||
) -> String {
|
||||
let Some(checkpoint) = checkpoint else {
|
||||
return format!("Checkpoint missing: {reorient_next_action}");
|
||||
};
|
||||
let next_action = checkpoint
|
||||
.next_action
|
||||
.as_deref()
|
||||
.unwrap_or(reorient_next_action);
|
||||
let disposition = checkpoint.disposition.as_deref().unwrap_or("unknown");
|
||||
format!("{disposition}: {next_action}")
|
||||
}
|
||||
|
||||
fn job_note(job: &EpiphanyRoleBoardJob) -> Option<String> {
|
||||
job.blocking_reason
|
||||
.clone()
|
||||
.or_else(|| job.progress_note.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn input() -> EpiphanyRoleBoardInput {
|
||||
EpiphanyRoleBoardInput {
|
||||
state_present: true,
|
||||
planning: EpiphanyRoleBoardPlanningSummary {
|
||||
capture_count: 0,
|
||||
backlog_item_count: 0,
|
||||
roadmap_stream_count: 0,
|
||||
objective_draft_count: 0,
|
||||
},
|
||||
checkpoint: Some(EpiphanyRoleBoardCheckpointSummary {
|
||||
disposition: Some("resume_ready".to_string()),
|
||||
next_action: Some("keep cutting".to_string()),
|
||||
}),
|
||||
reorient_next_action: "regather".to_string(),
|
||||
jobs: Vec::new(),
|
||||
crrc_action: EpiphanyCrrcAction::Continue,
|
||||
crrc_recommended_scene_action: Some(EpiphanyCoordinatorSceneAction::Reorient),
|
||||
crrc_reason: "continue".to_string(),
|
||||
reorient_decision_action: "Resume".to_string(),
|
||||
pressure_level: "Low".to_string(),
|
||||
reorient_result_status: EpiphanyCrrcResultStatus::MissingBinding,
|
||||
reorient_job: None,
|
||||
imagination_binding_id: "imagination-synthesis-worker".to_string(),
|
||||
modeling_binding_id: "modeling-checkpoint-worker".to_string(),
|
||||
verification_binding_id: "verification-review-worker".to_string(),
|
||||
reorient_owner_role: "epiphany-reorienter".to_string(),
|
||||
imagination_owner_role: "epiphany-imagination".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projects_mvp_lanes() {
|
||||
let roles = derive_role_board(EpiphanyRoleBoardInput {
|
||||
jobs: vec![EpiphanyRoleBoardJob {
|
||||
id: "verification".to_string(),
|
||||
owner_role: "epiphany-verifier".to_string(),
|
||||
status: EpiphanyRoleBoardJobStatus::Needed,
|
||||
progress_note: None,
|
||||
blocking_reason: None,
|
||||
}],
|
||||
..input()
|
||||
});
|
||||
|
||||
assert_eq!(roles.len(), 5);
|
||||
assert_eq!(roles[0].id, EpiphanyCoordinatorRoleId::Implementation);
|
||||
assert_eq!(roles[0].status, EpiphanyCoordinatorRoleStatus::Ready);
|
||||
assert_eq!(roles[1].status, EpiphanyCoordinatorRoleStatus::Needed);
|
||||
assert_eq!(roles[2].status, EpiphanyCoordinatorRoleStatus::Ready);
|
||||
assert_eq!(roles[3].status, EpiphanyCoordinatorRoleStatus::Needed);
|
||||
assert_eq!(roles[4].status, EpiphanyCoordinatorRoleStatus::Ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_implementation_when_crrc_blocks() {
|
||||
let roles = derive_role_board(EpiphanyRoleBoardInput {
|
||||
crrc_action: EpiphanyCrrcAction::LaunchReorientWorker,
|
||||
crrc_recommended_scene_action: Some(EpiphanyCoordinatorSceneAction::ReorientLaunch),
|
||||
..input()
|
||||
});
|
||||
|
||||
assert_eq!(roles[0].status, EpiphanyCoordinatorRoleStatus::Blocked);
|
||||
assert_eq!(
|
||||
roles[0].recommended_action,
|
||||
Some(EpiphanyCoordinatorSceneAction::ReorientLaunch)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorientation_waits_for_running_worker() {
|
||||
assert_eq!(
|
||||
reorientation_role_status(
|
||||
EpiphanyCrrcAction::Continue,
|
||||
EpiphanyCrrcResultStatus::Running
|
||||
),
|
||||
EpiphanyCoordinatorRoleStatus::Waiting
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ The spine exists in more than twenty-five live paths:
|
|||
- pre-compaction checkpoint intervention: app-server `TokenCount` handling now checks the same pressure mapper, and for loaded Epiphany threads crossing `shouldPrepareCompaction` it steers the active turn once with a bounded CRRC checkpoint directive to bank working context before compaction/reorientation. The per-turn dedupe bit and the pending compact handoff live in listener `ThreadState`; successful steering marks the active turn for a forced compact action at clean turn completion, while aborted turns clear the handoff. The intervention uses existing turn steering and turn-boundary automation rather than a new scheduler in [bespoke_event_handling.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/bespoke_event_handling.rs:1467), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15405), and [thread_state.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/thread_state.rs:144).
|
||||
- reorientation policy: the `reorient` view lens reuses the same live/stored thread view plus mapped pressure/freshness/watcher signals, and derives a bounded `resume`/`regather` verdict with checkpoint-path/frontier reasons without mutating, compacting, scheduling, notifying, or continuing work in [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4514), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4394), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11424).
|
||||
- CRRC coordinator recommendation: the `crrc` view lens reuses pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, and calls pure `epiphany-core` CRRC policy to return one bounded recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without mutating, launching, accepting, compacting, notifying, scheduling, or silently continuing work in [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4661), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4546).
|
||||
- role ownership projection: the `roles` view lens reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state.
|
||||
- role ownership projection: the `roles` view lens reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state. App-server adapts concrete protocol jobs and thread state, while the pure role-board projection policy now lives in [role_board.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_board.rs:70).
|
||||
- role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` accepts fixed Imagination/planning, modeling/checkpoint, and verification/review role ids, builds bounded specialist payloads from authoritative Epiphany state, routes them through the heartbeat-backed `epiphany_launch_job` seam, opens a typed runtime-spine job receipt, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` resolves heartbeat bindings through typed runtime-spine job results and seals legacy `agent_jobs` bindings, projecting structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional `statePatch`, optional `selfPatch`, coordinator `selfPersistence` review, raw result, and errors without mutation, promotion, scheduling, or hidden continuation. `selfPatch` is a Ghostlight-shaped lane-memory request, not project truth: the coordinator accepts only role-matched, bounded, evidence-grounded memory/goal/value/private-note mutations and refuses objectives, graphs, checkpoints, scratch, planning, jobs, authority, code edits, or raw transcripts with reasons. `roleAccept` accepts completed heartbeat findings from typed runtime-spine results, accepts completed Imagination findings only when they contain a valid planning-only patch with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, applies accepted project patches through the existing Epiphany state validator, appends audit observation/evidence, and carries the same `selfPersistence` review. GUI/coordinator accept paths then apply accepted `selfPatch` requests to typed CultCache role dossiers in `state/agents.msgpack` through the native `epiphany-agent-memory-store` binary. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path.
|
||||
- shared prompt memory projection and role dossiers: the prompt config now carries `[shared].persistent_memory` in [epiphany_specialists.toml](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/prompts/epiphany_specialists.toml:2). App-server parses that block, prepends it through `epiphany_agent_prompt_with_memory`, and therefore projects the same Ghostlight-derived memory into fixed role specialist instructions, reorientation-worker instructions, coordinator notes, and CRRC checkpoint steering. The base `<epiphany_state>` doctrine reinforces the Perfect Machine rule through [prompt.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/prompt.rs:31) and [epiphany_doctrine.md](E:/Projects/EpiphanyAgent/epiphany-core/src/prompts/epiphany_doctrine.md:1). Individual Ghostlight-shaped role dossiers now live in typed CultCache `state/agents.msgpack`, and the native `epiphany-agent-memory-store` binary validates, reviews, and applies bounded self-memory patches through the Rust store.
|
||||
- fixed-lane coordinator: the `coordinator` view lens composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. App-server assembles host/protocol inputs, but the pure fixed-lane decision ladder now lives in `epiphany-core` in [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132). Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather.
|
||||
|
|
@ -55,7 +55,7 @@ The thick Epiphany-owned implementation is mostly outside vendored Codex now:
|
|||
- deterministic observation distillation lives in [epiphany-core/src/distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28).
|
||||
- deterministic map/churn proposal from verified, evidence-backed observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115).
|
||||
- verifier-backed promotion policy lives in [epiphany-core/src/promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33).
|
||||
- pressure, pure CRRC recommendation policy, and fixed-lane coordinator decision policy live in [pressure.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/pressure.rs:15), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), and [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132).
|
||||
- pressure, pure CRRC recommendation policy, fixed-lane coordinator decision policy, and role-board projection policy live in [pressure.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/pressure.rs:15), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132), and [role_board.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_board.rs:70).
|
||||
|
||||
## Whole Control Flow
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ remembers doctrine; the branch remembers the blade.
|
|||
## Current Orientation
|
||||
|
||||
- Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`.
|
||||
- Top priority is still foundation cleanup, not outward feature work. Read `notes/epiphany-architectural-teardown.md` before touching Epiphany control-plane code. The May 2026 suspicion pass found that the app-server Epiphany control plane had become architectural Jenga: too much policy, projection, lifecycle glue, prompt packet assembly, coordinator sequencing, and test authority lives in `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`. The rebuild has begun: pressure policy, pure CRRC recommendation policy, and fixed-lane coordinator decision policy moved into `epiphany-core`, typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links.
|
||||
- Top priority is still foundation cleanup, not outward feature work. Read `notes/epiphany-architectural-teardown.md` before touching Epiphany control-plane code. The May 2026 suspicion pass found that the app-server Epiphany control plane had become architectural Jenga: too much policy, projection, lifecycle glue, prompt packet assembly, coordinator sequencing, and test authority lives in `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`. The rebuild has begun: pressure policy, pure CRRC recommendation policy, fixed-lane coordinator decision policy, and role-board projection policy moved into `epiphany-core`, typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links.
|
||||
- Read `notes/epiphany-ideal-architecture-rebuild-plan.md` immediately after the teardown. It defines the smallest coherent replacement architecture: durable `EpiphanyState`, runtime-spine-owned `RuntimeState`, separate role memory/heartbeat state, `epiphany-core` as policy owner, app-server as adapter, Aquarium as reflector, typed acceptance receipts, and derived view lenses instead of a protocol verb zoo.
|
||||
- Do not continue Aquarium UI, bridge, Face, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage.
|
||||
- Phase 1 through Phase 5 are complete enough.
|
||||
|
|
@ -367,14 +367,15 @@ The next real move is still foundation cleanup. Start from
|
|||
`notes/epiphany-ideal-architecture-rebuild-plan.md`, not from the older outward
|
||||
Phase 6 bridge/UI trail. Landed rebuild cuts: `epiphany-core/src/surfaces/`
|
||||
exists, pressure view derivation/tests, pure CRRC recommendation policy/tests,
|
||||
and fixed-lane coordinator decision policy/tests live in core, live acceptance writes typed receipts keyed by runtime result id,
|
||||
fixed-lane coordinator decision policy/tests, and role-board projection
|
||||
policy/tests live in core, live acceptance writes typed receipts keyed by runtime result id,
|
||||
summary-string acceptance identity is gone for live paths, launches dual-write
|
||||
runtime links, result read-back prefers those runtime links, normal public
|
||||
`raw_result` is sealed, MVP consumers use `thread/epiphany/view`, heartbeat
|
||||
cognition is quarantined, and the old scene/jobs/roles/planning/pressure/reorient/
|
||||
CRRC/coordinator read wrappers are deleted. The next cut is role-board
|
||||
projection policy or role-result interpretation extraction into `epiphany-core`.
|
||||
No Agile confetti. No new balcony on the swaying tower.
|
||||
CRRC/coordinator read wrappers are deleted. The next cut is role-result
|
||||
interpretation or acceptance policy extraction into `epiphany-core`. No Agile
|
||||
confetti. No new balcony on the swaying tower.
|
||||
|
||||
The Phase 6 freshness slice is landed. It exposes read-only
|
||||
`thread/epiphany/freshness` from live retrieval summaries plus graph
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -100,7 +100,7 @@ accepted_design:
|
|||
- `thread/epiphany/retrieve`, `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/graphQuery`, and `thread/epiphany/view` stay read-only.
|
||||
- `thread/epiphany/index` writes the retrieval catalog, not durable Epiphany understanding; catalog is auspex output, not doctrine.
|
||||
- Qdrant is the preferred persistent semantic backend; BM25 remains the bootstrap/fallback/control path.
|
||||
- `epiphany-core` owns prompt rendering, rollout replay helpers, retrieval/indexing, distillation, proposal, promotion policy, pressure derivation, pure CRRC recommendation policy, and fixed-lane coordinator decision policy where possible.
|
||||
- `epiphany-core` owns prompt rendering, rollout replay helpers, retrieval/indexing, distillation, proposal, promotion policy, pressure derivation, pure CRRC recommendation policy, fixed-lane coordinator decision policy, and role-board projection policy where possible.
|
||||
- Vendored Codex keeps the thin typed host seam: protocol types, thread bridges, app-server protocol, request routing, and rollout integration.
|
||||
- The current algorithmic map is the source-grounded description of live control flow.
|
||||
- Compact-Rehydrate-Reorient-Continue is an architecture primitive; automatic runtime coordination is not landed yet.
|
||||
|
|
@ -273,4 +273,4 @@ rejected_paths:
|
|||
|
||||
current_status:
|
||||
summary: Phase 1 durable state through Phase 6 coordinator/specialist/CRRC/backend organs are landed, and the May 2026 rebuild has started cutting the app-server control-plane tumor. Pressure policy now lives in `epiphany-core`; role/reorient acceptance writes typed `acceptance_receipts`; live duplicate-acceptance checks use runtime result receipt identity instead of summary strings; launch writes durable `runtime_links`; and role/reorient result read-back follows runtime links before falling back to legacy bindings.
|
||||
next_action: Continue the teardown/rebuild migration from `notes/epiphany-ideal-architecture-rebuild-plan.md`. Landed cuts now shrink `jobBindings`, remove normal public `raw_result`, add the canonical `thread/epiphany/view` lens wrapper, migrate MVP consumers, quarantine heartbeat cognition blobs, delete the old scene/jobs/roles/planning/pressure/reorient/CRRC/coordinator read wrappers, and move pure CRRC plus fixed-lane coordinator decision policy into `epiphany-core`. The next bounded cut is to extract role-board projection policy or role-result interpretation from app-server into `epiphany-core`. Do not resume outward Aquarium/bridge/Face/dogfood expansion until this foundation cleanup is materially farther along.
|
||||
next_action: Continue the teardown/rebuild migration from `notes/epiphany-ideal-architecture-rebuild-plan.md`. Landed cuts now shrink `jobBindings`, remove normal public `raw_result`, add the canonical `thread/epiphany/view` lens wrapper, migrate MVP consumers, quarantine heartbeat cognition blobs, delete the old scene/jobs/roles/planning/pressure/reorient/CRRC/coordinator read wrappers, and move pure CRRC, fixed-lane coordinator decision policy, and role-board projection policy into `epiphany-core`. The next bounded cut is to extract role-result interpretation or acceptance policy from app-server into `epiphany-core`. Do not resume outward Aquarium/bridge/Face/dogfood expansion until this foundation cleanup is materially farther along.
|
||||
|
|
|
|||
|
|
@ -517,13 +517,21 @@ use epiphany_core::EpiphanyPressure;
|
|||
use epiphany_core::EpiphanyPressureBasis as CoreEpiphanyPressureBasis;
|
||||
use epiphany_core::EpiphanyPressureLevel as CoreEpiphanyPressureLevel;
|
||||
use epiphany_core::EpiphanyPressureStatus as CoreEpiphanyPressureStatus;
|
||||
use epiphany_core::EpiphanyRoleBoardCheckpointSummary;
|
||||
use epiphany_core::EpiphanyRoleBoardInput;
|
||||
use epiphany_core::EpiphanyRoleBoardJob;
|
||||
use epiphany_core::EpiphanyRoleBoardJobStatus;
|
||||
use epiphany_core::EpiphanyRoleBoardLane;
|
||||
use epiphany_core::EpiphanyRoleBoardPlanningSummary;
|
||||
use epiphany_core::EpiphanyRuntimeJobResult;
|
||||
use epiphany_core::EpiphanyRuntimeJobSnapshot;
|
||||
use epiphany_core::EpiphanyRuntimeJobStatus;
|
||||
use epiphany_core::coordinator_automation_action;
|
||||
use epiphany_core::derive_pressure_view;
|
||||
use epiphany_core::derive_role_board;
|
||||
use epiphany_core::recommend_coordinator_action;
|
||||
use epiphany_core::recommend_crrc_action;
|
||||
use epiphany_core::render_role_board_note;
|
||||
use epiphany_core::runtime_job_snapshot;
|
||||
use epiphany_core::select_coordinator_automation_action;
|
||||
use std::collections::BTreeMap;
|
||||
|
|
@ -14910,305 +14918,160 @@ fn map_epiphany_roles(
|
|||
result_status: ThreadEpiphanyReorientResultStatus,
|
||||
reorient_job: Option<&ThreadEpiphanyJob>,
|
||||
) -> Vec<ThreadEpiphanyRoleLane> {
|
||||
let state_present = state.is_some();
|
||||
let planning = state.map(|state| &state.planning);
|
||||
let checkpoint = state.and_then(|state| state.investigation_checkpoint.as_ref());
|
||||
let imagination_jobs = jobs
|
||||
.iter()
|
||||
.filter(|job| job.id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let imagination_bound_job = imagination_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID);
|
||||
let imagination_has_bound_job = imagination_bound_job.is_some();
|
||||
let imagination_status = imagination_bound_job
|
||||
.as_ref()
|
||||
.map(|job| map_epiphany_job_status_to_role_status(job.status))
|
||||
.unwrap_or_else(|| map_epiphany_imagination_role_status(state));
|
||||
let imagination_note = imagination_bound_job
|
||||
.as_ref()
|
||||
.and_then(|job| {
|
||||
job.blocking_reason
|
||||
.clone()
|
||||
.or_else(|| job.progress_note.clone())
|
||||
})
|
||||
.unwrap_or_else(|| render_epiphany_imagination_role_note(state));
|
||||
let imagination_recommended_action = if imagination_has_bound_job {
|
||||
Some(ThreadEpiphanySceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(ThreadEpiphanySceneAction::RoleLaunch)
|
||||
} else {
|
||||
Some(ThreadEpiphanySceneAction::Update)
|
||||
};
|
||||
let modeling_jobs = jobs
|
||||
.iter()
|
||||
.filter(|job| {
|
||||
matches!(
|
||||
job.id.as_str(),
|
||||
EPIPHANY_MODELING_ROLE_BINDING_ID | "graph-remap"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let modeling_bound_job = modeling_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == EPIPHANY_MODELING_ROLE_BINDING_ID);
|
||||
let modeling_has_bound_job = modeling_bound_job.is_some();
|
||||
let modeling_status = map_epiphany_modeling_role_status(
|
||||
checkpoint.is_some(),
|
||||
modeling_bound_job.or_else(|| modeling_jobs.first()),
|
||||
);
|
||||
let modeling_recommended_action = if modeling_has_bound_job {
|
||||
Some(ThreadEpiphanySceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(ThreadEpiphanySceneAction::RoleLaunch)
|
||||
} else {
|
||||
Some(ThreadEpiphanySceneAction::Update)
|
||||
};
|
||||
let verification_jobs = jobs
|
||||
.iter()
|
||||
.filter(|job| {
|
||||
matches!(
|
||||
job.id.as_str(),
|
||||
EPIPHANY_VERIFICATION_ROLE_BINDING_ID | "verification"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let verification_bound_job = verification_jobs
|
||||
.iter()
|
||||
.find(|job| job.id == EPIPHANY_VERIFICATION_ROLE_BINDING_ID);
|
||||
let verification_has_bound_job = verification_bound_job.is_some();
|
||||
let verification_primary_job = verification_bound_job.or_else(|| verification_jobs.first());
|
||||
let verification_status = verification_primary_job
|
||||
.as_ref()
|
||||
.map(|job| map_epiphany_job_status_to_role_status(job.status))
|
||||
.unwrap_or(ThreadEpiphanyRoleStatus::Unavailable);
|
||||
let verification_note = verification_primary_job
|
||||
.as_ref()
|
||||
.and_then(|job| {
|
||||
job.blocking_reason
|
||||
.clone()
|
||||
.or_else(|| job.progress_note.clone())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
"Review evidence, worker findings, and verifier results before promotion.".to_string()
|
||||
});
|
||||
let verification_recommended_action = if verification_has_bound_job {
|
||||
Some(ThreadEpiphanySceneAction::RoleResult)
|
||||
} else if state_present {
|
||||
Some(ThreadEpiphanySceneAction::RoleLaunch)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let reorientation_jobs = reorient_job.cloned().into_iter().collect::<Vec<_>>();
|
||||
|
||||
vec![
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: ThreadEpiphanyRoleId::Implementation,
|
||||
title: "Implementation".to_string(),
|
||||
owner_role: "coding-agent".to_string(),
|
||||
status: if recommendation.action == ThreadEpiphanyCrrcAction::Continue {
|
||||
ThreadEpiphanyRoleStatus::Ready
|
||||
} else {
|
||||
ThreadEpiphanyRoleStatus::Blocked
|
||||
},
|
||||
note: if recommendation.action == ThreadEpiphanyCrrcAction::Continue {
|
||||
"Continue the bounded coding task.".to_string()
|
||||
} else {
|
||||
format!("Wait for CRRC action: {:?}.", recommendation.action)
|
||||
},
|
||||
jobs: jobs
|
||||
.iter()
|
||||
.filter(|job| {
|
||||
matches!(
|
||||
job.owner_role.as_str(),
|
||||
"coding-agent" | "implementation" | "epiphany-implementation"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
authority_scopes: Vec::new(),
|
||||
recommended_action: if recommendation.action == ThreadEpiphanyCrrcAction::Continue {
|
||||
None
|
||||
} else {
|
||||
recommendation.recommended_scene_action
|
||||
},
|
||||
},
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: ThreadEpiphanyRoleId::Imagination,
|
||||
title: "Imagination / Planning".to_string(),
|
||||
owner_role: EPIPHANY_IMAGINATION_OWNER_ROLE.to_string(),
|
||||
status: imagination_status,
|
||||
note: imagination_note,
|
||||
jobs: imagination_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/update".to_string(),
|
||||
],
|
||||
recommended_action: imagination_recommended_action,
|
||||
},
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: ThreadEpiphanyRoleId::Modeling,
|
||||
title: "Modeling / Checkpoint".to_string(),
|
||||
owner_role: "epiphany-modeler".to_string(),
|
||||
status: modeling_status,
|
||||
note: render_epiphany_modeling_role_note(checkpoint, decision),
|
||||
jobs: modeling_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/update".to_string(),
|
||||
],
|
||||
recommended_action: modeling_recommended_action,
|
||||
},
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: ThreadEpiphanyRoleId::Verification,
|
||||
title: "Verification / Review".to_string(),
|
||||
owner_role: "epiphany-verifier".to_string(),
|
||||
status: verification_status,
|
||||
note: verification_note,
|
||||
jobs: verification_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/roleLaunch".to_string(),
|
||||
"thread/epiphany/roleResult".to_string(),
|
||||
"thread/epiphany/roleAccept".to_string(),
|
||||
"thread/epiphany/distill".to_string(),
|
||||
"thread/epiphany/propose".to_string(),
|
||||
"thread/epiphany/promote".to_string(),
|
||||
],
|
||||
recommended_action: verification_recommended_action,
|
||||
},
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: ThreadEpiphanyRoleId::Reorientation,
|
||||
title: "Reorientation".to_string(),
|
||||
owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(),
|
||||
status: map_epiphany_reorientation_role_status(recommendation.action, result_status),
|
||||
note: format!(
|
||||
"{:?} verdict, result {:?}, pressure {:?}. {}",
|
||||
decision.action, result_status, pressure.level, recommendation.reason
|
||||
),
|
||||
jobs: reorientation_jobs,
|
||||
authority_scopes: vec![
|
||||
"thread/epiphany/reorientLaunch".to_string(),
|
||||
"thread/epiphany/reorientResult".to_string(),
|
||||
"thread/epiphany/reorientAccept".to_string(),
|
||||
],
|
||||
recommended_action: recommendation.recommended_scene_action,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn map_epiphany_imagination_role_status(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
) -> ThreadEpiphanyRoleStatus {
|
||||
let Some(state) = state else {
|
||||
return ThreadEpiphanyRoleStatus::Blocked;
|
||||
};
|
||||
if state.planning.is_empty() {
|
||||
ThreadEpiphanyRoleStatus::Needed
|
||||
} else {
|
||||
ThreadEpiphanyRoleStatus::Ready
|
||||
}
|
||||
}
|
||||
|
||||
fn render_epiphany_imagination_role_note(state: Option<&EpiphanyThreadState>) -> String {
|
||||
let Some(state) = state else {
|
||||
return "No authoritative Epiphany state exists for planning synthesis.".to_string();
|
||||
};
|
||||
let planning = &state.planning;
|
||||
if planning.is_empty() {
|
||||
return "Planning substrate is empty; capture or import backlog material before synthesis."
|
||||
.to_string();
|
||||
}
|
||||
format!(
|
||||
"Planning material ready: {} captures, {} backlog items, {} roadmap streams, {} objective drafts.",
|
||||
planning.captures.len(),
|
||||
planning.backlog_items.len(),
|
||||
planning.roadmap_streams.len(),
|
||||
planning.objective_drafts.len()
|
||||
)
|
||||
}
|
||||
|
||||
fn map_epiphany_modeling_role_status(
|
||||
checkpoint_present: bool,
|
||||
graph_remap_job: Option<&ThreadEpiphanyJob>,
|
||||
) -> ThreadEpiphanyRoleStatus {
|
||||
if let Some(job) = graph_remap_job
|
||||
&& matches!(
|
||||
job.status,
|
||||
ThreadEpiphanyJobStatus::Pending | ThreadEpiphanyJobStatus::Running
|
||||
)
|
||||
let mut source_jobs = jobs.to_vec();
|
||||
if let Some(job) = reorient_job
|
||||
&& !source_jobs
|
||||
.iter()
|
||||
.any(|source_job| source_job.id == job.id && source_job.owner_role == job.owner_role)
|
||||
{
|
||||
return ThreadEpiphanyRoleStatus::Running;
|
||||
source_jobs.push(job.clone());
|
||||
}
|
||||
if checkpoint_present {
|
||||
ThreadEpiphanyRoleStatus::Ready
|
||||
} else {
|
||||
ThreadEpiphanyRoleStatus::Needed
|
||||
derive_role_board(EpiphanyRoleBoardInput {
|
||||
state_present: state.is_some(),
|
||||
planning: EpiphanyRoleBoardPlanningSummary {
|
||||
capture_count: planning
|
||||
.map(|planning| planning.captures.len())
|
||||
.unwrap_or(0),
|
||||
backlog_item_count: planning
|
||||
.map(|planning| planning.backlog_items.len())
|
||||
.unwrap_or(0),
|
||||
roadmap_stream_count: planning
|
||||
.map(|planning| planning.roadmap_streams.len())
|
||||
.unwrap_or(0),
|
||||
objective_draft_count: planning
|
||||
.map(|planning| planning.objective_drafts.len())
|
||||
.unwrap_or(0),
|
||||
},
|
||||
checkpoint: checkpoint.map(|checkpoint| EpiphanyRoleBoardCheckpointSummary {
|
||||
disposition: Some(format!("{:?}", checkpoint.disposition)),
|
||||
next_action: checkpoint.next_action.clone(),
|
||||
}),
|
||||
reorient_next_action: decision.next_action.clone(),
|
||||
jobs: source_jobs.iter().map(map_core_role_board_job).collect(),
|
||||
crrc_action: map_core_crrc_action_from_protocol(recommendation.action),
|
||||
crrc_recommended_scene_action: recommendation
|
||||
.recommended_scene_action
|
||||
.map(map_core_coordinator_scene_action_from_protocol),
|
||||
crrc_reason: recommendation.reason.clone(),
|
||||
reorient_decision_action: format!("{:?}", decision.action),
|
||||
pressure_level: format!("{:?}", pressure.level),
|
||||
reorient_result_status: map_core_crrc_result_status(result_status),
|
||||
reorient_job: reorient_job.map(map_core_role_board_job),
|
||||
imagination_binding_id: EPIPHANY_IMAGINATION_ROLE_BINDING_ID.to_string(),
|
||||
modeling_binding_id: EPIPHANY_MODELING_ROLE_BINDING_ID.to_string(),
|
||||
verification_binding_id: EPIPHANY_VERIFICATION_ROLE_BINDING_ID.to_string(),
|
||||
reorient_owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(),
|
||||
imagination_owner_role: EPIPHANY_IMAGINATION_OWNER_ROLE.to_string(),
|
||||
})
|
||||
.into_iter()
|
||||
.map(|lane| map_protocol_role_board_lane(lane, &source_jobs))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn map_core_role_board_job(job: &ThreadEpiphanyJob) -> EpiphanyRoleBoardJob {
|
||||
EpiphanyRoleBoardJob {
|
||||
id: job.id.clone(),
|
||||
owner_role: job.owner_role.clone(),
|
||||
status: map_core_role_board_job_status(job.status),
|
||||
progress_note: job.progress_note.clone(),
|
||||
blocking_reason: job.blocking_reason.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_epiphany_modeling_role_note(
|
||||
checkpoint: Option<&EpiphanyInvestigationCheckpoint>,
|
||||
decision: &ThreadEpiphanyReorientDecision,
|
||||
) -> String {
|
||||
let Some(checkpoint) = checkpoint else {
|
||||
return format!("Checkpoint missing: {}", decision.next_action);
|
||||
};
|
||||
let next_action = checkpoint
|
||||
.next_action
|
||||
.as_deref()
|
||||
.unwrap_or(decision.next_action.as_str());
|
||||
format!("{:?}: {next_action}", checkpoint.disposition)
|
||||
fn map_core_role_board_job_status(status: ThreadEpiphanyJobStatus) -> EpiphanyRoleBoardJobStatus {
|
||||
match status {
|
||||
ThreadEpiphanyJobStatus::Idle => EpiphanyRoleBoardJobStatus::Idle,
|
||||
ThreadEpiphanyJobStatus::Needed => EpiphanyRoleBoardJobStatus::Needed,
|
||||
ThreadEpiphanyJobStatus::Pending => EpiphanyRoleBoardJobStatus::Pending,
|
||||
ThreadEpiphanyJobStatus::Running => EpiphanyRoleBoardJobStatus::Running,
|
||||
ThreadEpiphanyJobStatus::Completed => EpiphanyRoleBoardJobStatus::Completed,
|
||||
ThreadEpiphanyJobStatus::Failed => EpiphanyRoleBoardJobStatus::Failed,
|
||||
ThreadEpiphanyJobStatus::Cancelled => EpiphanyRoleBoardJobStatus::Cancelled,
|
||||
ThreadEpiphanyJobStatus::Blocked => EpiphanyRoleBoardJobStatus::Blocked,
|
||||
ThreadEpiphanyJobStatus::Unavailable => EpiphanyRoleBoardJobStatus::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_epiphany_job_status_to_role_status(
|
||||
status: ThreadEpiphanyJobStatus,
|
||||
fn map_protocol_role_board_lane(
|
||||
lane: EpiphanyRoleBoardLane,
|
||||
source_jobs: &[ThreadEpiphanyJob],
|
||||
) -> ThreadEpiphanyRoleLane {
|
||||
ThreadEpiphanyRoleLane {
|
||||
id: map_protocol_coordinator_role_id(lane.id),
|
||||
title: lane.title,
|
||||
owner_role: lane.owner_role,
|
||||
status: map_protocol_coordinator_role_status(lane.status),
|
||||
note: lane.note,
|
||||
jobs: lane
|
||||
.jobs
|
||||
.iter()
|
||||
.map(|job| map_protocol_role_board_job(job, source_jobs))
|
||||
.collect(),
|
||||
authority_scopes: lane.authority_scopes,
|
||||
recommended_action: lane
|
||||
.recommended_action
|
||||
.map(map_protocol_coordinator_scene_action),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_protocol_role_board_job(
|
||||
job: &EpiphanyRoleBoardJob,
|
||||
source_jobs: &[ThreadEpiphanyJob],
|
||||
) -> ThreadEpiphanyJob {
|
||||
source_jobs
|
||||
.iter()
|
||||
.find(|source_job| source_job.id == job.id && source_job.owner_role == job.owner_role)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ThreadEpiphanyJob {
|
||||
id: job.id.clone(),
|
||||
kind: ThreadEpiphanyJobKind::Specialist,
|
||||
scope: job.id.clone(),
|
||||
owner_role: job.owner_role.clone(),
|
||||
launcher_job_id: None,
|
||||
authority_scope: None,
|
||||
backend_job_id: None,
|
||||
status: map_protocol_role_board_job_status(job.status),
|
||||
items_processed: None,
|
||||
items_total: None,
|
||||
progress_note: job.progress_note.clone(),
|
||||
last_checkpoint_at_unix_seconds: None,
|
||||
blocking_reason: job.blocking_reason.clone(),
|
||||
active_thread_ids: Vec::new(),
|
||||
linked_subgoal_ids: Vec::new(),
|
||||
linked_graph_node_ids: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_protocol_role_board_job_status(
|
||||
status: EpiphanyRoleBoardJobStatus,
|
||||
) -> ThreadEpiphanyJobStatus {
|
||||
match status {
|
||||
EpiphanyRoleBoardJobStatus::Idle => ThreadEpiphanyJobStatus::Idle,
|
||||
EpiphanyRoleBoardJobStatus::Needed => ThreadEpiphanyJobStatus::Needed,
|
||||
EpiphanyRoleBoardJobStatus::Pending => ThreadEpiphanyJobStatus::Pending,
|
||||
EpiphanyRoleBoardJobStatus::Running => ThreadEpiphanyJobStatus::Running,
|
||||
EpiphanyRoleBoardJobStatus::Completed => ThreadEpiphanyJobStatus::Completed,
|
||||
EpiphanyRoleBoardJobStatus::Failed => ThreadEpiphanyJobStatus::Failed,
|
||||
EpiphanyRoleBoardJobStatus::Cancelled => ThreadEpiphanyJobStatus::Cancelled,
|
||||
EpiphanyRoleBoardJobStatus::Blocked => ThreadEpiphanyJobStatus::Blocked,
|
||||
EpiphanyRoleBoardJobStatus::Unavailable => ThreadEpiphanyJobStatus::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_protocol_coordinator_role_status(
|
||||
status: CoreEpiphanyCoordinatorRoleStatus,
|
||||
) -> ThreadEpiphanyRoleStatus {
|
||||
match status {
|
||||
ThreadEpiphanyJobStatus::Idle => ThreadEpiphanyRoleStatus::Ready,
|
||||
ThreadEpiphanyJobStatus::Needed => ThreadEpiphanyRoleStatus::Needed,
|
||||
ThreadEpiphanyJobStatus::Pending | ThreadEpiphanyJobStatus::Running => {
|
||||
ThreadEpiphanyRoleStatus::Running
|
||||
}
|
||||
ThreadEpiphanyJobStatus::Completed => ThreadEpiphanyRoleStatus::Completed,
|
||||
ThreadEpiphanyJobStatus::Failed
|
||||
| ThreadEpiphanyJobStatus::Cancelled
|
||||
| ThreadEpiphanyJobStatus::Blocked => ThreadEpiphanyRoleStatus::Blocked,
|
||||
ThreadEpiphanyJobStatus::Unavailable => ThreadEpiphanyRoleStatus::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_epiphany_reorientation_role_status(
|
||||
action: ThreadEpiphanyCrrcAction,
|
||||
result_status: ThreadEpiphanyReorientResultStatus,
|
||||
) -> ThreadEpiphanyRoleStatus {
|
||||
match result_status {
|
||||
ThreadEpiphanyReorientResultStatus::Pending
|
||||
| ThreadEpiphanyReorientResultStatus::Running => {
|
||||
return ThreadEpiphanyRoleStatus::Waiting;
|
||||
}
|
||||
ThreadEpiphanyReorientResultStatus::Failed
|
||||
| ThreadEpiphanyReorientResultStatus::Cancelled
|
||||
| ThreadEpiphanyReorientResultStatus::BackendUnavailable
|
||||
| ThreadEpiphanyReorientResultStatus::BackendMissing => {
|
||||
return ThreadEpiphanyRoleStatus::Blocked;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match action {
|
||||
ThreadEpiphanyCrrcAction::Continue => ThreadEpiphanyRoleStatus::Ready,
|
||||
ThreadEpiphanyCrrcAction::PrepareCheckpoint
|
||||
| ThreadEpiphanyCrrcAction::LaunchReorientWorker
|
||||
| ThreadEpiphanyCrrcAction::RegatherManually => ThreadEpiphanyRoleStatus::Needed,
|
||||
ThreadEpiphanyCrrcAction::WaitForReorientWorker => ThreadEpiphanyRoleStatus::Waiting,
|
||||
ThreadEpiphanyCrrcAction::ReviewReorientResult
|
||||
| ThreadEpiphanyCrrcAction::AcceptReorientResult => ThreadEpiphanyRoleStatus::Review,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Ready => ThreadEpiphanyRoleStatus::Ready,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Needed => ThreadEpiphanyRoleStatus::Needed,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Running => ThreadEpiphanyRoleStatus::Running,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Waiting => ThreadEpiphanyRoleStatus::Waiting,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Review => ThreadEpiphanyRoleStatus::Review,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Blocked => ThreadEpiphanyRoleStatus::Blocked,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Unavailable => ThreadEpiphanyRoleStatus::Unavailable,
|
||||
CoreEpiphanyCoordinatorRoleStatus::Completed => ThreadEpiphanyRoleStatus::Completed,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -15217,18 +15080,25 @@ fn render_epiphany_roles_note(
|
|||
state_status: ThreadEpiphanyReorientStateStatus,
|
||||
recommendation: ThreadEpiphanyCrrcAction,
|
||||
) -> String {
|
||||
let blocked_count = roles
|
||||
let core_roles = roles
|
||||
.iter()
|
||||
.filter(|role| {
|
||||
matches!(
|
||||
role.status,
|
||||
ThreadEpiphanyRoleStatus::Blocked | ThreadEpiphanyRoleStatus::Needed
|
||||
)
|
||||
.map(|role| EpiphanyRoleBoardLane {
|
||||
id: map_core_coordinator_role_id(role.id),
|
||||
title: role.title.clone(),
|
||||
owner_role: role.owner_role.clone(),
|
||||
status: map_core_coordinator_role_status(role.status),
|
||||
note: role.note.clone(),
|
||||
jobs: role.jobs.iter().map(map_core_role_board_job).collect(),
|
||||
authority_scopes: role.authority_scopes.clone(),
|
||||
recommended_action: role
|
||||
.recommended_action
|
||||
.map(map_core_coordinator_scene_action_from_protocol),
|
||||
})
|
||||
.count();
|
||||
format!(
|
||||
"Role ownership is derived read-only from Epiphany state, jobs, and CRRC. State: {:?}; recommendation: {:?}; blocked-or-needed lanes: {blocked_count}.",
|
||||
state_status, recommendation
|
||||
.collect::<Vec<_>>();
|
||||
render_role_board_note(
|
||||
&core_roles,
|
||||
format!("{:?}", state_status).as_str(),
|
||||
map_core_crrc_action_from_protocol(recommendation),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue