0Pricing
Claude Architect · Lección

Antipatrón: analizar texto para detectar la finalización

Por qué buscar 'done' en la salida es frágil e incorrecto.

Antipatrón: analizar texto para detectar la finalización es una lección gratuita de Claude Architect en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Claude Architect, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Claude Architect incluye 4 lecciones en total.

Partes de esta lección aún no han sido traducidas y se muestran en inglés.

The Trap

You build an agent. It runs tools, thinks, replies. Now you need to know when it's done. A tempting shortcut: scan the model's text for a word like done, finished, or complete, and stop the loop when you see it.

This is the anti-pattern Parsing Text for Completion. It feels intuitive, but it is fragile and wrong. The Claude API already hands you a precise, structured completion signal. In this lesson you'll learn why text-scanning breaks and what to use instead.

How the Agentic Loop Really Works

The agentic loop is driven by one field on the response: stop_reason. The loop is simple and deterministic:

  • Send the request (with the full message history every turn).
  • Inspect stop_reason.
  • If it is tool_use, run the tools, append the results to history, and repeat.
  • If it is end_turn, the task is complete. Stop.

The model itself tells you, through structured metadata, whether it wants to keep going or is finished. You never have to guess by reading prose.

while True:
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=16000,
        tools=tools,
        messages=messages,
    )
    if response.stop_reason == "end_turn":
        break  # complete
    # else: tool_use -> run tools, append results, loop

The Four Stop Reasons

Terminate on the signal, not on the words. Claude returns one of four stop reasons:

  • end_turn — Claude finished naturally. The task is complete.
  • tool_use — Claude wants a tool run; execute it and continue the loop.
  • max_tokens — the output was truncated by the token cap.
  • stop_sequence — a configured stop sequence was hit.

Each is unambiguous and machine-readable. Compare that to scanning free text, where the meaning depends entirely on how the model happened to phrase itself this time.

Why Text-Scanning Is Fragile

Natural language is not a control protocol. The same completed task can end a dozen different ways:

  • "All done!"
  • "That completes the migration."
  • "I've finished the analysis."
  • "Everything is in place now."

Your keyword check for done matches the first, maybe the second, and silently misses the rest. The loop never stops. You burn tokens and time, or hit a hard cap, on a task that was actually finished turns ago.

# ANTI-PATTERN: brittle keyword scan
if "done" in response_text.lower():
    break  # misses "finished", "complete", "all set"...

False Positives Are Worse

Missing the signal wastes resources. The opposite failure is more dangerous: stopping too early.

Suppose the user asks Claude to "check whether the deployment is done." Claude might reply, mid-investigation, "Let me verify the deploy is done before I continue." Your scanner sees done and kills the loop — abandoning the task halfway, before any tool ran or any answer was produced.

The model uses words like done conversationally. A keyword match cannot tell narration apart from a genuine completion signal. stop_reason can.

The Model Keeps No State

Here is the deeper reason text-scanning is the wrong layer. The Claude API is stateless: the model keeps no memory between requests. You send the entire conversation — system, every prior messages turn, tool results — on every single call.

So "is the agent done?" is a question about the loop you control, answered by the structured stop_reason the API returns — not something to be reverse-engineered from prose. The control plane (your loop) and the content plane (the model's text) are separate. Keep completion logic in the control plane.

# Stateless: full history goes up every turn
response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=16000,
    system=system_prompt,
    tools=tools,
    messages=messages,  # ENTIRE history, every call
)
messages.append({"role": "assistant", "content": response.content})

The Correct Termination Check

Drive the loop off stop_reason alone. When it is tool_use, run the requested tools and append their results to the history with the matching tool_use_id. When it is end_turn, you are done.

This works no matter how Claude phrases its final message — "All set," "Finished," or nothing at all. The decision is made by metadata you can trust, not by string matching you have to babysit.

while True:
    response = client.messages.create(
        model="claude-opus-4-8", max_tokens=16000,
        tools=tools, messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})

    if response.stop_reason == "tool_use":
        results = run_tools(response.content)  # each with tool_use_id
        messages.append({"role": "user", "content": results})
        continue
    if response.stop_reason == "end_turn":
        break  # done -- no text parsing anywhere

Decisions Are Model-Driven

