The tool-use loop
- Send contextsystem prompt, tools, history
- Model respondstext, tool calls, or both
- Execute toolsyour code runs each one
- Append resultsone result per call, matched by id
tool calls → round again · no tool calls → final answer
The loop, and the two details that break it
Every agent is this cycle. Claude receives the prompt, the system prompt, the tool definitions and the history; it responds with text, one or more tool calls, or both; your harness executes them and feeds the results back; and the cycle repeats. Each full cycle is one turn. The Agent SDK does all of this for you and ends the loop when Claude produces a response with no tool calls.
Two details cause most hand-written loops to misbehave. The first is the stopping rule: a turn that contains friendly text can also contain a tool call, so the presence of text proves nothing — the API's stop_reason does. The second is bookkeeping: one response can request several tools, and every tool_use block needs its own tool_result carrying the same tool_use_id, appended after the assistant turn that asked for it. Handling stop reasons is the subject of 4.1, and the error side of tool results is covered there too.
Concurrency is a property of the tool, not of the loop. When Claude requests several calls in one turn, read-only tools such as Read, Glob and Grep can run at the same time, while tools that change state — Edit, Write, Bash — run one after another so they cannot collide. A custom tool is sequential by default and opts into parallel execution with the readOnlyHint annotation.
Subagents: a second desk in another room
A subagent is a separate agent instance with its own conversation. What matters for design is the boundary. A non-fork subagent starts with no parent history at all: it receives its own system prompt, the prompt string the Agent tool passed it, its tool definitions, and project memory files unless told to omit them. It does not receive the parent's conversation, tool results or system prompt. When it finishes, only its final message returns to the parent as the tool result.
What crosses the boundary
Goes in
- The subagent's own system prompt
- The prompt string from the
Agenttool - Its tool definitions, or the subset in
tools - Project
CLAUDE.md, unless omitted
Stays behind
- The parent's conversation and tool results
- The parent's system prompt
- Everything the subagent read while working
- Skill content it was not given
That asymmetry is what makes subagents a context-management technique. A subagent that reads forty files costs the parent one summary. It is also why vague delegation fails: a brief like “look into the outage” leaves the subagent without the file paths, error messages and decisions the parent already has. State the objective, the output format, which tools to use and where the task ends. The architecture side of this — managers, supervisors, when to delegate at all — is 1.1.
Memory: three different things with one name
Ask three developers what agent memory means and you get three answers, and all three appear on this exam.
| Kind | What it is | Lives |
|---|---|---|
| Session continuity | The conversation transcript, resumed or forked by session_id | On disk with the harness, or in a session store you supply |
| Memory files | Notes the agent writes and reads back — the API memory tool, or CLAUDE.md-style instruction files | Files, outside the conversation |
| External state | Plans, findings and artefacts written to storage, referenced by pointer | Your database, object store or filesystem |
Sessions are the cheapest of the three. The SDK writes the conversation to disk automatically; capture session_id from the result message and pass it back as resume to carry on with everything the agent already read and decided. fork_session / forkSession copies the history into a new session so you can try a second approach without losing the first, and the original keeps its own id. Be clear on the limit: a session persists the conversation, not the filesystem.
# session_id came from ResultMessage.session_id on an earlier run
async for message in query(
prompt="Instead of JWT, outline how OAuth2 would work here",
options=ClaudeAgentOptions(
resume=session_id, # start from that conversation
fork_session=True, # ...but branch: the original is untouched
max_turns=5,
),
):
if isinstance(message, ResultMessage):
forked_id = message.session_id # a new id, not the originalMemory files are for what must survive the window. Anthropic's research system writes the lead agent's plan to external storage precisely because context can be exceeded mid-run, and subagents store large outputs externally and pass lightweight references back rather than copying everything through the conversation. The API's memory tool formalises the same idea: Claude is warned as the clearing threshold approaches, writes what matters to memory files, and can read it back on demand.
Managing the window
The context window does not reset between turns. The system prompt, tool definitions, memory files, every prompt, every response, every tool input and every tool output accumulate until the session ends. Large tool outputs dominate: one verbose command or one big file can cost thousands of tokens in a single turn.
What is taking up the window
fixed at the top, growing at the bottom
- System promptsmall, every request
- Tool definitionsper tool; MCP schemas can be deferred
- Memory filesre-sent each request, prompt-cached
- Conversation historyprompts and responses
- Tool inputs and outputsgrows fastest by far
There are four levers, and a good design uses more than one. Compaction: when the window approaches its limit the SDK summarises older history automatically and emits a compact_boundary message. Context editing: on the API, the clear_tool_uses_20250919 strategy clears old tool results server-side once a trigger is passed, keeping the most recent few. Isolation: push wide reading into subagents. Restraint: fewer tools in the definition list, lower-cost tool outputs, and deferred MCP schemas.
response = client.beta.messages.create(
model=MODEL, max_tokens=4096, messages=messages,
tools=[{"type": "memory_20250818", "name": "memory"}],
betas=["context-management-2025-06-27"],
context_management={"edits": [{
"type": "clear_tool_uses_20250919",
"trigger": {"type": "input_tokens", "value": 30000}, # start clearing here
"keep": {"type": "tool_uses", "value": 3}, # keep the latest 3
"exclude_tools": ["web_search"], # never clear these
}]},
)Abstraction frameworks: how much should be hidden
An agentic framework packages the loop, the tool plumbing and often the prompts. The Agent SDK is one; several third-party libraries are others. The caution in “Building effective agents” is specific: frameworks add layers that obscure the underlying prompts and responses, which makes debugging harder, and they tempt you into complexity when a simpler pattern would do. The advice is to start with the API directly, understand what is being sent, and adopt a framework once you can predict what it will produce.
Is this abstraction earning its place?
- Passes: You can see the exact prompt and tools that reach the APIRequired for debugging and for cost work
- Passes: You can read the termination state of a runSuccess, limit hit, or error
- Passes: Guardrails run as code, not as instructionsHooks or permission rules
- Check: You know what it does when the window fillsCompaction, clearing, or silent truncation?
- Fails: It hides retries and errors behind a single callYou cannot tell a model failure from a transport failure
- Fails: Adopted because it was familiar, not because of a needThe named anti-pattern
Traps the wrong answers are built from
| Tempting but wrong | Do this instead |
|---|---|
| Ending the loop when the model's reply contains text | End when the response carries no tool calls — read the stop reason, not the prose. |
| Delegating with a one-line prompt and assuming the subagent can see the parent's work | Write the file paths, errors and decisions into the Agent tool prompt; nothing else crosses. |
| Putting a permanent rule in the opening user message | Put it in a memory file that is re-injected each request, and enforce it with a hook. |
| Letting raw tool output accumulate until the window fills | Clear old tool results at a trigger, isolate wide reading in subagents, and keep outputs small. |
| Adopting a framework for the loop before you have written one | Build against the API first so you can see the prompts the framework will later hide. |
You should now be able to
- Describe a tool-use loop in terms of turns, tool results and the condition that ends it.
- State exactly what a subagent inherits and what it does not, and brief one accordingly.
- Distinguish session continuity, memory files and external state, and pick the right one.
- Name what consumes the context window and apply compaction, context editing and isolation to control it.
- Judge whether an agentic framework is hiding detail you will need when something breaks.