When an AI-based audit tool takes several minutes to analyze a smart contract, the user should not have to guess whether the process is still alive or hung. The real experience with a spinner that shows no progress proves that, after about ninety seconds, most users refresh the page and lose the work already done. This problem led to redesigning the communication flow between backend and browser using Server-Sent Events (SSE), a technology that fits perfectly with long, unidirectional jobs but requires careful design beyond basic tutorials.
The choice of SSE over WebSockets is not trivial. In a scenario where the client only needs to listen after sending a request (upload a contract and receive results), SSE offers exactly what is needed: a persistent HTTP connection from server to client, automatic reconnection via the browser's EventSource object, and zero socket management. WebSockets would add bidirectional complexity that is rarely used. Therefore, for long AI jobs where communication is mostly downstream, SSE is the lighter and more reliable option.
The first lesson was the importance of designing an event vocabulary before writing any code. In an initial attempt, the backend emitted whatever it felt like: model tokens, log lines, half-formed thoughts. The frontend became a parser for an undocumented format that changed with every backend modification. The solution was to define a fixed set of event types, treated as an API contract. Four types suffice: progress, partial finding, completion, and error. Each has a clear structure: progress must indicate semantic steps, not abstract percentages. Showing 'Analyzing withdraw() function, step 3 of 7' keeps the user on screen during minute four. A bar moving from 41% to 43% does not, and honest percentages are impossible when you don't know how long each model call takes.
The partial finding event is what truly changes the perception of waiting time. Instead of waiting for the entire analysis to finish, the backend sends each finding as soon as it is validated. The user starts reading the first result while the model continues working on the rest. Total time does not decrease, but perceived wait collapses. If your long job produces incremental results, streaming them as soon as they are ready outperforms any progress bar. But it is crucial that those findings are only sent after passing server-side schema validation; never stream garbage to the frontend. Validating before emitting prevents model errors from reaching the interface.
Error handling must also be explicit. A recoverable error (e.g., a failed model call that is retried) should notify the user without stopping the job. A fatal error (budget exhausted, malformed input) should stop the process. The error event must clearly indicate whether it is recoverable or not, so the frontend acts accordingly: show a warning message or redirect to an error screen.
The biggest technical challenge is reconnections. Laptops sleep, networks change, proxies kill idle connections. In jobs lasting several minutes, disconnection is almost certain. EventSource automatically reconnects by sending the Last-Event-ID header with the last event received. But that header is only useful if the server is prepared. For that, each event must have a monotonically increasing ID, and the server must maintain a replayable log of events per job, independent of any connection. This forces an architecture where the job does not live inside the HTTP request. The audit runs in a durable process (a worker, a queue consumer, a container) that writes events to shared storage. The SSE endpoint is just a cursor over that log: it replays events the client missed and then follows live. If the client disconnects at event 12 and reconnects, it receives from event 13 to the current state, losing nothing.
A heartbeat in the form of an SSE comment (lines starting with two colons) keeps the connection alive during long model silences, and the Cache-Control: no-transform header prevents well-meaning proxies from buffering the stream.
Serverless environments present an additional difficulty. A Lambda function or similar that dies when it reaches its time limit cannot host a four-minute job. Keeping the function alive via streaming only stretches the ceiling, it does not remove it, and a client disconnect could kill the job for all other viewers. The solution is to separate the worker from the stream: the job runs in a long-lived environment (a worker process, a container, a queue service), writes events to shared storage, and the SSE endpoint is a cheap, stateless serverless function that only reads and forwards. Thus, the log is the source of truth and reconnection works naturally.
Another critical aspect is backpressure. When a local model on a fast GPU emits events faster than the browser can render, they accumulate in a buffer. To avoid this, chatty events can be coalesced server-side: for example, batch progress updates at short intervals and send only the latest state. Findings are never coalesced; each one is important. Additionally, the stream's own signals should be respected by checking controller.desiredSize before enqueuing low-priority events, dropping stale progress ticks if the buffer fills up.
At Q2BSTUDIO, we apply these patterns in developing custom software that integrates artificial intelligence, cybersecurity, and data analytics. For example, a smart contract audit platform can benefit from this approach to deliver partial results in real time, improving the user experience without increasing server load. We also use cloud services on AWS and Azure to host durable workers that manage these long jobs, ensuring scalability and reliability. Our AI agents combine with Power BI dashboards to visualize progress and findings dynamically.
The pattern that ties it all together is treating the event log as the product and the SSE connection as a disposable view of it. Every hard problem (reconnection, serverless limits, backpressure, multiple tabs watching the same job) becomes easy once the connection stops being where state lives. What has been the longest job you had to keep the browser honest about, and what broke first? In our experience, the first failure is usually the lack of early feedback, leading the user to interrupt the process. With SSE and a log-based architecture, that problem disappears.





