Docs
Message judge
The judge has a model read a message before it is delivered. It passes it, redacts personal data, refuses it, or holds it until a person reviews it. It is off unless you turn it on, and without a model and a key it never calls one.
On the hosted relay
The relay judges one org's messages between its machines once the org's owner turns it on. The relay operator sets the model and the key:
cd apps/relay
bunx wrangler d1 migrations apply scp-relay --remote # adds orgs.judge_enabled (migration 0002)
bunx wrangler secret put JUDGE_API_KEY # or OPENROUTER_API_KEY
and in wrangler.jsonc:
"vars": {
"JUDGE_PROVIDER": "openrouter",
"JUDGE_MODEL": "<vendor>/<model>"
}
| Name | Kind | Purpose |
|---|---|---|
JUDGE_API_KEY |
secret | The model key; OPENROUTER_API_KEY when unset |
JUDGE_PROVIDER |
var | The model's provider: openrouter (default), openai or anthropic |
JUDGE_MODEL |
var | The model name at that provider; no default |
Without JUDGE_MODEL or a key, or with any other JUDGE_PROVIDER, the judge stays inert for every org: no model calls and no holds, whatever an owner turned on.
Each org starts with the judge off. An owner turns it on or off under Members in the org on the dashboard. While it is on, every message between the org's machines, or from a client connected straight to the hub, is judged before the hub delivers it, with effort medium, failing open (delivered, with the pre-filter's redactions) when the model errs, answers malformed or does not answer in 12 seconds. The relay sets no judge budgets and no cap on held messages. A held message reaches the sender as held {hold_id, side: "local"} and is not delivered; owners and admins see the held messages (sender, recipients, categories, the judge's reason and expiry, never the body) on the same page and release or refuse each one. The sender gets a notice either way. A hold no one reviews expires with the message.
In a local broker
The broker reads a judge section from its policy file, ~/.config/scp/policy.json:
{
"rules": [],
"judge": {
"mode": "enforce",
"fail": "open",
"timeout_s": 12,
"model": { "id": "openrouter/<vendor>/<model>", "effort": "medium", "key_env": "SCP_JUDGE_KEY" },
"boundaries": [{ "from": "*", "to": "*" }]
}
}
| Field | Values |
|---|---|
mode |
off, shadow (default: deliver, then judge and audit only) or enforce |
fail |
open (default) or closed (hold) when the model fails |
timeout_s |
1 to 30, default 12 |
model.id |
provider/name, for example openrouter/<vendor>/<model> |
model.effort |
low, medium (default), high or xhigh |
model.key_env |
The broker environment variable holding the key; or key_file, an absolute path read at each call |
budget |
{ "per_minute", "per_hour" } across the broker, default 120 and 1200 |
boundaries |
1 to 64 { from, to, across? } selectors; the first match judges a message |
Start the broker with the key in its environment (SCP_JUDGE_KEY=... swarm up). A key written into the policy is refused. Without a readable key the broker judges with its deterministic pre-filter alone. Held messages are reviewed by the human at a terminal with swarm holds.