Bueno, amigos, Leo Grant aquí, de regreso con otra inmersión profunda en el salvaje mundo del desarrollo de agentes. Hoy quiero hablar sobre algo que me ha estado inquietando, algo que he visto aparecer en foro tras foro, y, honestamente, algo con lo que también luché hace solo unos meses: La trampa del “Marco de Agente Nuevo Brillante”. Todos hemos estado allí, ¿verdad?
Es 2026, y parece que cada dos semanas aparece un nuevo SDK o marco de agente. Cada uno promete ser más rápido, más inteligente, más escalable o simplemente más fácil para construir agentes autónomos. Y como alguien que vive y respira esto, mi reacción inicial siempre es una mezcla de emoción y miedo a perderme algo. “¿Es este el indicado? ¿Es esta la herramienta que finalmente convierte mi proyecto personal en una realidad sin semanas de plantillas?”
Recuerdo que en octubre, estaba trabajando en un agente asistente financiero personal. La idea era simple: un agente que pudiera monitorear mis gastos, identificar servicios de suscripción que podría no estar utilizando e incluso negociar mejores tarifas en mi nombre. Tenía un script básico en Python funcionando, utilizando un bus de mensajes personalizado y un montón de sentencias `if/else`. Era torpe, claro, pero funcionaba.
Entonces vi el anuncio de “Aether”, un nuevo marco de agente basado en Rust que presumía de una concurrencia increíble y un lenguaje de definición de agentes declarativo. Mi cerebro reptiliano gritó de inmediato, “¡Reescríbelo! ¡Rust es el futuro! ¡Mi desastre en Python es una vergüenza!” Así que pasé dos semanas sólidas portando todo mi proyecto a Aether. ¿Y adivina qué? Aunque Aether efectivamente era eficiente y elegante, terminé con un proyecto que era funcionalmente idéntico a mi versión en Python, pero ahora tenía que aprender todo un nuevo ecosistema de herramientas de construcción, gestión de dependencias y manejo de errores específicos de Aether.
Fue una lección valiosa, y una que quiero compartir contigo hoy. El mayor obstáculo en el desarrollo de agentes no siempre es encontrar el marco “mejor”; es entender cuándo usar un marco y, más importante aún, cuándo simplemente ceñirse a lo que ya conoces y construir desde principios básicos. Hoy vamos a hablar sobre la mentalidad de “construir”, específicamente en lo que respecta a la comunicación central y la gestión del estado de tus agentes, en lugar de adoptar ciegamente el último SDK.
El Problema Central: Sobrecarga de Comunicación
La mayoría de los marcos de agentes, en su esencia, están tratando de resolver dos problemas principales:
- Comunicación entre Agentes: ¿Cómo se comunican los agentes entre sí? ¿Colas de mensajes, RPC, estado compartido?
- Gestión del Estado del Agente: ¿Cómo mantiene un agente el seguimiento de lo que sabe, lo que ha hecho y lo que necesita hacer a continuación?
Y estos son problemas críticos, sin duda. Pero a menudo, los marcos ofrecen una solución tan completa y sesgada que se vuelve excesiva para proyectos más simples o introduce complejidad innecesaria. Mi agente financiero, por ejemplo, solo necesitaba comunicarse con un otro “agente” (una API de banco simulada) y su propio estado interno. Un bus de mensajes completo con enrutamiento complejo era como usar un lanzacohetes para aplastar una mosca.
Entonces, ¿y si simplificamos? ¿Y si pensamos en el mínimo absoluto que necesitas para hacer que los agentes se comuniquen y recuerden cosas? Esto no se trata de rechazar los marcos para siempre, sino de construir una base sólida primero, entender la mecánica subyacente y luego decidir si un marco realmente aporta valor en lugar de solo sobrecarga.
Mensajería Simple: La Base HTTP/JSON
Seamos brutalmente honestos: para un gran número de casos de uso de agentes, especialmente aquellos que interactúan con servicios web u otros sistemas externos, HTTP y JSON son tus mejores amigos. Son ubicuos, bien entendidos y increíblemente flexibles. No necesitas un protocolo personalizado o un corredor de mensajes complejo si tus agentes están enviando principalmente solicitudes y recibiendo respuestas.
Considera un escenario donde tienes un “Agente Raspador” que recoge datos de un sitio web y un “Agente Procesador” que los limpia y analiza. ¿Cómo se comunican?
# scraper_agent.py (simplificado)
import requests
import json
def scrape_data(url):
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
def send_to_processor(data):
headers = {'Content-Type': 'application/json'}
payload = {'raw_data': data}
try:
response = requests.post('http://localhost:8001/process', headers=headers, data=json.dumps(payload))
response.raise_for_status() # Lanza una excepción para errores HTTP
print(f"Datos enviados al procesador: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"Error enviando datos al procesador: {e}")
if __name__ == "__main__":
url_to_scrape = "https://example.com/some_data" # Reemplazar con una URL real
raw_content = scrape_data(url_to_scrape)
if raw_content:
send_to_processor(raw_content)
# processor_agent.py (simplificado usando FastAPI)
from fastapi import FastAPI, Request
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class DataPayload(BaseModel):
raw_data: str
@app.post("/process")
async def process_data(payload: DataPayload):
# En un escenario real, harías un procesamiento real aquí
print(f"Datos crudos recibidos para procesamiento: {payload.raw_data[:50]}...")
processed_result = f"Procesado: {payload.raw_data.upper()}" # Ejemplo de procesamiento
return {"status": "success", "processed_data": processed_result}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
Esto es básico, lo sé. Pero también es increíblemente poderoso. Tienes dos agentes, funcionando como servicios separados, comunicándose entre sí. Sin SDK especial, sin formato de mensaje personalizado. Solo prácticas web estándar. La belleza aquí es que puedes escalar estos de forma independiente, desplegarlos en cualquier lugar y depurarlos con herramientas HTTP estándar. Este enfoque cubre una sorprendente cantidad de necesidades de comunicación entre agentes sin ningún bloqueo en un marco.
Gestión del Estado: Aceptando la Persistencia
La segunda gran pieza es el estado. Un agente no es mucho un agente si olvida todo entre ejecuciones. Muchos marcos ofrecen estado en memoria o máquinas de estado complejas. Pero a menudo, lo que realmente necesitas es una persistencia simple y confiable.
Para mi agente financiero, necesitaba almacenar cosas como:
- Mis suscripciones actuales (nombre, costo, fecha de renovación)
- Mis categorías de gasto
- Intentos de negociación históricos
Inicialmente, traté de usar algunos mecanismos de estado en memoria proporcionados por un marco, pero tan pronto como el agente se reinició (lo cual pasa durante el desarrollo, créeme), todos esos valiosos datos desaparecieron. ¡Frustrante!
¿La solución? Una base de datos simple. Para muchos proyectos personales o incluso sistemas de producción más pequeños, SQLite es una excelente opción. Es basada en archivos, no requiere un servidor separado y Python tiene un excelente soporte incorporado.
# agent_state.py
import sqlite3
import json
class AgentState:
def __init__(self, db_path='agent_data.db'):
self.conn = sqlite3.connect(db_path)
self._create_table()
def _create_table(self):
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS agent_knowledge (
key TEXT PRIMARY KEY,
value TEXT
)
''')
self.conn.commit()
def set(self, key, data):
cursor = self.conn.cursor()
value = json.dumps(data) # Almacenar objetos complejos como cadenas JSON
cursor.execute('INSERT OR REPLACE INTO agent_knowledge (key, value) VALUES (?, ?)', (key, value))
self.conn.commit()
def get(self, key):
cursor = self.conn.cursor()
cursor.execute('SELECT value FROM agent_knowledge WHERE key = ?', (key,))
row = cursor.fetchone()
if row:
return json.loads(row[0])
return None
def close(self):
self.conn.close()
# Uso de ejemplo:
if __name__ == "__main__":
state = AgentState()
# Almacenar una suscripción
state.set('subscription:netflix', {'name': 'Netflix', 'cost': 15.99, 'renewal': '2026-04-01'})
state.set('subscription:spotify', {'name': 'Spotify', 'cost': 10.99, 'renewal': '2026-03-25'})
# Obtener una suscripción
netflix_sub = state.get('subscription:netflix')
print(f"Suscripción a Netflix: {netflix_sub}")
# Actualizar una suscripción
if netflix_sub:
netflix_sub['cost'] = 16.99 # ¡Aumento de precio!
state.set('subscription:netflix', netflix_sub)
print(f"Suscripción a Netflix actualizada: {state.get('subscription:netflix')}")
# Almacenar una lista de categorías de gasto
state.set('spending_categories', ['compras', 'entretenimiento', 'servicios'])
print(f"Categorías de gasto: {state.get('spending_categories')}")
state.close()
Esta clase `AgentState` es bastante básica, pero proporciona un almacén de clave-valor que persiste a través de reinicios del agente. Puedes almacenar diccionarios, listas, cadenas, cualquier cosa que se pueda serializar en JSON. Para relaciones más complejas, definirías más tablas, pero para muchos agentes, un simple almacén de clave-valor es todo lo que necesitas para su “memoria”.
Juntándolo Todo: El Agente “Bare Bones”
Entonces, si combinamos estas ideas, ¿cómo se ve un agente “bare bones”? Es un proceso que:
- Puedes recibir comandos (por ejemplo, a través de un endpoint HTTP o una simple cola de mensajes).
- Puedes realizar acciones (por ejemplo, hacer solicitudes HTTP, ejecutar scripts locales).
- Puedes recordar cosas (por ejemplo, utilizando un almacén de estado persistente como SQLite).
- Tiene un bucle principal o programador para decidir qué hacer a continuación.
Imaginemos nuestro Agente Raspador de antes, pero ahora con algo de memoria. Necesita recordar qué URLs ya ha raspado y cuándo, para evitar trabajos redundantes o para volver a intentar intentos fallidos.
# smart_scraper_agent.py
import requests
import json
import time
from datetime import datetime, timedelta
from agent_state import AgentState # Suponiendo que agent_state.py está en el mismo directorio
# Usaremos FastAPI para recibir comandos para iniciar el scraping
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import uvicorn
app = FastAPI()
agent_state = AgentState(db_path='scraper_agent_data.db')
class ScrapeRequest(BaseModel):
url: str
target_processor_url: str
def _perform_scrape_and_send(url: str, target_processor_url: str):
"""Función interna para realizar el scraping y el envío."""
last_scraped_info = agent_state.get(f'last_scraped:{url}')
if last_scraped_info:
last_scrape_time = datetime.fromisoformat(last_scraped_info['timestamp'])
# Solo hacemos scraping si ha pasado más de una hora (ejemplo)
if datetime.now() - last_scrape_time < timedelta(hours=1):
print(f"Saliendo de {url}, scrapeado recientemente a las {last_scrape_time}")
return
print(f"Scrapeando {url}...")
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
raw_content = response.text
# Enviar al procesador
headers = {'Content-Type': 'application/json'}
payload = {'raw_data': raw_content}
processor_response = requests.post(target_processor_url, headers=headers, data=json.dumps(payload))
processor_response.raise_for_status()
print(f"Datos de {url} enviados al procesador: {processor_response.json()}")
# Actualizar estado con un scraping exitoso
agent_state.set(f'last_scraped:{url}', {
'timestamp': datetime.now().isoformat(),
'status': 'success',
'processor_response': processor_response.json()
})
except requests.exceptions.RequestException as e:
print(f"Error durante el scraping o el envío para {url}: {e}")
# Actualizar estado con fallo
agent_state.set(f'last_scraped:{url}', {
'timestamp': datetime.now().isoformat(),
'status': 'failed',
'error': str(e)
})
finally:
agent_state.close() # Importante cerrar la conexión si no se gestiona globalmente
@app.post("/scrape")
async def start_scrape(request: ScrapeRequest, background_tasks: BackgroundTasks):
"""Endpoint para iniciar una acción de scraping."""
background_tasks.add_task(_perform_scrape_and_send, request.url, request.target_processor_url)
return {"message": f"Scraping de {request.url} iniciado."}
if __name__ == "__main__":
# También podría haber una tarea programada aquí que consulte el estado para URLs a scrapear
# Por ahora, solo ejecutaremos el servidor FastAPI.
# Para ejecutar: uvicorn smart_scraper_agent:app --reload --port 8000
uvicorn.run(app, host="0.0.0.0", port=8000)
Este `smart_scraper_agent.py` combina nuestra comunicación HTTP básica con un estado persistente. Evita el scraping redundante dentro de un marco de tiempo definido y almacena el resultado de cada scraping. Sigue siendo simple, pero ya comienza a mostrar un comportamiento "agente" – recordando, decidiendo y actuando en función de su estado interno y estímulos externos.
Cuándo Considerar un Marco (y Por Qué)
Ahora, no estoy diciendo que los marcos sean malos. Lejos de eso. Absolutamente tienen su lugar. Deberías empezar a considerarlos cuando:
- Coordinación Compleja: Tienes decenas o cientos de agentes que necesitan coordinar tareas complejas, formar equipos o descubrirse dinámicamente entre sí. Aquí, un bus de mensajes eficiente, descubrimiento de servicios, y potencialmente una capa de orquestación de agentes se vuelven invaluables.
- Comportamientos Estandarizados: Tus agentes necesitan implementar comportamientos comunes como planificación, establecimiento de objetivos o comprensión avanzada del lenguaje natural. Los marcos a menudo proporcionan abstracciones o integraciones para estos.
- Necesidades de Escalabilidad: Estás manejando un muy alto flujo de mensajes o un gran número de agentes concurrentes, y necesitas protocolos de comunicación altamente optimizados o gestión de estado distribuida lista para usar.
- Comunidad y Ecosistema: Quieres aprovechar una gran comunidad, complementos existentes y patrones probados para arquitecturas específicas de agentes (por ejemplo, agentes compatibles con FIPA).
Aun así, los principios de comunicación clara y gestión de estado eficiente que hemos discutido hoy siguen siendo fundamentales. Un buen marco se basa en estos, no reemplaza la necesidad de entenderlos.
Conclusiones Accionables
Mi esperanza para ti hoy es que te vayas con un renovado sentido de empoderamiento y un ojo crítico para el próximo anuncio de SDK de agente "revolucionario". Aquí tienes lo que quiero que recuerdes:
- Empieza Simple: Antes de lanzarte a un marco complejo, establece las necesidades de comunicación y estado mínimas absolutas para tu agente. ¿Puedes resolver el 80% con HTTP/JSON y una base de datos simple como SQLite? Probablemente.
- Entiende los Primitivos: Incluso si eventualmente usas un marco, dedica un tiempo a entender cómo funciona el paso de mensajes y la persistencia de estado a un nivel fundamental. Este conocimiento te hará un mejor depurador y arquitecto.
- Itera, No Reescribas: Construye primero la funcionalidad de tu agente. Haz que funcione. Si te enfrentas a una verdadera pared de escalabilidad o complejidad que un marco resuelve de manera demostrable, entonces considera adoptar uno. Evita el impulso de "reescribir todo".
- Enfócate en la Lógica del Agente: El verdadero valor de tu agente está en su toma de decisiones, su razonamiento y las tareas únicas que realiza. No dejes que las preocupaciones de infraestructura eclipsen el desarrollo de esa lógica fundamental.
- Elige Herramientas con Sabiduría: Solo porque exista un marco no significa que sea la herramienta adecuada para tu trabajo específico. Evalúa su sobrecarga frente a sus beneficios.
La próxima vez que inicies un proyecto de agente, intenta construir la comunicación central y la gestión de estado tú mismo, aunque sea solo para una prueba de concepto. Aprenderás un montón, construirás un agente más resistente y probablemente te ahorrarás muchos dolores de cabeza a futuro. Sigue construyendo, sigue experimentando y no tengas miedo de ensuciarte las manos con los fundamentos. Leo fuera.
🕒 Published: