Sep 10One Agent, Three Surfaces

Why AI Agents Break Request-Response

The request-response model assumes a client asks, a server answers within milliseconds, and the exchange ends. AI agents violate every clause: they run for minutes, produce output continuously, accept input mid-run, outlive dropped connections, and delegate to tools and sub-agents whose activity must reach the user. Connecting an agent to a user-facing application therefore requires a long-lived, bi-directional, event-based session, not a single round trip.

Request-response

Agent session

One round trip and the exchange is over. An agent session streams output, takes steering input mid-run, and recovers from a dropped connection.

Nearly every layer of internet infrastructure assumes the same shape of conversation: a client asks, a server answers, the exchange ends. Agents do not have that shape. Understanding exactly where the mismatch lies explains most of the difficulty in agent-user connectivity, and why the fix is a protocol rather than a workaround.

The contract the web was built on

HTTP’s contract is simple: open a connection, send one request, receive one response, done. The exchange is short, stateless, and initiated entirely by the client. Three decades of infrastructure are tuned to that contract. Load balancers and proxies kill connections that idle too long. Serverless platforms cap execution time. Retry logic assumes a failed request can be safely re-sent. Caches assume the same request yields the same response. None of these are bugs; they are optimizations for the shape of traffic the web actually carried. Agents break that shape in five distinct ways.

Break one: agents are long-running

A web server answers in milliseconds. An agent works for seconds, minutes, sometimes longer: it plans, calls tools, evaluates results, and iterates. Holding a request open for the duration fails twice over. Intermediate infrastructure times the connection out, and even when it survives, the user stares at a spinner with no idea whether the agent is making progress or stuck.

The output of an agent is not one response. It is a sequence: reasoning, partial text, tool activity, results, produced continuously. The user needs that sequence as it happens, which means the server must push. Chunked responses and server-sent events (SSE) deliver exactly this, and streaming is genuinely part of the answer. But it repairs only the response half of the contract.

Break two: agents are steerable mid-run

In request-response, the client’s participation ends the moment the request is sent. Agents invert this: the most valuable user input often arrives while the run is in flight. A user watches the agent head down the wrong path and corrects it. The agent reaches a consequential action and pauses for human-in-the-loop approval. The user answers a clarifying question, or cancels a branch of work that is no longer needed.

Every one of these is input into a running exchange, and the classic model has no channel for it. Streaming does not help here; SSE is strictly server-to-client. Agent steering requires the connection to be bi-directional at the semantic level: the user-facing application must be able to inject messages, approvals, and corrections into a run that is already executing.

Break three: connections fail before runs finish

The longer an exchange lasts, the more likely the connection dies before it ends. A laptop lid closes, a phone switches from Wi-Fi to cellular, a tab reloads. Request-response has a standard remedy: retry. That works because the exchange is short and stateless. For an agent twenty minutes into a run, retry means losing twenty minutes of work.

The session must therefore outlive the connection. The run keeps executing server-side, identified by thread and run identifiers rather than by the socket that started it. On reconnect, the client receives a snapshot of the current state and message history, then resumes consuming incremental updates. Transport-level tricks like SSE’s Last-Event-ID header replay missed bytes, but semantic resumption, rejoining a run and recovering its state, has to be designed into the layer above the transport.

Break four: agents are recursive

An agent is not a single endpoint. It calls tools, and it delegates to sub-agents, which call tools of their own. Connecting a user to an agent means connecting the user to that whole tree, because the activity inside it is what the user most needs to see: which tool is running, with what arguments, what it returned, which sub-agent owns the current step.

A single response body cannot represent a tree of concurrent, nested activity. What can represent it is a stream of typed events with explicit lifecycle boundaries: runs that start and finish, steps within them, tool calls that open, stream arguments, and close with results. Structure in the wire format is what lets the interface render the agent’s work coherently instead of as an undifferentiated wall of text.

