Risk and policy model
When your agent asks to do something, Ony.ai has to answer two questions: how risky is it, and what does approving it require? Both answers are computed on the server. The client can send hints, but it cannot talk itself into a lower classification. This page describes the shipped model (ADR 0007).
The client is never trusted
Section titled “The client is never trusted”An agent event can carry an action_type hint and a risk_level hint. The server:
- Ignores the
risk_levelhint entirely. It is accepted on the wire only to make the “we received it and discarded it” contract explicit. It never influences the outcome. - Validates the
action_typeagainst a closed taxonomy (below). Any value that is not a known member - including a missing value or an unknown string - becomesunknown. - Derives risk from the validated
action_typeusing a fixed server-owned table.
Because unknown maps to critical, an unrecognized or malformed action fails closed:
it is treated as the most dangerous class, not the least.
The action taxonomy
Section titled “The action taxonomy”action_type is a closed set. Each member maps to exactly one risk level:
| Risk | Action types |
|---|---|
| low | continue_task, choose_approach, explain_request, status_query |
| medium | file_create, file_modify, package_install, schema_change_dev |
| high | file_delete, db_migration, branch_push, env_change, secret_access, send_email, external_paid_api |
| critical | deploy_production, db_production_change, payment_settings_change, dns_change, credential_rotation, destructive_command, infra_change |
| critical (fail-closed) | unknown - any unrecognized action |
This table is the single source of truth for risk. There are four risk levels, ordered
low < medium < high < critical.
The policy engine
Section titled “The policy engine”Once the server has the authoritative action_type and risk_level, the policy engine
decides what approving the action requires. Rules are data, evaluated in order (most
specific first), and they match only on the server-derived action_type and
risk_level - never on anything the client supplied. Each rule can set:
allow_voice- whether the action can be approved on a phone call at all.require_readback- whether the caller must hear the action read back and confirm, rather than approving on a single press.require_pin- whether the caller must also enter a PIN/TOTP second factor.dashboard_required- whether the action can only be approved in the dashboard.
The first matching rule wins. If no rule matches, the engine fails closed: voice is denied and the dashboard is required.
Default rules
Section titled “Default rules”These are the rules shipped out of the box (and shown on the dashboard Policy page):
| Rule | Matches | Effect |
|---|---|---|
block_production_deploy_by_voice |
action_type = deploy_production |
Voice denied, dashboard required |
critical_blocked_by_voice |
risk = critical |
Voice denied, dashboard required |
high_requires_readback |
risk = high |
Voice allowed with read-back confirmation |
medium_requires_readback |
risk = medium |
Voice allowed with read-back confirmation |
low_allowed |
risk = low |
Voice allowed on a single press (no read-back) |
| (no match) | anything unmodeled | Voice denied, dashboard required |
So a low-risk ask finalizes on one keypress; medium and high risk add a spoken read-back turn; critical actions and production deploys can never be approved by voice and must be confirmed in the dashboard.
Why no PIN by default
Section titled “Why no PIN by default”No default rule requires a PIN. Ony.ai dials the org’s verified number for an approval, so answering that call is itself the possession factor - the same trust anchor as SMS 2FA. Demanding a TOTP on top by default would make approval impossible for any account without an authenticator enrolled (deny would work, approve would not).
require_pin stays available per rule as opt-in hardening for orgs that do enroll TOTP.
Independent of the rules, two things always hold: inbound calls still demand the
TOTP/SMS second factor (caller ID is spoofable), and deny/refuse keypresses are always
PIN-exempt.
Putting it together
Section titled “Putting it together”agent event (action_type + risk hints) | vcoerce action_type to the closed taxonomy (unknown -> unknown) | vderive risk from action_type (client risk hint ignored) | vpolicy engine evaluates rules in order (server-derived values only) | vfirst match wins; no match -> dashboard required (fail closed)The result travels back to the agent as a signed verdict bound to that specific handoff, session, tool call, and expiry. See how it works and the threat model for how the verdict is signed and verified.