Una demostración reciente mostró una idea que parece pequeña pero cambia bastante la arquitectura de los agentes: darle a un agente de IA un número de teléfono y permitirle hacer llamadas, recibirlas, enviar SMS y reaccionar a eventos telefónicos como si fueran cualquier otra herramienta.
La parte interesante no es que un modelo “sepa hablar”. Eso existe desde hace tiempo mediante ASR, modelos de lenguaje y síntesis de voz. El salto arquitectónico ocurre cuando la telefonía entra en el mismo loop de herramientas que el shell, GitHub, una base de datos o un navegador.
usuario
↓
agente
↓
policy / planner
↓
phone tool
├── llamada saliente
├── llamada entrante
├── SMS
└── eventos
↓
red telefónica
↓
persona o sistema externo
A partir de ahí el teléfono deja de ser una interfaz separada. Se convierte en una capacidad operacional más del agente.
El patrón: phone-as-a-tool
Un agente moderno suele trabajar con herramientas estructuradas:
read_file(path)
run_command(cmd)
create_issue(title, body)
query_database(sql)
La telefonía puede representarse de la misma forma:
place_call(to, instruction, idempotency_key)
get_call(call_id)
send_message(to, body)
wait_for(event_type)
El modelo no necesita implementar SIP, RTP, carriers, WebRTC ni señalización telefónica. Un gateway externo encapsula esa complejidad y expone una superficie sencilla por CLI, SDK, REST o MCP.
Una implementación pública actual, por ejemplo, documenta precisamente ese modelo: su CLI devuelve JSON, usa códigos de salida convencionales y permite que un agente espere eventos de llamadas o mensajes. La misma plataforma expone SDK y MCP para evitar que el modelo tenga que interpretar texto libre. (documentación)
Ese detalle importa mucho: una tool para agentes debería comportarse como una API, no como una terminal pensada solamente para humanos.
CLI vs. MCP vs. SDK
Hay tres formas razonables de integrar telefonía en un agente.
1. CLI
Es probablemente la opción más simple para agentes de coding u operadores que ya tienen shell:
phone call \
--to "$DESTINATION" \
--instruction "Confirma la hora de la cita y pregunta si hace falta llevar documentación" \
--idempotency-key "$REQUEST_ID" \
--json
Ventajas:
- funciona con cualquier agente que pueda ejecutar comandos;
- es fácil de auditar;
- los exit codes permiten branching determinista;
- JSON evita parsing frágil de lenguaje natural;
- puede envolverse en scripts y políticas del sistema operativo.
Una implementación real sigue exactamente este patrón y añade --json a los comandos destinados a agentes. (referencia CLI)
2. MCP
Con Model Context Protocol, la telefonía pasa a ser una tool nativa:
phone.place_call
phone.get_call
phone.send_message
phone.wait_for_event
Esto elimina una capa de shell parsing y hace que los argumentos estén tipados desde el principio.
Para un agente persistente, MCP tiene otra ventaja: el runtime puede aplicar permisos por tool. Por ejemplo:
phone.get_call ALLOW
phone.send_message REQUIRE_APPROVAL
phone.place_call REQUIRE_APPROVAL
phone.buy_number DENY
La política queda fuera del modelo.
3. SDK / REST
Es la opción adecuada cuando la telefonía forma parte de una aplicación más grande.
result = phone.place_call(
to=destination,
instruction=task,
idempotency_key=request_id,
)
Aquí el modelo puede ser solo una pieza del workflow. El backend conserva el control sobre autenticación, rate limits, auditoría, retries y almacenamiento.
Una llamada debería ser un job asíncrono
Una llamada telefónica no es una función de 50 ms. Puede tardar segundos en conectar y varios minutos en terminar.
Por eso el patrón correcto se parece más a una cola de trabajos:
POST /calls
↓
202 Accepted
↓
call_id
↓
conversation running
↓
call.ended event
↓
GET /calls/{id}
↓
status + duration + transcript
Una plataforma orientada a agentes documenta exactamente esta semántica: iniciar la llamada devuelve inmediatamente un estado inicial y, cuando termina, aparece un evento terminal junto con la transcripción y duración. (voice-call lifecycle)
Esto encaja de forma natural con arquitecturas agentic.
El agente puede hacer otras cosas mientras la llamada está en progreso:
1. iniciar llamada al proveedor
2. guardar call_id
3. continuar procesando otros proveedores
4. esperar call.ended
5. recuperar transcript
6. extraer precio, fecha y condiciones
7. comparar resultados
8. decidir siguiente acción
Idempotencia: no llames dos veces por accidente
Este punto parece menor hasta que un agente ejecuta un retry.
Supongamos:
agent → place_call()
↓
timeout de red
El agente no sabe si la llamada se creó o no. Si repite el comando puede generar una segunda llamada real.
La solución es una idempotency key:
request_id = sha256(task_id + destination + purpose)
Luego:
place_call(
to=destination,
instruction=instruction,
idempotency_key=request_id
)
Si el runtime vuelve a ejecutar la operación, el proveedor debe devolver la llamada ya creada en vez de originar otra.
Al menos una implementación actual de CLI para agentes soporta explícitamente una clave de idempotencia en llamadas salientes y advierte que otras acciones pueden duplicarse si se reintentan a ciegas. (documentación)
Para agentes autónomos esta propiedad no es opcional. Es parte del contrato de la tool.
Los eventos convierten el teléfono en un bus de entrada
El segundo cambio importante aparece con las llamadas y mensajes entrantes.
Normalmente pensamos en un agente como algo iniciado por un prompt:
usuario → agente
Pero un número telefónico introduce eventos externos:
incoming.call
incoming.message
call.started
call.ended
message.received
Eso convierte el sistema en una arquitectura reactiva:
telefono
↓
event stream
↓
router
↓
agent runtime
↓
policy
↓
action
Un mensaje puede despertar al agente exactamente igual que un webhook de GitHub o un evento de una cola.
Por ejemplo:
message.received
↓
clasificar intención
↓
consultar CRM
↓
generar respuesta
↓
policy check
↓
send_message
O para voz:
incoming.call
↓
voice agent
↓
resolver consulta
↓
si confidence < threshold
→ escalar a humano
El agente principal no debería hablar directamente con el carrier
Una arquitectura robusta separa responsabilidades.
┌─────────────────────┐
│ AI agent │
│ planning/reasoning │
└──────────┬──────────┘
│ tool call
▼
┌─────────────────────┐
│ policy gateway │
│ approvals / limits │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ phone adapter │
│ CLI / MCP / SDK │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ telephony provider │
└──────────┬──────────┘
│
▼
PSTN/SMS
La capa de política debería poder decidir cosas que el modelo no puede modificar:
- destinos permitidos;
- países permitidos;
- número máximo de llamadas por hora;
- presupuesto diario;
- horarios;
- si una acción necesita aprobación humana;
- qué prompts pueden usarse;
- si la conversación puede grabarse;
- cuánto tiempo se conserva una transcripción.
La regla general es la misma que con cualquier agente con acciones reales:
el modelo propone; una capa externa autoriza y ejecuta.
Credenciales: nunca dentro del prompt
Una mala integración sería:
Aquí tienes mi API key: ...
úsala para llamar.
La integración correcta mantiene el secreto fuera del contexto del modelo.
agent
↓
phone tool
↓
credential store
↓
provider
La tool puede leer credenciales desde:
- un secret manager;
- variables inyectadas en runtime;
- un archivo local con permisos restrictivos;
- identidad de workload;
- un broker de tokens efímeros.
El modelo solo recibe el resultado necesario.
{
"ok": true,
"status": "initiated",
"call_id": "<opaque-id>"
}
Nunca debería necesitar ver el secreto que autorizó esa operación.
Transcripciones como datos estructurados
El verdadero valor de una llamada agentic aparece después de la voz.
El audio puede convertirse en:
{
"objective": "confirm appointment",
"outcome": "confirmed",
"date": "2026-09-15",
"time": "14:30",
"requirements": ["photo ID"],
"follow_up_required": false
}
El transcript es evidencia; el JSON es el resultado operacional.
Un pipeline recomendable sería:
audio
↓
transcript
↓
structured extraction
↓
validation
↓
domain action
Por ejemplo:
call transcript
↓
extract_quote()
↓
validate currency + total
↓
store quote
↓
compare vendors
Así el agente no solo “habla”. Convierte una conversación humana en estado computable.
Observabilidad: cada llamada debe poder reconstruirse
Si una llamada produce una decisión real, hay que poder responder:
- ¿qué tarea originó la llamada?
- ¿qué agente la solicitó?
- ¿qué policy la autorizó?
- ¿qué prompt recibió el agente de voz?
- ¿qué número lógico se usó?
- ¿cuándo comenzó y terminó?
- ¿cuál fue el resultado?
- ¿qué acción posterior se ejecutó?
Un buen trace puede verse así:
trace_id
├── task.created
├── policy.approved
├── phone.call.requested
├── phone.call.started
├── phone.call.ended
├── transcript.received
├── extraction.completed
└── workflow.updated
No hace falta almacenar todo para siempre. De hecho, con voz y SMS suele ser mejor aplicar minimización y retención corta cuando sea posible.
Ideas útiles de verdad
1. Agente que pide cotizaciones
lista de proveedores
↓
llamadas en paralelo controlado
↓
transcripciones
↓
extracción estructurada
↓
tabla precio / disponibilidad / condiciones
El humano recibe comparación, no cinco conversaciones.
2. Recepcionista fuera de horario
El agente responde preguntas sencillas y crea una tarea cuando necesita intervención humana.
incoming.call
↓
FAQ / knowledge base
↓
confidence gate
├── alto → resolver
└── bajo → ticket + callback
3. Coordinación de citas
El agente puede llamar para confirmar, reprogramar o cancelar una cita, siempre con límites claros sobre qué cambios puede autorizar.
4. Operaciones e incidentes
Un sistema de observabilidad detecta un incidente crítico y el agente:
alerta
↓
consulta runbook
↓
identifica responsable on-call
↓
llamada
↓
confirma recepción
↓
actualiza incidente
Aquí el teléfono funciona como un canal de escalamiento, no como un chatbot.
5. Dispatch y trabajo de campo
Para técnicos, entregas o mantenimiento:
job delayed
↓
agente llama al contacto
↓
confirma nueva ETA
↓
actualiza sistema
↓
notifica al siguiente actor
6. Seguimiento de solicitudes
En vez de que una persona revise manualmente si un trámite avanzó, el agente puede consultar por teléfono cuando no existe API y registrar el estado obtenido.
7. Interfaz telefónica para un agente local
Un agente que corre en una máquina privada puede tener un número como interfaz remota:
persona llama
↓
identificación + política
↓
agente local
↓
consulta estado
↓
responde por voz
La clave está en limitar muy bien qué operaciones pueden activarse por una llamada entrante.
Lo que no deberíamos automatizar a ciegas
El hecho de que sea técnicamente posible no significa que toda llamada deba delegarse.
Hay varias fronteras importantes:
- no ocultar que el interlocutor está hablando con un sistema automatizado cuando la legislación o el contexto exijan transparencia;
- respetar reglas de consentimiento, llamadas automáticas y grabación aplicables en cada jurisdicción;
- no usar SMS o llamadas para saltarse controles de cuentas ajenas;
- tratar OTP, PII y transcripciones como datos sensibles;
- evitar decisiones médicas, legales, financieras o de emergencia sin controles apropiados;
- incorporar escalamiento humano cuando el resultado tenga consecuencias significativas.
La arquitectura debería asumir que una conversación telefónica puede contener información privada incluso cuando la tarea original parecía inocua.
Un diseño mínimo razonable
Para experimentar sin crear un sistema difícil de controlar:
agent runtime
↓
phone skill / MCP
↓
policy gateway
├── allowlist de destinos
├── límite de llamadas
├── presupuesto
├── approval threshold
└── audit log
↓
telephony provider
↓
event queue
↓
transcript processor
↓
structured result
Y un contrato de tool pequeño:
class PhoneTool:
def call(self, destination, instruction, idempotency_key): ...
def get_call(self, call_id): ...
def send_message(self, destination, body): ...
def wait_for(self, event_type, timeout): ...
No empezaría permitiendo compra de números, edición de billing o cambios de seguridad desde el mismo agente. Es mejor separar operaciones de comunicación de administración de infraestructura.
La idea más profunda
Dar teléfono a un agente no es principalmente una feature de voz.
Es otro paso en la transición:
modelo que responde
↓
modelo que usa tools
↓
agente que opera sistemas
↓
agente que interactúa con personas
↓
agente que participa en procesos del mundo real
Cuando una llamada se convierte en una tool, desaparece una frontera histórica entre software y coordinación humana.
Un sistema puede consultar una API cuando existe y llamar por teléfono cuando no existe.
Eso abre workflows muy poderosos, pero también cambia el nivel de responsabilidad del diseño. Ya no basta con evaluar si el modelo genera buen texto. Hay que pensar en idempotencia, presupuesto, consentimiento, permisos, auditoría, privacidad, escalamiento y blast radius.
La pregunta deja de ser “¿puede la IA hablar por teléfono?”.
La pregunta útil pasa a ser:
¿qué procesos que todavía dependen de una conversación humana pueden convertirse en una tool segura, observable y reversible para un agente?