Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions cmd/router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
"workweave/router/internal/router/policy"
"workweave/router/internal/router/rl"
"workweave/router/internal/router/sessionpin"
"workweave/router/internal/router/sessionstrategy"
"workweave/router/internal/server"
"workweave/router/internal/websearch"
"workweave/router/internal/wif"
Expand Down Expand Up @@ -795,6 +796,7 @@ func main() {
// hmm then routes through it. Unset fails closed with 503.
var hmmRouter router.Router
var hmmEmbeddingRouter router.Router
var hmmBetaRouter router.Router
var hmmCapabilities policy.Capabilities
var hmmReadinessChecker admin.HealthChecker
var hmmRosterSource policy.RosterSource
Expand Down Expand Up @@ -889,6 +891,73 @@ func main() {
logger.Info("HMM policy routers disabled (ROUTER_HMM_SIDECAR_URL unset); HMM strategies will return 503")
}

// Separate sidecar: /beta opts into an independently deployed beta policy;
// absent or unhealthy beta fails closed without affecting stable routing.
var hmmBetaCapabilities policy.Capabilities
if hmmBetaSidecarURL := config.GetOr("ROUTER_HMM_BETA_SIDECAR_URL", ""); hmmBetaSidecarURL != "" {
hmmBetaTimeout := parseEnvDurationMs("ROUTER_HMM_BETA_SIDECAR_TIMEOUT_MS", policyclient.DefaultTimeout)
hmmBetaAuthMode := config.GetOr("ROUTER_HMM_BETA_SIDECAR_AUTH", policySidecarAuthNone)
hmmBetaAttemptTimeout := parseEnvAttemptTimeoutMs(
"ROUTER_HMM_BETA_SIDECAR_ATTEMPT_TIMEOUT_MS",
policyclient.DeriveAttemptTimeout(hmmBetaTimeout),
)
hmmBetaClient, clientErr := buildHMMBetaPolicyClient(
hmmBetaSidecarURL,
hmmBetaAuthMode,
hmmBetaTimeout,
policyclient.WithAttemptTimeout(hmmBetaAttemptTimeout),
)
if clientErr != nil {
// Beta is an optional isolation ring. A malformed beta-only auth
// setting must not take the stable router down with it.
logger.Error("beta HMM policy sidecar client failed to build; beta disabled", "auth_mode", hmmBetaAuthMode, "err", clientErr)
} else {
capabilityCtx, cancelCapabilityDiscovery := context.WithTimeout(context.Background(), hmmBetaTimeout)
var capabilityErr error
hmmBetaCapabilities, capabilityErr = hmmBetaClient.Capabilities(capabilityCtx)
cancelCapabilityDiscovery()
if capabilityErr != nil {
logger.Warn("beta HMM policy sidecar capabilities unavailable at boot; optional behavior remains disabled", "sidecar_url", hmmBetaSidecarURL, "err", capabilityErr)
}
hmmBetaPolicyRouter := hmm.NewForStrategy(
router.StrategyHMMBeta,
hmmBetaClient,
availableProviders,
)
hmmBetaPolicyRouter.WithCapabilities(hmmBetaCapabilities)
if capabilityErr != nil {
go func() {
retryErr := retryPolicyCapabilitiesUntilAvailable(
context.Background(),
hmmBetaClient,
hmmBetaTimeout,
hmmCapabilityRetryInterval,
func(capabilities policy.Capabilities) {
hmmBetaPolicyRouter.WithCapabilities(capabilities)
},
)
if retryErr != nil {
logger.Warn("beta HMM policy sidecar capability refresh stopped", "sidecar_url", hmmBetaSidecarURL, "err", retryErr)
return
}
logger.Info("beta HMM policy sidecar capabilities discovered after boot", "sidecar_url", hmmBetaSidecarURL)
}()
}
hmmBetaRouter = hmmBetaPolicyRouter
logger.Info(
"beta HMM policy router wired",
"sidecar_url", hmmBetaSidecarURL,
"auth_mode", hmmBetaAuthMode,
"timeout_ms", hmmBetaTimeout.Milliseconds(),
"attempt_timeout_ms", hmmBetaAttemptTimeout.Milliseconds(),
"candidate_models", len(routingTargets),
"strategy", router.StrategyHMMBeta,
)
}
} else {
logger.Info("beta HMM policy router disabled (ROUTER_HMM_BETA_SIDECAR_URL unset); /beta will be unavailable")
}

// Wired only when ROUTER_BANDIT_POSTERIOR_FILE points at a ts_posterior.json;
// x-weave-router-strategy: bandit then routes through it. Wraps the raw
// cluster scorer, not the explore wrapper. Unset -> nil -> 503.
Expand Down Expand Up @@ -952,7 +1021,12 @@ func main() {
flags.KeyEmbedOnlyUserMessage: boolDefault(embedOnlyUser),
})

// Always wire even when beta is unavailable: existing beta sessions fail
// closed via nil policy registration rather than silently falling to stable.
var sessionStrategyStore sessionstrategy.Store = postgres.NewSessionStrategyRepo(pool)

