0Pricing
Claude Architect · Ders

stop_reason ile Sonlandırma

Döngüyü end_turn sonlandırsın; dize eşleştirmesine güvenmeyin.

stop_reason ile Sonlandırma, CoddyKit'te ücretsiz bir Claude Architect dersidir. Bu, 4 dersinin 2. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Claude Architect öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Claude Architect kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

The Loop Needs an Exit

An agentic loop is simple: you send a request, Claude responds, and you decide whether to keep going. The hard question is when to stop.

Every API response carries a stop_reason field. This is the model's own signal about why it stopped generating. Your loop should listen to that signal — not guess by reading the words in the reply.

This lesson teaches one rule that separates robust agents from fragile ones: let end_turn end the loop, not your string matching.

The Four Stop Reasons

Claude returns one of four stop_reason values on every turn:

  • end_turn — the model finished its response naturally. The task turn is complete.
  • tool_use — the model wants to call a tool. Run it, append the result, and continue.
  • max_tokens — output was truncated by your max_tokens limit.
  • stop_sequence — a custom stop sequence you configured was hit.

These four values are a complete, reliable contract. Your control flow should branch on them directly.

resp = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)
print(resp.stop_reason)  # end_turn | tool_use | max_tokens | stop_sequence

The Anti-Pattern: Parsing Text

A tempting shortcut is to read the model's text and look for a keyword like "done" or "finished" to decide the loop is over.

This is a classic anti-pattern. Text is probabilistic. The model might say "I'm done thinking, now let me call a tool" — and your matcher stops too early. Or it phrases completion differently and your loop runs forever.

Never parse text for completion signals. The structured stop_reason exists precisely so you don't have to.

# ANTI-PATTERN: do NOT do this
text = resp.content[0].text
if "done" in text.lower():
    break  # fragile, unreliable, exam-wrong

The Canonical Loop

Here is the correct shape of the loop. You inspect stop_reason each turn. When it is tool_use, you run the tools and append their results to the conversation. You repeat until stop_reason is end_turn.

Notice the loop is driven entirely by the structured signal — no text inspection decides termination.

while True:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason == "end_turn":
        break

    if resp.stop_reason == "tool_use":
        results = run_tools(resp.content)
        messages.append({"role": "user", "content": results})
        continue

Full History, Every Turn

One reason the loop works: the model keeps no state between requests. Each API call must include the FULL messages history.

That is why, after a tool_use turn, you append the assistant's tool-call content AND the tool results back into messages before the next request. The model re-reads the entire conversation and decides whether more tools are needed or it can finish with end_turn.

Drop history, and the model loses the thread — it can't reach a coherent end_turn.

# Each request resends EVERYTHING
messages = [
    {"role": "user",      "content": "Refund order 4471."},
    {"role": "assistant", "content": [tool_use_block]},   # prior turn
    {"role": "user",      "content": [tool_result_block]}, # prior turn
]
resp = client.messages.create(model=MODEL, max_tokens=1024,
                              tools=tools, messages=messages)

tool_use Is Not a Stop

A common mistake is treating tool_use as a terminal state. It is not. It means "pause, run this tool, then come back to me."

When you see tool_use, you:

  • Execute the requested tool(s) in your own code.
  • Append the tool_result blocks to messages.
  • Send the request again so the model can continue.

Only end_turn means the work for this turn is genuinely finished.

if resp.stop_reason == "tool_use":
    tool_results = []
    for block in resp.content:
        if block.type == "tool_use":
            output = dispatch(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": output,
            })
    messages.append({"role": "user", "content": tool_results})
    # loop continues -> next request

Decisions Are Model-Driven

The deeper principle: let the model decide when it is done. The model has the full context — the user's goal, the tool results, the conversation so far. It is better positioned than a hard-coded rule to judge whether the task is complete.

Your job as the architect is to provide good tools and clear instructions, then trust the end_turn signal. Reserve hard-coded control flow for guarantees you cannot leave to probability.

The Iteration Cap Is a Safety Net

