Design decisions (ADRs)
Ony.ai’s behavior is pinned by a set of Architecture Decision Records (ADRs). They are the load-bearing choices behind the wire contracts, the trust boundary, and the connector - the reasons the code is shaped the way it is. We keep them so contributors do not re-derive (or accidentally re-open) a settled question.
This page is a curated index: each entry gives the ADR number, its title, and one plain-language sentence on what it settled. The full text - context, the corrections made during review, and the follow-up guardrails - lives in the repo:
- Read the full ADRs: github.com/ony-ai/ony/tree/main/docs/adr
Foundations
Section titled “Foundations”ADR 0001 - Python / FastAPI first for the control plane. Build the control plane in Python 3.12+ with FastAPI and async-by-default I/O (because telephony is webhook-driven), and keep call orchestration as in-process asyncio tasks rather than a separate worker service until retries, metering, or billing actually need one.
ADR 0002 - Provider-neutral architecture via adapters at the edge.
Every telephony, model, and agent integration is an adapter behind a neutral core contract, so no vendor
SDK, markup (TwiML/cXML), synchronous RPC shape, or untyped error ever reaches ony_core.
Connector and delivery
Section titled “Connector and delivery”ADR 0003 - Ony.ai as the local connector (outbound-only, daemon-owned session state). The connector runs as an outbound-only daemon that owns session state over one authenticated WebSocket (no inbound port opens on the dev machine), and it verifies every inbound decision as a signed, replay-bound execution trigger before acting.
ADR 0004 - No terminal scraping; decisions via official mechanisms, never keystroke injection.
Decisions reach the agent only through official channels - a signed per-call verdict honored by the
blocking permission hook (PermissionRequest interactively; PreToolUse on headless installs and
Codex), with pause-then-resume (claude -p --resume) as the fallback for a new turn - and never
through tmux send-keys / PTY keystroke injection. See how it works for the
delivery model in practice.
ADR 0005 - Session-scoped /ony toggle; project/global scope explicit.
/ony is a Claude Code skill (no separate command bridge) that arms the pause/resume loop; the shipped
default scope is the current project - a 0.4.0 amendment to the original session-scope decision - with
global scope set explicitly via --global. See modes.
Telephony and risk
Section titled “Telephony and risk”ADR 0006 - Telephony is a webhook-driven IVR state machine, not an imperative gather() RPC.
Model telephony as a webhook-driven IVR state machine persisted by provider_call_id and emitting a
neutral VoiceInstruction set, because a synchronous gather() that returns the caller’s digits is
not something Twilio or SignalWire can actually implement.
ADR 0007 - Server-authoritative risk; confirmed decisions bound by signed, single-use tokens.
The server (not the untrusted connector) classifies risk from a validated action_type and fails closed
on anything unknown, and it binds every confirmed decision with a signed, single-use, scope-bound token
so the action that executes must match the one the human approved. This is the backbone of the
threat model and the policy engine.
Managed plane and escalation
Section titled “Managed plane and escalation”ADR 0008 - The managed multi-tenant plane attaches to the open core at defined seams.
The hosted plane binds every account to a verified phone plus a second factor and attaches to the AGPL
core only at named seams (identity, number allocation, entitlements, billing), keeping number pools,
cross-tenant routing, and billing in the proprietary ony_cloud module so the core stays fully functional
standalone. See self-hosting.
ADR 0009 - Escalation policies and multi-channel fallback. Escalation is an ordered ladder of channel steps (call first, then SMS or chat) that all feed the one signed decision - first decision wins and cancels the rest - with weaker channels barred from deciding high-risk actions and durable DB-backed retry timing. See escalation.
ADR 0010 - Runtime extension registry for the open-core / cloud seam.
A runtime extension registry (entry-point discovered) lets ony_cloud supply per-org credentials and
quotas without the core ever importing it, so the AGPL boundary stays enforceable and an OSS deployment
is never affected.
Integration and compliance
Section titled “Integration and compliance”ADR 0011 - Attach-first agent integration (never replace the surface).
Ony.ai attaches to each agent’s own native surface - Claude’s hooks, Codex’s app-server, OpenCode’s HTTP
server - as an invisible on-call layer, rather than taking over the session the way orchestrator products
do; cached approvals, unsigned transport, and bypass-by-default are rejected on trust-model grounds.
ADR 0012 - The gate composes with the host agent’s permission modes, never overrides them.
Ony.ai’s hook stands aside silently ({}, never a forced "ask") when it has no opinion, so installing
Ony.ai never corrupts the host agent’s own permission UX - and the 0.4.0 v2 amendment moves permission-class
gating onto Claude’s PermissionRequest hook so composition is pure inheritance (allowlists,
acceptEdits, and bypass are the host’s job) rather than reconstruction.
ADR 0013 - Voice model roles: pipeline by default, realtime omni as opt-in. Model profiles gain per-role slots (brain, STT, TTS, realtime) with the redaction-preserving STT-plus-brain-plus-TTS pipeline as the default and realtime speech-to-speech omni as an opt-in premium tier - and the signed verdict always stays outside every model.
ADR 0014 - Question handoffs may speak redacted client text over the phone.
A narrow exception to the never-speak-client-text rule: choose_approach question handoffs (and a
redacted action gist on tool-gate calls) may read the question, options, and a clipped, redaction-scrubbed
summary aloud, because for a choice the question content is the decision.
Each ADR file records not only the decision but the review findings that shaped it and the guardrails that keep it honest. If you are changing a contract, the wire schema, or the trust boundary, read the relevant ADR first - and if your change contradicts one, that is a new ADR, not a silent edit.