Construir el bucle de control agentico que enruta por stop_reason tipado con budget duro y handlers explicitos, no por prosa.
Construir el bucle de control de un agente que enruta por stop_reason tipado, con budget duro y handlers explícitos por señal, en lugar de inferir el control desde la prosa del modelo. El loop es la columna vertebral del agente: cada iteración decide entre despachar herramientas, detenerse o fallar fuerte. La capacidad es diseñar esa máquina de estados para que sea determinista, observable y acotada en producción. [DOC]
"done" in text) hacia control por señal estructurada.No usarla cuando (anti-scope): el problema es de calidad del prompt o tono (eso es prompt engineering), de diseño de la herramienta en sí (esquema de inputs, idempotencia), o de orquestación multi-agente / routing entre agentes. Esta skill cubre un único loop de un único agente. [INFERENCIA]
Inputs [DOC]
messages inicial (historial), catálogo de tools (schemas), mapa handlers {nombre → callable}.max_iterations y/o presupuesto de tokens.Outputs [DOC]
end_turn).UnhandledStop(stop_reason) / BudgetExceeded(limit) — nunca un retorno ambiguo.while True (o for ... in range(max_iterations)) y resuelve el control únicamente con el campo stop_reason tipado de la respuesta. [CÓDIGO]stop_reason == "tool_use": despacha cada bloque a su handler, recoge el resultado y reinyéctalo en el historial como mensaje user con tool_result; continúa. Procesa todos los bloques de la respuesta (puede haber tool calls en paralelo), no solo el primero. [CÓDIGO]stop_reason == "end_turn": detén y devuelve el resultado final (halt limpio). [CÓDIGO]raise UnhandledStop(stop_reason) — falla fuerte, nunca un halt silencioso. Esto incluye señales que SÍ existen pero que aún no manejas (max_tokens, pause_turn, refusal): explícitas o raise. [CÓDIGO]iterations contra max_iterations que dispara BudgetExceeded. Configurable, no constante mágica. [CÓDIGO]Decisión — for range() vs while True + contador. El patrón usa for iteration in range(max_iterations) porque hace el budget estructural: es imposible olvidar incrementar el contador y el techo duro es la línea tras el loop. Trade-off: si más adelante el budget pasa a ser por tokens (no por iteraciones), conviene while True con chequeo explícito de tokens consumidos. [INFERENCIA]
# GOOD: control enrutado por stop_reason tipado, budget duro, fallo fuerte
def run_agent_loop(client, messages, tools, handlers, max_iterations=20):
for iteration in range(max_iterations):
resp = client.messages.create(model=MODEL, messages=messages, tools=tools)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return resp # halt limpio
if resp.stop_reason == "tool_use":
tool_results = []
for block in resp.content:
if block.type == "tool_use":
handler = handlers[block.name] # KeyError = fallo fuerte
result = handler(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
continue
raise UnhandledStop(resp.stop_reason) # señal no contemplada
raise BudgetExceeded(max_iterations) # techo duro# ANTI: control por prosa -> halt silencioso o bucle infinito
while True:
resp = client.messages.create(model=MODEL, messages=messages)
text = resp.content[0].text
if "done" in text.lower(): # frágil: el modelo dice "not done" y sale
break
if "use tool" in text.lower(): # no hay budget, no hay tool_result tipado
run_some_tool()
# sin max_iterations: si nunca aparece "done", bucle infinitoAnti-patrones adicionales a vetar [INFERENCIA]:
resp.content[0] cuando hay varios tool_use → tool calls en paralelo perdidos y tool_result huérfanos que rompen el siguiente turno.tool_result con rol assistant o sin tool_use_id → el modelo no correlaciona la respuesta y diverge.try/except que traga la excepción del handler y devuelve "" → el modelo cree que la herramienta tuvo éxito vacío (failure mode peor que el crash).tool_use puede traer N bloques; recolecta los N resultados en UN mensaje user. [CÓDIGO]tool_result con is_error: true tipado) — nunca silencies. La política la decides explícitamente, no por omisión. [INFERENCIA]stop_reason == "max_tokens": la respuesta está truncada; trátalo como señal no-feliz (continuar pidiendo más, o raise), nunca como end_turn. [SUPUESTO — verificar contra el SDK del proveedor en uso]handlers[block.name] con KeyError es el fallo fuerte deseado; documenta que el catálogo de tools y el mapa handlers deben estar sincronizados. [CÓDIGO]assistant con tool_use debe quedar en el historial ANTES del user con tool_result; invertirlo rompe la conversación. [CÓDIGO]El loop está listo solo si TODO lo siguiente es cierto:
stop_reason (señal tipada), no en el texto del modelo. [CÓDIGO]raise UnhandledStop. [CÓDIGO]tool_use de cada respuesta (paralelo cubierto). [CÓDIGO]tool_result se reinyectan como mensaje user con el tool_use_id correcto. [CÓDIGO]max_iterations / tokens) que dispara BudgetExceeded. [CÓDIGO]Self-correction — si al revisar encuentras if ... in text, un while True sin techo, un except: pass alrededor de un handler, o un tool_result fuera del rol user, detente y corrige antes de declarar la skill aplicada. [INFERENCIA]
Estos artefactos son los entregables esperados del paquete; verifica su presencia con
lsantes de invocarlos (pueden no existir aún en un checkout parcial). [SUPUESTO]
assets/loop-contract.schema.json y assets/loop-policy.json ANTES de escribir código.scripts/compile-agentic-loop.py <contrato.json> --output <loop.py> --report <reporte.md> convierte el contrato en un esqueleto Python verificable.bash skills/agentic-loop-engineering/scripts/check.sh antes de marcar la skill como lista.tool_result fuera del rol user, o con señales desconocidas que no terminen en UnhandledStop. [DOC]katas-01.katas-deterministic-agent-loop, tool-result-injection, agent-budget-control. [DOC]JaviMontano/jm-adk-beta
fdad39c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.