| User texts | Number | Routed to | Reply |
|---|---|---|---|
| Alice: “my order hasn’t arrived” | +1 415 555 0123 | support agent | Shipment lookup |
| Bob: “what does the Pro plan cost?” | +44 20 7183 8750 | sales agent | Pricing answer |
| Priya: brand-new number | never seen before | triage default agent | ”Who are you trying to reach?” |
Quick Start
Enable with a single agent
One number, one agent. Normalisation means the formatting you assign with does not have to match the inbound format.
Wire it into a WhatsApp bot
A real gateway calls
registry.resolve(inbound_number) on every message, then hands the chat to the chosen agent. See WhatsApp Bot for the full bot setup.How It Works
The gateway resolves the destination number to an agent, runs the chat, and replies on the same channel.| Step | What happens |
|---|---|
| Webhook | The inbound message carries the business number the user texted (to_number) |
| Resolve | registry.resolve(to_number) returns the assigned agent, the default_agent, or None |
| Chat | The gateway runs the chosen agent with the user’s text |
| Reply | The response is sent back on the same channel |
Number Normalisation
"+1 (415) 555-0123" and "+14155550123" resolve to the same agent because normalize_number strips every non-digit and preserves a single leading +. Blank, non-string, or digit-free inputs normalise to None.
| Input | Normalised key |
|---|---|
"+1 (415) 555-0123" | "+14155550123" |
"+44 20 7183 8750" | "+442071838750" |
"14155550123" (no leading +) | "14155550123" |
"", " ", None, non-string | None |
API Reference
AgentRegistry(default_agent=None)
In-memory phone-number → agent routing table. No I/O, no dependencies. Not thread-safe.
| Option | Type | Default | Description |
|---|---|---|---|
default_agent | Any | None | None | Fallback returned by resolve() when the inbound number is unknown or blank. When None, unknown numbers resolve to None. |
| Method | Signature | Returns | Description |
|---|---|---|---|
assign | assign(number: str, agent: Any) -> str | Normalised key (str) | Assign number to agent. Re-assigning replaces the previous agent. Raises ValueError if number normalises to empty. |
unassign | unassign(number: str) -> bool | bool | Remove any agent for number. Returns True if a mapping was removed, else False. |
resolve | resolve(number: Optional[str]) -> Optional[Any] | Agent or None | Return the agent assigned to number, else default_agent, else None. |
numbers | numbers() -> List[str] | List[str] | All assigned (normalised) numbers. |
__contains__ | number in registry | bool | Membership test using the normalised key. |
__len__ | len(registry) | int | Number of assigned numbers. |
__iter__ | for key, agent in registry | Iterator[Tuple[str, Any]] | Iterate (normalised_number, agent) pairs. |
normalize_number(number)
normalize_number(number: Optional[str]) -> Optional[str] returns the canonical key, or None when blank / non-string / no digits.
- Strips whitespace and formatting characters (spaces, dashes, parens, dots).
- Preserves a single leading
+.
AgentRegistry vs ChannelDirectory
Pick by message direction:AgentRegistry routes inbound by phone number; ChannelDirectory routes outbound by target name.
Common Patterns
Fallback agent — set adefault_agent so unknown numbers are never left unanswered.
Best Practices
Prefer E.164 numbers
Prefer E.164 numbers
Store numbers as
+<country><digits> (E.164). Normalisation still handles human-typed variants like "+1 (415) 555-0123", but E.164 is unambiguous and matches what most webhooks send.Build one registry per process
Build one registry per process
The registry holds references to already-constructed agents and does no I/O. Create it once at startup and consult it on every inbound message.
Always set a default_agent
Always set a default_agent
Set
AgentRegistry(default_agent=triage) if you never want to leave an inbound message unanswered. Without it, unknown numbers resolve to None and the caller must handle that case.AgentRegistry is not thread-safe
AgentRegistry is not thread-safe
Construct it before your gateway starts and treat it as read-mostly. If you mutate it from multiple threads, add your own lock.
Related
WhatsApp Bot
Connect an agent to WhatsApp — Cloud API or Web mode.
Channels Gateway
Route agents across Telegram, Discord, Slack, and WhatsApp.
Messaging Bots
All supported messaging platforms in one place.
Platform-Aware Agents
Outbound routing with ChannelDirectory and ReachableTarget.

