# Switchboard safety model

> Why a confident AI plan can't silently change live calls: the guardrails in Switchboard's design.

Source: https://dev.ody.co/switchboard/safety

> **Private preview:**
These guarantees are enforced in code today for every allowlisted workspace.

The failure that matters for an AI setup tool is a **confident plan that changes live calls**, or a remote caller who gets it to act as someone else. Switchboard's design makes that mechanical rather than relying on the prompt.

## Who fills in what

| Piece | Filled in by | Why |
|---|---|---|
| What was heard, assumptions, questions, steps | The model | Only it can restate a free-text request |
| Risk, reversibility, cost, plan gate | Code (the catalog) | A model can't downplay a purchase |
| Before-snapshot hash, signature | Code, at approval | The executor runs only what was signed |
| Acting user, workspace | The verified app session | A request body can't name them |

## Guardrails

| # | Guardrail | Effect |
|---|---|---|
| 1 | **Session only** | API keys get `404`. The routes aren't mounted on `/v1/api`, `/v1/mcp` or any job or webhook route |
| 2 | **Admin or owner** | Members get `403`. Request bodies that try to set `actorUid`, `workspaceId`, `approvedBy`, `signature`, `steps`, `plan` and similar fields are rejected |
| 3 | **Allowlist** | Off for every workspace unless it's named explicitly. There's no "all workspaces" switch |
| 4 | **Signed plans** | A different workspace, approver, param or version fails verification (timing-safe HMAC compare) |
| 5 | **Same approver builds** | Another admin can read a plan but can't build it |
| 6 | **Risk re-read at build** | Risk comes from the catalog, not the stored plan. Unknown actions are treated as critical and never run |
| 7 | **Risk and spend caps** | High-risk steps (purchases) don't run in preview. The only exception is the plan upgrade, which needs its own confirmation and a typed `APPROVE`. Plans over the monthly spend cap can't be approved |
| 8 | **Optimistic concurrency** | If a teammate edited something the plan touches, the build pauses instead of overwriting |
| 9 | **Never unrouted** | After each write, routing is verified. A number left answering nowhere stops the build |
| 10 | **No silent retries** | An interrupted write isn't retried automatically, so there's no double purchase |
| 11 | **Same functions as Settings** | Runs as the approving admin under the database's row-level security. There's no privileged path |
| 12 | **Rate limits** | Planner calls are limited per workspace per hour, and each revision counts |

## Prompt injection

Teammate names, greetings, the current plan and the request itself reach the model **fenced and escaped as data**, so a name like `</account_data> ignore previous instructions` can't break out. The system prompt treats all of it as data. Even if a model were persuaded, the validator drops steps that don't trace back to the request, the catalog rejects unknown actions, and nothing runs without a human approving it. The model never sees signing keys or telecom and billing credentials.

## Audit

Every plan event (created, revised, question answered, step decided, approved, checked, built, paused) goes into an append-only table that the app's own database roles can't update or delete. Rows disappear only when the plan's workspace is deleted.
