# Control

> Pin a model, exclude one, and read the router's explanation of every choice.

You control the router in two places, on purpose:

- **In LightHouse**, four controls a `client_admin` changes as a commercial choice: the router on or off, a quality bar per task class, a pinned model per task class, and models to exclude.
- **In your deployment’s own policy**, everything that is a sovereignty or infrastructure decision: the residency rule, the allow list, the latency needs, the escalation steps, the evidence path, and whether Atlantic’s published figures are used. Your engineers keep these. LightHouse cannot change them, because a portal that could widen residency would be a sovereignty breach reachable from a browser.

## Switching it on, and setting a bar

**Agents → Routing**, as a `client_admin` signed in as a person, with a reason. Or the API:

**curl**

```bash
# Read the controls and their version first
curl "https://$AAL_TENANT.atlanticagenticlabs.com/api/v1/routing/settings" \
  -H "Cookie: CF_Authorization=$CF_AUTHORIZATION"

# Then change them, quoting that version
curl -X PUT "https://$AAL_TENANT.atlanticagenticlabs.com/api/v1/routing/settings" \
  -H "Cookie: CF_Authorization=$CF_AUTHORIZATION" \
  -H "Content-Type: application/json" \
  -d '{"version": "3", "enabled": true, "bars": {"retrieval_qa": 0.92},
       "reason": "router on for retrieval after our own benchmark run"}'
```

**Python**

```python
import os

import httpx

url = f"https://{os.environ['AAL_TENANT']}.atlanticagenticlabs.com/api/v1/routing/settings"
cookies = {"CF_Authorization": os.environ["CF_AUTHORIZATION"]}

held = httpx.get(url, cookies=cookies).json()
r = httpx.put(url, cookies=cookies, json={
    "version": held["version"],
    "enabled": True,
    "bars": {**held["bars"], "retrieval_qa": 0.92},
    "reason": "router on for retrieval after our own benchmark run",
})
for sent in r.json()["sent"]:
    print(sent["deployment"], sent["change"], sent["status"])
```

**TypeScript**

```ts
const url = `https://${process.env.AAL_TENANT}.atlanticagenticlabs.com/api/v1/routing/settings`;
const headers = { Cookie: `CF_Authorization=${process.env.CF_AUTHORIZATION}`, "Content-Type": "application/json" };

const held = await (await fetch(url, { headers })).json();
const res = await fetch(url, {
  method: "PUT",
  headers,
  body: JSON.stringify({
    version: held.version,
    enabled: true,
    bars: { ...held.bars, retrieval_qa: 0.92 },
    reason: "router on for retrieval after our own benchmark run",
  }),
});
console.log((await res.json()).sent);
```

What happens when you save:

1. LightHouse records what you asked for, refusing a stale `version` with `409 version_conflict`.
2. Each change is sent to every active deployment as its own signed `routing.set` command, so a deployment can accept a bar and refuse a pin without the whole change failing. The answer’s `sent` lists each command with its deployment and status.
3. Your deployment’s own command policy decides whether to accept each one, checks the result loads **before** writing it, and the router obeys within a minute.
4. Your audit log records who asked, the reason, and the before and after.

**Lowering a bar** is the one change here that can make answers worse, so your deployment can treat it separately: its policy names who may lower a bar (`lower_bar_roles`) and a floor no bar may go below (`min_bar`). Raising a bar needs neither.

A key cannot change these controls (`403 person_required`).

## Pinning a model

Pin a model for a task class, and the router uses it for every call of that class, without weighing the others. A pin naming a bare family (`claude-sonnet-4-5`) resolves to the profile you may run.

A pin your policy, an exclusion or your residency rule forbids is **refused loudly**: the decision says `pin_not_permitted` and the call keeps its agent’s model. A pin and an exclusion of the same model at once is refused when you save.

Removing a pin from LightHouse: \[Coming]. Today a pin is removed in your deployment’s policy.

## Excluding a model

Exclude up to 50 models. An excluded model is never chosen and never escalated to. An exclusion made in LightHouse is **added** to your policy’s own exclusions, never subtracted from them, so the portal can only narrow what your engineers allow.

Removing an exclusion made in LightHouse: \[Coming]. Today it is removed in your deployment’s policy.

## Reading the explanation

Every decision carries its whole working, content-free: model IDs, fixed reason codes and numbers. In LightHouse, open any decision under **Agents → Routing** to see its candidate table. In the API, `GET /api/v1/routing/decisions/{id}`; through MCP, the `routing_decision` tool.

Each candidate row has the model, whether it was kept, the reason, its quality, its direct and expected cost, its p95 latency, the age of its evidence, and where the evidence came from. The reasons are a fixed list:

| Reason              | Meaning                                                                |
| ------------------- | ---------------------------------------------------------------------- |
| `chosen`            | Kept, and chosen                                                       |
| `kept`              | Kept, but cost more than the one chosen                                |
| `named`             | The agent’s own model, used as the fallback                            |
| `pinned`            | You pinned it for this task class                                      |
| `pin_not_permitted` | Pinned, but your policy or an exclusion forbids it                     |
| `excluded`          | You excluded it                                                        |
| `not_allowed`       | Your policy’s allow list or price card does not admit it for this call |
| `residency`         | Its residency is not the one your policy requires                      |
| `not_available`     | Your deployment reports it is not available                            |
| `context_window`    | Its window is smaller than this call needs                             |
| `no_evidence`       | Nothing has measured it on this task class                             |
| `stale_evidence`    | Its score is too old, or on an older suite version                     |
| `below_bar`         | Its measured quality is below the bar for this class                   |
| `too_slow`          | Its measured p95 latency is above what the call needs                  |

A decision’s own `mode` is `route` (chosen on evidence), `pinned`, `off` or `fallback`, and its `reason` says why, from the same list plus `router_off`, `unknown_task_class` and `no_candidates`. [A decision, worked](/router/decision/) walks through one.

Each decision names the run (`run_id_hash`) and the step (`span`) it belongs to. Showing the router’s explanation on the step itself, in [the run map](/build/run-map/): \[Coming]

Source: https://docs.atlanticagenticlabs.com/router/control/
