Weak area
Mid-loop redirect
Changing an agent’s goal while a tool_use is still awaiting its result — without a 400 and without dropping the session.
Surfaced by
Your Agent SDK assistant just ran a Bash tool to dump a large service log, and the tool_use is awaiting its result. Before you send it, the PM changes the goal: instead of fixing the error, they want a summary of failure patterns. You must redirect the agent without losing the session’s accumulated context. What do you send?
The rule that decides it
A pending tool_use is a debt: the next user turn must open with its tool_result. The redirect is a text block appended after it, in the same message.
01
A pending tool_use is a debt, not a pause
A stop_reason of tool_use leaves an open block. Nothing else may be sent until it is closed.
user turn must open with a tool_result carrying the matching tool_use_id. A text block can follow it in the same message — it cannot replace it.Tool results are tool_result blocks inside a user turn — there is no tool role. So "send the redirect as a user message" and "send the tool result" are the same message slot, competing for it. They do not have to compete: a content array holds both.
02
Answer, then redirect — in one message
The order inside the array is the whole answer. tool_result blocks first, text after.
// two separate messages
{ role: "user",
content: "Actually — forget the fix.
Summarise the failure patterns." }
→ 400 invalid_request_error
the assistant turn's tool_use block
was never answeredThe goal reached the model but the turn never validated. Nothing was preserved because nothing was accepted.
// one message, two blocks, in this order
{ role: "user", content: [
{ type: "tool_result",
tool_use_id: "toolu_01A…",
content: "<log — full or trimmed>" },
{ type: "text",
text: "New goal: stop debugging.
Summarise the failure patterns
you see in that log." },
] }One turn closes the loop and changes the objective. The model reads both blocks together and pivots on its next turn.
If the assistant emitted several tool_use blocks in parallel, every matching tool_result goes in this same message. Splitting them across messages teaches the model to stop calling tools in parallel.
Beyond the exam: on Claude Opus 5 / 4.8 and Fable 5 you can instead append a{ role: "system" } message to messages[] after thetool_result turn. It carries operator authority rather than user authority and leaves the cached prefix intact. Not supported on Sonnet 5 — a 400 there.
03
The result you send is not the result you wanted
A tool_result is mandatory. Its contents are yours to choose.
04
Continuity is the message array, not a session id
“Without losing accumulated context” rules out every option that starts over.
| move | context | when it is right |
|---|---|---|
append to messages[] | kept, exact | this case — the array is the session |
| new session | lost | context is stale or poisoned, not when the goal changes |
--resume name | kept, possibly stale | picking a prior investigation back up later |
fork_session | kept, branched | comparing two approaches from one shared point |
/compact | kept, degraded | the window is full — costs you exact numbers and dates |
The distractor that tempts here is start fresh — real advice, but forstale context, where old tool reads no longer match the repo. A goal change does not make the accumulated investigation stale. It makes it the thing you are trying to keep.
Recall in 60 seconds
- A pending
tool_usemust be answered by atool_resultwith the matchingtool_use_idbefore any other content. - Both live in one
usermessage:tool_resultblocks first, then the redirectingtextblock. - There is no
toolrole — the result and the instruction compete for the same message slot, so put them in one content array. - Parallel
tool_useblocks need all their results in that single message. - The result may be trimmed; it may not be absent, and it is not an error just because you stopped wanting it.
is_error: truemeans the tool failed — it invites a retry, not an abandonment.- Appending preserves context exactly. A new session preserves nothing;
/compactpreserves the gist and loses the specifics.