use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use anyhow::{Context, Result, bail};
use futures::future::{FutureExt, Shared, TryFutureExt, join_all};
use futures::stream::{FuturesUnordered, StreamExt};
use mj_client::session::BoxFuture;
use mj_core::config::{Config, HarnessKind, HarnessProfile, SubagentConfig};
use mj_core::worker_launch::ProfileConfig;
use tokio_util::sync::CancellationToken;
#[derive(Clone, PartialEq)]
struct ProfilesKey {
profiles: BTreeMap<String, HarnessProfile>,
subagents: SubagentConfig,
}
impl ProfilesKey {
fn of(config: &Config) -> Self {
Self {
profiles: config.profiles.clone(),
subagents: config.subagents.clone(),
}
}
fn matches(&self, config: &Config) -> bool {
self.profiles == config.profiles && self.subagents == config.subagents
}
fn candidates(&self, parent: &str) -> Vec<(String, HarnessKind)> {
self.profiles
.iter()
.filter(|(id, profile)| {
profile.enabled && self.subagents.profile_is_eligible(parent, id)
})
.map(|(id, profile)| (id.clone(), profile.kind))
.collect()
}
fn warm_set(&self) -> Vec<String> {
if !self.subagents.enabled {
return Vec::new();
}
self.profiles
.iter()
.filter(|(_, profile)| profile.enabled)
.map(|(id, _)| id.clone())
.collect()
}
}
pub(crate) type Probe = dyn Fn(String) -> BoxFuture<'static, Result<ProfileConfig>> + Send + Sync;
#[derive(Clone, Debug)]
struct DiscoveryFailure(Arc<str>);
impl fmt::Display for DiscoveryFailure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl std::error::Error for DiscoveryFailure {}
type Attempt = Shared<BoxFuture<'static, Result<ProfileConfig, DiscoveryFailure>>>;
enum Entry {
Ready(ProfileConfig),
Pending(Attempt),
}
#[derive(Default)]
struct Inner {
generation: u64,
key: Option<ProfilesKey>,
entries: BTreeMap<String, Entry>,
}
impl Inner {
fn adopted(&self) -> Result<&ProfilesKey> {
self.key
.as_ref()
.context("the profile catalogue has not adopted a configuration")
}
fn generation_for(&self, profiles: &[String]) -> Result<u64> {
let key = self.adopted()?;
for profile in profiles {
if !key.profiles.contains_key(profile) {
bail!("the adopted configuration holds no '{profile}' profile");
}
}
Ok(self.generation)
}
}
pub(crate) struct ProfileCatalog {
cancellation: CancellationToken,
probe: Arc<Probe>,
inner: Mutex<Inner>,
}
impl ProfileCatalog {
pub(crate) fn new(cancellation: CancellationToken) -> Arc<Self> {
Self::build(
cancellation,
Arc::new(|profile| {
Box::pin(crate::controller::profile_config::discover(
profile, None, false,
))
}),
)
}
fn build(cancellation: CancellationToken, probe: Arc<Probe>) -> Arc<Self> {
Arc::new(Self {
cancellation,
probe,
inner: Mutex::new(Inner::default()),
})
}
pub(crate) fn sync(self: &Arc<Self>, config: &Config) {
let Some((generation, key)) = self.claim_pass(config) else {
return;
};
let catalog = self.clone();
tokio::spawn(async move { catalog.warm_pass(generation, key).await });
}
fn claim_pass(&self, config: &Config) -> Option<(u64, ProfilesKey)> {
let mut inner = self.lock();
if inner.key.as_ref().is_some_and(|key| key.matches(config)) {
return None;
}
let key = ProfilesKey::of(config);
inner.generation = inner.generation.wrapping_add(1);
inner.entries.clear();
inner.key = Some(key.clone());
Some((inner.generation, key))
}
pub(crate) fn candidates(&self, parent: &str) -> Result<Vec<(String, HarnessKind)>> {
let inner = self.lock();
Ok(inner.adopted()?.candidates(parent))
}
pub(crate) fn published(&self, profile: &str) -> Option<ProfileConfig> {
let inner = self.lock();
inner.key.as_ref()?;
match inner.entries.get(profile) {
Some(Entry::Ready(config)) => Some(config.clone()),
_ => None,
}
}
pub(crate) async fn capabilities(&self, profiles: &[String]) -> Result<Vec<ProfileConfig>> {
let generation = {
let inner = self.lock();
inner.generation_for(profiles)?
};
let mut discoveries: Vec<BoxFuture<'_, Result<ProfileConfig>>> =
Vec::with_capacity(profiles.len());
for profile in profiles {
match self.entry(generation, profile) {
Some(Entry::Ready(config)) => discoveries.push(async move { Ok(config) }.boxed()),
Some(Entry::Pending(attempt)) => {
let catalog = self;
let profile = profile.clone();
discoveries.push(
async move {
match attempt.await {
Ok(config) => {
catalog.remember(generation, &profile, &config);
Ok(config)
}
Err(failure) => {
catalog.forget(generation, &profile);
Err(anyhow::anyhow!(
"could not discover the capabilities of the \
'{profile}' profile: {failure}"
))
}
}
}
.boxed(),
);
}
None => bail!(
"the profile catalogue adopted a new configuration before it could \
answer for the '{profile}' profile"
),
}
}
let results = tokio::select! {
_ = self.cancellation.cancelled() => bail!(
"the server stopped before the profile catalogue discovered what \
list_profiles needs"
),
results = join_all(discoveries) => results,
};
results.into_iter().collect()
}
fn entry(&self, generation: u64, profile: &str) -> Option<Entry> {
let mut inner = self.lock();
if inner.generation != generation {
return None;
}
Some(match inner.entries.get(profile) {
Some(Entry::Ready(config)) => Entry::Ready(config.clone()),
Some(Entry::Pending(attempt)) => Entry::Pending(attempt.clone()),
None => {
let attempt = (self.probe)(profile.to_owned())
.map_err(|error| DiscoveryFailure(format!("{error:#}").into()))
.boxed()
.shared();
inner
.entries
.insert(profile.to_owned(), Entry::Pending(attempt.clone()));
Entry::Pending(attempt)
}
})
}
fn remember(&self, generation: u64, profile: &str, config: &ProfileConfig) {
let mut inner = self.lock();
if inner.generation != generation {
return;
}
inner
.entries
.insert(profile.to_owned(), Entry::Ready(config.clone()));
}
fn forget(&self, generation: u64, profile: &str) {
let mut inner = self.lock();
if inner.generation != generation {
return;
}
inner.entries.remove(profile);
}
async fn warm_pass(self: Arc<Self>, generation: u64, key: ProfilesKey) {
let mut discoveries = FuturesUnordered::new();
for profile in key.warm_set() {
if self.cancellation.is_cancelled() {
break;
}
match self.entry(generation, &profile) {
None => break,
Some(Entry::Ready(_)) => {}
Some(Entry::Pending(attempt)) => {
discoveries.push(async move { (profile, attempt.await) });
}
}
}
loop {
let next = tokio::select! {
_ = self.cancellation.cancelled() => break,
next = discoveries.next() => next,
};
let Some((profile, result)) = next else {
break;
};
if !self.is_current(generation) {
break;
}
match result {
Ok(config) => self.remember(generation, &profile, &config),
Err(failure) => {
self.forget(generation, &profile);
tracing::warn!(
profile,
error = %failure,
"profile catalog discovery failed; list_profiles will retry it on demand"
);
}
}
}
}
fn is_current(&self, generation: u64) -> bool {
self.lock().generation == generation
}
fn lock(&self) -> MutexGuard<'_, Inner> {
self.inner.lock().unwrap_or_else(PoisonError::into_inner)
}
#[cfg(test)]
pub(crate) fn with_probe(probe: Arc<Probe>) -> Arc<Self> {
Self::build(CancellationToken::new(), probe)
}
#[cfg(test)]
pub(crate) async fn sync_now(self: &Arc<Self>, config: &Config) {
if let Some((generation, key)) = self.claim_pass(config) {
self.clone().warm_pass(generation, key).await;
}
}
}
#[cfg(test)]
pub(crate) fn test_config(profiles: &[(&str, HarnessKind)], eligible: &[&str]) -> Config {
Config {
profiles: profiles
.iter()
.map(|(id, kind)| {
(
(*id).to_owned(),
HarnessProfile {
enabled: true,
kind: *kind,
home: std::path::PathBuf::from("/home/agent").join(id),
environment: BTreeMap::new(),
context_window_bytes: None,
guardian_review_model: None,
},
)
})
.collect(),
subagents: SubagentConfig {
eligible_profiles: eligible.iter().map(|id| ((*id).to_owned(), true)).collect(),
..SubagentConfig::default()
},
..Config::default()
}
}
#[cfg(test)]
pub(crate) fn counting_probe(calls: Arc<std::sync::atomic::AtomicUsize>) -> Arc<Probe> {
Arc::new(move |profile| {
calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Box::pin(async move { Ok(test_choices(&profile)) })
})
}
#[cfg(test)]
fn test_choices(profile: &str) -> ProfileConfig {
ProfileConfig {
model: Some(format!("{profile}-model")),
models: vec![mj_core::acp::SessionConfigChoice {
value: format!("{profile}-model"),
name: format!("{profile} model"),
description: None,
}],
efforts: Vec::new(),
observed_at: 1_700_000_000,
}
}
#[cfg(test)]
fn flag_probe(
fails: Arc<std::sync::atomic::AtomicBool>,
calls: Arc<std::sync::atomic::AtomicUsize>,
) -> Arc<Probe> {
Arc::new(move |profile| {
let fails = fails.clone();
let calls = calls.clone();
Box::pin(async move {
calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if fails.load(std::sync::atomic::Ordering::SeqCst) {
anyhow::bail!("harness is not installed")
}
Ok(test_choices(&profile))
})
})
}
#[cfg(test)]
fn gated_probe(
calls: Arc<std::sync::atomic::AtomicUsize>,
started: tokio::sync::mpsc::UnboundedSender<String>,
gate: tokio::sync::watch::Receiver<bool>,
) -> Arc<Probe> {
Arc::new(move |profile| {
let calls = calls.clone();
let started = started.clone();
let mut gate = gate.clone();
Box::pin(async move {
calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let _ = started.send(profile.clone());
if !*gate.borrow_and_update() {
let _ = gate.changed().await;
}
Ok(test_choices(&profile))
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
fn calls() -> Arc<AtomicUsize> {
Arc::new(AtomicUsize::new(0))
}
fn fails() -> Arc<std::sync::atomic::AtomicBool> {
Arc::new(std::sync::atomic::AtomicBool::new(true))
}
#[tokio::test]
async fn a_warm_pass_discovers_every_enabled_profile_for_every_parent() {
let calls = calls();
let catalog = ProfileCatalog::with_probe(counting_probe(calls.clone()));
let config = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
("private", HarnessKind::Kimi),
],
&["helper"],
);
catalog.sync_now(&config).await;
assert_eq!(
catalog
.candidates("parent")
.expect("a configuration is adopted"),
vec![
("helper".to_owned(), HarnessKind::Claude),
("parent".to_owned(), HarnessKind::Codex)
],
"a parent may delegate to itself and to the profiles listed as eligible"
);
assert_eq!(
calls.load(Ordering::SeqCst),
3,
"every enabled profile is discovered once, whether or not a parent may offer it"
);
let choices = catalog
.capabilities(&["parent".to_owned(), "helper".to_owned()])
.await
.expect("the pass discovered both");
assert_eq!(choices[0].model.as_deref(), Some("parent-model"));
assert_eq!(choices[1].model.as_deref(), Some("helper-model"));
assert_eq!(
calls.load(Ordering::SeqCst),
3,
"an answer from the warm catalogue discovers nothing"
);
}
#[tokio::test]
async fn a_warm_pass_probes_nothing_when_the_sub_agent_policy_is_disabled() {
let calls = calls();
let catalog = ProfileCatalog::with_probe(counting_probe(calls.clone()));
let mut config = test_config(&[("parent", HarnessKind::Codex)], &[]);
config.subagents.enabled = false;
catalog.sync_now(&config).await;
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"no parent is offered anything, so no harness is started"
);
assert!(
catalog
.candidates("parent")
.expect("a configuration is adopted")
.is_empty()
);
}
#[tokio::test]
async fn a_configuration_change_invalidates_and_re_warms_the_catalogue() {
let calls = calls();
let catalog = ProfileCatalog::with_probe(counting_probe(calls.clone()));
let config = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
],
&["helper"],
);
catalog.sync_now(&config).await;
catalog.sync_now(&config).await;
assert_eq!(calls.load(Ordering::SeqCst), 2);
let changed = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
("sibling", HarnessKind::Kimi),
],
&["helper", "sibling"],
);
catalog.sync_now(&changed).await;
assert_eq!(
catalog
.candidates("parent")
.expect("the replacement configuration is adopted")
.iter()
.map(|(id, _)| id.as_str())
.collect::<Vec<_>>(),
vec!["helper", "parent", "sibling"]
);
assert_eq!(
calls.load(Ordering::SeqCst),
5,
"the new configuration is discovered from scratch"
);
let choices = catalog
.capabilities(&["sibling".to_owned()])
.await
.expect("the new pass discovered the profile it added");
assert_eq!(choices[0].model.as_deref(), Some("sibling-model"));
}
#[tokio::test]
async fn a_superseded_pass_starts_no_harness() {
let calls = calls();
let catalog = ProfileCatalog::with_probe(counting_probe(calls.clone()));
let first = test_config(&[("parent", HarnessKind::Codex)], &[]);
let second = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
],
&["helper"],
);
let (generation, key) = catalog
.claim_pass(&first)
.expect("the first pass is claimed");
catalog
.claim_pass(&second)
.expect("the change claims a new pass");
catalog.clone().warm_pass(generation, key).await;
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"a superseded pass must stop before it starts a harness"
);
}
#[tokio::test]
async fn a_discovery_of_a_superseded_generation_is_not_published() {
let calls = calls();
let probes_fail = fails();
let catalog = ProfileCatalog::with_probe(flag_probe(probes_fail.clone(), calls.clone()));
let first = test_config(&[("parent", HarnessKind::Codex)], &[]);
let (stale, _key) = catalog
.claim_pass(&first)
.expect("the first pass is claimed");
let stale_entry = catalog
.entry(stale, "parent")
.expect("the superseded pass planned its discovery");
let second = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
],
&["helper"],
);
catalog.sync_now(&second).await;
probes_fail.store(false, Ordering::SeqCst);
let Entry::Pending(attempt) = stale_entry else {
panic!("the pending discovery of the superseded generation is at hand")
};
let config = attempt.await.expect("the probe itself succeeds");
catalog.remember(stale, "parent", &config);
let before = calls.load(Ordering::SeqCst);
let choices = catalog
.capabilities(&["parent".to_owned()])
.await
.expect("the adopted configuration answers");
assert_eq!(choices[0].model.as_deref(), Some("parent-model"));
assert_eq!(
calls.load(Ordering::SeqCst),
before + 1,
"the superseded discovery was dropped, so this call discovers the profile itself"
);
}
#[tokio::test]
async fn a_failed_discovery_is_not_cached_and_the_next_call_retries_it() {
let calls = calls();
let probes_fail = fails();
let catalog = ProfileCatalog::with_probe(flag_probe(probes_fail.clone(), calls.clone()));
let config = test_config(&[("parent", HarnessKind::Codex)], &[]);
catalog.sync_now(&config).await;
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"the pass tries the profile once"
);
let error = catalog
.capabilities(&["parent".to_owned()])
.await
.expect_err("the harness is still broken");
assert!(
format!("{error:#}").contains("harness is not installed"),
"{error:#}"
);
probes_fail.store(false, Ordering::SeqCst);
let choices = catalog
.capabilities(&["parent".to_owned()])
.await
.expect("the retry succeeds");
assert_eq!(choices[0].model.as_deref(), Some("parent-model"));
let discoveries = calls.load(Ordering::SeqCst);
catalog
.capabilities(&["parent".to_owned()])
.await
.expect("the retry's answer is kept");
assert_eq!(
calls.load(Ordering::SeqCst),
discoveries,
"a discovery the call waited for serves the calls after it"
);
}
#[tokio::test]
async fn a_call_waits_for_the_discovery_the_background_pass_is_running() {
let calls = calls();
let (started_tx, mut started) = tokio::sync::mpsc::unbounded_channel();
let (release, gate) = tokio::sync::watch::channel(false);
let catalog =
ProfileCatalog::with_probe(gated_probe(calls.clone(), started_tx, gate.clone()));
let config = test_config(
&[
("parent", HarnessKind::Codex),
("helper", HarnessKind::Claude),
],
&["helper"],
);
catalog.sync(&config);
let mut in_flight = Vec::new();
while in_flight.len() < 2 {
in_flight.push(started.recv().await.expect("the pass starts its probes"));
}
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"the pass discovers each enabled profile once: {in_flight:?}"
);
let call = {
let catalog = catalog.clone();
tokio::spawn(async move {
catalog
.capabilities(&["parent".to_owned(), "helper".to_owned()])
.await
})
};
tokio::task::yield_now().await;
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"the call must not start a second discovery for either profile"
);
release.send(true).expect("the gate is held by the test");
let choices = call
.await
.expect("the call task is not cancelled")
.expect("the pass's discoveries answer the call");
assert_eq!(choices[0].model.as_deref(), Some("parent-model"));
assert_eq!(choices[1].model.as_deref(), Some("helper-model"));
catalog
.capabilities(&["parent".to_owned()])
.await
.expect("the pass's discovery is now the catalogue's answer");
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"the discovery the call waited for is kept"
);
}
#[tokio::test]
async fn published_answers_only_from_what_the_pass_has_already_published() {
let calls = calls();
let (started_tx, mut started) = tokio::sync::mpsc::unbounded_channel();
let (release, gate) = tokio::sync::watch::channel(false);
let catalog = ProfileCatalog::with_probe(gated_probe(calls.clone(), started_tx, gate));
let config = test_config(&[("parent", HarnessKind::Codex)], &[]);
assert!(
catalog.published("parent").is_none(),
"nothing has been adopted yet"
);
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"the lookup must not start a discovery"
);
catalog.sync(&config);
started.recv().await.expect("the pass starts its probe");
assert!(
catalog.published("parent").is_none(),
"the discovery is still in flight, so there is nothing to answer with"
);
assert!(
catalog.published("unknown").is_none(),
"the adopted configuration holds no such profile"
);
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"neither lookup started a discovery of its own"
);
release.send(true).expect("the gate is held by the test");
catalog
.capabilities(&["parent".to_owned()])
.await
.expect("the discovery lands");
assert_eq!(
catalog
.published("parent")
.expect("the discovery is published now")
.model
.as_deref(),
Some("parent-model")
);
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"the published answer costs no discovery"
);
}
#[tokio::test]
async fn an_answer_before_a_configuration_is_adopted_reports_that() {
let calls = calls();
let catalog = ProfileCatalog::with_probe(counting_probe(calls.clone()));
let error = catalog
.candidates("parent")
.expect_err("nothing has been adopted");
assert!(
format!("{error:#}").contains("has not adopted a configuration"),
"{error:#}"
);
let error = catalog
.capabilities(&["parent".to_owned()])
.await
.expect_err("nothing has been adopted");
assert!(
format!("{error:#}").contains("has not adopted a configuration"),
"{error:#}"
);
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"a call that cannot be answered starts no harness"
);
}
}