use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use anyhow::{bail, Context, Result};
use serde::Serialize;
use crate::extensions;
use crate::handoff::{self, BranchMode, CheckoutContinuityAdvisory};
use crate::memory::{
entries as memory_entries, pod_migration, provider as memory_provider, recall as memory_recall,
};
use crate::output::{self, CommandReport, WorkStreamView};
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::repo::registry as repo_registry;
use crate::repo::truth as project_truth;
use crate::session_boundary::{SessionBoundaryAction, SessionBoundaryRecommendation};
use crate::state::compiled as compiled_state;
use crate::state::config_migration;
use crate::state::consistency;
use crate::state::escalation as escalation_state;
use crate::state::machine_presence;
use crate::state::pod_identity;
use crate::state::policy_projection;
use crate::state::runtime as runtime_state;
use crate::state::session as session_state;
use crate::state::session_gates;
use crate::state::validation_profile;
use crate::telemetry::host_loop::{ContextSectionSpec, ContextSessionSnapshot};
use crate::timestamps;
use serde_json::Value;
use tracing::{debug, info_span};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StartupDisposition {
ResumeActiveContinuity,
NoActiveContinuity,
WorkflowAttentionRequired,
ResumeBlockedContinuity,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum StartRenderSection {
EffectivePolicy,
EffectiveMemory,
ExecutionGates,
Handoff,
}
impl StartRenderSection {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::EffectivePolicy => "effective_policy",
Self::EffectiveMemory => "effective_memory",
Self::ExecutionGates => "execution_gates",
Self::Handoff => "handoff",
}
}
}
pub(crate) const START_RENDER_SECTION_ORDER: &[StartRenderSection] = &[
StartRenderSection::EffectivePolicy,
StartRenderSection::EffectiveMemory,
StartRenderSection::ExecutionGates,
StartRenderSection::Handoff,
];
const HOST_CONTEXT_NEXT_FOCUS_LIMIT: usize = 3;
const HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS: usize = 120;
const HOST_CONTEXT_REMEMBER_LIMIT: usize = 2;
const HOST_CONTEXT_REMEMBER_MAX_CHARS: usize = 120;
const HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT: usize = 6;
const HOST_CONTEXT_GUARDRAILS_LIMIT: usize = 3;
const HOST_CONTEXT_GUARDRAILS_MAX_CHARS: usize = 100;
#[derive(Serialize)]
pub struct StartReport {
command: &'static str,
ok: bool,
disposition: StartupDisposition,
session_boundary: SessionBoundaryRecommendation,
path: String,
profile: String,
project_id: String,
workspace_path: String,
work_stream: WorkStreamView,
locality_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
compact_summary: CompactSummary,
source_order: &'static str,
manifest: ManifestView,
pod_identity: pod_identity::PodIdentityView,
machine_identity: pod_identity::MachineIdentityView,
machine_presence: machine_presence::MachinePresenceView,
execution_context: machine_presence::MachineExecutionContextView,
takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
coordination_scope: pod_identity::CoordinationScopeView,
sources: Vec<SourceView>,
effective_policy: RenderedView,
policy_projection: policy_projection::PolicyProjectionView,
effective_memory: MemoryView,
memory_recall: memory_recall::StartRecallView,
memory_provider: memory_provider::MemoryProviderView,
startup_recall: memory_provider::StartupRecallView,
execution_gates: session_gates::ExecutionGatesView,
handoff: HandoffView,
recovery: runtime_state::RuntimeRecoveryView,
session_state: SessionStateView,
#[serde(skip_serializing_if = "Option::is_none")]
activation: Option<session_state::SessionStateStartReport>,
escalation: escalation_state::EscalationView,
compiled_state: compiled_state::CompiledStateSurfaceView,
alerts: Vec<StartAlert>,
#[serde(skip_serializing_if = "Vec::is_empty")]
refresh_actions: Vec<RefreshAction>,
warnings: Vec<String>,
}
const START_CHECK_EXIT_NOT_READY: u8 = 3;
const START_CHECK_EXIT_BLOCKED_ESCALATION: u8 = 2;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StartCheckReadiness {
Ready,
Blocked,
NotReady,
}
#[derive(Serialize)]
pub struct StartCheckReport {
command: &'static str,
ok: bool,
disposition: StartupDisposition,
path: String,
profile: String,
project_id: String,
workspace_path: String,
work_stream: WorkStreamView,
locality_id: String,
mode: &'static str,
readiness: StartCheckReadiness,
escalation: escalation_state::EscalationView,
alerts: Vec<StartAlert>,
#[serde(skip_serializing_if = "Vec::is_empty")]
refresh_actions: Vec<RefreshAction>,
warnings: Vec<String>,
}
#[derive(Serialize)]
struct StartReadinessView {
status: StartReadinessStatus,
exit_code: u8,
note: &'static str,
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
enum StartReadinessStatus {
Ready,
Blocked,
NotReady,
}
#[derive(Serialize)]
struct ManifestView {
path: String,
status: &'static str,
entries: Vec<String>,
}
#[derive(Serialize)]
struct SourceView {
kind: &'static str,
path: String,
status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
}
#[derive(Serialize)]
struct RenderedView {
status: &'static str,
content: String,
parts: Vec<RenderedPart>,
}
#[derive(Serialize)]
struct MemoryView {
status: &'static str,
content: String,
parts: Vec<RenderedPart>,
structured: memory_entries::StructuredMemoryView,
}
#[derive(Serialize)]
struct RenderedPart {
kind: &'static str,
path: String,
status: &'static str,
}
#[derive(Clone, Copy)]
pub(crate) enum HostContextMode {
Startup,
PromptBuild,
}
pub(crate) struct HostContextPayload {
pub(crate) session_boundary: SessionBoundaryRecommendation,
pub(crate) context: Value,
pub(crate) sections: Vec<ContextSectionSpec>,
pub(crate) remember_selection: Option<RememberSelectionView>,
pub(crate) source_fingerprint: String,
pub(crate) session: ContextSessionSnapshot,
}
#[derive(Clone, Serialize)]
pub(crate) struct RememberSelectionView {
pub(crate) budget: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) query: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) selected: Vec<RememberSelectionItemView>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) suppressed: Vec<RememberSelectionItemView>,
}
#[derive(Clone, Serialize)]
pub(crate) struct RememberSelectionItemView {
pub(crate) rendered: String,
pub(crate) scope: &'static str,
pub(crate) score: i64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) matched_tokens: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) reasons: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) suppressed_reasons: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) last_touched_session: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) source_ref: Option<String>,
}
struct EffectiveMemorySurfaces<'a> {
profile: &'a runtime_state::RuntimeTextSurface,
locality: &'a runtime_state::RuntimeTextSurface,
pod: &'a runtime_state::RuntimeTextSurface,
branch: &'a runtime_state::RuntimeTextSurface,
clone: &'a runtime_state::RuntimeTextSurface,
}
#[derive(Serialize)]
struct HandoffView {
path: String,
status: &'static str,
content: String,
}
#[derive(Serialize)]
struct SessionStateView {
path: String,
status: &'static str,
schema_version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
started_at_epoch_s: Option<u64>,
last_started_at_epoch_s: Option<u64>,
start_count: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
mode: Option<session_state::SessionMode>,
#[serde(flatten)]
lifecycle: session_state::SessionLifecycleProjection,
}
#[derive(Clone, Serialize)]
struct StartAlert {
check: &'static str,
severity: StartAlertSeverity,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<serde_json::Value>,
}
impl StartAlert {
fn warning(check: &'static str, message: String) -> Self {
Self {
check,
severity: StartAlertSeverity::Warning,
message,
details: None,
}
}
fn error(check: &'static str, message: String) -> Self {
Self {
check,
severity: StartAlertSeverity::Error,
message,
details: None,
}
}
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
enum StartAlertSeverity {
Info,
Warning,
Error,
}
#[derive(Clone, Serialize)]
pub struct RefreshAction {
target: &'static str,
status: &'static str,
message: String,
}
#[derive(Serialize)]
struct CompactSummary {
current_state: CompactCurrentState,
next_focus: CompactNextFocus,
active_guardrails: CompactGuardrails,
}
#[derive(Serialize)]
struct CompactCurrentState {
source: &'static str,
title: String,
current_system_state: Vec<String>,
}
#[derive(Serialize)]
struct CompactNextFocus {
continuity_immediate_actions: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
execution_gate_anchor: Option<String>,
}
#[derive(Serialize)]
struct CompactGuardrails {
operational_guardrails: Vec<String>,
effective_memory: compiled_state::CompiledMemoryView,
}
struct ManifestResolution {
source_order: &'static str,
manifest: ManifestView,
project_truth_sources: Vec<SourceView>,
}
#[derive(Clone)]
struct TextSource {
kind: &'static str,
path: PathBuf,
status: &'static str,
content: Option<String>,
}
impl StartReport {
pub(crate) fn with_activation(
mut self,
activation: session_state::SessionStateStartReport,
) -> Self {
self.activation = Some(activation);
self
}
}
impl CommandReport for StartReport {
fn exit_code(&self) -> ExitCode {
ExitCode::SUCCESS
}
fn render_text(&self) {
if let Some(attention) = start_operator_attention(self) {
println!("Attention: {attention}");
}
println!("Next: {}", start_operator_prompt(self));
}
}
impl CommandReport for StartCheckReport {
fn exit_code(&self) -> ExitCode {
ExitCode::from(self.readiness.exit_code())
}
fn render_text(&self) {
println!(
"Start readiness is {} for profile {} and linked project {}.",
self.readiness.status_label(),
self.profile,
self.project_id
);
println!("{}", self.readiness.note());
println!(
"Exit codes: 0 ready, {START_CHECK_EXIT_BLOCKED_ESCALATION} blocked, {START_CHECK_EXIT_NOT_READY} not ready, 1 command error."
);
if !self.refresh_actions.is_empty() {
println!("Refresh actions:");
for action in &self.refresh_actions {
println!("- {} [{}] {}", action.target, action.status, action.message);
}
}
if self.alerts.is_empty() {
println!("No readiness alerts are active.");
if self.readiness.status() == StartReadinessStatus::Blocked {
println!(
"Blocking escalations: {} blocking, {} non-blocking at {}.",
self.escalation.blocking_count,
self.escalation.non_blocking_count,
self.escalation.path
);
for entry in self.escalation.entries.iter().filter(|entry| {
matches!(entry.kind, escalation_state::EscalationKind::Blocking)
}) {
println!("- {}: {}", entry.id, entry.reason);
}
}
return;
}
if self.readiness.status() == StartReadinessStatus::Blocked {
println!(
"Blocking escalations: {} blocking, {} non-blocking at {}.",
self.escalation.blocking_count,
self.escalation.non_blocking_count,
self.escalation.path
);
for entry in
self.escalation.entries.iter().filter(|entry| {
matches!(entry.kind, escalation_state::EscalationKind::Blocking)
})
{
println!("- {}: {}", entry.id, entry.reason);
}
}
let blocking_alerts: Vec<_> = self
.alerts
.iter()
.filter(|a| !matches!(a.severity, StartAlertSeverity::Info))
.collect();
if !blocking_alerts.is_empty() {
println!("Blocking alerts (warnings also block in `--check`):");
for alert in &blocking_alerts {
println!(
"- [{}] {}: {}",
alert.severity.upper_label(),
alert.check,
alert.message
);
}
}
let info_alerts: Vec<_> = self
.alerts
.iter()
.filter(|a| matches!(a.severity, StartAlertSeverity::Info))
.collect();
if !info_alerts.is_empty() {
println!("Informational:");
for alert in &info_alerts {
println!(
"- [{}] {}: {}",
alert.severity.upper_label(),
alert.check,
alert.message
);
}
}
}
}
impl StartCheckReadiness {
fn ok(self) -> bool {
matches!(self, Self::Ready)
}
fn exit_code(self) -> u8 {
match self {
Self::Ready => 0,
Self::Blocked => START_CHECK_EXIT_BLOCKED_ESCALATION,
Self::NotReady => START_CHECK_EXIT_NOT_READY,
}
}
fn note(self) -> &'static str {
match self {
Self::Ready => {
"No start alerts are active; the workspace-local session context is ready to load."
}
Self::Blocked => "Unresolved blocking escalation prevents readiness.",
Self::NotReady => {
"One or more start alerts are active; `start --check` is intentionally fail-closed, so warnings also block until the workspace state is reconciled."
}
}
}
fn status(self) -> StartReadinessStatus {
match self {
Self::Ready => StartReadinessStatus::Ready,
Self::Blocked => StartReadinessStatus::Blocked,
Self::NotReady => StartReadinessStatus::NotReady,
}
}
fn status_label(self) -> &'static str {
match self {
Self::Ready => "ready",
Self::Blocked => "blocked",
Self::NotReady => "not_ready",
}
}
fn view(self) -> StartReadinessView {
StartReadinessView {
status: self.status(),
exit_code: self.exit_code(),
note: self.note(),
}
}
}
impl Serialize for StartCheckReadiness {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.view().serialize(serializer)
}
}
impl StartAlertSeverity {
fn upper_label(self) -> &'static str {
match self {
Self::Info => "INFO",
Self::Warning => "WARN",
Self::Error => "ERROR",
}
}
}
fn start_operator_prompt(report: &StartReport) -> String {
if let Some(title) = operator_focus_label(&report.compact_summary.current_state.title) {
return operator_question(&title);
}
if let Some(step) = report
.compact_summary
.next_focus
.continuity_immediate_actions
.first()
{
return operator_question(step);
}
if let Some(anchor) = report
.compact_summary
.next_focus
.execution_gate_anchor
.as_ref()
{
return operator_question(anchor);
}
operator_question(&report.session_boundary.summary)
}
fn start_operator_attention(report: &StartReport) -> Option<String> {
if report.escalation.blocking_count > 0 {
let reasons = report
.escalation
.entries
.iter()
.filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
.map(|entry| entry.reason.trim())
.filter(|reason| !reason.is_empty())
.take(2)
.collect::<Vec<_>>();
if !reasons.is_empty() {
return Some(reasons.join(" "));
}
}
for check in ["session_state", "checkout_state"] {
if let Some(message) = report
.alerts
.iter()
.find(|alert| alert.check == check)
.map(|alert| alert.message.trim())
.filter(|message| !message.is_empty())
{
return Some(message.to_owned());
}
}
if let Some(warning) = report
.activation
.as_ref()
.and_then(session_state::SessionStateStartReport::warning_message)
.map(str::trim)
.filter(|warning| !warning.is_empty())
{
return Some(warning.to_owned());
}
match report.session_boundary.action {
SessionBoundaryAction::Continue => None,
SessionBoundaryAction::Refresh | SessionBoundaryAction::Stop => {
Some(report.session_boundary.summary.clone())
}
SessionBoundaryAction::WrapUp => Some(report.session_boundary.summary.clone()),
}
}
fn operator_focus_label(value: &str) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
return None;
}
Some(
trimmed
.strip_prefix("Next Session:")
.or_else(|| trimmed.strip_prefix("Next:"))
.unwrap_or(trimmed)
.trim()
.to_owned(),
)
}
fn operator_question(value: &str) -> String {
let trimmed = value.trim().trim_end_matches(['.', '!', '?']);
if trimmed.is_empty() {
return "continue?".to_owned();
}
format!("{trimmed}?")
}
pub fn run(repo_root: &Path, explicit_profile: Option<&str>, refresh: bool) -> Result<StartReport> {
let _span = info_span!("start").entered();
let core = resolve_core(repo_root, explicit_profile, refresh)?;
handoff::export::cleanup_legacy_markdown(&core.layout)?;
let disposition = derive_disposition(&core);
debug!(?disposition, "start disposition determined");
let compact_summary = build_compact_summary(&core);
let session_boundary = build_session_boundary(&core, disposition);
let _ = config_migration::migrate_repo_overlay_if_needed(&core.layout, &core.locality_id);
let _ = extensions::migrate_repo_overlay_config_if_needed(&core.layout, &core.locality_id);
let manifest = resolve_manifest(repo_root, &core.layout, &core.locality_id)?;
let pod_manifest = core
.active_pod_identity
.as_ref()
.map(|identity| read_text_source("pod_manifest", &identity.manifest_path))
.transpose()?;
let policy_sources = policy_projection::read_policy_sources(
&core.layout,
&core.locality_id,
core.active_pod_identity.as_ref(),
)?;
let effective_policy = render_effective_policy_view(&policy_sources);
let policy_projection = policy_projection::build_view(
&policy_sources,
policy_projection::PolicySessionContext::from_start_view(
core.loaded_session.view.mode,
&core.loaded_session.view.lifecycle,
),
&core.escalation,
);
let effective_memory = render_effective_memory_view(
EffectiveMemorySurfaces {
profile: &core.sources.profile_memory,
locality: &core.sources.locality_memory,
pod: &core.sources.pod_memory,
branch: &core.sources.branch_memory,
clone: &core.sources.clone_memory,
},
runtime_state::structured_memory_view(&core.sources),
core.compiled_state.store.pod_identity_active,
&core.compiled_state.store.effective_memory,
);
let handoff_view = into_compiled_handoff_view(
&core.sources.handoff,
compiled_state::render_handoff_content(&core.compiled_state.store.handoff),
);
let mut sources = manifest.project_truth_sources;
if let Some(source) = pod_manifest {
sources.push(into_source_view(source));
}
sources.push(runtime_source_view(&core.sources.profile_memory));
sources.push(runtime_source_view(&core.sources.locality_memory));
sources.push(runtime_source_view(&core.sources.pod_memory));
sources.push(runtime_source_view(&core.sources.branch_memory));
sources.push(runtime_source_view(&core.sources.clone_memory));
if core.validation_profile.status != "missing" {
sources.push(validation_profile_source_view(&core.validation_profile));
}
sources.push(runtime_source_view(&core.sources.handoff));
sources.push(execution_gates_source_view(
&core.execution_gates.view,
core.execution_gates.raw.as_ref(),
)?);
sources.push(session_state_source_view(
&core.loaded_session.view,
core.loaded_session.raw.as_ref(),
)?);
sources.push(SourceView {
kind: "escalation_state",
path: core.escalation.path.clone(),
status: core.escalation.status,
content: None,
});
sources.push(SourceView {
kind: "compiled_state",
path: core.compiled_state.surface.path.clone(),
status: core.compiled_state.surface.status,
content: None,
});
Ok(StartReport {
command: "start",
ok: true,
disposition,
session_boundary,
path: repo_root.display().to_string(),
profile: core.profile,
project_id: core.locality_id.clone(),
workspace_path: repo_root.display().to_string(),
work_stream: output::work_stream_view(core.git.as_ref()),
locality_id: core.locality_id,
session_id: core.session_id,
compact_summary,
source_order: manifest.source_order,
manifest: manifest.manifest,
pod_identity: core.pod_identity,
machine_identity: core.machine_identity,
machine_presence: core.machine_presence,
execution_context: core.execution_context,
takeover_preconditions: core.takeover_preconditions,
coordination_scope: core.coordination_scope,
sources,
effective_policy,
policy_projection,
effective_memory,
memory_recall: core.memory_recall,
memory_provider: core.memory_provider,
startup_recall: core.startup_recall,
execution_gates: core.execution_gates.view,
handoff: handoff_view,
recovery: runtime_state::recovery_view(&core.recovery),
session_state: core.loaded_session.view,
activation: None,
escalation: core.escalation,
compiled_state: core.compiled_state.surface,
alerts: core.alerts,
refresh_actions: core.refresh_actions,
warnings: core.warnings,
})
}
pub fn run_check(
repo_root: &Path,
explicit_profile: Option<&str>,
refresh: bool,
) -> Result<StartCheckReport> {
let core = resolve_core(repo_root, explicit_profile, refresh)?;
let disposition = derive_disposition(&core);
let readiness = start_readiness_view(&core.alerts);
Ok(StartCheckReport {
command: "start",
ok: readiness.ok(),
disposition,
path: repo_root.display().to_string(),
profile: core.profile,
project_id: core.locality_id.clone(),
workspace_path: repo_root.display().to_string(),
work_stream: output::work_stream_view(core.git.as_ref()),
locality_id: core.locality_id,
mode: "check",
readiness,
escalation: core.escalation,
alerts: core.alerts,
refresh_actions: core.refresh_actions,
warnings: core.warnings,
})
}
pub fn run_compiled_only(
repo_root: &Path,
explicit_profile: Option<&str>,
refresh: bool,
) -> Result<StartReport> {
let core = resolve_core(repo_root, explicit_profile, refresh)?;
handoff::export::cleanup_legacy_markdown(&core.layout)?;
let disposition = derive_disposition(&core);
let compact_summary = build_compact_summary(&core);
let session_boundary = build_session_boundary(&core, disposition);
Ok(StartReport {
command: "start",
ok: true,
disposition,
session_boundary,
path: repo_root.display().to_string(),
profile: core.profile,
project_id: core.locality_id.clone(),
workspace_path: repo_root.display().to_string(),
work_stream: output::work_stream_view(core.git.as_ref()),
locality_id: core.locality_id,
session_id: core.session_id,
compact_summary,
source_order: "manifest",
manifest: ManifestView {
path: String::new(),
status: "skipped",
entries: Vec::new(),
},
pod_identity: core.pod_identity,
machine_identity: core.machine_identity,
machine_presence: core.machine_presence,
execution_context: core.execution_context,
takeover_preconditions: core.takeover_preconditions,
coordination_scope: core.coordination_scope,
sources: Vec::new(),
effective_policy: RenderedView {
status: "skipped",
content: String::new(),
parts: Vec::new(),
},
policy_projection: policy_projection::skipped_view(),
effective_memory: MemoryView {
status: "skipped",
content: String::new(),
parts: Vec::new(),
structured: memory_entries::StructuredMemoryView {
status: "skipped",
profile_entries: Vec::new(),
repo_entries: Vec::new(),
pod_entries: Vec::new(),
branch_entries: Vec::new(),
clone_entries: Vec::new(),
diagnostics: Vec::new(),
},
},
memory_recall: memory_recall::StartRecallView::skipped(),
memory_provider: core.memory_provider,
startup_recall: core.startup_recall,
execution_gates: core.execution_gates.view,
handoff: HandoffView {
path: String::new(),
status: "skipped",
content: String::new(),
},
recovery: runtime_state::recovery_view(&core.recovery),
session_state: core.loaded_session.view,
activation: None,
escalation: core.escalation,
compiled_state: core.compiled_state.surface,
alerts: core.alerts,
refresh_actions: core.refresh_actions,
warnings: core.warnings,
})
}
pub(crate) fn build_host_context(
repo_root: &Path,
explicit_profile: Option<&str>,
refresh: bool,
mode: HostContextMode,
) -> Result<HostContextPayload> {
let core = resolve_core(repo_root, explicit_profile, refresh)?;
let disposition = derive_disposition(&core);
let session_boundary = build_session_boundary(&core, disposition);
let bundle = compiled_state::bundle_view_from_store(&core.compiled_state.store);
let mut sections = Vec::new();
sections.push(ContextSectionSpec {
name: "task",
inclusion_reason: "always_include_primary_task",
value: Value::String(bundle.task.clone()),
});
if matches!(mode, HostContextMode::Startup) {
let state = host_context_state_lines(&core);
if !state.is_empty() {
sections.push(ContextSectionSpec {
name: "state",
inclusion_reason: "startup_only_checkout_orientation",
value: serde_json::to_value(state)?,
});
}
}
let next_focus = host_context_next_focus(&bundle);
if !next_focus.is_empty() {
sections.push(ContextSectionSpec {
name: "next_focus",
inclusion_reason: "active_immediate_actions_and_focus_sections",
value: serde_json::to_value(next_focus)?,
});
}
let remember_selection = host_context_remember_selection(&core);
let remember = remember_selection
.as_ref()
.map(host_context_remember_items)
.unwrap_or_default();
if !remember.is_empty() {
sections.push(ContextSectionSpec {
name: "remember",
inclusion_reason: "ranked_memory_selection",
value: serde_json::to_value(remember)?,
});
}
let active_guardrails = host_context_section_items(
&bundle,
compiled_state::BundleSectionKind::Rules,
HOST_CONTEXT_GUARDRAILS_LIMIT,
HOST_CONTEXT_GUARDRAILS_MAX_CHARS,
);
if !active_guardrails.is_empty() {
sections.push(ContextSectionSpec {
name: "active_guardrails",
inclusion_reason: "active_operational_guardrails",
value: serde_json::to_value(active_guardrails)?,
});
}
let mut context = serde_json::Map::new();
for section in §ions {
context.insert(section.name.to_owned(), section.value.clone());
}
Ok(HostContextPayload {
session_boundary,
context: Value::Object(context),
sections,
remember_selection,
source_fingerprint: bundle.source_fingerprint,
session: ContextSessionSnapshot {
session_id: core.loaded_session.view.session_id.clone(),
started_at_epoch_s: core.loaded_session.view.started_at_epoch_s,
last_started_at_epoch_s: core.loaded_session.view.last_started_at_epoch_s,
start_count: core.loaded_session.view.start_count,
},
})
}
struct ResolvedCore {
profile: String,
locality_id: String,
layout: StateLayout,
git: Option<handoff::GitState>,
sources: runtime_state::RuntimeSourceSurfaces,
recovery: runtime_state::LoadedRuntimeRecoveryState,
compiled_state: compiled_state::PreparedCompiledState,
active_pod_identity: Option<pod_identity::LoadedPodIdentity>,
pod_identity: pod_identity::PodIdentityView,
machine_identity: pod_identity::MachineIdentityView,
machine_presence: machine_presence::MachinePresenceView,
execution_context: machine_presence::MachineExecutionContextView,
takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
coordination_scope: pod_identity::CoordinationScopeView,
memory_recall: memory_recall::StartRecallView,
memory_provider: memory_provider::MemoryProviderView,
startup_recall: memory_provider::StartupRecallView,
loaded_session: LoadedSessionState,
execution_gates: LoadedExecutionGates,
validation_profile: validation_profile::LoadedValidationProfile,
session_id: Option<String>,
escalation: escalation_state::EscalationView,
alerts: Vec<StartAlert>,
refresh_actions: Vec<RefreshAction>,
warnings: Vec<String>,
}
struct WorkspacePhase {
profile: String,
locality_id: String,
layout: StateLayout,
git: Option<handoff::GitState>,
session_id: Option<String>,
refresh_actions: Vec<RefreshAction>,
checkout_continuity_advisory: Option<CheckoutContinuityAdvisory>,
}
struct IdentityPhase {
active_pod_identity: Option<pod_identity::LoadedPodIdentity>,
pod_memory_binding: Option<pod_identity::ResolvedPodMemoryBinding>,
pod_identity: pod_identity::PodIdentityView,
machine_identity: pod_identity::MachineIdentityView,
coordination_scope: pod_identity::CoordinationScopeView,
}
struct SessionPhase {
loaded_session: LoadedSessionState,
execution_gates: LoadedExecutionGates,
escalation: escalation_state::EscalationView,
machine_presence: machine_presence::MachinePresenceView,
execution_context: machine_presence::MachineExecutionContextView,
takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
}
struct RuntimePhase {
sources: runtime_state::RuntimeSourceSurfaces,
recovery: runtime_state::LoadedRuntimeRecoveryState,
compiled_state: compiled_state::PreparedCompiledState,
validation_profile: validation_profile::LoadedValidationProfile,
memory_provider_inspection: memory_provider::MemoryProviderInspection,
startup_recall: memory_provider::StartupRecallView,
memory_recall: memory_recall::StartRecallView,
}
fn resolve_core(
repo_root: &Path,
explicit_profile: Option<&str>,
refresh: bool,
) -> Result<ResolvedCore> {
let workspace = resolve_workspace_phase(repo_root, explicit_profile, refresh)?;
let identity = resolve_identity_phase(&workspace.layout, &workspace.locality_id)?;
let session = resolve_session_phase(&workspace, &identity)?;
let runtime = resolve_runtime_phase(repo_root, &workspace, &identity)?;
let (alerts, warnings) =
build_start_alerts(repo_root, &workspace, &identity, &session, &runtime)?;
Ok(ResolvedCore {
profile: workspace.profile,
locality_id: workspace.locality_id,
layout: workspace.layout,
git: workspace.git,
sources: runtime.sources,
recovery: runtime.recovery,
compiled_state: runtime.compiled_state,
active_pod_identity: identity.active_pod_identity,
pod_identity: identity.pod_identity,
machine_identity: identity.machine_identity,
machine_presence: session.machine_presence,
execution_context: session.execution_context,
takeover_preconditions: session.takeover_preconditions,
coordination_scope: identity.coordination_scope,
memory_recall: runtime.memory_recall,
memory_provider: runtime.memory_provider_inspection.view,
startup_recall: runtime.startup_recall,
loaded_session: session.loaded_session,
execution_gates: session.execution_gates,
validation_profile: runtime.validation_profile,
session_id: workspace.session_id,
escalation: session.escalation,
alerts,
refresh_actions: workspace.refresh_actions,
warnings,
})
}
fn resolve_workspace_phase(
repo_root: &Path,
explicit_profile: Option<&str>,
refresh: bool,
) -> Result<WorkspacePhase> {
let profile = profile::resolve(explicit_profile)?;
let layout = StateLayout::resolve(repo_root, profile.clone())?;
ensure_profile_exists(&layout)?;
debug!(profile = %profile, "profile resolved");
let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
let attach_command = if layout.profile().as_str() == profile::DEFAULT_PROFILE {
format!("ccd attach --path {}", repo_root.display())
} else {
format!(
"ccd attach --path {} --profile {}",
repo_root.display(),
layout.profile()
)
};
let link_command = if layout.profile().as_str() == profile::DEFAULT_PROFILE {
format!("ccd link --path {}", repo_root.display())
} else {
format!(
"ccd link --path {} --profile {}",
repo_root.display(),
layout.profile()
)
};
anyhow::anyhow!(
"ccd start cannot load policy, memory, handoff, or dispatch state because this workspace is not linked: {} is missing. Run `{attach_command}` to bootstrap a new project overlay, or `{link_command}` to reconnect to an existing one.",
repo_root.join(repo_marker::MARKER_FILE).display(),
)
})?;
let locality_id = marker.locality_id;
ensure_repo_registry_exists(&layout, &locality_id)?;
debug!(locality_id = %locality_id, "repo linked");
let clone_profile_root = layout.clone_profile_root();
fs::create_dir_all(&clone_profile_root).with_context(|| {
format!(
"failed to create directory {}",
clone_profile_root.display()
)
})?;
let session_id = session_state::load_session_id(&layout)?;
let _ = refresh;
let refresh_actions = Vec::new();
let git = if layout.resolved_substrate().is_git() {
Some(handoff::read_git_state(
repo_root,
BranchMode::AllowDetachedHead,
)?)
} else {
None
};
let checkout_continuity_advisory = git
.as_ref()
.and_then(|git| handoff::checkout_continuity_advisory(repo_root, git));
Ok(WorkspacePhase {
profile: profile.to_string(),
locality_id,
layout,
git,
session_id,
refresh_actions,
checkout_continuity_advisory,
})
}
fn resolve_identity_phase(layout: &StateLayout, locality_id: &str) -> Result<IdentityPhase> {
let active_pod_identity = pod_identity::resolve_active_identity(layout)?;
let pod_memory_binding = pod_identity::resolve_pod_memory_binding(layout, locality_id)?;
let pod_identity = pod_identity::resolve_pod_identity_view(layout)?;
let machine_identity = pod_identity::resolve_machine_identity_view(layout)?;
let coordination_scope = pod_identity::resolve_coordination_scope_view(layout, locality_id)?;
Ok(IdentityPhase {
active_pod_identity,
pod_memory_binding,
pod_identity,
machine_identity,
coordination_scope,
})
}
fn resolve_session_phase(
workspace: &WorkspacePhase,
identity: &IdentityPhase,
) -> Result<SessionPhase> {
let loaded_session = load_session_state(&workspace.layout)?;
let execution_gates = load_execution_gates(&workspace.layout)?;
let escalation_entries = escalation_state::load_for_layout(&workspace.layout)?;
let escalation = escalation_state::build_view(&workspace.layout, &escalation_entries);
let machine_presence = machine_presence::resolve_machine_presence_view(&workspace.layout)?;
let execution_context = machine_presence::build_execution_context_view(
&workspace.layout,
Some(&workspace.locality_id),
&identity.machine_identity,
&machine_presence,
Some(&loaded_session.view.lifecycle),
);
let takeover_preconditions = machine_presence::build_takeover_preconditions_view(
&execution_context,
Some(&loaded_session.view.lifecycle),
escalation.blocking_count > 0,
escalation.blocking_count,
true,
);
Ok(SessionPhase {
loaded_session,
execution_gates,
escalation,
machine_presence,
execution_context,
takeover_preconditions,
})
}
fn resolve_runtime_phase(
repo_root: &Path,
workspace: &WorkspacePhase,
identity: &IdentityPhase,
) -> Result<RuntimePhase> {
let raw = runtime_state::load_raw_start_runtime_sources(
repo_root,
&workspace.layout,
&workspace.locality_id,
identity
.pod_memory_binding
.as_ref()
.map(|binding| binding.name.as_str()),
identity.active_pod_identity.is_some(),
)?;
debug!("runtime state loaded");
let validation_profile =
validation_profile::load_for_layout(&workspace.layout, &workspace.locality_id)?;
let memory_provider_inspection = memory_provider::inspect_provider(
repo_root,
&workspace.layout,
&workspace.locality_id,
true,
)?;
let start_result = compiled_state::ensure_for_start(&workspace.layout, raw)?;
debug!("compiled state ready");
let compiled_state = start_result.compiled;
let sources = start_result.sources;
let recovery = start_result.recovery;
let startup_recall = match memory_provider::collect_startup_recall(
&memory_provider_inspection,
repo_root,
&workspace.layout,
&workspace.locality_id,
&compiled_state.store.handoff,
) {
Ok(view) => view,
Err(error) => memory_provider::StartupRecallView {
status: "fallback".to_owned(),
query: None,
provider: memory_provider_inspection
.view
.configured_recall_provider
.clone()
.or_else(|| {
Some(
memory_provider_inspection
.view
.effective_recall_provider
.name
.clone(),
)
}),
results: Vec::new(),
message: Some(error.to_string()),
},
};
let mut recall_setup =
memory_provider::prepare_start_recall(&workspace.layout, repo_root, &workspace.locality_id);
let memory_recall = if let Some(error) = recall_setup.config_error.clone() {
memory_recall::StartRecallView {
status: "error",
configured_provider: None,
configured_provider_kind: None,
used_provider: None,
used_provider_kind: None,
fallback_used: false,
query: None,
budget: recall_setup.budget,
search_results: Vec::new(),
described_results: Vec::new(),
expanded_results: Vec::new(),
error: Some(error),
warnings: Vec::new(),
}
} else if let Some(provider) = recall_setup.provider.as_mut() {
let fallback_provider = if provider.is_builtin_markdown() {
None
} else {
Some(&mut recall_setup.fallback as &mut dyn memory_recall::RecallProvider)
};
memory_recall::run_start_recall(
provider,
fallback_provider,
memory_recall::build_start_query(
&compiled_state.store.handoff.title,
&compiled_state.store.handoff.immediate_actions,
),
recall_setup.budget,
)
} else {
memory_recall::StartRecallView::missing()
};
Ok(RuntimePhase {
sources,
recovery,
compiled_state,
validation_profile,
memory_provider_inspection,
startup_recall,
memory_recall,
})
}
fn build_start_alerts(
repo_root: &Path,
workspace: &WorkspacePhase,
identity: &IdentityPhase,
session: &SessionPhase,
runtime: &RuntimePhase,
) -> Result<(Vec<StartAlert>, Vec<String>)> {
let mut alerts = workspace
.refresh_actions
.iter()
.filter(|action| action.status == "failed")
.map(|action| StartAlert::warning(action.target, action.message.clone()))
.collect::<Vec<_>>();
for issue in &runtime.memory_provider_inspection.issues {
alerts.push(StartAlert::warning(issue.check, issue.message.clone()));
}
if runtime.startup_recall.status == "fallback" {
if let Some(message) = &runtime.startup_recall.message {
alerts.push(StartAlert::warning("memory_provider", message.clone()));
}
}
if let Some(error) = runtime.memory_recall.error.as_deref() {
alerts.push(StartAlert::warning(
"memory_provider",
format!("memory recall provider failed during start recall: {error}"),
));
}
for warning in &runtime.memory_recall.warnings {
alerts.push(StartAlert::warning("memory_provider", warning.clone()));
}
if identity.active_pod_identity.is_some() {
let migration_preview = pod_migration::analyze(&workspace.layout, &workspace.locality_id)?;
let suggested_memory = migration_preview
.memory_items
.iter()
.filter(|item| {
item.classification == pod_migration::MigrationClassification::PodWideCandidate
})
.count();
let suggested_policy = migration_preview
.policy_items
.iter()
.filter(|item| {
item.classification == pod_migration::MigrationClassification::PodWideCandidate
})
.count();
if suggested_memory > 0 || suggested_policy > 0 {
alerts.push(StartAlert::warning(
"pod_migration",
format!(
"pod identity is active but profile defaults still look pod-wide ({} memory, {} policy); preview them with `ccd pod migrate-defaults --path {}`",
suggested_memory,
suggested_policy,
repo_root.display()
),
));
}
}
if session.loaded_session.view.status == "active" {
alerts.push(StartAlert::warning(
"session_state",
format!(
"another active session is already recorded for profile `{}` in this workspace; continue it, or run `ccd session-state clear --path {}` if it is stale",
workspace.profile,
repo_root.display()
),
));
}
if runtime.validation_profile.status == "invalid" {
alerts.push(StartAlert::error(
"repo_validation_profile",
format!(
"failed to load {}: {}",
runtime.validation_profile.path.display(),
runtime
.validation_profile
.error
.clone()
.unwrap_or_else(|| "invalid validation profile".to_owned())
),
));
}
let current_digests = runtime
.compiled_state
.store
.projection_digests
.clone()
.unwrap_or_else(|| {
compiled_state::compute_projection_digests(&runtime.compiled_state.store)
});
let latest_projection_observation =
consistency::load_latest_projection_observation(&workspace.layout)?;
let compiled_handoff = compiled_handoff_state(&runtime.compiled_state.store.handoff);
let consistency = consistency::evaluate_with_inputs(consistency::ConsistencyInputs {
repo_root,
layout: &workspace.layout,
locality_id: &workspace.locality_id,
handoff: &compiled_handoff,
checkpoint: runtime.recovery.state.checkpoint.as_ref(),
include_checkpoint_references: false,
source_fingerprint: &runtime.compiled_state.store.source_fingerprint,
current_digests: ¤t_digests,
latest_observation: latest_projection_observation.as_ref(),
})?;
for axis in consistency.axes {
if axis.status.is_drift() {
alerts.push(StartAlert::warning(axis.id, axis.summary));
}
}
if let Some(advisory) = workspace.checkout_continuity_advisory.as_ref() {
alerts.push(StartAlert::warning(
"checkout_state",
advisory.start_alert_message(
workspace
.git
.as_ref()
.expect("checkout advisory requires git"),
),
));
}
for ext_diag in
extensions::health_diagnostics(&workspace.layout, repo_root, &workspace.locality_id)?
{
alerts.push(StartAlert {
check: ext_diag.check,
severity: match ext_diag.severity {
"error" => StartAlertSeverity::Error,
"info" => StartAlertSeverity::Info,
_ => StartAlertSeverity::Warning,
},
message: ext_diag.message,
details: ext_diag.details,
});
}
if let Some(message) = project_truth::legacy_roadmap_exclusion_warning(
repo_root,
&workspace.layout,
&workspace.locality_id,
)? {
alerts.push(StartAlert::warning("legacy_roadmap", message));
}
if session.escalation.blocking_count > 0 {
let blocking_reasons = session
.escalation
.entries
.iter()
.filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
.map(|entry| entry.reason.clone())
.collect::<Vec<_>>();
let reason = match blocking_reasons.as_slice() {
[] => "no blocking reasons recorded".to_owned(),
[single] => single.clone(),
reasons => reasons.join("; "),
};
let label = if session.escalation.blocking_count == 1 {
"blocking escalation"
} else {
"blocking escalations"
};
alerts.push(StartAlert::warning(
"escalation_state",
format!(
"{} {}: {}",
session.escalation.blocking_count, label, reason
),
));
}
let warnings = alerts
.iter()
.filter(|alert| matches!(alert.severity, StartAlertSeverity::Warning))
.map(|alert| alert.message.clone())
.collect::<Vec<_>>();
Ok((alerts, warnings))
}
fn compiled_handoff_state(
handoff: &compiled_state::CompiledHandoffView,
) -> runtime_state::RuntimeHandoffState {
runtime_state::RuntimeHandoffState {
title: handoff.title.clone(),
immediate_actions: compiled_handoff_items(&handoff.immediate_actions),
completed_state: compiled_handoff_items(&handoff.completed_state),
operational_guardrails: compiled_handoff_items(&handoff.operational_guardrails),
key_files: compiled_handoff_items(&handoff.key_files),
definition_of_done: compiled_handoff_items(&handoff.definition_of_done),
}
}
fn compiled_handoff_items(items: &[String]) -> Vec<runtime_state::RuntimeHandoffItem> {
items
.iter()
.map(|text| runtime_state::RuntimeHandoffItem {
text: text.clone(),
lifecycle: runtime_state::RuntimeLifecycle::Active,
})
.collect()
}
fn build_compact_summary(core: &ResolvedCore) -> CompactSummary {
let current_state_source = if core.git.is_some() {
"live_git"
} else {
"directory_workspace"
};
let mut current_system_state =
handoff::current_system_state_lines(core.git.as_ref(), core.session_id.as_deref());
if let Some(mode_line) = session_mode_current_state_line(&core.loaded_session.view) {
current_system_state.push(mode_line);
}
CompactSummary {
current_state: CompactCurrentState {
source: current_state_source,
title: core.compiled_state.store.handoff.title.clone(),
current_system_state,
},
next_focus: CompactNextFocus {
continuity_immediate_actions: core
.compiled_state
.store
.handoff
.immediate_actions
.clone(),
execution_gate_anchor: core
.execution_gates
.view
.attention_anchor
.as_ref()
.map(|anchor| format!("[{}] {}", anchor.status.as_str(), anchor.text)),
},
active_guardrails: CompactGuardrails {
operational_guardrails: core
.compiled_state
.store
.handoff
.operational_guardrails
.clone(),
effective_memory: core.compiled_state.store.effective_memory.clone(),
},
}
}
fn host_context_state_lines(core: &ResolvedCore) -> Vec<String> {
let mut lines = Vec::new();
if let Some(git) = core.git.as_ref() {
lines.push(format!("branch:{}", git.branch));
lines.push(format!("head:{}", git.head));
if !git.clean {
lines.push("worktree:dirty".to_owned());
} else {
lines.push("worktree:clean".to_owned());
}
let tracking = match (git.upstream.as_deref(), git.ahead, git.behind) {
(Some(_), 0, 0) => Some("tracking:in_sync".to_owned()),
(Some(_), ahead, 0) => Some(format!("tracking:ahead:{ahead}")),
(Some(_), 0, behind) => Some(format!("tracking:behind:{behind}")),
(Some(_), ahead, behind) => Some(format!("tracking:diverged:{ahead}:{behind}")),
(None, _, _) => Some("tracking:none".to_owned()),
};
if let Some(tracking) = tracking {
lines.push(tracking);
}
}
if let Some(mode) = core.loaded_session.view.mode {
if mode != session_state::SessionMode::General {
lines.push(format!("session_mode:{}", mode.as_str()));
}
}
lines
}
fn host_context_next_focus(bundle: &compiled_state::BundleProjectionView) -> Vec<String> {
let mut items = host_context_section_items(
bundle,
compiled_state::BundleSectionKind::Do,
HOST_CONTEXT_NEXT_FOCUS_LIMIT,
HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS,
);
items.extend(host_context_section_items(
bundle,
compiled_state::BundleSectionKind::Focus,
HOST_CONTEXT_NEXT_FOCUS_LIMIT,
HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS,
));
dedupe_preserving_order(&items, HOST_CONTEXT_NEXT_FOCUS_LIMIT)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RememberScope {
Clone,
Branch,
Repo,
Profile,
Pod,
}
impl RememberScope {
fn as_str(self) -> &'static str {
match self {
Self::Clone => "clone",
Self::Branch => "branch",
Self::Repo => "repo",
Self::Profile => "profile",
Self::Pod => "pod",
}
}
fn label(self) -> &'static str {
match self {
Self::Clone => "Workspace",
Self::Branch => "Work stream",
Self::Repo => "Project",
Self::Profile => "Profile",
Self::Pod => "Pod",
}
}
fn score_bonus(self) -> i64 {
match self {
Self::Clone => 40,
Self::Branch => 30,
Self::Repo => 20,
Self::Profile => 5,
Self::Pod => 0,
}
}
fn sort_rank(self) -> usize {
match self {
Self::Clone => 0,
Self::Branch => 1,
Self::Repo => 2,
Self::Profile => 3,
Self::Pod => 4,
}
}
}
#[derive(Clone)]
struct RememberCandidate {
dedupe_key: String,
rendered: String,
scope: RememberScope,
score: i64,
matched_tokens: Vec<String>,
reasons: Vec<String>,
suppression_reasons: Vec<String>,
created_at: Option<String>,
last_touched_session: Option<u64>,
source_ref: Option<String>,
created_at_epoch_s: Option<i64>,
}
fn host_context_remember_items(selection: &RememberSelectionView) -> Vec<String> {
selection
.selected
.iter()
.map(|item| compact_host_context_item(&item.rendered, HOST_CONTEXT_REMEMBER_MAX_CHARS))
.collect()
}
fn host_context_remember_selection(core: &ResolvedCore) -> Option<RememberSelectionView> {
let query = memory_recall::build_start_query(
&core.compiled_state.store.handoff.title,
&core.compiled_state.store.handoff.immediate_actions,
);
let query_tokens = query
.as_deref()
.map(remember_query_tokens)
.unwrap_or_default();
let memory = runtime_state::memory_state_from_sources(&core.sources);
let mut candidates = Vec::new();
remember_scope_candidates(
&mut candidates,
&memory.clone,
RememberScope::Clone,
&query_tokens,
);
remember_scope_candidates(
&mut candidates,
&memory.branch,
RememberScope::Branch,
&query_tokens,
);
remember_scope_candidates(
&mut candidates,
&memory.locality,
RememberScope::Repo,
&query_tokens,
);
remember_scope_candidates(
&mut candidates,
&memory.profile,
RememberScope::Profile,
&query_tokens,
);
remember_scope_candidates(
&mut candidates,
&memory.pod,
RememberScope::Pod,
&query_tokens,
);
if candidates.is_empty() {
return None;
}
apply_recency_bonus(&mut candidates);
candidates.sort_by(|left, right| {
right
.score
.cmp(&left.score)
.then_with(|| right.matched_tokens.len().cmp(&left.matched_tokens.len()))
.then_with(|| left.scope.sort_rank().cmp(&right.scope.sort_rank()))
.then_with(|| right.last_touched_session.cmp(&left.last_touched_session))
.then_with(|| right.created_at_epoch_s.cmp(&left.created_at_epoch_s))
.then_with(|| left.rendered.cmp(&right.rendered))
});
let mut selected = Vec::new();
let mut suppressed = Vec::new();
let mut seen = std::collections::HashSet::new();
for mut candidate in candidates {
if !seen.insert(candidate.dedupe_key.clone()) {
candidate
.suppression_reasons
.push("duplicate_of_higher_ranked_item".to_owned());
if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
suppressed.push(remember_candidate_view(candidate));
}
continue;
}
if candidate.score <= 0 {
candidate
.suppression_reasons
.push("non_positive_score".to_owned());
if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
suppressed.push(remember_candidate_view(candidate));
}
continue;
}
if selected.len() < HOST_CONTEXT_REMEMBER_LIMIT {
selected.push(remember_candidate_view(candidate));
} else if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
candidate
.suppression_reasons
.push("budget_exhausted".to_owned());
suppressed.push(remember_candidate_view(candidate));
}
}
Some(RememberSelectionView {
budget: HOST_CONTEXT_REMEMBER_LIMIT,
query,
selected,
suppressed,
})
}
fn remember_scope_candidates(
candidates: &mut Vec<RememberCandidate>,
entries: &[runtime_state::RuntimeMemoryEntry],
scope: RememberScope,
query_tokens: &[String],
) {
for entry in entries {
if !host_context_memory_entry_is_active(entry) {
continue;
}
if let Some(candidate) = remember_candidate(entry, scope, query_tokens) {
candidates.push(candidate);
}
}
}
fn remember_candidate(
entry: &runtime_state::RuntimeMemoryEntry,
scope: RememberScope,
query_tokens: &[String],
) -> Option<RememberCandidate> {
let projection_text = entry.projection_text();
let dedupe_key = projection_text.trim().to_owned();
if dedupe_key.is_empty() {
return None;
}
let rendered = format!("{}: {}", scope.label(), projection_text);
let normalized_tokens = remember_text_token_index(&format!(
"{} {} {}",
projection_text,
entry.tags.join(" "),
entry.source_ref.as_deref().unwrap_or("")
));
let matched_tokens = query_tokens
.iter()
.filter(|token| normalized_tokens.contains(token.as_str()))
.cloned()
.collect::<Vec<_>>();
let mut reasons = Vec::new();
let mut suppression_reasons = Vec::new();
let query_score = i64::try_from(matched_tokens.len())
.unwrap_or(i64::MAX)
.saturating_mul(90);
if !matched_tokens.is_empty() {
reasons.push(format!(
"matched_query_tokens: {}",
matched_tokens.join(",")
));
}
let scope_bonus = scope.score_bonus();
reasons.push(format!("scope_bonus: {}", scope.as_str()));
let structured_bonus = match entry.origin {
runtime_state::RuntimeMemoryOrigin::Structured { .. } => {
reasons.push("structured_memory_bonus".to_owned());
10
}
runtime_state::RuntimeMemoryOrigin::Narrative => 0,
};
let mut score = query_score + scope_bonus + structured_bonus;
if query_tokens.is_empty() {
score -= 200;
suppression_reasons.push("no_query_tokens_available".to_owned());
} else if matched_tokens.is_empty() {
score -= 120;
suppression_reasons.push("generic_without_query_match".to_owned());
}
let char_count = rendered.chars().count();
if char_count > HOST_CONTEXT_REMEMBER_MAX_CHARS {
let penalty = if matched_tokens.is_empty() { 90 } else { 20 };
score -= penalty;
suppression_reasons.push("broad_snippet_penalty".to_owned());
} else if matched_tokens.is_empty() && char_count > 96 {
score -= 30;
suppression_reasons.push("broad_snippet_penalty".to_owned());
}
Some(RememberCandidate {
dedupe_key,
rendered,
scope,
score,
matched_tokens,
reasons,
suppression_reasons,
created_at: entry.created_at.clone(),
last_touched_session: entry.last_touched_session,
source_ref: entry.source_ref.clone(),
created_at_epoch_s: remember_created_at_epoch(entry),
})
}
fn apply_recency_bonus(candidates: &mut [RememberCandidate]) {
let mut ranked = candidates
.iter()
.enumerate()
.filter_map(|(index, candidate)| {
(candidate.last_touched_session.is_some() || candidate.created_at_epoch_s.is_some())
.then_some((
index,
candidate.last_touched_session,
candidate.created_at_epoch_s,
))
})
.collect::<Vec<_>>();
ranked.sort_by(|left, right| right.1.cmp(&left.1).then_with(|| right.2.cmp(&left.2)));
for (rank, (index, _, _)) in ranked.into_iter().enumerate() {
let bonus = match rank {
0 => 20,
1 => 10,
2 => 5,
_ => 0,
};
if bonus == 0 {
continue;
}
let candidate = &mut candidates[index];
candidate.score += bonus;
candidate.reasons.push(format!("recency_bonus:{bonus}"));
}
}
fn remember_candidate_view(candidate: RememberCandidate) -> RememberSelectionItemView {
RememberSelectionItemView {
rendered: compact_host_context_item(&candidate.rendered, HOST_CONTEXT_REMEMBER_MAX_CHARS),
scope: candidate.scope.as_str(),
score: candidate.score,
matched_tokens: candidate.matched_tokens,
reasons: candidate.reasons,
suppressed_reasons: candidate.suppression_reasons,
created_at: candidate.created_at,
last_touched_session: candidate.last_touched_session,
source_ref: candidate.source_ref,
}
}
fn remember_query_tokens(query: &str) -> Vec<String> {
const STOPWORDS: &[&str] = &[
"active",
"again",
"all",
"also",
"and",
"any",
"are",
"before",
"branch",
"cache",
"changes",
"choose",
"continuing",
"follow",
"focus",
"from",
"github",
"handoff",
"here",
"into",
"issue",
"keep",
"main",
"memory",
"names",
"next",
"numbers",
"only",
"open",
"queue",
"refresh",
"review",
"resume",
"scope",
"scoped",
"session",
"snapshot",
"start",
"state",
"step",
"startup",
"target",
"text",
"the",
"that",
"this",
"too",
"use",
"using",
"via",
"whether",
"with",
"work",
"wrapper",
];
const HIGH_SIGNAL_SHORT_TOKENS: &[&str] = &[
"api", "ccd", "ci", "db", "gh", "id", "mcp", "pod", "pr", "ui", "ux",
];
let mut seen = std::collections::HashSet::new();
remember_text_tokens(query)
.into_iter()
.filter(|token| {
token.len() >= 4
|| (token.len() >= 2 && token.chars().all(|ch| ch.is_ascii_digit()))
|| HIGH_SIGNAL_SHORT_TOKENS.contains(&token.as_str())
})
.filter(|token| !STOPWORDS.contains(&token.as_str()))
.filter(|token| seen.insert(token.clone()))
.collect()
}
fn remember_text_tokens(value: &str) -> Vec<String> {
value
.split(|ch: char| !ch.is_ascii_alphanumeric())
.map(|token| token.trim().to_ascii_lowercase())
.filter(|token| !token.is_empty())
.collect()
}
fn remember_text_token_index(value: &str) -> std::collections::HashSet<String> {
remember_text_tokens(value).into_iter().collect()
}
fn remember_created_at_epoch(entry: &runtime_state::RuntimeMemoryEntry) -> Option<i64> {
entry
.created_at
.as_deref()
.and_then(timestamps::parse_rfc3339)
.map(|value| value.unix_timestamp())
}
fn host_context_memory_entry_is_active(entry: &runtime_state::RuntimeMemoryEntry) -> bool {
if !entry.lifecycle.is_active() {
return false;
}
let Some(expires_at) = entry.expires_at.as_deref() else {
return true;
};
match timestamps::parse_rfc3339(expires_at) {
Some(expires_at) => expires_at > timestamps::now_utc(),
None => true,
}
}
fn host_context_section_items(
bundle: &compiled_state::BundleProjectionView,
kind: compiled_state::BundleSectionKind,
limit: usize,
max_chars_per_item: usize,
) -> Vec<String> {
let items = bundle
.sections
.iter()
.find(|section| section.kind == kind)
.map(|section| section.items.clone())
.unwrap_or_default();
let compact = items
.into_iter()
.map(|item| compact_host_context_item(&item, max_chars_per_item))
.filter(|item| !item.is_empty())
.collect::<Vec<_>>();
dedupe_preserving_order(&compact, limit)
}
fn dedupe_preserving_order(items: &[String], limit: usize) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut deduped = Vec::new();
for item in items {
if seen.insert(item.clone()) {
deduped.push(item.clone());
}
if deduped.len() >= limit {
break;
}
}
deduped
}
fn compact_host_context_item(item: &str, max_chars: usize) -> String {
let compact = item.split_whitespace().collect::<Vec<_>>().join(" ");
if compact.chars().count() <= max_chars {
return compact;
}
if max_chars <= 3 {
return ".".repeat(max_chars);
}
let mut shortened = compact.chars().take(max_chars - 3).collect::<String>();
shortened.push_str("...");
shortened
}
const DISPOSITION_QUALIFYING_WARNINGS: &[&str] = &["session_state"];
fn derive_disposition(core: &ResolvedCore) -> StartupDisposition {
if core
.alerts
.iter()
.any(|a| matches!(a.severity, StartAlertSeverity::Error))
{
return StartupDisposition::WorkflowAttentionRequired;
}
if core.alerts.iter().any(|a| {
matches!(a.severity, StartAlertSeverity::Warning)
&& DISPOSITION_QUALIFYING_WARNINGS.contains(&a.check)
}) {
return StartupDisposition::WorkflowAttentionRequired;
}
if core.escalation.blocking_count > 0 {
return StartupDisposition::ResumeBlockedContinuity;
}
let title = &core.compiled_state.store.handoff.title;
let handoff_content = &core.sources.handoff.content;
if title != "No active session" && !handoff_content.trim().is_empty() {
return StartupDisposition::ResumeActiveContinuity;
}
StartupDisposition::NoActiveContinuity
}
fn build_session_boundary(
core: &ResolvedCore,
disposition: StartupDisposition,
) -> SessionBoundaryRecommendation {
let recovery_note = recovery_boundary_note(&core.recovery);
if core.escalation.blocking_count > 0 {
let mut evidence = core
.escalation
.entries
.iter()
.filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
.map(|entry| format!("{}: {}", entry.id, entry.reason))
.collect::<Vec<_>>();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop and resolve blocking escalations before continuing this session.",
evidence,
);
}
let stop_alerts = core
.alerts
.iter()
.filter(|alert| alert.check == "checkout_state")
.collect::<Vec<_>>();
if !stop_alerts.is_empty() {
let mut evidence = stop_alerts
.iter()
.map(|alert| alert.message.clone())
.collect::<Vec<_>>();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop here and choose the next step from a live checkout before continuing.",
evidence,
);
}
let refresh_alerts = core
.alerts
.iter()
.filter(|alert| alert.check == "session_state")
.collect::<Vec<_>>();
if !refresh_alerts.is_empty() {
let mut evidence = refresh_alerts
.iter()
.map(|alert| alert.message.clone())
.collect::<Vec<_>>();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Refresh,
"Refresh or clear the current session record before continuing this session.",
evidence,
);
}
if matches!(disposition, StartupDisposition::NoActiveContinuity) {
let mut evidence = vec![
"No active continuity snapshot or explicit next-step selection is loaded yet."
.to_owned(),
];
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop here and confirm the next step before continuing this session.",
evidence,
);
}
let mut evidence = Vec::new();
if !core
.compiled_state
.store
.handoff
.immediate_actions
.is_empty()
{
evidence.push(format!(
"The handoff already carries {} immediate action(s).",
core.compiled_state.store.handoff.immediate_actions.len()
));
}
append_recovery_note(&mut evidence, recovery_note.as_deref());
SessionBoundaryRecommendation::new(
SessionBoundaryAction::Continue,
"Continue with the current continuity context.",
evidence,
)
}
fn recovery_boundary_note(recovery: &runtime_state::LoadedRuntimeRecoveryState) -> Option<String> {
(recovery.state.checkpoint.is_some() || recovery.state.working_buffer.is_some()).then_some(
"Recovery artifacts are loaded as supporting context only; durable continuity remains authoritative."
.to_owned(),
)
}
fn append_recovery_note(evidence: &mut Vec<String>, note: Option<&str>) {
if let Some(note) = note {
evidence.push(note.to_owned());
}
}
fn start_readiness_view(alerts: &[StartAlert]) -> StartCheckReadiness {
let non_info_alerts: Vec<_> = alerts
.iter()
.filter(|a| !matches!(a.severity, StartAlertSeverity::Info))
.collect();
if non_info_alerts.is_empty() {
StartCheckReadiness::Ready
} else if non_info_alerts
.iter()
.all(|alert| alert.check == "escalation_state")
{
StartCheckReadiness::Blocked
} else {
StartCheckReadiness::NotReady
}
}
fn ensure_profile_exists(layout: &StateLayout) -> Result<()> {
let profile_root = layout.profile_root();
if profile_root.is_dir() {
return Ok(());
}
bail!(
"profile `{}` does not exist at {}; bootstrap it with `ccd attach` before using `ccd start`",
layout.profile(),
profile_root.display()
)
}
fn ensure_repo_registry_exists(layout: &StateLayout, locality_id: &str) -> Result<()> {
let registry_path = layout.repo_metadata_path(locality_id)?;
if repo_registry::load(®istry_path)?.is_some() {
return Ok(());
}
bail!(
"project ID `{locality_id}` (`locality_id` compatibility) is not linked in the registry: {} is missing; re-run `ccd link --project-id {locality_id}` or `ccd attach`",
registry_path.display()
)
}
fn resolve_manifest(
repo_root: &Path,
layout: &StateLayout,
locality_id: &str,
) -> Result<ManifestResolution> {
let resolved = project_truth::resolve_manifest(repo_root, layout, locality_id)?;
let sources = resolved
.project_truth_paths
.iter()
.map(|path| read_existing_source("project_truth", path))
.collect::<Result<Vec<_>>>()?;
Ok(ManifestResolution {
source_order: resolved.source_order,
manifest: ManifestView {
path: resolved.manifest_path.display().to_string(),
status: resolved.manifest_status,
entries: resolved
.entries
.iter()
.map(|entry| entry.display().to_string())
.collect(),
},
project_truth_sources: sources,
})
}
fn read_existing_source(kind: &'static str, path: &Path) -> Result<SourceView> {
let source = read_text_source(kind, path)?;
Ok(into_source_view(source))
}
fn read_text_source(kind: &'static str, path: &Path) -> Result<TextSource> {
match fs::read_to_string(path) {
Ok(contents) => Ok(TextSource {
kind,
path: path.to_path_buf(),
status: "loaded",
content: Some(contents),
}),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(TextSource {
kind,
path: path.to_path_buf(),
status: "missing",
content: None,
}),
Err(error) => Err(error).with_context(|| format!("failed to read {}", path.display())),
}
}
fn into_compiled_handoff_view(
source: &runtime_state::RuntimeTextSurface,
compiled_content: String,
) -> HandoffView {
HandoffView {
path: source.path.display().to_string(),
status: if source.status.is_loaded_native() {
"loaded"
} else {
source.status.as_str()
},
content: compiled_content,
}
}
struct LoadedSessionState {
view: SessionStateView,
raw: Option<session_state::SessionStateFile>,
}
struct LoadedExecutionGates {
view: session_gates::ExecutionGatesView,
raw: Option<session_gates::ExecutionGateStateFile>,
}
fn load_session_state(layout: &StateLayout) -> Result<LoadedSessionState> {
let path = layout.state_db_path();
let Some(state) = session_state::load_for_layout(layout)? else {
return Ok(LoadedSessionState {
view: SessionStateView {
path: path.display().to_string(),
status: "missing",
schema_version: None,
session_id: None,
started_at_epoch_s: None,
last_started_at_epoch_s: None,
start_count: None,
mode: None,
lifecycle: session_state::SessionLifecycleProjection::missing(),
},
raw: None,
});
};
let now = session_state::now_epoch_s()?;
let activity = session_state::load_activity_for_layout(layout)?;
let lifecycle = session_state::lifecycle_projection(&state, now, None, activity.as_ref());
let status = if lifecycle.stale == Some(true) || state.session_id.is_none() {
"stale"
} else {
"active"
};
Ok(LoadedSessionState {
view: SessionStateView {
path: path.display().to_string(),
status,
schema_version: Some(state.schema_version),
session_id: state.session_id.clone(),
started_at_epoch_s: Some(state.started_at_epoch_s),
last_started_at_epoch_s: Some(state.last_started_at_epoch_s),
start_count: Some(state.start_count),
mode: Some(state.mode),
lifecycle,
},
raw: Some(state),
})
}
fn session_mode_current_state_line(view: &SessionStateView) -> Option<String> {
match (view.status, view.mode) {
("active", Some(mode)) if mode != session_state::SessionMode::General => {
Some(format!("Session mode: `{}`", mode.as_str()))
}
_ => None,
}
}
fn load_execution_gates(layout: &StateLayout) -> Result<LoadedExecutionGates> {
let raw = session_gates::load_for_layout(layout)?;
Ok(LoadedExecutionGates {
view: session_gates::build_view(layout, raw.clone()),
raw,
})
}
fn session_state_source_view(
view: &SessionStateView,
raw: Option<&session_state::SessionStateFile>,
) -> Result<SourceView> {
let content = raw.map(serde_json::to_string_pretty).transpose()?;
Ok(SourceView {
kind: "session_state",
path: view.path.clone(),
status: view.status,
content,
})
}
fn execution_gates_source_view(
view: &session_gates::ExecutionGatesView,
raw: Option<&session_gates::ExecutionGateStateFile>,
) -> Result<SourceView> {
let content = raw.map(serde_json::to_string_pretty).transpose()?;
Ok(SourceView {
kind: "execution_gates",
path: view.path.clone(),
status: view.status,
content,
})
}
fn validation_profile_source_view(
profile: &validation_profile::LoadedValidationProfile,
) -> SourceView {
SourceView {
kind: "repo_validation_profile",
path: profile.path.display().to_string(),
status: profile.status,
content: profile
.contents
.clone()
.and_then(|content| non_empty_content(&content)),
}
}
fn render_effective_policy_view(
parts_in_order: &[policy_projection::PolicySource],
) -> RenderedView {
let mut parts = Vec::new();
let mut segments = Vec::new();
for source in parts_in_order {
parts.push(RenderedPart {
kind: source.kind,
path: source.path.display().to_string(),
status: source.status,
});
if let Some(content) = &source.content {
if !content.is_empty() {
segments.push(content.clone());
}
}
}
let content = segments.join("\n\n");
let status = if content.is_empty() {
"empty"
} else {
"loaded"
};
RenderedView {
status,
content,
parts,
}
}
fn render_effective_memory_view(
surfaces: EffectiveMemorySurfaces<'_>,
structured: memory_entries::StructuredMemoryView,
pod_identity_active: bool,
compiled_memory: &compiled_state::CompiledMemoryView,
) -> MemoryView {
let compiled_content =
compiled_state::render_memory_content(compiled_memory, pod_identity_active);
let parts = if pod_identity_active {
vec![
runtime_rendered_part(surfaces.pod),
runtime_rendered_part(surfaces.profile),
runtime_rendered_part(surfaces.locality),
runtime_rendered_part(surfaces.branch),
runtime_rendered_part(surfaces.clone),
]
} else {
vec![
runtime_rendered_part(surfaces.profile),
runtime_rendered_part(surfaces.locality),
runtime_rendered_part(surfaces.pod),
runtime_rendered_part(surfaces.branch),
runtime_rendered_part(surfaces.clone),
]
};
MemoryView {
status: if compiled_content.is_empty() {
"empty"
} else {
"loaded"
},
content: compiled_content,
parts,
structured,
}
}
fn runtime_rendered_part(source: &runtime_state::RuntimeTextSurface) -> RenderedPart {
RenderedPart {
kind: source.kind,
path: source.path.display().to_string(),
status: source.status.as_str(),
}
}
fn runtime_source_view(source: &runtime_state::RuntimeTextSurface) -> SourceView {
SourceView {
kind: source.kind,
path: source.path.display().to_string(),
status: source.status.as_str(),
content: non_empty_content(&source.content),
}
}
fn into_source_view(source: TextSource) -> SourceView {
let content = source.content.unwrap_or_default();
let status = if source.status == "loaded" && content.is_empty() {
"empty"
} else {
source.status
};
SourceView {
kind: source.kind,
path: source.path.display().to_string(),
status,
content: non_empty_content(&content),
}
}
fn non_empty_content(content: &str) -> Option<String> {
if content.is_empty() {
None
} else {
Some(content.to_owned())
}
}