type = "composite"는 판정 모델과 스테이지 라우터를
결합합니다. 판정 모델이 스테이지 라우터의 fall-open 티어를 정하고, 신호 채점에는 손대지
않습니다. 신호가 결정하는 턴은 신호가 결정하고, 신호가 결론을 내지 못한 턴은 고정된
picker 대신 판정 모델이 고른 티어에 안착합니다.
설정
[[models]]
id = "claude-composite"
[models.router]
type = "composite"
[models.router.classifier]
target = "claude-haiku-4-5"
base_threshold = 0.5
classify_trigger = "user_turn"
[models.router.stage]
capable_target = "claude-opus-4-8"
efficient_target = "claude-sonnet-4-6"
confidence_threshold = 0.5
# recent_turn_window = 3
# capable_hold_turns = 0stage 테이블은 picker를 받지 않습니다 — 그 티어는 classifier가 공급하기 때문입니다.
그래서 여기에 picker를 적으면 시작 오류입니다. classifier.base_threshold는 그래도 효율
티어로 보내는 p_solve 하한입니다.
classifier.classify_trigger는 필수이며, 티어를 얼마나 자주 다시 고를지 정합니다.
user_turn은 사람이 말할 때마다 티어를 다시 고릅니다.new_session은 한 번 골라 유지합니다.every_request는 여기서 거부됩니다 — 도구 단계마다 판정을 부르는 비용이야말로 이 타입이 피하려는 것이기 때문입니다.
classifier.message_hash_fallback은 세션 id를 보내지 않는 클라이언트를 위해 첫 사용자
메시지를 해시해 티어를 유지합니다. stage.tool_semantics는 스테이지 라우터의
[models.router.tool_semantics]와 같은 네 목록을
받으며 규칙도 같습니다. 여섯 개 호출당 한도는
두 하위 테이블이 아니라 [models.router]에 놓습니다.
판정 모델에 닿지 못할 때
classifier가 닿지 못한 턴은 stage.efficient_target으로 fall-open하며, 이는 업스트림의
규칙이자 본문 없는 표면이 보고하는 값이기도 합니다. max_judge_calls가 판정 호출을 거부한
턴은 세션이 마지막으로 보존한 티어가 있으면 그 티어를 유지하고, 없으면 picker의 기본
티어로 갑니다. 그래서 사용자 턴과 그 뒤의 도구 후속 턴 사이에서 티어가 바뀌지 않습니다.
판정 호출이 필요 없는 턴은 거부되지 않습니다.
count_tokens 프로브는 신호를 채점하지 않고 판정 호출도 하지 않습니다. 세션이 보존한
티어가 있으면 그 티어로, 없으면 stage.efficient_target으로 응답합니다.
클라이언트가 보는 것
stage 쪽은 libsy 자신의 stage 라우트이므로, 결정적인 턴은 classifier 결정이 아니라 스테이지
라우터의 라우트 소스를 그대로 보고합니다 — composite의 신호 기반 턴을 평범한
stage_router의 턴과 같은 방식으로 읽을 수 있다는 뜻입니다
(클라이언트가 보는 것 참고).
신호가 결정하지 못한 턴은 어떤 티어로 fall-open하며, 소스는 그 티어가 어디서 왔는지
알려 줍니다. 이 턴의 판정 호출이 티어를 정했으면 llm-classifier, 앞선 턴이 보존한
티어면 classifier_retained입니다. 판정이 턴을 결정하지 못했으면 — 판정 모델에 닿지
못했거나 max_judge_calls가 호출을 거부했으면 — 소스는 classifier_fail_open이고, 티어는
판정 모델에 닿지 못할 때에서 설명한 것입니다.