Channel Routing
When an inbound SMS arrives, 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 SMS on phone number P from contact C │ ├─ 1. Contact assignment: contact_text_agent │ WHERE contact_id = C AND channel = "sms" │ → 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 generatedA 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) 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" 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" 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