Architecture
This page explains Riyaan's data model and runtime flow so you can understand how all the pieces connect.
Workspace Layout
The App — Central Entity
Everything in Riyaan hangs off a single entity: the App. An app represents one configured voice agent. In the database, it's a single row in the apps table, identified by a unique slug.
| Field | What it controls |
|---|---|
slug | Unique identifier (e.g. demo-facility-management) |
secret | Shared key for server-to-server auth |
systemPrompt | Instructions sent to the LLM as the system message |
personaName | Agent's display name (e.g. "Mina") |
personaGreeting | First message the agent says when a session starts |
personaTone | Tone guidance (e.g. "calm, operational, concise") |
voice | TTS voice selection (e.g. "Kore", "Puck") |
modelId | LLM model identifier |
isActive | Kill switch to disable the app |
What Hangs Off an App
Every app has five related data sets, all linked by appSlug:
| Related table | Cardinality | Purpose |
|---|---|---|
| Tools | N rows | Functions the agent can call (e.g. create_maintenance_ticket) |
| Guardrail Rules | N rows | Regex-based safety rules checked on every message |
| Knowledge Documents | N rows | RAG documents with vector embeddings for semantic search |
| Scenario State | 1 row | Mutable JSON blob that tool handlers read/write for demos |
| Outcome Definitions | N rows | Measurable business events for analytics and ROI |
There is no separate "scenario" entity. The app row is the scenario. The word "scenario" in the codebase just means "a pre-configured app with sample data."
Tools
Each tool has a name, description, and parametersSchema (JSON Schema). At runtime, tool calls from the LLM are routed to handlers that either call an external API or return mock data for demos. See Tools.
Guardrail Rules
Each rule has a type (e.g. topic, pii), a regex pattern, and an action (block, warn, log). See Guardrails.
Knowledge Documents
Documents are chunked and embedded at seed time. At runtime, the agent searches them by semantic similarity when it needs domain-specific information. See Knowledge Base.
Scenario State
This is how demo scenarios maintain state across tool calls — an appointment gets rescheduled, an order gets a return initiated, a maintenance ticket gets created. Tool handlers receive the current state and can return a stateUpdate to mutate it.
Outcome Definitions
Each definition has a name (e.g. appointment_booked), a displayName, and an optional estimatedValue. When an outcome fires at runtime, an event is logged for the analytics and ROI dashboards.
Authentication
There are two auth modes. Both resolve to the same app row.
App Secret (server-to-server)
Your trusted backend sends the appSlug and appSecret to Convex. Used by server routes, the agent process, and CLI scripts.
POST /api/auth/session
{ "appSlug": "my-app", "appSecret": "sk_..." }
Response: { "sessionToken": "...", "expiresAt": 1234567890 }Session Token (browser-safe)
The session token is passed to the browser. All subsequent API calls use it in the Authorization header.
GET /api/tools
Authorization: Bearer <sessionToken>The token resolves to a session row, which contains the appSlug, which resolves to the app row and all its related data.
APP_SECRET
The secret in the apps table is the same value as APP_SECRET in your web app's environment. It's how Convex knows "this request is authorized to use this app."
Runtime Flow
Here's what happens from the moment a user opens a demo page to when the agent responds:
-
Dashboard picks scenario — The frontend knows the
appSlugfor the selected scenario. -
Session created — The dashboard server route calls
POST /api/auth/sessionwithappSlug+appSecretand gets back asessionToken. -
LiveKit room created — The dashboard calls
POST /api/livekit/rooms. The room name encodes theappSlug. -
Agent joins — The agent process reads the
appSlugfrom the room name, fetches the app config from Convex (system prompt, persona, voice, tools), and connects to the LLM. -
User speaks — Audio flows through LiveKit to the agent, which sends it to the LLM. The LLM responds with text/audio and optional tool calls.
-
Tool execution — When the LLM calls a tool, the agent sends it to
POST /api/tools/execute. Convex routes to the appropriate handler, which reads scenario state, returns a result, and optionally updates the state. -
Guardrails check — Every message is checked against the app's guardrail rules. If a rule fires, the message is blocked or flagged before reaching the user.
Seeding
Seeding is a one-time setup step that writes all the configuration for an app into Convex. A single seed command creates:
- The
appsrow (identity, prompt, voice, auth) toolsrows (capabilities)guardrailRulesrows (safety)outcomeDefinitionsrows (metrics)knowledgeDocumentsrows with embeddings (RAG)scenarioStaterow (initial mock data for demos)
# Seed one scenario
npx convex run seedScenarios:seedScenarioBySlug \
'{"secret":"<APP_SECRET>","slug":"demo-facility-management"}' --prod
# Seed all scenarios
npx convex run seedScenarios:seedAllAction '{"secret":"<APP_SECRET>"}' --prod
# Seed a blank app (no tools/guardrails/knowledge)
npx convex run seed:seedApp \
'{"slug":"my-app","name":"My App","secret":"...","systemPrompt":"You are a helpful assistant."}'The secret parameter becomes the apps.secret field. Use the same value as APP_SECRET in your web app's environment.
Deployment
Deployments are triggered by git tags via GitHub Actions:
| Tag pattern | What it deploys | Where |
|---|---|---|
dashboard-v* | Next.js web app | Railway |
agent-v* | Voice agent process | Railway |
backend-v* | Convex functions | Convex cloud |
Important: Deploying the backend pushes code. Seeding writes config data. You need both when adding a new scenario.
Glossary
| Term | Meaning |
|---|---|
| App | A configured voice agent (one row in apps table) |
| Scenario | A pre-configured app with seed data — used interchangeably with "demo" |
| Slug | Unique string identifier for an app |
| Secret | Shared key between your server and Convex for auth |
| Session Token | Short-lived browser-safe token derived from app secret |
| Tool | A function the agent can call during conversation |
| Scenario State | Mutable JSON blob that tool handlers read/write |
| Guardrail Rule | Regex-based safety rule checked on every message |
| Knowledge Doc | RAG document with vector embedding |
| Outcome | A measurable business event |
| Persona | The agent's name, greeting, tone, and voice settings |
Last updated on