use crate::session_boundary::SessionBoundaryAction;
use crate::state::escalation as escalation_state;
use crate::state::runtime as runtime_state;
use crate::state::session as session_state;
use super::{
ContextCheckAction, ContextCheckPayload, ContextCheckTrigger, ContextCheckUrgency,
DriftAggregateStatus, NextStepStatus, RadarStateReport,
};
pub(super) struct ContextCheckDecision {
pub(super) action: ContextCheckAction,
pub(super) reason: &'static str,
pub(super) recommendation: String,
pub(super) urgency: ContextCheckUrgency,
pub(super) suppressed: bool,
pub(super) next_interval_hint_seconds: u64,
pub(super) evidence: Vec<String>,
}
pub(super) fn build_context_check_decision(
trigger: ContextCheckTrigger,
report: &RadarStateReport,
payload: &ContextCheckPayload,
) -> ContextCheckDecision {
if let Some(checkout_state) = report
.checkout_state
.as_ref()
.filter(|state| state.advisory)
{
return ContextCheckDecision {
action: ContextCheckAction::Escalate,
reason: "checkout_advisory",
recommendation: "seek_operator_attention".to_owned(),
urgency: ContextCheckUrgency::High,
suppressed: false,
next_interval_hint_seconds: 0,
evidence: vec![checkout_state.summary.clone()],
};
}
if report.escalation.blocking_count > 0 {
return ContextCheckDecision {
action: ContextCheckAction::Escalate,
reason: "blocking_escalation",
recommendation: "seek_operator_attention".to_owned(),
urgency: ContextCheckUrgency::Critical,
suppressed: false,
next_interval_hint_seconds: 0,
evidence: report
.escalation
.entries
.iter()
.filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
.map(|entry| format!("{}: {}", entry.id, entry.reason))
.collect(),
};
}
if report.behavioral_drift.status == DriftAggregateStatus::NeedsRecalibration {
return ContextCheckDecision {
action: ContextCheckAction::Escalate,
reason: "behavioral_drift",
recommendation: "recalibrate_session".to_owned(),
urgency: ContextCheckUrgency::High,
suppressed: false,
next_interval_hint_seconds: 0,
evidence: report.behavioral_drift.evidence.clone(),
};
}
if matches!(
trigger,
ContextCheckTrigger::PreCompaction | ContextCheckTrigger::IdleReset
) {
let reason = match trigger {
ContextCheckTrigger::PreCompaction => "pre_compaction",
ContextCheckTrigger::IdleReset => "idle_reset",
_ => unreachable!("guarded above"),
};
let mut evidence = vec![payload.risk_summary.clone()];
evidence.extend(report.session_boundary.evidence.iter().take(2).cloned());
return ContextCheckDecision {
action: ContextCheckAction::FlushForCompaction,
reason,
recommendation: "capture_before_compaction".to_owned(),
urgency: ContextCheckUrgency::High,
suppressed: false,
next_interval_hint_seconds: 0,
evidence,
};
}
if matches!(
report.context_health.recommendation,
"wrap_up_soon" | "wrap_up_and_clear"
) {
return ContextCheckDecision {
action: ContextCheckAction::WrapUpRequired,
reason: "wrap_up_window",
recommendation: report.context_health.recommendation.to_owned(),
urgency: match report.context_health.recommendation {
"wrap_up_and_clear" => ContextCheckUrgency::Critical,
_ => ContextCheckUrgency::High,
},
suppressed: false,
next_interval_hint_seconds: 0,
evidence: if report.session_boundary.evidence.is_empty() {
vec![payload.risk_summary.clone()]
} else {
report.session_boundary.evidence.clone()
},
};
}
if report.session_boundary.action == SessionBoundaryAction::Refresh
|| report.evaluation.next_step.status == NextStepStatus::ReviewRequired
{
return ContextCheckDecision {
action: ContextCheckAction::CheckpointNow,
reason: "handoff_refresh_needed",
recommendation: "checkpoint_now".to_owned(),
urgency: ContextCheckUrgency::Moderate,
suppressed: false,
next_interval_hint_seconds: 0,
evidence: if report.evaluation.next_step.evidence.is_empty() {
vec![payload.state_delta.clone()]
} else {
report.evaluation.next_step.evidence.clone()
},
};
}
let manualish_trigger = matches!(
trigger,
ContextCheckTrigger::Manual
| ContextCheckTrigger::Resume
| ContextCheckTrigger::SupervisorPoll
);
let moderate_pressure = matches!(report.context_health.band, "moderate" | "risky")
|| report.execution_gates.unfinished_count > 0
|| report.recovery.status == "loaded";
if manualish_trigger || moderate_pressure {
return ContextCheckDecision {
action: ContextCheckAction::InjectDelta,
reason: match trigger {
ContextCheckTrigger::Resume => "resume_refresh",
ContextCheckTrigger::SupervisorPoll => "supervisor_poll",
ContextCheckTrigger::Manual => "manual_refresh",
_ => "band_transition",
},
recommendation: match trigger {
ContextCheckTrigger::Resume => "resume_with_digest".to_owned(),
_ => report.context_health.recommendation.to_owned(),
},
urgency: if matches!(report.context_health.band, "risky") {
ContextCheckUrgency::High
} else {
ContextCheckUrgency::Moderate
},
suppressed: false,
next_interval_hint_seconds: if matches!(trigger, ContextCheckTrigger::Resume) {
300
} else {
600
},
evidence: vec![payload.state_delta.clone(), payload.risk_summary.clone()],
};
}
ContextCheckDecision {
action: ContextCheckAction::None,
reason: if trigger == ContextCheckTrigger::Interval {
"interval_suppressed"
} else {
"no_action"
},
recommendation: "continue".to_owned(),
urgency: ContextCheckUrgency::Low,
suppressed: trigger == ContextCheckTrigger::Interval,
next_interval_hint_seconds: 900,
evidence: vec![payload.risk_summary.clone()],
}
}
pub(super) fn build_policy_digest(runtime: &runtime_state::LoadedRuntimeState) -> String {
let guardrails: Vec<String> = runtime
.state
.handoff
.operational_guardrails
.iter()
.map(|item| item.text.clone())
.collect();
if guardrails.is_empty() {
return "No compiled operational guardrails are currently recorded.".to_owned();
}
summarize_lines(&guardrails, 2)
}
pub(super) fn build_focus_digest(report: &RadarStateReport) -> String {
let mut parts = vec![report.handoff.title.clone()];
if let Some(mode) = report
.session_state
.mode
.filter(|mode| *mode != session_state::SessionMode::General)
{
parts.push(format!("mode `{}`", mode.as_str()));
}
if let Some(anchor) = &report.execution_gates.attention_anchor {
parts.push(format!(
"gate [{} #{}/{}] {}",
anchor.status.as_str(),
anchor.index,
report.execution_gates.total_count,
anchor.text
));
} else if let Some(next_action) = report.handoff.immediate_actions.first() {
parts.push(format!("next `{next_action}`"));
}
parts.join("; ")
}
pub(super) fn build_state_delta(report: &RadarStateReport) -> String {
let mut parts = Vec::new();
if !report.candidate_updates.handoff.is_empty() {
parts.push(format!(
"{} handoff update candidate(s)",
report.candidate_updates.handoff.len()
));
}
if !report.candidate_updates.memory.is_empty() {
parts.push(format!(
"{} memory promotion candidate(s)",
report.candidate_updates.memory.len()
));
}
if let Some(anchor) = &report.execution_gates.attention_anchor {
parts.push(format!(
"execution gate [{} #{}/{}] {}",
anchor.status.as_str(),
anchor.index,
report.execution_gates.total_count,
anchor.text
));
}
if parts.is_empty() {
parts.push(report.evaluation.next_step.summary.clone());
}
parts.join("; ")
}
pub(super) fn build_risk_summary(
trigger: ContextCheckTrigger,
report: &RadarStateReport,
) -> String {
let mut parts = vec![format!(
"trigger `{}` with context band `{}` (`{}`)",
trigger.as_str(),
report.context_health.band,
report.context_health.recommendation
)];
if report.behavioral_drift.status == DriftAggregateStatus::NeedsRecalibration {
parts.push("behavioral drift needs recalibration".to_owned());
}
if report.escalation.blocking_count > 0 {
parts.push(format!(
"{} blocking escalation(s) active",
report.escalation.blocking_count
));
}
parts.push(format!(
"session boundary is `{}`",
report.session_boundary.action.as_str()
));
parts.join("; ")
}
fn summarize_lines(lines: &[String], limit: usize) -> String {
let mut parts: Vec<String> = lines.iter().take(limit).cloned().collect();
if lines.len() > limit {
parts.push(format!("+{} more", lines.len() - limit));
}
parts.join("; ")
}