You should still add a maximum-iteration guard — but understand its role. An iteration cap is a safety net to prevent runaway loops or cost blowouts. It is NOT the primary stop mechanism.

The primary stop is always end_turn. The cap only fires in the rare pathological case where the model never converges. If your loop relies on the cap to end normally, your design is broken.

MAX_ITERS = 20  # safety net, NOT the normal exit
for i in range(MAX_ITERS):
    resp = client.messages.create(model=MODEL, max_tokens=1024,
                                  tools=tools, messages=messages)
    messages.append({"role": "assistant", "content": resp.content})
    if resp.stop_reason == "end_turn":
        break  # normal exit
    # ... handle tool_use ...
else:
    log.warning("Hit iteration cap without end_turn")

Handling max_tokens

max_tokens is a distinct case that needs its own handling. It means the response was truncated mid-generation — the model did not finish its thought.

Treating this as a clean completion would silently cut off the agent's work. Depending on your design you might raise max_tokens, ask the model to continue, or flag the turn. What you must NOT do is fall through and assume the task is done.

if resp.stop_reason == "max_tokens":
    # output was cut off - NOT a completion
    log.warning("Response truncated; consider raising max_tokens or continuing")
    # handle explicitly; do not treat as end_turn

Where Hard Code Belongs

If model-driven decisions are the default, when do you reach for deterministic code?

For guarantees — outcomes that must hold every single time regardless of the model's judgment. Examples: a precondition that blocks a refund until get_customer returns a verified ID, or a hook that rejects any refund over a policy threshold.

These are 100% deterministic enforcement points. Termination, by contrast, is a model-driven decision you read from stop_reason. Don't confuse the two: hard-code guarantees, trust end_turn for flow.

Putting It Together

A production-grade agentic loop combines all the pieces:

  • Branch on stop_reason — never on text.
  • Resend full history each turn (the model is stateless).
  • tool_use → run, append, continue. end_turn → stop.
  • Handle max_tokens explicitly — truncation is not completion.
  • Keep an iteration cap as a safety net only.

This is the backbone of every reliable agent you'll architect.

for _ in range(MAX_ITERS):
    resp = client.messages.create(model=MODEL, max_tokens=2048,
                                  tools=tools, messages=messages)
    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason == "end_turn":
        break
    if resp.stop_reason == "max_tokens":
        handle_truncation(resp); break
    if resp.stop_reason == "tool_use":
        messages.append({"role": "user",
                         "content": run_tools(resp.content)})
        continue

Quick Check: Ending the Loop

A support agent built on the Agent SDK sometimes loops forever and sometimes stops before calling a needed tool. The loop currently breaks when the assistant's text contains the word "resolved". What is the correct fix?

Recap: Trust the Signal

Key takeaways:

  • Terminate on stop_reason, never by parsing text for words like "done" or "resolved".
  • end_turn ends the loop; tool_use means run tools, append results, and continue.
  • Handle max_tokens explicitly — truncation is not completion.
  • The model is stateless: resend the full message history every turn.
  • Termination is a model-driven decision; the iteration cap is only a safety net.
  • Reserve hard-coded enforcement for guarantees (preconditions, hooks), not for ending the loop.

Let end_turn end the loop. That single discipline makes your agents predictable and production-ready.

Sıkça Sorulan Sorular

“stop_reason ile Sonlandırma” dersi ücretsiz mi?

Evet — “stop_reason ile Sonlandırma” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Claude Architect kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Claude Architect kursu toplamda 4 dersten oluşur.

“stop_reason ile Sonlandırma” dersinde ne öğreneceğim?

Döngüyü end_turn sonlandırsın; dize eşleştirmesine güvenmeyin. Claude Architect ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Claude Architect öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Claude Architect, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 2. dersidir.

“stop_reason ile Sonlandırma” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Claude Architect dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Claude Architect dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Temel Döngü
  2. stop_reason ile Sonlandırma
  3. Karşıt Örüntü: Tamamlanmayı Metin Ayrıştırarak Algılama
  4. Karşıt Örüntü: Keyfî Yineleme Sınırları
← Claude Architect Sayfasına Dön