CCA-FWeak areasMid-loop redirect

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.

D1D4composed

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.

messages[]usertext · "fix the error in the payments service"assistanttext · "dumping the log"tool_useid: toolu_01A… · Bashstop_reason: "tool_use"must be answereduserone messagetool_result · tool_use_id: toolu_01A…required · first blocktext · "new goal: summarise failure patterns"the redirect · after the debt is paidhistory untouched — you appended to it, you did not replace it
The next 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.

Skips the debt
// 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 answered

The goal reached the model but the turn never validated. Nothing was preserved because nothing was accepted.

Pays it, then steers
// 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.

full dumpLegal and safe. But the goal changed — you are paying for a large log the agent no longer needs in full.
trimmed sliceAlso legal. Nothing requires the result to be complete: send the head and tail plus a one-line note on what was cut, and the redirect still lands.
is_error: trueWrong here. The Bash tool succeeded. Flagging an error invites the model to recover — it will try the dump again.
no block at allNot a choice. That is the 400.

04

Continuity is the message array, not a session id

“Without losing accumulated context” rules out every option that starts over.

movecontextwhen it is right
append to messages[]kept, exactthis case — the array is the session
new sessionlostcontext is stale or poisoned, not when the goal changes
--resume namekept, possibly stalepicking a prior investigation back up later
fork_sessionkept, branchedcomparing two approaches from one shared point
/compactkept, degradedthe 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

  1. A pending tool_use must be answered by a tool_result with the matching tool_use_id before any other content.
  2. Both live in one user message: tool_result blocks first, then the redirecting text block.
  3. There is no tool role — the result and the instruction compete for the same message slot, so put them in one content array.
  4. Parallel tool_use blocks need all their results in that single message.
  5. The result may be trimmed; it may not be absent, and it is not an error just because you stopped wanting it.
  6. is_error: true means the tool failed — it invites a retry, not an abandonment.
  7. Appending preserves context exactly. A new session preserves nothing; /compact preserves the gist and loses the specifics.