use argonauts_core::display_names::DisplayNames;
use argonauts_core::names::{ModelRef, RoleName};
use argonauts_core::session_runner::model_run;
use argonauts_core::settings::Settings;
use argonauts_core::settings::crew::CrewRole;
use crate::engine::host::Host;
pub mod tasks;
pub fn candidates(settings: &Settings, role: RoleName) -> Vec<ModelRef> {
if role == RoleName::Main {
return vec![settings.crew.main.clone()];
}
let configured = settings
.roles
.get(&role)
.map(|role| role.models.as_slice())
.unwrap_or_default();
let crew_role = CrewRole::followed_by(role);
let pool = if !configured.is_empty() {
configured.to_vec()
} else if let Some(followed) = settings.crew.followed(crew_role) {
candidates(settings, followed.fine_role())
} else {
settings.models_for(crew_role)
};
let mut workers: Vec<ModelRef> = Vec::with_capacity(pool.len());
for worker in pool {
if !workers.contains(&worker) {
workers.push(worker);
}
}
if workers.is_empty() {
workers.push(settings.crew.main.clone());
}
workers
}
pub fn provider(host: &Host, worker: &ModelRef) -> String {
host.services().registry.provider(worker)
}
pub fn runs(host: &Host, worker: &ModelRef) -> ModelRef {
model_run(
Some(host.services().workers.as_ref()),
host.settings(),
worker,
)
}
pub fn display_name(worker: &ModelRef) -> String {
DisplayNames::default().worker(worker)
}
pub fn short_name(worker: &ModelRef) -> String {
DisplayNames::default().short(worker)
}
pub fn conflict(
role: RoleName,
model: &ModelRef,
worker: Option<&ModelRef>,
providers: &dyn Fn(&ModelRef) -> String,
) -> Option<String> {
let worker = worker?;
let what = match role {
RoleName::Reviewer => "review its own task",
RoleName::Judge => "judge its own work",
_ => return None,
};
same_agent(model, worker, providers).then(|| format!("{} can't {what}", display_name(model)))
}
fn same_agent(a: &ModelRef, b: &ModelRef, providers: &dyn Fn(&ModelRef) -> String) -> bool {
if a.agent != b.agent {
return false;
}
match (&a.model, &b.model) {
(Some(x), Some(y)) => x == y,
_ => providers(a) == providers(b),
}
}
#[cfg(test)]
mod tests {
use argonauts_core::settings::roles::RoleSettings;
use super::*;
fn worker(text: &str) -> ModelRef {
text.parse().unwrap()
}
#[test]
fn a_worker_is_named_as_people_read_it() {
let name = display_name(&worker("codex/gpt-5.6-luna"));
assert_eq!(name, "GPT 5.6 Luna on Codex");
assert!(!name.contains("codex/") && !name.contains("gpt-5.6-luna"));
}
fn provider_of(worker: &ModelRef) -> String {
match worker.agent.as_str() {
"claude-code" => "claude".to_owned(),
"codex" => "gpt".to_owned(),
_ => "gemini".to_owned(),
}
}
#[test]
fn candidates_come_from_the_role_else_the_crew() {
let mut settings = Settings {
crew: vec![worker("claude-code/opus"), worker("codex"), worker("codex")].into(),
..Settings::default()
};
assert_eq!(
candidates(&settings, RoleName::Reviewer),
vec![worker("claude-code/opus"), worker("codex")]
);
settings.roles.insert(
RoleName::Reviewer,
RoleSettings {
models: vec![worker("agy/gemini-3.6-pro")],
..RoleSettings::default()
},
);
assert_eq!(
candidates(&settings, RoleName::Reviewer),
vec![worker("agy/gemini-3.6-pro")]
);
assert_eq!(
candidates(&settings, RoleName::Main),
vec![settings.crew.main.clone()]
);
settings.crew.clear();
assert_eq!(
candidates(&settings, RoleName::Worker),
vec![settings.crew.main.clone()]
);
}
#[test]
fn role_conflicts_blocked() {
let opus = worker("claude-code/opus");
let blocked = [RoleName::Reviewer, RoleName::Judge];
for role in blocked {
let reason = conflict(role, &opus, Some(&opus), &provider_of);
assert!(reason.is_some(), "{role:?} of its own work");
}
assert_eq!(
conflict(RoleName::Reviewer, &opus, Some(&opus), &provider_of).as_deref(),
Some("Opus on Claude Code can't review its own task")
);
let default_model = worker("claude-code");
assert!(
conflict(
RoleName::Reviewer,
&default_model,
Some(&opus),
&provider_of
)
.is_some()
);
let sonnet = worker("claude-code/sonnet");
assert!(conflict(RoleName::Reviewer, &sonnet, Some(&opus), &provider_of).is_none());
assert!(
conflict(
RoleName::Reviewer,
&worker("codex"),
Some(&opus),
&provider_of
)
.is_none()
);
assert!(conflict(RoleName::Worker, &opus, Some(&opus), &provider_of).is_none());
assert!(conflict(RoleName::Reviewer, &opus, None, &provider_of).is_none());
}
}