proxySvc := proxy.NewService(routeEntry, providerMap, telemetryEmitter, embedOnlyUser, semanticCache, pinStore, hardPinExplore, hardPinProvider, hardPinModel, repo.Telemetry).
WithSessionStrategyStore(sessionStrategyStore).
WithTranslationCompatibilityMode(proxy.TranslationCompatibilityMode(translationCompatibilityMode)).
WithPolicyStrategy(policy.StrategySpec{Strategy: router.StrategyRL, Router: rlRouter, Unavailable: rl.ErrPolicyUnavailable}).
WithPolicyStrategy(policy.StrategySpec{
Expand All @@ -963,6 +1037,10 @@ func main() {
Strategy: router.StrategyHMMEmbedding, Router: hmmEmbeddingRouter, Unavailable: hmm.ErrHMMUnavailable,
Capabilities: hmmCapabilities,
}).
WithPolicyStrategy(policy.StrategySpec{
Strategy: router.StrategyHMMBeta, Router: hmmBetaRouter, Unavailable: hmm.ErrHMMUnavailable,
Capabilities: hmmBetaCapabilities,
}).
WithPolicyStrategy(policy.StrategySpec{Strategy: router.StrategyBandit, Router: banditRouter, Unavailable: bandit.ErrBanditUnavailable}).
WithContentCapture(captureMode, captureMaxBytes, nil).
WithFeedback(repo.Feedback, feedbackSigner, feedbackBaseURL).
Expand Down
16 changes: 16 additions & 0 deletions cmd/router/policy_sidecar_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ func buildHMMPolicyClient(
)
}

func buildHMMBetaPolicyClient(
sidecarURL, authMode string,
timeout time.Duration,
opts ...policyclient.Option,
) (*policyclient.Client, error) {
return buildPolicyClientWithGoogleIDTokenFactory(
sidecarURL,
authMode,
timeout,
nil,
"ROUTER_HMM_BETA_SIDECAR_AUTH",
policyclient.NewGoogleIDToken,
opts...,
)
}

func buildHMMPolicyClientWithGoogleIDTokenFactory(
sidecarURL, authMode string,
timeout time.Duration,
Expand Down
8 changes: 8 additions & 0 deletions cmd/router/policy_sidecar_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ func TestBuildHMMPolicyClientRejectsUnknownAuthMode(t *testing.T) {
assert.Contains(t, err.Error(), "unsupported ROUTER_HMM_SIDECAR_AUTH")
}

func TestBuildHMMBetaPolicyClientNamesBetaAuthSetting(t *testing.T) {
client, err := buildHMMBetaPolicyClient("https://sidecar.internal", "api-key", time.Second)

require.Error(t, err)
assert.Nil(t, client)
assert.Contains(t, err.Error(), "unsupported ROUTER_HMM_BETA_SIDECAR_AUTH")
}

func TestBuildHMMPolicyClientFailsClosedWhenGoogleCredentialsCannotBuild(t *testing.T) {
wantErr := errors.New("ADC unavailable")
client, err := buildHMMPolicyClientWithGoogleIDTokenFactory(
Expand Down
1 change: 1 addition & 0 deletions cmd/router/policy_sidecars.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var reservedPolicyStrategies = map[router.Strategy]struct{}{
router.StrategyRL: {},
router.StrategyHMM: {},
router.StrategyHMMEmbedding: {},
router.StrategyHMMBeta: {},
router.StrategyBandit: {},
}

Expand Down
1 change: 1 addition & 0 deletions cmd/router/policy_sidecars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func TestBuildConfiguredPolicySidecarsRejectsReservedAndInvalidConfiguration(t *
for _, raw := range []string{
`{"hmm":"https://sidecar.internal"}`,
`{"hmm_embedding":"https://sidecar.internal"}`,
`{"hmm_beta":"https://sidecar.internal"}`,
`{"future":"not-a-url"}`,
`{"future policy":"https://sidecar.internal"}`,
`{"Future":"https://one.internal","future":"https://two.internal"}`,
Expand Down
8 changes: 8 additions & 0 deletions db/migrations/0070_session-strategy-preferences.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BEGIN;

DROP TABLE router.session_strategy_preferences;

ALTER TABLE router.session_pins
DROP COLUMN routing_strategy;

COMMIT;
18 changes: 18 additions & 0 deletions db/migrations/0070_session-strategy-preferences.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN;

ALTER TABLE router.session_pins
ADD COLUMN routing_strategy VARCHAR(32) NOT NULL DEFAULT '';

CREATE TABLE router.session_strategy_preferences (
installation_id UUID NOT NULL,
session_key BYTEA NOT NULL CHECK (octet_length(session_key) = 16),
strategy VARCHAR(32) NOT NULL CHECK (strategy = 'hmm_beta'),
PRIMARY KEY (installation_id, session_key),
FOREIGN KEY (installation_id)
REFERENCES router.model_router_installations(id) ON DELETE CASCADE
);

COMMENT ON TABLE router.session_strategy_preferences IS
'Explicit per-session router strategy preferences';

COMMIT;
Loading
Loading