The AI-native contact center OS — open infrastructure for customer conversations that remember, route, and resolve.
Orvexa is a four-plane, event-driven Go core where customers, conversations, AI agents and business workflows form one continuous system — with every carrier, LLM and datastore behind tested, swappable ports.
The contact-center industry still runs on 1990s CRUD: banks of agents with no memory of the customer, per-seat pricing with per-minute carrier lock-in, and bolt-on AI that never touches the actual workflow. Orvexa rebuilds the stack as infrastructure:
- One conversation, many channels. A phone call is not the customer relationship — it is one interaction inside a continuous conversation. WhatsApp → voice → email on the same conversation keeps one context; identity normalization means a phone number can never fork into two customers (asserted by tests:
internal/customers). - Four planes, one critical path. Interaction, Intelligence, Execution and Control communicate only through the event bus, defined interfaces or the API — nothing outside telephony → routing → agent may block a live call.
- Carrier-agnostic by construction. Twilio, WhatsApp Cloud, Africa's Talking, FreeSWITCH, Asterisk and the built-in simulator sit behind two ports (
internal/telephony/ports.go,internal/messaging/ports.go) and must pass the same conformance kit to merge. Switching carriers is an env var, not a rewrite. - AI with a safety boundary. AI suggestions are metered; every AI→side-effect call passes a deny-by-default tool gateway that allowlists, schema-validates, rate-limits and audits — refusals included.
Four planes around a transactional event backbone; three stateless deployables; carriers behind a hexagon:
flowchart TB
CH["Channels — voice · WhatsApp · SMS · USSD"] --> API
IDP["OIDC identity provider"] --> API
API["cmd/api<br/>REST v1 · dual auth · capability RBAC · signed webhook ingress"]
WK["cmd/worker<br/>outbox dispatcher · audit · facts · search indexer · workflow executor"]
RT["cmd/realtime<br/>WebSocket fan-out"]
API --> INT
INT --> BUS
BUS --> WK
BUS --> RT
subgraph PLANES["Domain planes — bounded contexts"]
direction LR
INT["INTERACTION — critical path<br/>telephony · messaging · routing · presence · agents · queues"]
EXE["EXECUTION<br/>durable workflows · callbacks · notifications"]
NTL["INTELLIGENCE<br/>AI gateway · tool gateway · analytics facts · search"]
end
CTL["CONTROL — everywhere<br/>tenancy · identity · capability RBAC · audit · metering"]
CTL -. fast checks on every request .-> API
BUS["EVENT BUS — transactional outbox → at-least-once<br/>inproc default · NATS JetStream driver"]
INT -. "VoiceProvider / MessagingProvider ports" .-> CAR
subgraph CAR["Conformance-tested carrier adapters"]
direction LR
TW["Twilio"] --- WA["WhatsApp Cloud"] --- ATC["Africa's Talking"] --- FSW["FreeSWITCH"] --- AST["Asterisk"] --- SIM["simulator (default)"]
end
| Plane | Owns | Critical path? |
|---|---|---|
| Interaction | Realtime customer interactions: telephony, messaging, routing, agent assignment | Yes — nothing else may block a call |
| Intelligence | AI gateway, tool gateway, transcription, analytics facts, search | No — consumes events, fails independently |
| Execution | Durable workflows, callbacks, notifications | No — event-driven consumers |
| Control | Tenancy, identity, authorization, metering, audit | Yes — but only as fast checks |
Core domain model: Customer → Conversation → Interaction → Case → Workflow. Decisions live in the ADRs — start with ADR-0001 (planes), ADR-0004 (outbox), ADR-0005 (hexagon).
| Claim | Evidence |
|---|---|
| 32 test packages green under the race detector; build, vet, gofmt, migration and boot gates pass | verified per ADR-0003, recorded in qa/QA_REPORT.md; re-run on this PR |
| Provider conformance kit — every carrier adapter must pass the same lifecycle contract, audited by negative controls | internal/comms/conformance/README.md |
| Transactional outbox: state + events in one SQL transaction, lease-based dispatcher, at-least-once with idempotent consumers, failures parked — never deleted | internal/platform/outbox, ADR-0004 |
| Tool gateway: the only path from AI to side-effects — allowlists, schemas, rate windows, audited refusals; AI holds no credentials | internal/tools, architecture § AI boundary |
| Capability RBAC: IdP owns identity, Orvexa owns authorization; role→capability matrix pinned by tests; revocation lands within the TTL; 10-attack JWT forgery matrix | docs/rbac.md, internal/identity, ADR-0008 |
| Tenant isolation: tenant resolved only from the authenticated key or verified token — never the wire; cross-tenant ids read as 404; hard isolation on realtime fan-out; server-side tenant filter on search | internal/tenancy, internal/realtime, QA §4 |
| Fail-closed webhook ingress: per-provider HMAC (Twilio / WhatsApp Cloud / Africa's Talking / platform), tamper + replay + empty-secret rejection, no unsigned path exists | internal/webhooks, QA §2 |
| Rate limiting on all 24 mutation routes with server-side keying and memory-bounded eviction | security posture §4, internal/platform/httpx |
OpenAPI contract tests: router ⇄ spec bidirectional conformance, uniform {data,meta} / error envelope |
tests/contract, api/openapi/orvexa-v1.yaml (40 paths) |
| Security sweep verdict: PUBLIC-READY — secret scan clean (canary-validated), headers complete, govulncheck 0 reachable vulnerabilities | docs/security/security-posture.md, scripts/security-scan.sh |
Truthful health: /readyz reports component truth (503 when degraded), workers fail fast without storage, degradation is typed (never silent) |
QA §1, operations runbook |
Full-loop integration suites (webhook dedup against provider_events, outbox lease/ack) run against real PostgreSQL via the devstack: make integration.
Verified end to end on a fresh clone (full transcript attached to the README pull request): no cloud accounts, no docker, no sudo — Go 1.26+, bash, curl, jq.
# 1. dev stack: userland PostgreSQL 16 + migrations (idempotent; docker compose also available)
make devstack-up
# 2. run the platform (two terminals)
export ORVEXA_DATABASE_URL='postgres://postgres:postgres@127.0.0.1:55432/orvexa?sslmode=disable'
export ORVEXA_WEBHOOK_HMAC_SECRET='dev-secret-change-me' # signs/validates every provider webhook
go run ./cmd/api # REST + webhook gateway on :8080
go run ./cmd/worker # outbox dispatcher, audit + facts consumers, workflow executor
# 3. bootstrap org + tenant + API key (SQL until the admin API lands — see runbook)
psql "$ORVEXA_DATABASE_URL" <<'SQL'
INSERT INTO organizations (id, name, slug) VALUES (gen_random_uuid(), 'Acme', 'acme');
INSERT INTO tenants (id, organization_id, name) VALUES (gen_random_uuid(), (SELECT id FROM organizations), 'Acme Support');
INSERT INTO api_keys (id, tenant_id, name, key_hash, scopes)
VALUES (gen_random_uuid(), (SELECT id FROM tenants), 'bootstrap',
encode(sha256('orvx_dev-bootstrap-key'::bytea), 'hex'), '{api}');
SELECT id AS tenant_id FROM tenants;
SQL
# 4. drive the loop through the public contract
export ORVEXA_KEY='orvx_dev-bootstrap-key'
curl -s http://localhost:8080/readyz | jq -c . # {"status":"ready"}
# a customer with two channel identities — one identity, not two silos
CUST=$(curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' \
-d '{"display_name":"Jane Wanjiku","identifiers":[{"type":"phone","value":"+254 712 345 678"},{"type":"whatsapp","value":"+254712345678","is_primary":true}]}' \
http://localhost:8080/api/v1/customers | jq -r .data.id)
# place an outbound call: the simulator carrier reports progress via SIGNED webhooks
# through the same public gateway a real carrier uses — the hexagon, live
CALL=$(curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' \
-d "{\"customer_id\":\"$CUST\",\"to\":\"+254712345678\"}" \
http://localhost:8080/api/v1/calls | jq -r .data.ID)
# prove the ingress is fail-closed and idempotent: sign, replay, tamper
BODY=$(jq -nc --arg i "$CALL" --arg n "qs-$(date +%s)" '{event:"call.connected",interaction_id:$i,nonce:$n,tenant_id:"<tenant_id from step 3>"}')
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac 'dev-secret-change-me' -hex | awk '{print $2}')
curl -s -X POST -H 'Content-Type: application/json' -H "X-Orvexa-Signature: $SIG" -d "$BODY" http://localhost:8080/api/v1/webhooks/simulator | jq -c . # processed:true
curl -s -X POST -H 'Content-Type: application/json' -H "X-Orvexa-Signature: $SIG" -d "$BODY" http://localhost:8080/api/v1/webhooks/simulator | jq -c . # duplicate:true — single effect
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' -H "X-Orvexa-Signature: deadbeef" -d "$BODY" http://localhost:8080/api/v1/webhooks/simulator # 401 — tamper rejected
# finish the interaction, open a case, schedule a callback workflow
curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' -d '{"action":"hangup"}' http://localhost:8080/api/v1/calls/$CALL/actions | jq -r .data.Status
curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' -d '{"action":"complete"}' http://localhost:8080/api/v1/calls/$CALL/actions | jq -r .data.Status
CASE_ID=$(curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' \
-d "{\"customer_id\":\"$CUST\",\"subject\":\"Billing dispute\"}" \
http://localhost:8080/api/v1/cases | jq -r .data.id)
curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" "http://localhost:8080/api/v1/cases/$CASE_ID/interactions/$CALL/link" -o /dev/null -w 'linked: %{http_code}\n'
curl -s -X POST -H "X-API-Key: $ORVEXA_KEY" -H 'Content-Type: application/json' \
-d "{\"customer_id\":\"$CUST\",\"phone\":\"+254712345678\",\"notes\":\"dispute follow-up\"}" \
http://localhost:8080/api/v1/workflows/callbacks | jq -c '{type:.data.type,status:.data.status}' # waiting_timer
# stop: make devstack-down · full loop incl. analytics: scripts/e2e-demo.sh (see limitations)Analytics facts are asynchronous (worker consumes the outbox): give it a beat, then GET /api/v1/analytics/summary. Configuration reference: operations runbook.
| Path | What it is |
|---|---|
cmd/ |
Deployables: api (REST + ingress), worker (dispatcher + consumers), realtime (WebSocket fan-out) |
internal/ |
Domain modules — customers · conversations · interactions · cases · queues · agents · routing · telephony · messaging · comms (+ conformance kit) · webhooks · realtime · ai · tools · workflows · analytics · search · identity · tenancy · httpserver · platform (bus, outbox, httpx, db) |
pkg/ |
Shared kernels: events (closed topic registry), errors, idempotency, pagination, config, logging |
migrations/ |
12 ordered, forward-only SQL migrations (11 PostgreSQL + 1 ClickHouse engine-specific) |
api/openapi/ |
Orvexa API v1 — 40 paths, envelope + stable machine error codes, contract-tested |
docs/ |
Architecture · domain map · RBAC · runbook · devstack · ADRs · security |
qa/ |
Release-gate report: QA_REPORT.md — the evidence-backed verdict |
scripts/ |
devstack.sh · e2e-demo.sh · security-scan.sh |
tests/ |
contract (OpenAPI ⇄ router) · integration (DB-backed, tag-gated) |
Makefile |
make race · make integration · make devstack-up · make lint-todos |
- Market. Contact centers are a large, sticky category still sold as seats-and-minutes CRUD. Orvexa is the platform layer underneath: conversations as first-class infrastructure with an event backbone every AI capability can safely plug into. The leverage is agent time — metered AI suggestions, audited tool calls and durable workflows that complete follow-ups without a human holding the thread.
- Defensible architecture. The moat is the contract set: hexagonal carrier ports + conformance kits make new carriers cheap and safe; the transactional outbox makes every state change auditable and replayable; capability RBAC and tenant isolation are enforced by construction, not by review; OpenAPI contract tests keep the surface stable for integrators. Competitors must rebuild discipline that is already pinned by tests here.
- Pilot readiness. Quoting qa/QA_REPORT.md: "GO for pilot onboarding (single-tenant pilots, sandbox/demo posture)" — "the full interaction loop … is implemented end to end with zero external dependencies in the default profile, and clean extraction paths to production infrastructure. Known limitations are documented below and tracked as issues; none are silent."
- Honest limitations (same report, §5): no hosted CI on this account (the local ADR-0003 matrix is the authoritative gate; CI workflow is armed), production carriers/NATS/Redis wired as tested library swap points pending binary glue (roadmap), SQL-first tenant bootstrap, single-region posture. Trust is the product — the ledger is public.
- Hexagonal carriers with a self-auditing conformance kit. Adapters embed a lifecycle contract; broken adapters are proven to fail it (negative controls).
- Transactional outbox done properly. One SQL transaction for state + events,
SKIP LOCKEDleasing, at-least-once delivery, idempotent consumers, a closed topic registry that makes unregistered topics impossible. - A real AI security boundary. The tool gateway is deny-by-default with schema validation, per-agent rate isolation and audited refusals — AI never holds credentials (internal/tools).
- Capability RBAC without cookie-soup. RS256-only OIDC verification with kid rotation + amplification floors, static role catalog pinned Go↔SQL, revocation inside the token's cryptographically-valid lifetime (docs/rbac.md).
- Durable workflows as data. Deterministic steps, timer rows, immutable step trace, crash-safe resume — with an optional Temporal driver behind a build tag (ADR-0006, ADR-0009).
- Truthful operations.
/readyzthat lies is debt: health reports component truth, workers fail fast when they cannot do their job, degradation is typed (search.not_configured, 429 +Retry-After), and the runbook documents every env var the code reads.
Shipped: foundation → domain core → event backbone → communications → routing → realtime → intelligence → execution → release gate → devstack → carriers → search/Redis/Temporal → OIDC RBAC → security sweep → contract tests (issues #1–#41, all merged via PR — see QA §6).
Next, per umbrella #10: wire the JetStream bus driver and Redis presence/limiter drivers into the binaries (both shipped and integration-tested as library swap points) · LLM adapter wiring behind the AI gateway · real-carrier production runs per-adapter acceptance criteria · admin/onboarding API to replace SQL bootstrap (#56) · multi-region readiness per ADR-0007.
From qa/QA_REPORT.md §5 and this PR's verification: the default profile runs the in-process bus and simulator carrier (production drivers are swap points, not yet wired into cmd) · no hosted CI — the local matrix is the gate (ADR-0003) · tenant bootstrap is SQL (no admin API yet) · single-region, single-Postgres posture · scripts/e2e-demo.sh needs a payload refresh to match the strict processor vocabulary (#89) and the second outbound call per tenant is blocked by an empty-provider_ref dedupe collision (#90) — both verified while validating the quickstart above, which stays inside the proven path.
MIT — © 2026 Orvexa contributors.