Agentic loops for autonomous task execution
An agentic loop is plain control flow around the Messages API. You send the conversation plus tool definitions. Claude either asks for tools or finishes. Your code decides what to do by reading stop_reason, never by reading Claude's prose.
messages = [{"role": "user", "content": task}]
while True:
resp = client.messages.create(model=MODEL, max_tokens=4096,
system=SYSTEM, tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content}) # keep the full turn
if resp.stop_reason == "end_turn":
break # the model decided it is finished
if resp.stop_reason == "tool_use":
results = []
for block in resp.content:
if block.type == "tool_use":
out = run_tool(block.name, block.input)
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": out.text, "is_error": out.failed})
messages.append({"role": "user", "content": results}) # results go back as a user turn
continue
handle_other(resp) # e.g. max_tokens = output truncated; decide to continue or fail
Key ideas
- Tool results are appended to history. That is how the model "sees" what happened and reasons about the next action. Each
tool_resultmust reference the matchingtool_use_id. - Model-driven vs pre-configured. In an agent, Claude decides which tool to call next from context. A hard-coded decision tree or fixed tool sequence is a workflow, not an agent. Use the agent when the path depends on what is discovered; use fixed code when the path is known.
- A single response can contain text and several
tool_useblocks. Execute all of them before the next call.
- Stopping when the text says "I'm done" or "Task complete" (parsing natural language for termination).
- Using an iteration cap as the primary stop condition. A cap is fine as a safety net, not as the design.
- Treating "the response contains text" as completion. Claude often writes text alongside a tool call.
Your support agent sometimes ends the conversation mid-investigation. The loop exits whenever resp.content[0].type == "text". Claude had written "Let me check your order" and then requested lookup_order. Fix: branch on stop_reason only.