From ReAct to Agent Harness: Engineering Interactive, Recoverable, and Controllable AI Agents
The article analyzes how Agent engineering evolves from a simple ReAct loop to a full‑featured Agent Harness, detailing the need for shared state schemas, runtime‑generated facts, and clear UI‑runtime boundaries to make model behavior interactive, recoverable, controllable, and traceable.
Agent Is More Than Model + Loop
Early Agent engineering can be expressed as pseudo‑code:
while not done:
reason
act
observeThe user inputs a sentence, the model thinks, decides to call a tool, receives the tool result, thinks again, and repeats. This is the basic ReAct pattern (Thought → Action → Observation). A demo showed the loop works for simple command‑line agents, but real products encounter many runtime issues:
After a page refresh, does the pending tool approval persist?
If the backend restarts mid‑execution, where does it resume?
How does the UI display the state of a background sub‑Agent?
Where is the reference, status, and ownership of a generated artifact stored?
When the user clicks stop, which operations are cancelled and which are kept?
ReAct does not answer these questions; it only explains the model’s thinking and acting.
ReAct’s Explanation Boundary
ReAct’s minimal unit is: Thought → Action → Observation In an engineering system these units become events, state, checkpoints, and controls. A single tool call expands into the following events:
run.started
message.created
assistant.text.delta
tool.call.created
tool.approval_required
tool.call.running
tool.call.completed
artifact.created
run.finishedObservation in ReAct is merely text fed back to the model. Product systems need richer facts so that the UI can show approval buttons, the backend can resume after a crash, and the artifact panel can locate results.
Where Facts Come From
Many open‑source agents simply let the ReAct loop run unchanged and add a UI adapter that reads messages, observations, and tool results to infer states such as isBusy or pendingApproval. This approach introduces facts only after the fact, making recovery and consistency difficult.
Instead, the runtime should emit facts at the moment they occur, e.g. tool.approval_required when a tool call needs approval, artifact.created with a stable reference when a file is generated, and control events that map user actions back to the runtime.
These facts must be part of the execution path:
tool call started → approval required → execution completed → artifact created → checkpoint writtenOnly then can the system reliably restore state after a refresh or restart.
From Loop to Protocol
A product‑ready Agent data flow looks like:
runtime event → agent.state → agent.view → UIand the reverse direction: user action → agent.control → runtime event Three key concepts are introduced:
state – the immutable facts produced by the runtime (e.g., messages, activeRun, checkpoint, pendingApproval, artifactRefs).
view – UI‑derived representations calculated from state (e.g., isBusy, canStop, approvalBanner).
control – commands issued by the UI that affect the runtime (e.g., invoke(input, stateSnapshot), resume(approvalDecision), stop(runId), updateState(patch), reload(threadId)).
The boundary rule is simple: the runtime writes facts; the UI only consumes them.
State Schema Defines Fact Boundary
Before building an Agent, design a state schema that answers:
Which facts must be recoverable?
Which facts must be consistent across front‑ and back‑ends?
Which facts are merely views?
Which facts belong to the user’s session?
Which facts can be derived from messages?
Which facts become first‑class state?
Example of a pending‑approval state:
agent.state.pendingApproval = {
id,
runId,
turnId,
toolCallId,
toolName,
arguments,
policy
}When the user approves:
agent.control.resume({
approvalId,
decision: "allow"
})Artifacts are stored as references in state rather than raw content:
artifactRef: {
id,
type,
title,
status,
ownerRunId,
createdByToolCallId,
contentRef
}Sub‑Agents also need full fact records (id, parentRunId, status, timestamps, resultRef, etc.) so the UI can render panels without guessing IDs from text.
UI Can Only Consume Facts, Not Write Them
The UI renders, interacts, and derives view state from agent.state. Facts such as pendingApproval, artifactRef, or activeRun must be written by the runtime. If the runtime omits a fact, the UI must not infer it from message text, otherwise consistency after a refresh or restart breaks.
Boundary rule (illustrated in the image below): runtime writes fact → UI consumes fact.
System Must Remember What Happened
Agent Harness guarantees that key runtime facts are persisted and can be audited:
After a page refresh, pendingApproval still points to the same tool call.
After a process restart, the system can resume from the checkpoint to the same run/turn.
When the user stops, activeRun terminates and no further tool calls are executed.
When an artifactRef exists, its content can be located and traced back to the owning run and tool call.
When a sub‑Agent finishes, the parent turn receives a resultRef for display.
These checks reduce the problem to a single question: does every runtime fact have a clear, persistent owner?
Conclusion
ReAct explains how the model decides step by step; Agent Harness turns those steps into trustworthy software facts that are interactive, recoverable, controllable, and auditable.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
