The short answer is that the agent is not living inside a simple linear tool loop. It lives inside a larger event landscape: an append-only timeline, an uncached operational tail, a rolling sources pool, subsystem/widget delivery, distributed isolated execution, and explicit workspace activation. Once those requirements are real, pure tool calling stops being the whole shape of the problem.

Important nuance: this is not an anti-tool-calling position. React is still a tool-using agent. The design choice is specifically about not making provider-native tool calling the primary orchestration protocol for the whole system.

1. The context is not just assistant actions

Native tool-calling transcripts are good at representing a very specific causal chain: the assistant decides, the tool runs, the result comes back, the assistant continues. But React sees more than that.

The timeline can contain user prompts, attachments, plan updates, workspace notices, cache warnings, steer events, feedback, source updates, and service alerts. Some of these are caused by the agent. Some are caused by runtime or external systems. Some are caused by the operator in the middle of the turn. All of them can matter.

React sees a shared event landscape, not only tool-caused actions Four contributor cards on the left (user/operator, React agent, runtime/system, other contributors) send arrows into a shared rendered timeline on the right. The timeline contains seven event types arranged temporally. The tool-call-plus-result block is highlighted as the only native pair. A note at the bottom calls this out explicitly. Shared event landscape React must interpret A provider-native tool-call transcript captures only one of these event types cleanly. React must reason over all of them. User / operator prompts, attachments, steer React agent notes, tool calls, completion Runtime / system ANNOUNCE, pruning, workspace Other contributors sources pool, feedback, alerts Rendered timeline + ANNOUNCE tail The agent sees a temporal composition of many event sources, not only tool invocations. user prompt + attachment pruning notice sources pool update steer / follow-up tool call + tool result ← only native pair → workspace publish assistant completion sees all of above ANNOUNCE tail uncached Only one block above is a native tool-call/result pair. All the others still land on the timeline and shape the agent’s next decision.
Provider-native tool calling cleanly captures only one event type. React has to reason over a shared event landscape where runtime notices, source updates, steer, and publish events are visible alongside tool-caused events.
The model is not only reacting to the consequences of its own last tool call. It is reacting to the current state of the solution environment.

That is why the main shared state is the rendered timeline rather than a provider-managed transcript of tool calls.

2. ANNOUNCE is not a normal message

React relies on an uncached operational tail called ANNOUNCE. This is where runtime places high-frequency state that should grab model attention immediately:

  • authoritative temporal context
  • open plan state
  • workspace status
  • compaction and pruning warnings
  • fresh operational notices and mitigation hints

That surface exists because the system wants a place for information that is cheap to refresh, cheap to place at the tail, and deliberately outside the normal cache story. You can emulate pieces of that with ordinary messages, but you do not get the same explicit operational board semantics.

3. One generation has multiple consumers

React does not generate one flat response and then hope downstream consumers can peel it apart. It uses channeled generation. A single round can produce:

<channel:thinking>brief user-visible progress</channel:thinking>
<channel:ReactDecisionOutV2>validated decision JSON</channel:ReactDecisionOutV2>
<channel:code>raw executable code</channel:code>

These outputs are not interchangeable. The UI wants the progress text immediately. The runtime wants the structured decision for validation and tool dispatch. The execution subsystem wants raw code without having to recover it from a JSON argument string.

Layer Main consumer Why it is separate
thinking Operator-facing stream Immediate short progress without exposing tool or code internals.
ReactDecisionOutV2 Runtime validator Strict structured decision contract for tool execution.
code Exec subsystem / code widget Raw code must be streamed, validated, and packaged independently.
Pure tool-calling transcript versus React timeline-first plus channeled output Two side-by-side columns. Left: a linear provider-native transcript starting with system and user messages, through assistant, tool call, tool result, and a final assistant answer. Right: React with a single engineered input surface (timeline plus sources pool plus ANNOUNCE), three output channels (thinking, ReactDecisionOutV2, code), and three downstream consumers (user progress, runtime validation/widgets, isolated exec). Same problem, different orchestration surfaces Left: provider-native transcript. Right: React timeline-first input with multi-channel, multi-consumer output. Pure provider-native tool-calling view Strong for one causal chain. Hard to extend for runtime signals. system message protocol + tool catalog user message prompt or query assistant message decides the next action tool call structured function args tool result returned to assistant assistant answer resolves the turn Awkward to represent: • uncached ANNOUNCE board • pruning / compaction notices • sources pool refreshes • steer events mid-turn • separate code stream • subsystem/widget deltas • non-assistant-caused events • multi-consumer routing React timeline-first view Timeline-first input. Multi-channel, multi-consumer output. Input surface • cached system instruction • rendered human message: timeline · sources pool · ANNOUNCE tail timeline-first — not a provider-owned transcript decision round → Output channels thinking user progress ReactDecisionOutV2 structured action code raw code stream Downstream consumers user progress stream runtime validation tool step / widgets exec widget isolated runtime runtime validates, routes, packages, executes SDK-owned orchestration — not provider-owned React is not "one assistant reply + maybe a tool call". It is timeline-first on input, channelized on output, and multi-consumer in delivery.
Pure provider-native tool calling gives one main transcript. React keeps tool use, but moves orchestration into a timeline-first input and multi-channel output contract owned by the SDK runtime.

Provider-native tool calling is good at one of these layers. React needs all three at once.

4. Widgets and subsystem delivery matter during the round

This is one of the practical reasons the argument should not stay abstract. When React starts an execution workflow, the platform can surface a widget-like panel on the client before the turn is finished. The client may receive:

  • thinking progress
  • structured subsystem payloads
  • code stream chunks
  • later status updates like preparing, executing, completed, or error

That lifecycle is implemented through the communicator and subsystem/canvas delivery patterns. The tool call alone is only one moment in a larger streamed interaction contract.

5. Distributed execution changes the workspace story

React is not designed as a machine-bound personal agent. A turn can start on a node that has no warm workspace for that user and conversation. That pushes the system toward explicit logical namespaces and runtime-owned workspace hydration.

The workspace rule is simple: the agent should not hallucinate local continuity. If it needs historical project state, it should activate exactly the slice it needs.

That is why the system uses explicit logical paths like fi:, ar:, so:, and tc:, and why historical workspace access is mediated through react.pull(...) rather than by pretending every turn begins with a full project tree already mounted.

6. The real choice is SDK-owned orchestration vs provider-owned orchestration

It is tempting to frame the question as “tool-calling models versus non-tool-calling models,” but that is not actually the interesting split here. The real split is:

provider-native orchestration
vs
SDK-owned orchestration

React chooses the second one. The reason is portability and control. The same timeline model, ANNOUNCE semantics, workspace rules, and channeled streaming contract should survive model-provider changes instead of being rebuilt around whichever tool-calling surface one backend happens to expose.

7. This choice has real costs

This is not free elegance. We accept several forms of engineering cost:

  • we own protocol parsing and validation
  • we own channel parsing and delivery
  • we own more runtime complexity around execution and workspace state
  • we own more documentation burden
  • we own more prompt discipline

The trade is worthwhile only because the runtime requirements are already larger than a simple message-level tool loop.

Conclusion

Pure tool calling is often a good fit when the world is mostly linear and the important state transitions are dominated by assistant-invoked functions. React v2 is working in a different shape of system: timeline-first, announce-aware, multi-channel, distributed, and workspace-governed.

Once those constraints are real, the question is no longer “why not just use tool calling?” The question becomes: what is the narrowest protocol that still preserves the semantics of the system we actually built? For React, the answer is a custom one.