feat(routing): decision policy
Pure-function Policy{Floor,Ceil} with Decide(*float64, uint64) Decision.
Rules in priority order: nil → local; ≥floor → local; <ceil → claude;
sample band → low bit of requestHash.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
47
internal/routing/policy.go
Normal file
47
internal/routing/policy.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package routing
|
||||
|
||||
// Decision is the route picked for a single skill call.
|
||||
type Decision int
|
||||
|
||||
const (
|
||||
DecideLocal Decision = iota
|
||||
DecideClaude
|
||||
)
|
||||
|
||||
func (d Decision) String() string {
|
||||
if d == DecideLocal {
|
||||
return "local"
|
||||
}
|
||||
return "claude"
|
||||
}
|
||||
|
||||
// Policy holds the floor/ceil thresholds for routing decisions.
|
||||
//
|
||||
// Rules (in order):
|
||||
//
|
||||
// 1. passRate == nil → DecideLocal (default-to-local for cost-routable skills)
|
||||
// 2. *passRate >= Floor → DecideLocal (trust local)
|
||||
// 3. *passRate < Ceil → DecideClaude (don't trust local)
|
||||
// 4. otherwise (sample band) → requestHash low bit picks: 0=local, 1=claude
|
||||
type Policy struct {
|
||||
Floor float64
|
||||
Ceil float64
|
||||
}
|
||||
|
||||
// Decide returns the routing decision for a single call.
|
||||
// requestHash is consulted only when passRate is in the sample band [Ceil, Floor).
|
||||
func (p Policy) Decide(passRate *float64, requestHash uint64) Decision {
|
||||
if passRate == nil {
|
||||
return DecideLocal
|
||||
}
|
||||
if *passRate >= p.Floor {
|
||||
return DecideLocal
|
||||
}
|
||||
if *passRate < p.Ceil {
|
||||
return DecideClaude
|
||||
}
|
||||
if requestHash&1 == 0 {
|
||||
return DecideLocal
|
||||
}
|
||||
return DecideClaude
|
||||
}
|
||||
36
internal/routing/policy_test.go
Normal file
36
internal/routing/policy_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package routing_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mathiasbq/supervisor/internal/routing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func ptr(f float64) *float64 { return &f }
|
||||
|
||||
func TestPolicyDecide(t *testing.T) {
|
||||
p := routing.Policy{Floor: 0.9, Ceil: 0.7}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
passRate *float64
|
||||
hash uint64
|
||||
want routing.Decision
|
||||
}{
|
||||
{"null pass rate → local", nil, 0, routing.DecideLocal},
|
||||
{"null pass rate, hash irrelevant → local", nil, 0xDEADBEEF, routing.DecideLocal},
|
||||
{"at floor → local", ptr(0.9), 0, routing.DecideLocal},
|
||||
{"above floor → local", ptr(0.95), 0, routing.DecideLocal},
|
||||
{"below ceil → claude", ptr(0.5), 0, routing.DecideClaude},
|
||||
{"at ceil → sample-band even-hash → local", ptr(0.7), 0, routing.DecideLocal},
|
||||
{"sample band, even hash → local", ptr(0.8), 2, routing.DecideLocal},
|
||||
{"sample band, odd hash → claude", ptr(0.8), 3, routing.DecideClaude},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, p.Decide(tc.passRate, tc.hash))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user