use async_trait::async_trait;
use crate::Result;
use crate::core::algorithm::Driver;
use crate::core::classifier::{Classification, Classifier, Score};
use switchyard_protocol::{Metadata, Request, Response};
pub struct SubagentOverride {
worker: String,
}
impl SubagentOverride {
pub fn new(worker: impl Into<String>) -> Self {
Self {
worker: worker.into(),
}
}
}
#[async_trait]
impl<S> Classifier<S> for SubagentOverride
where
S: Send + 'static,
{
async fn score(
&self,
_state: &mut S,
request: &mut Request,
_driver: Option<&Driver>,
) -> Result<(Classification, Option<Response>)> {
let is_delegated_work = request
.metadata
.as_ref()
.is_some_and(Metadata::is_subagent_work);
Ok((
Classification::Scores(if is_delegated_work {
vec![Score {
confidence: 1.0,
target: self.worker.clone(),
}]
} else {
Vec::new()
}),
None,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use switchyard_protocol::{slice_to_header_map, text_request};
fn request(headers: &[(&str, &str)]) -> Request {
let metadata =
(!headers.is_empty()).then(|| Metadata::from_headers(&slice_to_header_map(headers)));
Request {
llm_request: text_request(Some("auto".to_string()), "hi"),
raw_request: None,
metadata,
}
}
async fn selected(headers: &[(&str, &str)]) -> Result<Option<String>> {
let mut state = ();
let classification = SubagentOverride::new("worker")
.score(&mut state, &mut request(headers), None)
.await?;
Ok(classification.0.argmax(false)?.map(|score| score.target))
}
#[tokio::test]
async fn requests_without_metadata_abstain() -> Result<()> {
assert_eq!(selected(&[]).await?, None);
Ok(())
}
#[tokio::test]
async fn subagent_work_scores_the_worker() -> Result<()> {
let claude = &[
("x-claude-code-session-id", "root"),
("x-claude-code-agent-id", "child-1"),
];
assert_eq!(selected(claude).await?, Some("worker".to_string()));
assert_eq!(
selected(&[("x-openai-subagent", "review")]).await?,
Some("worker".to_string())
);
assert_eq!(
selected(&[("x-openai-subagent", "collab_spawn")]).await?,
Some("worker".to_string())
);
Ok(())
}
#[tokio::test]
async fn harness_maintenance_turns_abstain() -> Result<()> {
assert_eq!(selected(&[("x-openai-subagent", "compact")]).await?, None);
assert_eq!(
selected(&[("x-switchyard-is-subagent", "false")]).await?,
None
);
Ok(())
}
#[tokio::test]
async fn delegated_work_is_scored_definitively() -> Result<()> {
let mut state = ();
let classification = SubagentOverride::new("worker")
.score(
&mut state,
&mut request(&[("x-openai-subagent", "review")]),
None,
)
.await?;
match classification.0 {
Classification::Scores(scores) => {
assert_eq!(scores.len(), 1);
assert_eq!(scores[0].confidence, 1.0);
}
Classification::Ambiguous(_) => panic!("override must score definitively"),
}
Ok(())
}
}