Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia
Dlaczego wyszukiwanie w danych wyjściowych słowa „done” jest zawodne i błędne
Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia to bezpłatna lekcja Claude Architect na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Claude Architect, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Claude Architect zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
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.
Często zadawane pytania
Czy lekcja „Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia” jest bezpłatna?
Tak — pełny tekst „Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Claude Architect, przejdź na CoddyKit PRO. Kurs Claude Architect zawiera 4 lekcji w sumie.
Co nauczysz się w „Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia”?
Dlaczego wyszukiwanie w danych wyjściowych słowa „done” jest zawodne i błędne Ćwiczysz Claude Architect z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Claude Architect?
Nie wymagamy żadnego doświadczenia. Claude Architect w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Claude Architect?
Tak. Każda lekcja Claude Architect zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Główna pętla
- Kończenie na podstawie stop_reason
- Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia
- Antywzorzec: arbitralne limity iteracji