Break five: the exchange mixes structured and unstructured data

A request-response exchange carries one payload with one content type. An agent session interleaves several kinds of data at once: unstructured text and voice tokens, structured tool calls and their results, state updates that keep the application and the agent synchronized, and generative UI payloads that render as live interface rather than prose. State alone needs two representations: full snapshots for (re)establishing ground truth, and compact deltas (JSON Patch operations) for keeping it current without resending everything.

Cramming these into a text stream forces every team to invent private framing conventions, and every client to parse them back out. The data is heterogeneous; the wire format has to be typed.

What the fix looks like

Each break points at the same conclusion. The unit of agent-user communication is not a response; it is an event, and the connection is a long-lived, bi-directional session that carries a stream of them. The event vocabulary follows directly from the five breaks: lifecycle events for runs and steps, start-content-end sequences for streamed messages, tool call events that carry arguments and results, state snapshots and deltas, and an input path for messages, approvals, and corrections flowing back into the run.

This is the layer the AG-UI protocol (the Agent-User Interaction protocol) standardizes: a common event vocabulary between any agent backend and any user-facing application, transport-agnostic across SSE, WebSockets, and webhooks, with runs addressed by thread and run identifiers so sessions survive reconnects. It completes the protocol triad: MCP connects agents to tools, A2A connects agents to agents, AG-UI connects agents to users.

Why bolting a chat window onto HTTP fails

The five breaks are not theoretical; they arrive on a schedule. A team ships a chat endpoint that returns a completion. Users complain about the wait, so streaming gets added. Then the agent gains tools, and the team invents ad hoc JSON frames inside the text stream to display them. Then an approval step needs input mid-run, so a side channel appears, with its own message types. Then a user closes a laptop mid-run, and the team discovers none of this reconnects. Each patch is rational; the sum is a private, brittle protocol that one team maintains forever and no other client can speak.

The user-visible symptom is the bolted-on feeling: a chat window that cannot see the application’s state, cannot update its UI, and goes blank when the network blips. The engineering symptom is the rewrite. Solving the connectivity layer once, as a standard protocol with open implementations (this is where infrastructure like CopilotKit sits), is what turns an agent from a demo behind an endpoint into a feature of the product.

FAQs

Can't I just use WebSockets?
A transport is necessary but not sufficient. WebSockets give you a bi-directional byte stream; they say nothing about what flows through it. You still need a shared event vocabulary: run lifecycle, streamed message chunks, tool calls, state snapshots and deltas, and resumption semantics. That vocabulary is the protocol layer. AG-UI defines it and is transport-agnostic, so it runs over SSE, WebSockets, or webhooks.
Doesn't streaming solve the problem?
Streaming solves exactly one of the five breaks: delivering output as it is produced. A streamed HTTP response is still one-way, still untyped text unless you invent framing, still offers no channel for input mid-run, and still loses everything when the connection drops. Streaming is a piece of the answer, not the answer.
Is this why chatbots feel bolted on?
Yes. A chat window wired to a request-response endpoint can only exchange text. It cannot see application state, render tool activity, update the surrounding UI, or pause for an approval mid-run, so it behaves like a separate application embedded in the product rather than a part of it.
Does request-response go away entirely?
No. Inside an agent session, individual operations remain request-response: a tool call, a database query, a CRUD endpoint. Those are short, stateless exchanges, and the model fits them as well as it ever did. What changes is the user-facing session that wraps them, which becomes a long-lived, bi-directional event stream.
How does an agent session survive a dropped connection?
By separating the session from the connection. The run continues executing server-side, and its identity lives in thread and run identifiers rather than in the socket. When the client reconnects, it receives snapshots of the current state and message history, then incremental deltas resume. The user picks up mid-run instead of starting over.

Related concepts

Talk to an engineer about agent-user connectivity

Bring your use case. A CopilotKit engineer will walk you through how this applies to what you are building.