DEV Community

Xavier Gutiérrez
Xavier Gutiérrez

Posted on

Agentes 101 - 02: El ciclo del agente como State Machine (y Hierarchical State Machines)

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:

  1. Recibe una tarea
  2. Razona sobre qué hacer
  3. Decide si necesita usar una herramienta
  4. Ejecuta la herramienta
  5. Observa el resultado
  6. 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:

  1. Salir del estado thinking
  2. Entrar al estado acting
  3. Ejecutar una acción
  4. Recibir una observación
  5. Actualizar el estado
  6. 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.

Top comments (0)