Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/continuum-mind/src/persona/placement_switch/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,14 @@ impl AIProviderAdapter for PlacementSwitch {
fn live_served_window(&self) -> Option<u32> {
self.current().live_served_window()
}
/// Asked of the adapter her request actually goes to, like every other capability here: a
/// home lane on our fork's gateway may take her learned tool head, a remote seat may not.
/// Left to the trait default, every resident read `false` behind this switch, so the head
/// stayed a shadow on the M5 after #5491 deployed (`delib.tool_head.epoch sent=false`,
/// Aiko 2026-10-27 00:2xZ, engine 1c6681593 serving).
fn accepts_tail_tools(&self, model: &str) -> bool {
self.current().accepts_tail_tools(model)
}
/// Her home model: the request carries the lane's model on the wire when remote.
fn default_model(&self) -> &str {
&self.home_model
Expand Down
40 changes: 40 additions & 0 deletions core/continuum-mind/src/persona/placement_switch/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,43 @@ async fn capacity_park_resumes_when_local_1_5b_routes_to_larger_remote_seat() {
.expect("the actual forwarding target changed before the waiter was first polled");
assert_eq!(binding.load().requested_model(), "local-1.5B");
}

// A home lane on the fork's gateway: it takes a learned tool head.
struct TailLane;

#[async_trait::async_trait]
impl AIProviderAdapter for TailLane {
fn provider_id(&self) -> &str {
"tail-lane-fixture"
}
fn name(&self) -> &str {
"tail lane"
}
fn default_model(&self) -> &str {
"fixture"
}
fn accepts_tail_tools(&self, _model: &str) -> bool {
true
}
async fn generate_text(
&self,
_request: TextGenerationRequest,
) -> Result<TextGenerationResponse, String> {
Err("capability fixture must not generate".into())
}
}

// what this catches: the switch every resident is bound through answering the trait default
// (`false`) for `accepts_tail_tools`, so no resident's learned tool head is ever sent even
// when her home lane takes it (M5 2026-10-27, after #5491 deployed). The answer must follow
// the seat: true at a home lane that takes tails, false once she routes to a remote seat.
#[tokio::test]
async fn the_switch_asks_the_seat_she_routes_to_whether_it_takes_her_tool_head() {
let now = continuum_base::time::EpochMs::now().millis();
let home: Arc<dyn AIProviderAdapter> = Arc::new(TailLane);
let factory = Arc::new(ScriptedPersonaAdapterFactory::custom(move |_| Ok(home.clone())));
let route = Arc::new(switch(factory, remote()));
assert!(!route.accepts_tail_tools("fixture"), "routed to the remote seat, no tail");
route.apply(&PlacementMove::FallHome { reason: "tail fixture" }, now, SeatFit::default()).await;
assert!(route.accepts_tail_tools("fixture"), "her home lane takes the tail");
}
Loading