use kmp_domain::{KmpBundle, KmpMode, RelationSemanticClass};
use crate::queries::EndpointHint;
pub(crate) fn resolve_mode(
explicit_mode: KmpMode,
bundle: &KmpBundle,
token_budget: Option<u32>,
focus_node_id: Option<&str>,
endpoint_hint: EndpointHint,
) -> KmpMode {
match explicit_mode {
KmpMode::Auto => auto_detect(bundle, token_budget, focus_node_id, endpoint_hint),
concrete => concrete,
}
}
const TOKENS_PER_NODE_THRESHOLD: u32 = 30;
const FOCUSED_TOKENS_PER_NODE_THRESHOLD: u32 = 60;
const SESSION_TOKENS_PER_NODE_THRESHOLD: u32 = 15;
const CAUSAL_DENSITY_PRESERVE_THRESHOLD: f64 = 0.5;
const STRUCTURAL_OVERRIDE_DENSITY: f64 = 0.2;
fn auto_detect(
bundle: &KmpBundle,
token_budget: Option<u32>,
focus_node_id: Option<&str>,
endpoint_hint: EndpointHint,
) -> KmpMode {
let Some(budget) = token_budget else {
return KmpMode::ReasonPreserving;
};
let total_nodes = bundle.stats().selected_nodes();
if total_nodes == 0 {
return KmpMode::ReasonPreserving;
}
let tokens_per_node = budget / total_nodes;
let causal_density = bundle_causal_density(bundle);
let effective_threshold = match endpoint_hint {
EndpointHint::SessionSnapshot => SESSION_TOKENS_PER_NODE_THRESHOLD,
_ if focus_node_id.is_some() => FOCUSED_TOKENS_PER_NODE_THRESHOLD,
_ => TOKENS_PER_NODE_THRESHOLD,
};
if tokens_per_node >= effective_threshold {
if causal_density < STRUCTURAL_OVERRIDE_DENSITY {
return KmpMode::ResumeFocused;
}
return KmpMode::ReasonPreserving;
}
if causal_density >= CAUSAL_DENSITY_PRESERVE_THRESHOLD {
KmpMode::ReasonPreserving
} else {
KmpMode::ResumeFocused
}
}
fn bundle_causal_density(bundle: &KmpBundle) -> f64 {
let total = bundle.relationships().len();
if total == 0 {
return 0.0;
}
let explanatory = bundle
.relationships()
.iter()
.filter(|r| {
matches!(
r.explanation().semantic_class(),
RelationSemanticClass::Causal
| RelationSemanticClass::Motivational
| RelationSemanticClass::Evidential
)
})
.count();
explanatory as f64 / total as f64
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use kmp_domain::{
BundleMetadata, BundleNode, BundleRelationship, CaseId, KmpBundle, KmpMode,
RelationExplanation, RelationSemanticClass, Role,
};
use super::resolve_mode;
use crate::queries::EndpointHint;
fn bundle_with_nodes(count: usize) -> KmpBundle {
let root = BundleNode::new(
"case",
"case",
"Root",
"",
"ACTIVE",
vec![],
BTreeMap::new(),
);
let neighbors: Vec<_> = (0..count.saturating_sub(1))
.map(|i| {
BundleNode::new(
format!("n{i}"),
"task",
format!("N{i}"),
"",
"ACTIVE",
vec![],
BTreeMap::new(),
)
})
.collect();
KmpBundle::new(
CaseId::new("case").expect("valid"),
Role::new("dev").expect("valid"),
root,
neighbors,
Vec::new(),
Vec::new(),
BundleMetadata::initial("0.1.0"),
)
.expect("valid")
}
fn bundle_with_causal_relations(node_count: usize, causal_count: usize) -> KmpBundle {
let root = BundleNode::new(
"case",
"case",
"Root",
"",
"ACTIVE",
vec![],
BTreeMap::new(),
);
let neighbors: Vec<_> = (0..node_count.saturating_sub(1))
.map(|i| {
BundleNode::new(
format!("n{i}"),
"task",
format!("N{i}"),
"",
"ACTIVE",
vec![],
BTreeMap::new(),
)
})
.collect();
let mut relationships = Vec::new();
for i in 0..node_count.saturating_sub(1) {
let class = if i < causal_count {
RelationSemanticClass::Causal
} else {
RelationSemanticClass::Structural
};
relationships.push(BundleRelationship::new(
"case",
format!("n{i}"),
"RELATES",
RelationExplanation::new(class),
));
}
KmpBundle::new(
CaseId::new("case").expect("valid"),
Role::new("dev").expect("valid"),
root,
neighbors,
relationships,
Vec::new(),
BundleMetadata::initial("0.1.0"),
)
.expect("valid")
}
#[test]
fn auto_selects_resume_focused_when_budget_tight_and_structural() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(512),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn auto_keeps_reason_preserving_when_budget_tight_but_high_causal_density() {
let bundle = bundle_with_causal_relations(49, 40);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(512),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn auto_selects_resume_focused_when_budget_tight_and_low_causal_density() {
let bundle = bundle_with_causal_relations(49, 5);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(512),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn auto_selects_reason_preserving_when_no_budget() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
None,
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn explicit_mode_passes_through() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::ResumeFocused,
&bundle,
Some(4096),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn auto_selects_resume_focused_for_single_node_tight_budget() {
let bundle = bundle_with_nodes(1);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(10),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn focus_biases_toward_resume_focused_at_moderate_budget() {
let bundle = bundle_with_nodes(21);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(1000),
Some("n1"),
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn focus_keeps_reason_preserving_at_generous_budget() {
let bundle = bundle_with_causal_relations(10, 5); let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(1000),
Some("n1"),
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn focus_does_not_affect_no_budget_case() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
None,
Some("n1"),
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn session_snapshot_keeps_reason_preserving_at_moderate_pressure() {
let bundle = bundle_with_causal_relations(49, 30); let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(1000),
None,
EndpointHint::SessionSnapshot,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn session_snapshot_falls_to_resume_focused_at_extreme_pressure() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(200),
None,
EndpointHint::SessionSnapshot,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn focused_path_hint_activates_focus_threshold() {
let bundle = bundle_with_nodes(21);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(1000),
None,
EndpointHint::FocusedPath,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn generous_budget_structural_graph_switches_to_resume_focused() {
let bundle = bundle_with_nodes(21);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(4096),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ResumeFocused);
}
#[test]
fn generous_budget_causal_graph_stays_reason_preserving() {
let bundle = bundle_with_causal_relations(21, 10); let mode = resolve_mode(
KmpMode::Auto,
&bundle,
Some(4096),
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
#[test]
fn no_budget_stays_reason_preserving_even_when_all_structural() {
let bundle = bundle_with_nodes(49);
let mode = resolve_mode(
KmpMode::Auto,
&bundle,
None,
None,
EndpointHint::Neighborhood,
);
assert_eq!(mode, KmpMode::ReasonPreserving);
}
}