Channel Routing
When an inbound message arrives on any supported channel (SMS, WhatsApp, or Email), Loquent uses a two-tier lookup to find which text agent should handle it. Understanding this hierarchy is key to configuring agents correctly.
Resolution Order
Section titled “Resolution Order”Inbound message on channel CH from contact C │ ├─ 1. Contact assignment: contact_text_agent │ WHERE contact_id = C AND channel = CH │ → Found: use this agent + its auto_reply setting │ └─ 2. Phone-level default: phone_number.text_agent_id WHERE phone_number.id = P → Found: use this agent + phone_number.text_agent_auto_reply → Not found: skip — no suggestions generatedThe same resolution logic applies to SMS, WhatsApp, and Email. The phone-level default fallback only applies to phone-based channels (SMS, WhatsApp) — Email contacts require an explicit contact assignment.
A contact assignment always wins over the phone default. The phone-level agent only applies when no explicit assignment exists for that contact + channel pair.
Phone-Level Default Agent
Section titled “Phone-Level Default Agent”Each phone number can have a text agent assigned as its org-wide default for inbound SMS. This is set in the Phone Details view under Text Agent.
- Assign:
PUT /api/phones/{id}withtext_agent_idset - Auto-reply: the
text_agent_auto_replytoggle on the phone record controls whether the agent sends replies automatically for all contacts without an explicit assignment - Clear:
PUT /api/phones/{id}withtext_agent_id: null
See Phone for full details on the phone-level assignment UI and API.
Contact Assignments
Section titled “Contact Assignments”Explicit per-contact assignments override the default and unlock auto-reply.
Assigning an agent to a contact
Section titled “Assigning an agent to a contact”Via the UI: Open a contact’s detail page → sidebar → Text Agents section. Each channel (SMS, WhatsApp, Email) has a dropdown:
- Select an agent → creates/replaces the assignment for that channel
- Select None → removes the assignment, falling back to the org default
- When no explicit assignment exists, the dropdown shows the org default name in italics
Via API:
POST /api/contacts/{contact_id}/text-agentsBody: { text_agent_id: "...", channel: "sms", auto_reply: false}This is an upsert — submitting a new assignment for an existing (contact, channel) pair replaces it atomically (delete + insert in a transaction).
Removing an assignment
Section titled “Removing an assignment”DELETE /api/contacts/{contact_id}/text-agents/{channel}Listing a contact’s assignments
Section titled “Listing a contact’s assignments”GET /api/contacts/{contact_id}/text-agentsReturns all assignments for the contact across all channels.
Data Model
Section titled “Data Model”-- Contact assignment (one per contact + channel)CREATE TABLE contact_text_agent ( id UUID PRIMARY KEY, organization_id UUID NOT NULL, contact_id UUID NOT NULL REFERENCES contact(id) ON DELETE CASCADE, text_agent_id UUID NOT NULL REFERENCES text_agent(id) ON DELETE CASCADE, channel TEXT NOT NULL, -- "sms" | "whatsapp" | "email" auto_reply BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP NOT NULL, UNIQUE (contact_id, channel));
-- Org default per channel (one per org + channel)CREATE TABLE text_agent_default_channel ( id UUID PRIMARY KEY, organization_id UUID NOT NULL REFERENCES organization(id) ON DELETE CASCADE, channel TEXT NOT NULL, -- "sms" | "whatsapp" | "email" text_agent_id UUID NOT NULL REFERENCES text_agent(id) ON DELETE CASCADE, created_at TIMESTAMP NOT NULL, UNIQUE (organization_id, channel));Auto-Reply
Section titled “Auto-Reply”Auto-reply sends the highest-confidence suggestion automatically when a message arrives.
Enabling auto-reply:
Auto-reply can be enabled at two levels:
- Contact assignment — toggle in the contact sidebar, or
auto_reply: truein the upsert API. Applies only to that specific contact. - Phone number —
text_agent_auto_replyon the phone record. Applies to all contacts handled by the phone-level default (i.e. those without an explicit contact assignment).
How it behaves:
| Condition | Result |
|---|---|
auto_reply = false | Suggestions generated and saved; human selects from the messaging view |
auto_reply = true, confidence ≥ threshold | Best suggestion sent automatically; text_agent_suggestion.auto_sent_body set |
auto_reply = true, confidence < threshold | Best suggestion still sent; a warning alert note is added to the contact record |
| No agent configured | Nothing — message sits in the inbox |
The threshold is configured per agent (text_agent.confidence_threshold, default 0.7). See Configuration for how to tune it.
Permissions
Section titled “Permissions”| Action | Permission Required |
|---|---|
| Set default channel | TextAgent:Collection:Create |
| Remove default channel | TextAgent:Instance:Update |
| Assign agent to contact | TextAgent:Instance:Update |
| Remove contact assignment | TextAgent:Instance:Update |
| List contact assignments | (no ABAC check — session org-scoped) |
Module Structure
Section titled “Module Structure”src/mods/text_agent/├── api/│ ├── get_contact_text_agents_api.rs # GET /api/contacts/{id}/text-agents│ ├── upsert_contact_text_agent_api.rs # POST /api/contacts/{id}/text-agents│ ├── delete_contact_text_agent_api.rs # DELETE /api/contacts/{id}/text-agents/{ch}│ ├── get_text_agent_default_channels_api.rs # GET /api/text-agents/defaults│ ├── set_text_agent_default_channel_api.rs # POST /api/text-agents/defaults│ └── delete_text_agent_default_channel_api.rs # DELETE /api/text-agents/defaults/{ch}├── types/│ ├── contact_text_agent_type.rs│ ├── contact_text_agent_data_type.rs│ ├── text_agent_default_channel_type.rs│ └── text_agent_default_channel_data_type.rs└── components/ └── contact_text_agent_section_component.rs # Contact sidebar UI