En el artículo anterior definimos un agente de forma práctica:
Un agente LLM es un modelo dentro de un ciclo donde puede razonar, actuar, observar el resultado y decidir qué hacer después.
Esa definición es correcta. Pero todavía es demasiado abstracta.
Hoy mucha gente está construyendo agentes con vibe coding. Le pide al modelo que le arme un agente, copia el código y sigue. Los modelos actuales son realmente buenos escribiendo código, y eso hace que el proceso se sienta mágico.
El problema es que muchas veces se termina implementando algo sin entender los conceptos base.
Y cuando no se entienden los conceptos, el modelo puede proponer soluciones más complejas de lo necesario. Por ejemplo, sugerirte una Hierarchical State Machine cuando una Finite State Machine simple habría sido más que suficiente (y capaz ni siquiera te das cuenta de que es una FSM o HSM)..
Por eso este artículo.
No para complicar las cosas, sino para tener claridad sobre qué estamos construyendo realmente.
La pregunta central es:
¿Cómo modelamos el ciclo de un agente de forma explícita, controlable y escalable?
La respuesta es: tratarlo como una máquina de estados.
El ciclo Think Act Observe como máquina de estados
Imaginemos el flujo más simple de un agente:
- Recibe una tarea
- Razona sobre qué hacer
- Decide si necesita usar una herramienta
- Ejecuta la herramienta
- Observa el resultado
- Decide si terminó o si debe seguir
Ese flujo se puede modelar perfectamente como una Finite State Machine (FSM):
| Estado | Descripción |
|---|---|
'thinking' |
El modelo está razonando |
'acting' |
Está ejecutando una herramienta |
'observing' |
Está procesando el resultado |
'done' |
Terminó la tarea |
'error' |
Algo falló |
Las transiciones dependen del resultado de cada paso.
Eso es exactamente lo que hace un agente.
Una State Machine simple
Aquí un ejemplo conceptual (pseudo-código):
state = {
"task": "Implementar CORS en API Gateway con CDK",
"code": "",
"last_observation": None,
"status": "thinking"
}
while state["status"] not in ["done", "error"]:
if state["status"] == "thinking":
decision = llm.think(state)
if decision.needs_tool:
state["status"] = "acting"
state["next_tool"] = decision.tool
else:
state["status"] = "done"
state["final_answer"] = decision.answer
elif state["status"] == "acting":
result = run_tool(state["next_tool"], state)
state["last_observation"] = result
state["status"] = "observing"
elif state["status"] == "observing":
# El modelo decide qué hacer con la observación
next_action = llm.observe(state)
state["status"] = next_action # puede ser "thinking", "done", "error"...
Enter fullscreen mode Exit fullscreen mode
Este patrón es el corazón de casi todos los frameworks de agentes.
La diferencia está en qué tan explícito se hace el control del estado.
Tools = acciones que cambian el estado
Las herramientas no son un "extra".
Son acciones que producen una observación y, por lo tanto, cambian el estado de la máquina.
Cuando el agente llama a search_docs o run_tests, lo que realmente está haciendo es:
- Salir del estado
thinking - Entrar al estado
acting - Ejecutar una acción
- Recibir una observación
- Actualizar el estado
- Decidir la siguiente transición
Si pensamos las tools de esta forma, el diseño se vuelve mucho más claro:
- Una tool bien diseñada tiene un contrato claro de entrada/salida. El resultado de la tool debe poder interpretarse para decidir la siguiente transición.
- El manejo de errores de tools se convierte en transiciones hacia el estado error o hacia un estado de recuperación.
Cuando la máquina se complica: Hierarchical State Machines
Una FSM plana funciona bien para agentes simples.
Pero cuando el agente crece, empiezan a aparecer problemas:
- Demasiados estados
- Transiciones difíciles de seguir
- Lógica de "modo investigación" mezclada con "modo implementación"
- Dificultad para reutilizar partes del flujo
Aquí es donde aparecen las Hierarchical State Machines (HSM).
La idea es simple: un estado puede contener otra máquina de estados completa.
Ejemplo:
resolver_tarea (estado de alto nivel)
├── investigar
│ ├── buscar_documentación
│ ├── analizar_código_existente
│ └── sintetizar_hallazgos
├── implementar
│ ├── escribir_código
│ └── correr_tests
│ └── corregir_errores
└── validar
├── revisar_calidad
└── generar_respuesta_final
Enter fullscreen mode Exit fullscreen mode
Cada sub-máquina tiene su propio estado interno, pero el padre solo ve el resultado agregado.
Esto es exactamente cómo trabaja un desarrollador humano experimentado: no piensa en cada detalle al mismo tiempo. Entra en "modo investigación", luego en "modo implementación", etc.
Cómo lo implementa LangGraph
LangGraph es, en la práctica, una de las implementaciones más limpias de este enfoque.
1. StateGraph = la máquina de estados
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class AgentState(TypedDict):
task: str
messages: list
code: str
status: str
builder = StateGraph(AgentState)
Enter fullscreen mode Exit fullscreen mode
- El State es el estado compartido.
- Los nodes son las funciones que actualizan el estado.
- Las edges (especialmente las condicionales) definen las transiciones.
2. Subgraphs = Hierarchical State Machines
LangGraph permite meter un grafo completo como nodo de otro grafo.
Eso es literalmente una Hierarchical State Machine.
Hay dos patrones principales:
Patrón A - Shared State
El subgraph y el parent comparten claves del estado.
# El subgraph se agrega directamente como nodo
builder.add_node("investigar", investigar_subgraph)
Enter fullscreen mode Exit fullscreen mode
Patrón B - Isolated State
Cada subgraph tiene su propio schema y se hace una transformación explícita.
def call_investigar(state: AgentState):
subgraph_input = {"query": state["task"]}
result = investigar_subgraph.invoke(subgraph_input)
return {"code": result["findings"]}
Enter fullscreen mode Exit fullscreen mode
Además, LangGraph gestiona checkpoint namespaces por subgraph, lo que permite:
- Aislar el estado de cada sub-máquina
- Recuperar el estado después de fallos
- Hacer human-in-the-loop a nivel de sub-proceso
Esto es extremadamente poderoso cuando pasamos de demos a sistemas reales.
¿Cuándo vale la pena formalizarlo?
| Situación | Recomendación |
|---|---|
| Prototipo/demo rápida | Loop simple suele alcanzar |
| Agente con pocas tools | FSM plana |
| Agente con modos claros | HSM (subgraphs) |
| Multi-agente / equipos | HSM + supervisor |
| Producción con observabilidad | State machine explícita |
No hay que formalizar todo desde el día uno.
Pero cuando el flujo empieza a volverse difícil de seguir, convertir el ciclo en una máquina de estados (y eventualmente jerárquica) suele ser el siguiente paso natural.
Y aquí vuelve el punto del principio: si no tenés claros estos conceptos, es fácil que el modelo te proponga una HSM cuando en realidad necesitabas una FSM simple. O al revés.
Conclusión
El ciclo Think Act Observe no es solo una metáfora.
Es una máquina de estados.
Las Hierarchical State Machines nos permiten escalar esa idea sin que el sistema se convierta en un laberinto de ifs y prompts.
Y frameworks como LangGraph nos dan las primitivas exactas para implementarlo de forma limpia: StateGraph + Subgraphs.
Resumen:
Un agente no es solo un modelo con tools.
Es un modelo operando dentro de una máquina de estados (que puede ser jerárquica).
Entender esto cambia bastante la forma en que diseñamos, debuggeamos y operamos agentes.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.