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.
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.
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. |
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.
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.