@@ -34,6 +34,8 @@ const LABEL_TO_CLASS_RANK = {
3434 "best coder" : 4
3535} ;
3636
37+ const LOW_CONFIDENCE_THRESHOLD = 0.7 ;
38+
3739const PRIVACY_TIER_RANK = {
3840 external : 1 ,
3941 standard : 2 ,
@@ -276,6 +278,84 @@ function buildConstraintInputs(session = {}) {
276278 } ;
277279}
278280
281+ function strongerClass ( currentClass , candidateClass ) {
282+ const currentRank = LABEL_TO_CLASS_RANK [ CLASS_TO_LABEL [ currentClass ] ] || 0 ;
283+ const candidateRank = LABEL_TO_CLASS_RANK [ CLASS_TO_LABEL [ candidateClass ] ] || 0 ;
284+ return candidateRank > currentRank ? candidateClass : currentClass ;
285+ }
286+
287+ function preferredLabelOrderFor ( desiredLabel ) {
288+ let fallbackOrder ;
289+ if ( desiredLabel === "best coder" ) {
290+ fallbackOrder = [ "best coder" , "deep reasoning" , "balanced" , "quick" ] ;
291+ } else if ( desiredLabel === "deep reasoning" ) {
292+ fallbackOrder = [ "deep reasoning" , "best coder" , "balanced" , "quick" ] ;
293+ } else if ( desiredLabel === "quick" ) {
294+ fallbackOrder = [ "quick" , "balanced" , "deep reasoning" , "best coder" ] ;
295+ } else {
296+ fallbackOrder = [ "balanced" , "deep reasoning" , "best coder" , "quick" ] ;
297+ }
298+
299+ return [ desiredLabel , ...fallbackOrder . filter ( ( label ) => label !== desiredLabel ) ] ;
300+ }
301+
302+ function resolveEscalationPolicy ( { classification = { } , session = { } , mode } ) {
303+ let desiredClass = MODE_TO_CLASS [ mode ] || "medium_reasoning" ;
304+ const reasons = [ ] ;
305+
306+ const lowConfidence = Number ( classification . confidence || 0 ) < LOW_CONFIDENCE_THRESHOLD ;
307+ const userCorrection = classification . reason === "user_correction_signal" ;
308+ const repeatedFailures =
309+ Number ( session ?. failureSignals ?. recentToolFailures || 0 ) +
310+ Number ( session ?. failureSignals ?. recentTestFailures || 0 ) >=
311+ 2 ;
312+ const highRiskImplementation = mode === "implement" && session . riskLevel === "high" ;
313+
314+ if ( userCorrection ) {
315+ desiredClass = strongerClass ( desiredClass , "strong_reasoning" ) ;
316+ reasons . push ( "user_correction" ) ;
317+ }
318+
319+ if ( repeatedFailures && mode !== "summarize" ) {
320+ const repeatedFailureClass = [ "implement" , "debug" ] . includes ( mode )
321+ ? "strong_coding"
322+ : "strong_reasoning" ;
323+ desiredClass = strongerClass ( desiredClass , repeatedFailureClass ) ;
324+ reasons . push ( "repeated_failures" ) ;
325+ }
326+
327+ if ( highRiskImplementation ) {
328+ desiredClass = strongerClass ( desiredClass , "strong_coding" ) ;
329+ reasons . push ( "high_risk_implementation" ) ;
330+ }
331+
332+ if ( lowConfidence && [ "implement" , "debug" , "review" ] . includes ( mode ) ) {
333+ const lowConfidenceClass = mode === "review" ? "strong_reasoning" : "strong_coding" ;
334+ desiredClass = strongerClass ( desiredClass , lowConfidenceClass ) ;
335+ reasons . push ( "low_confidence" ) ;
336+ }
337+
338+ if ( classification . escalate ) {
339+ const escalatedClass = strongerClass ( desiredClass , classification . escalate ) ;
340+ if ( escalatedClass !== desiredClass ) {
341+ desiredClass = escalatedClass ;
342+ reasons . push ( "classification_escalation" ) ;
343+ }
344+ }
345+
346+ return {
347+ applied : reasons . length > 0 ,
348+ reasons,
349+ desiredClass,
350+ signals : {
351+ lowConfidence,
352+ userCorrection,
353+ repeatedFailures,
354+ highRiskImplementation
355+ }
356+ } ;
357+ }
358+
279359function describeCurrentTargetStatus ( { session, targets = [ ] , eligible = [ ] , blocked = [ ] } ) {
280360 const currentTargetId = session . currentTargetId || null ;
281361 if ( ! currentTargetId ) {
@@ -440,7 +520,8 @@ export function routePrompt({
440520 const modeResolution = resolveSessionMode ( session , classification ) ;
441521 const mode = modeResolution . resolvedMode ;
442522 const requiredCapabilities = buildRequiredCapabilities ( mode , classification . taskType ) ;
443- const desiredClass = classification . escalate || MODE_TO_CLASS [ mode ] || "medium_reasoning" ;
523+ const escalationPolicy = resolveEscalationPolicy ( { classification, session, mode } ) ;
524+ const desiredClass = escalationPolicy . desiredClass ;
444525 const projectOverrideLabel = resolveProjectOverrideLabel ( session , mode ) ;
445526 const desiredLabel = projectOverrideLabel || CLASS_TO_LABEL [ desiredClass ] || "balanced" ;
446527
@@ -461,7 +542,7 @@ export function routePrompt({
461542 }
462543
463544 const overrideSelection = applyRoutingOverride ( { eligible, desiredLabel, session, targets, blocked } ) ;
464- const preferredOrder = [ desiredLabel , "balanced" , "deep reasoning" , "best coder" , "quick" ] ;
545+ const preferredOrder = preferredLabelOrderFor ( desiredLabel ) ;
465546 const preferredTarget = overrideSelection . target || selectByLabelPriority ( eligible , preferredOrder ) ;
466547 const continuitySelection = applyContinuitySwitchPolicy ( {
467548 selectedTarget : preferredTarget ,
@@ -491,6 +572,7 @@ export function routePrompt({
491572 classification,
492573 modeResolution,
493574 policyInputs : constraintInputs ,
575+ escalationPolicy,
494576 routingOverride : {
495577 requested : overrideSelection . override ,
496578 applied : overrideSelection . overrideApplied ,
@@ -514,6 +596,10 @@ export function routePrompt({
514596 if ( requiredCapabilities . includes ( "file_edit" ) ) whyParts . push ( "repo edits" ) ;
515597 if ( requiredCapabilities . includes ( "test_execution" ) ) whyParts . push ( "test execution" ) ;
516598
599+ if ( escalationPolicy . applied ) {
600+ whyParts . push ( `escalation(${ escalationPolicy . reasons . join ( "," ) } )` ) ;
601+ }
602+
517603 return {
518604 status : "ok" ,
519605 action,
@@ -529,6 +615,7 @@ export function routePrompt({
529615 classification,
530616 modeResolution,
531617 policyInputs : constraintInputs ,
618+ escalationPolicy,
532619 routingOverride : {
533620 requested : overrideSelection . override ,
534621 applied : overrideSelection . overrideApplied ,
0 commit comments