Routing settings, plan switch and modes
Epic 12251 (outcome-driven routing and agent marketplace), child C1. PRD = Task 12252, architecture = Task 12253 (read them via task_read on the epic's analysis children if you need more context; the pinned shapes are below).
## Original requirement (verbatim, epic ask 4)
"Modes via plan_terms like merge_lane_enabled: Shadow writes decisions and grades them against the actual dispatch, with a "router vs default" scoreboard; Recommend shows the choice and reason on the task form and the dispatch plan decision, one click to accept; Auto dispatches on it inside org policy, every decision visible and overridable on the task. The plan feature `intelligent_routing` is OFF initially; the org Admin sets the org's maximum mode; each project opts in to a mode up to that maximum (default inherits off). Shadow decision records are measurement and are always written when the ledger exists; Recommend and Auto are behaviour and need the feature plus project opt-in."
Epic Done means (verbatim, part): "an org Admin lowering the org maximum demotes every project above it."
## Settled decisions this child realizes
- R4: `intelligent_routing` is a plan switch with an org override, following `merge_lane_enabled` (`apps/api/app/plan_terms.py:35-36,186-199,836-838`). OFF on every plan.
- R5/A5: org maximum mode `off`/`recommend`/`auto` (default `off`), editable only with the plan feature. Lowering it REWRITES projects above it down (durable, audited); raising it never restores.
- R6: project mode `inherit` (= off) / `recommend` / `auto`, capped at the org max; options above the max disabled with the reason in words and refused by the API.
- A6: org policy settings: allowed providers/agents (null = all), model tier floor/ceiling.
- D7: dev rollout on dev.jaah.ai only.
## Approach (Architect §3.3, §6 — pinned)
Migration (additive, `YYYYMMDDHHMM_slug`):
```
billing_plans.intelligent_routing bool not null default false; orgs.intelligent_routing_override bool null
+ SwitchSpec("intelligent_routing","Intelligent routing") in plan_terms.SWITCHES; reader plan_terms.intelligent_routing(session, org) beside merge_lane_enabled
orgs.routing_max_mode text not null default 'off' check in ('off','recommend','auto')
orgs.routing_allowed jsonb null -- null = all; else {"providers":[...], "agent_ids":[...]}
orgs.routing_tier_floor / routing_tier_ceiling text null -- model_catalog tier names
projects.routing_mode text not null default 'inherit' check in ('inherit','recommend','auto')
projects.routing_latency_cap_hours numeric null
projects.blind_review bool not null default false; projects.blind_review_other_vendor bool not null default true
```
New package `apps/api/app/agent_router/` (NOT `jaah_routing`, which means Bedrock account routing):
```python
# agent_router/modes.py
class Mode(StrEnum): SHADOW="shadow"; RECOMMEND="recommend"; AUTO="auto"
def effective_mode(session, org, project) -> Mode # not feature or project 'inherit' -> SHADOW; else min(project.routing_mode, org.routing_max_mode), off<recommend<auto
def mode_basis(session, org, project) -> dict # {plan_feature, org_max, project_mode}
# agent_router/policy_settings.py
@dataclass(frozen=True) class OrgRoutingPolicy: allowed_providers: frozenset[str]|None; allowed_agent_ids: frozenset[UUID]|None; tier_floor: str|None; tier_ceiling: str|None; version: str
def org_policy(session, org_id) -> OrgRoutingPolicy
```
Demotion in the org PATCH (`routes/orgs.py:941`), same transaction: `UPDATE projects SET routing_mode = (:new if :new != 'off' else 'inherit') WHERE org_id=:org AND rank(routing_mode) > rank(:new)`; one `user_activity.record_write(..., kind='routing.mode_demoted')` per changed project. Project PUT (`routes/projects.py:1925`, `_validated_fields :931`) refuses a mode above the max with a named 422. Without the plan feature the org controls are disabled with the reason shown and PATCH returns 403 `NOT_IN_PLAN` (pattern `routes/nodevault.py:230-235`).
UI: org settings "Intelligent routing" section (max mode, allowed providers/agents, tier floor/ceiling); project "Routing" group on `ProjectEditPage.tsx`: mode, latency cap, Blind review, "Prefer a different vendor". (The Experiment budget field belongs to child C2.) Blind review toggle is shown but its node-capability refusal is added by C6a. Follow `docs/standards/ui-style-guide.md` and `docs/standards/ui-copy-standards.md`.
After deploy (D7, via the console UI only): dev Jaah org override ON, org max Auto; Jaah project Recommend; a second test project Auto; confirm two built-in providers' accounts in Credentials › Runners.
Credentials & reach:
- Dev UI flows (AC4, AC9): `me/jaah/debug/main/operator-login` (operator + tenant switch, app.dev.jaah.ai) and `org/console/dev-login` (dev tenant) — verified (both on the roster, credential_get succeeded, app.dev.jaah.ai returned 200).
- AC9 DB cross-check: `org/aws/cp-psql-reader` via `tools/cp_psql.sh dev` — deferred (broker refused it; the operator will grant it before the build). Fallback: the audit entries in the console.
- Dev Jaah org accounts on two built-in providers — deferred (operator says it has or will have them; this child confirms in Credentials › Runners).
Out of scope: changing account quota semantics or spillover; training a model from scratch; routing analysis personas before coder tasks have data; public marketplace billing; any ledger metric definition (owned by the outcome ledger epic). Do NOT touch `resolve_agent`, `jaah_scheduler.py`, `account_auth.py`, placement, or the decision table (C3a). MUST NOT turn `intelligent_routing` on for any plan by default or on production (`app.jaah.ai`) orgs.
Done means:
- AC4 Plan feature and mode settings. → Verify: on dev as operator, observe `intelligent_routing` OFF on every plan in plan admin. As the dev Jaah org Admin, observe org maximum mode = Auto (D7). Open the Jaah project settings and observe mode = Recommend, with Auto selectable and nothing above the maximum. In an org without the feature, observe the org mode control disabled with its reason shown.
- AC9 Lowering the org maximum demotes projects. → Verify: on dev as org Admin, with the test project at Auto and the Jaah project at Recommend, set the org maximum to Recommend. Observe the test project now Recommend and the Jaah project unchanged. Set it to off and observe both off. Raise it back to Auto and observe the projects stay off (A5). Observe an audit entry per demotion.
- API tests: effective_mode over {feature off/on} × {org max off/recommend/auto} × {project inherit/recommend/auto}; demotion rewrites and audits; project PUT above max → named 422; org PATCH without feature → 403 NOT_IN_PLAN.
- Quality floor: disabled options state the reason as a full sentence (no enum names), labels follow ui-copy-standards, the settings work at 390px without page-level horizontal scroll.
- Gates (AC16): `./dc check quick` green; changed-area suites green (skill testing); merge lane `./dc check full` green.
Activity
- Draft
- Ready
- Runner started