Antipadrão: Analisar Texto para Detectar Conclusão
Por que procurar 'done' na saída é frágil e incorreto.
Antipadrão: Analisar Texto para Detectar Conclusão é uma aula grátis de Claude Architect no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Claude Architect, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Claude Architect inclui 4 aulas no total.
Partes desta aula ainda não foram traduzidas e aparecem em 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, loopThe 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 anywhereDecisions 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 proseQuick 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_tokensas truncation, not success. - Need a structured outcome? Use a tool or JSON Schema (force it with
tool_choice: "any") — never parse free text.
Perguntas Frequentes
A aula “Antipadrão: Analisar Texto para Detectar Conclusão” é grátis?
Sim — o texto completo de “Antipadrão: Analisar Texto para Detectar Conclusão” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Claude Architect, atualize para CoddyKit PRO. O curso de Claude Architect inclui 4 aulas no total.
O que vou aprender em “Antipadrão: Analisar Texto para Detectar Conclusão”?
Por que procurar 'done' na saída é frágil e incorreto. Você pratica Claude Architect com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Claude Architect?
Nenhuma experiência prévia é necessária. Claude Architect no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Antipadrão: Analisar Texto para Detectar Conclusão”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Claude Architect?
Sim. Cada aula de Claude Architect inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- O Ciclo Principal
- Encerrando em stop_reason
- Antipadrão: Analisar Texto para Detectar Conclusão
- Antipadrão: Limites Arbitrários de Iteração