A key architectural principle: in an agentic loop, the flow is decided by the model, surfaced through stop_reason. You don't dictate "after 3 tools, you must be finished." Claude signals when it needs another tool and when it's done.

Reserve hard-coded control for things that need a guarantee — a deterministic invariant your business requires. Completion detection is not one of those things; the API already provides it cleanly. Hand-rolling it with text parsing replaces a reliable signal with a guess.

Iteration Caps Are a Safety Net

"Fine," you say, "I'll just stop after N iterations." Careful: an iteration cap is a safety net, not the primary stop mechanism. It exists to bound runaway loops and protect against bugs — not to decide that work is complete.

If your loop relies on the cap to terminate normally, you have the same disease as text-scanning: you're substituting an arbitrary heuristic for the real, model-driven signal. Terminate on stop_reason first; let the cap catch only the pathological case.

MAX_ITERS = 20  # safety net, NOT the primary stop
for _ in range(MAX_ITERS):
    response = client.messages.create(...)
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason == "end_turn":
        break  # primary: model-driven
    # ... handle tool_use ...
else:
    log.warning("hit iteration cap -- investigate, don't trust as 'done'")

Handle the Other Stop Reasons

A robust loop branches on every stop reason, not just the happy path. In particular, max_tokens means the response was cut off — it is not a completion. Treating a truncated turn as "done" silently ships half an answer.

When you see max_tokens, the fix is to raise max_tokens or switch to streaming for large outputs, then continue — never to break out of the loop as if the task succeeded.

if response.stop_reason == "end_turn":
    break
elif response.stop_reason == "tool_use":
    messages.append({"role": "user", "content": run_tools(response.content)})
elif response.stop_reason == "max_tokens":
    # truncated, NOT complete -- raise cap or stream, then continue
    raise OutputTruncated("increase max_tokens or stream")

Need a Structured Done Flag? Use a Tool

Sometimes you genuinely want the model to report a structured outcome — a status, a confidence, a summary — when it finishes. The answer is still not to parse prose. Give Claude a tool (or a JSON Schema via structured outputs) and let it emit a typed result.

With tool_choice: "any" you can force the model to call some tool, guaranteeing structured output instead of free text. The loop still terminates on stop_reason; the tool just carries the machine-readable verdict. Structure replaces string-matching everywhere it matters.

tools = [{
    "name": "submit_result",
    "description": "Report the final task outcome.",
    "input_schema": {
        "type": "object",
        "properties": {
            "status": {"type": "string", "enum": ["success", "failed"]},
            "summary": {"type": "string"},
        },
        "required": ["status", "summary"],
    },
}]
# tool_choice={"type": "any"} forces a structured call, not prose

Quick Check

Test your judgment on a realistic design decision.

Key Takeaways

You now know why scanning output for completion is the wrong tool for the job:

  • Terminate on stop_reason, never on text. end_turn = complete; tool_use = run tools and continue.
  • Text-scanning is fragile both ways — it misses real completions (varied phrasing) and triggers false positives (conversational use of "done").
  • The API is stateless and model-driven. Send full history every turn; let the structured signal, not prose, drive the loop.
  • Iteration caps are a safety net, not the primary stop. Handle max_tokens as truncation, not success.
  • Need a structured outcome? Use a tool or JSON Schema (force it with tool_choice: "any") — never parse free text.

Preguntas frecuentes

¿La lección «Antipatrón: analizar texto para detectar la finalización» es gratis?

Sí — el texto completo de «Antipatrón: analizar texto para detectar la finalización» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Claude Architect, actualiza a CoddyKit PRO. El curso de Claude Architect incluye 4 lecciones en total.

¿Qué aprenderé en «Antipatrón: analizar texto para detectar la finalización»?

Por qué buscar 'done' en la salida es frágil e incorrecto. Practicas Claude Architect con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Claude Architect?

No se requiere experiencia previa. Claude Architect en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Antipatrón: analizar texto para detectar la finalización»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Claude Architect?

Sí. Cada lección de Claude Architect incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. El bucle principal
  2. Finalización mediante stop_reason
  3. Antipatrón: analizar texto para detectar la finalización
  4. Antipatrón: límites arbitrarios de iteraciones
← Volver a Claude Architect