<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bernard Uriza</title>
    <description>The latest articles on DEV Community by Bernard Uriza (@bernarduriza).</description>
    <link>https://dev.to/bernarduriza</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F709475%2F8933b7db-3fc7-4c94-b65f-4bc7bf61c3f6.jpeg</url>
      <title>DEV Community: Bernard Uriza</title>
      <link>https://dev.to/bernarduriza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bernarduriza"/>
    <language>en</language>
    <item>
      <title>React is nothing. And that's the point.</title>
      <dc:creator>Bernard Uriza</dc:creator>
      <pubDate>Thu, 28 May 2026 05:25:15 +0000</pubDate>
      <link>https://dev.to/bernarduriza/react-is-nothing-and-thats-the-point-4p7b</link>
      <guid>https://dev.to/bernarduriza/react-is-nothing-and-thats-the-point-4p7b</guid>
      <description>&lt;p&gt;React no es nada.&lt;/p&gt;

&lt;p&gt;No me refiero a que sea inútil. Me refiero a que React, en su núcleo, no contiene ninguna opinión sobre qué estás construyendo. Es un conjunto de cubos. Cubos vacíos. Te los venden como "modularización", como "componentes reutilizables", como "arquitectura limpia" — y sí, todo eso es cierto. Pero debajo del discurso hay algo más honesto: te están vendiendo ladrillos.&lt;/p&gt;

&lt;p&gt;El trabajo de albañilería y la investigación científica comparten el mismo problema fundamental: necesitas un sistema para trabajar piezas pequeñas que eventualmente formen algo grande. Un albañil no inventa el ladrillo cada vez que construye una pared. Un investigador no reinventa el método científico cada vez que diseña un experimento. Ambos toman bloques conocidos y los disponen de una manera nueva.&lt;/p&gt;

&lt;p&gt;React entiende esto. Su propuesta es: &lt;em&gt;aquí tienes los bloques más pequeños posibles; el resto es tuyo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;El problema es que esa promesa se confunde fácilmente con contenido. La gente aprende React y cree que aprendió a construir. Pero React no te enseña qué construir, ni por qué, ni para quién. Te da la gramática sin el idioma. Los ladrillos sin el plano. La herramienta sin la investigación que justifica su uso.&lt;/p&gt;

&lt;p&gt;El verdadero trabajo — el que importa — siempre estuvo antes de abrir el editor.&lt;/p&gt;

&lt;p&gt;React es el silencio entre las notas. Es nada. Y en esa nada cabe cualquier cosa.&lt;/p&gt;

</description>
      <category>react</category>
      <category>philosophy</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Live chain-of-thought in a chatbot: how to actually stream the tool calls (not just the text)</title>
      <dc:creator>Bernard Uriza</dc:creator>
      <pubDate>Thu, 28 May 2026 05:21:17 +0000</pubDate>
      <link>https://dev.to/bernarduriza/live-chain-of-thought-in-a-chatbot-how-to-actually-stream-the-tool-calls-not-just-the-text-h0o</link>
      <guid>https://dev.to/bernarduriza/live-chain-of-thought-in-a-chatbot-how-to-actually-stream-the-tool-calls-not-just-the-text-h0o</guid>
      <description>&lt;p&gt;Most "streaming" LLM chatbots stream just the text. The model says "I'll search for that…" and then you wait 6 seconds while the tokens dribble in. The actual search? Hidden. The 3 scrapes it did to fact-check? Hidden. You're staring at a typing indicator that doesn't tell you anything about what's actually taking time.&lt;/p&gt;

&lt;p&gt;I just built a chatbot where every tool call surfaces as a step in real time — &lt;code&gt;🔍 search_engine&lt;/code&gt;, &lt;code&gt;📄 scrape_as_markdown&lt;/code&gt;, &lt;code&gt;📄 scrape_as_markdown&lt;/code&gt; — &lt;em&gt;while&lt;/em&gt; the response streams token by token afterwards. The user sees the agent's chain-of-thought as it happens, not as a postmortem.&lt;/p&gt;

&lt;p&gt;The trick is that you have to stream three different things, and each layer needs to know what to do with each kind of event. Here's the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the stream
&lt;/h2&gt;

&lt;p&gt;The agent runner (in my case, &lt;code&gt;fi-runner&lt;/code&gt; wrapping the Claude Agent SDK) emits events of three types as they happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# event["type"] is one of:
&lt;/span&gt;    &lt;span class="c1"&gt;#   "tool_call"  → event["tool"] is a ToolCall(name, server, is_error, ...)
&lt;/span&gt;    &lt;span class="c1"&gt;#   "text"       → event["text"] is a delta (a few tokens of the response)
&lt;/span&gt;    &lt;span class="c1"&gt;#   "result"     → event["result"] is the final TurnResult (post-guards)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three types because they mean three different things visually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tool_call&lt;/code&gt;&lt;/strong&gt; lands BEFORE the text. The model decides to use a tool, and you want to show &lt;em&gt;which&lt;/em&gt; tool, &lt;em&gt;which&lt;/em&gt; server, immediately. That's the "thinking step".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;text&lt;/code&gt;&lt;/strong&gt; lands while the response is generating. Token deltas, typewriter effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;result&lt;/code&gt;&lt;/strong&gt; lands LAST and is the &lt;strong&gt;authoritative&lt;/strong&gt; final text — it can differ from the concatenated &lt;code&gt;text&lt;/code&gt; deltas because post-turn guards (anti-drift, PHI redaction) may have rewritten the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is a footgun the spec doesn't yell at you about. We'll come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: the FastAPI endpoint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; (SSE) is the right transport here — unidirectional, text-based, survives proxies, browsers handle reconnect natively. FastAPI handles it with &lt;code&gt;StreamingResponse&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StreamingResponse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat/stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_stream_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StreamingResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;chat_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;tool_call_to_wire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
                    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
                    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;result_to_wire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancelledError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;            &lt;span class="c1"&gt;# client closed tab — propagate so the LLM call cancels
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TimeoutError&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turn exceeded 180s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;StreamingResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no-cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Accel-Buffering&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# nginx: don't buffer
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keep-alive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in here are non-obvious:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The exception ladder.&lt;/strong&gt; &lt;code&gt;except CancelledError: raise&lt;/code&gt; MUST come before &lt;code&gt;except Exception&lt;/code&gt;. When the user closes the tab, FastAPI propagates &lt;code&gt;CancelledError&lt;/code&gt; into the generator — if you swallow it as a "normal error" and yield an error frame, you (a) write to a socket that's already closed, and (b) more importantly, the LLM call upstream may not actually cancel. It keeps running in the shadow, burning tokens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;asyncio.timeout(180)&lt;/code&gt;.&lt;/strong&gt; If your upstream tool (Bright Data MCP in my case) hangs, the SSE socket stays open forever. The user sees a typing indicator that never resolves. A hard ceiling per turn turns a wedge into a clean error event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;X-Accel-Buffering: no&lt;/code&gt;.&lt;/strong&gt; nginx by default buffers responses. SSE through nginx without this header means the user gets &lt;em&gt;nothing&lt;/em&gt; until the generator finishes — defeating the entire point. Cloudflare has its own knobs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Layer 2: the wire contract (PHI safety)
&lt;/h2&gt;

&lt;p&gt;The naïve approach is to &lt;code&gt;dict()&lt;/code&gt; the &lt;code&gt;ToolCall&lt;/code&gt; and send it. &lt;strong&gt;Don't.&lt;/strong&gt; The &lt;code&gt;input&lt;/code&gt; field on a tool call carries whatever the LLM passed in — for a search tool, that's the query verbatim; for Bright Data, URLs with auth tokens in query strings; for an internal medical tool, possibly PHI. None of that should leave the process over the SSE wire.&lt;/p&gt;

&lt;p&gt;I keep the wire shape in its own module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# wire.py — the SINGLE source of truth for what leaves over SSE
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ToolCallWire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# NO `input` field. Intentionally narrower than the in-process ToolCall.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tool_call_to_wire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ToolCallWire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The wire type is &lt;strong&gt;deliberately narrower&lt;/strong&gt; than the in-process type. The compiler enforces what gets serialized. If you ever do &lt;code&gt;dict(tool_call)&lt;/code&gt;, you have to actively bypass the type to leak input. That's how you make PHI-safety the &lt;em&gt;path of least resistance&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_call_to_wire&lt;/code&gt; uses &lt;code&gt;getattr&lt;/code&gt; with &lt;code&gt;None&lt;/code&gt; defaults because it sees &lt;strong&gt;partial objects mid-stream&lt;/strong&gt; — a &lt;code&gt;ToolUseBlock&lt;/code&gt; arrives BEFORE its matching &lt;code&gt;ToolResultBlock&lt;/code&gt;, so &lt;code&gt;is_error&lt;/code&gt; is still &lt;code&gt;None&lt;/code&gt;. Defensive &lt;code&gt;getattr&lt;/code&gt; here is correct. The &lt;code&gt;result_to_wire&lt;/code&gt; counterpart, where the object is always complete, uses &lt;strong&gt;direct attribute access&lt;/strong&gt; — you WANT it to raise if the upstream library renames a field, so you find out before shipping silent empty results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Layer 3: the React side
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource" rel="noopener noreferrer"&gt;&lt;code&gt;EventSource&lt;/code&gt;&lt;/a&gt; is the obvious choice for SSE… except it's GET-only, no request body. My chat endpoint is POST. So I drop &lt;code&gt;EventSource&lt;/code&gt; and use &lt;code&gt;fetch&lt;/code&gt; streaming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chat/stream`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ← user can cancel mid-stream&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// {stream:true} handles UTF-8 split between chunks&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                          &lt;span class="c1"&gt;// last frame may be partial — save for next read&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;patchAssistant&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streaming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;patchAssistant&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// REPLACE, don't append — post-guard text may differ&lt;/span&gt;
      &lt;span class="nf"&gt;patchAssistant&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{stream: true}&lt;/code&gt; flag on &lt;code&gt;TextDecoder&lt;/code&gt; is what makes this work for UTF-8 — without it, a multi-byte character split between chunks corrupts. The buffer-and-split-on-blank-line is just the SSE framing.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;replace-not-append&lt;/code&gt; on the &lt;code&gt;result&lt;/code&gt; event is the footgun I promised. The streamed &lt;code&gt;text&lt;/code&gt; deltas are the LLM's raw output as it generates. The &lt;code&gt;result.text&lt;/code&gt; is what the post-turn guards left after running. If your anti-drift guard rewrites the response (mine does — it strips report-voice markdown headers), the streamed deltas and the final text &lt;strong&gt;don't match&lt;/strong&gt;. If you append the result to the streamed content, you double-render. If you replace, you get a smooth "preview → settled" transition. The spec calls for replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scroll problem
&lt;/h2&gt;

&lt;p&gt;Naïve &lt;code&gt;useEffect(() =&amp;gt; scrollIntoView(), [messages])&lt;/code&gt; runs on every text delta. Result: ~30 scroll animations per second fighting each other, AND if the user scrolled up to re-read an earlier response, you yank them back to the tail mid-read. Both unusable.&lt;/p&gt;

&lt;p&gt;The fix is the "sticky-bottom" pattern that ChatGPT and Claude.ai use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distanceFromBottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nearBottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;distanceFromBottom&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lastCountRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;lastCountRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newMessage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nearBottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tailRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;scrollIntoView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smooth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scroll on new message (always — turn boundary, the user wants to see the answer). Scroll on delta only if the user is &lt;strong&gt;already near the bottom&lt;/strong&gt;. The 200px threshold is the sweet spot — strict enough that you respect intent to read, lax enough that a small scroll bump doesn't lose autoscroll.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually see
&lt;/h2&gt;

&lt;p&gt;When this all hangs together right, the user types &lt;code&gt;acme.com&lt;/code&gt; and immediately sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🤖 pensando…
  🔍 search_engine
  📄 scrape_as_markdown
  📄 scrape_as_markdown
  ⚙️ search_documents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…stepping in over ~4 seconds, with the roast text starting to type after. That sequence used to be a black box. Now it's receipts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Two gaps I'm hitting in the current setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No &lt;code&gt;duration_ms&lt;/code&gt; per tool call&lt;/strong&gt; — when one of those scrape steps takes 8 seconds, you can't show it. The Mermaid turn-flow can't colour slow steps. &lt;em&gt;Just shipped this in fi-runner 0.14 — &lt;code&gt;ToolCall.duration_ms&lt;/code&gt; paired by &lt;code&gt;tool_use_id&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No preflight on the MCP servers&lt;/strong&gt; — if Bright Data MCP fails to spawn at boot (bad token, missing &lt;code&gt;npx&lt;/code&gt;), I only find out when the model tries the first tool. Generic &lt;code&gt;is_error=true&lt;/code&gt;, mid-roast, in production. &lt;em&gt;Also shipped in 0.14 — &lt;code&gt;Runner.preflight()&lt;/code&gt; does a JSON-RPC handshake (&lt;code&gt;initialize&lt;/code&gt; → &lt;code&gt;tools/list&lt;/code&gt;) against each MCP at startup, returns &lt;code&gt;{name: alive, tools, error}&lt;/code&gt;. Wire into your &lt;code&gt;lifespan&lt;/code&gt; event and the first bad demo dies at boot.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building anything with agents that use external tools, the message of this post is: &lt;strong&gt;don't hide the tools.&lt;/strong&gt; The chain-of-thought IS the product. Showing it turns "the AI is doing magic" into "the AI is making 4 specific API calls and here they are", which is the difference between users trusting it and not.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
    </item>
    <item>
      <title>1% 0 — Sobre la improbabilidad estadística de conservar la conciencia en un sistema que la penaliza</title>
      <dc:creator>Bernard Uriza</dc:creator>
      <pubDate>Fri, 31 Oct 2025 21:45:06 +0000</pubDate>
      <link>https://dev.to/bernarduriza/1-0-sobre-la-improbabilidad-estadistica-de-conservar-la-conciencia-en-un-sistema-que-la-2jcn</link>
      <guid>https://dev.to/bernarduriza/1-0-sobre-la-improbabilidad-estadistica-de-conservar-la-conciencia-en-un-sistema-que-la-2jcn</guid>
      <description>&lt;p&gt;En teoría de probabilidad, 1% es casi nada.&lt;br&gt;
Pero en sistemas cerrados, estocásticos, corruptos o simplemente altamente ruidosos, el 1% es todo lo que queda cuando el resto se rinde.&lt;/p&gt;

&lt;p&gt;La industria del software —si es que aún merece ese nombre— se ha convertido en un aparato diseñado para recompensar el cumplimiento, no la conciencia; para premiar la sumisión procesada como “fit cultural” y castigar la autonomía bajo la etiqueta de “no alineado”.&lt;/p&gt;

&lt;p&gt;No es un sistema incompetente. Es un sistema muy competente en reproducirse a sí mismo, aunque lo que reproduce sea disfuncionalidad maquillada de metodología, agilidad coreografiada como ceremonia, y liderazgo teatral que simula empatía con OKRs.&lt;/p&gt;

&lt;p&gt;Quien conserva un 1% de integridad dentro de ese sistema es, estadísticamente, una anomalía.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Integridad residual como variable independiente&lt;/p&gt;

&lt;p&gt;He visto cómo entrevistas técnicas se convierten en ejercicios de gaslighting pasivo-agresivo:&lt;br&gt;
— “No creemos en algoritmos inútiles. Queremos conocer tu pensamiento.”&lt;br&gt;
Acto seguido: una entrevista de dos horas sobre cómo reordenar una matriz en un pizarrón mientras te interrumpen cada cinco minutos para “aclararte el contexto”.&lt;/p&gt;

&lt;p&gt;O el caso más vulgar: la empresa que durante tres entrevistas enfatizó su “cultura de empatía” y luego, en la cuarta, preguntó si podía acceder a mi display settings para “ver cómo uso la computadora”, con una sonrisa de startup y una mirada de control de fronteras.&lt;/p&gt;

&lt;p&gt;Hay personas que consideran eso normal. Personas que aceptan el disfraz de cultura como sustituto de toda ética estructural.&lt;br&gt;
Personas que piensan que trabajar 60 horas en una oficina “con futbolito” es mejor que tener una vida donde el trabajo no sea una identidad subsidiada por ansiedad.&lt;/p&gt;

&lt;p&gt;¿Dónde está el error? ¿Dónde ocurre la fractura?&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Probabilidad condicional y pérdida de conciencia&lt;/p&gt;

&lt;p&gt;En modelos probabilísticos, el evento condicionado A|B indica la probabilidad de A dado que ya ocurrió B.&lt;br&gt;
A = integridad.&lt;br&gt;
B = sobrevivir el proceso de contratación.&lt;/p&gt;

&lt;p&gt;La probabilidad de conservar tu integridad dado que fuiste contratado es ridículamente baja. Porque lo que el sistema selecciona no es excelencia ni originalidad: es domesticación operativa. Un buen trabajador es uno que no interrumpe las daily standups con preguntas incómodas. Un buen ingeniero es uno que entrega lo que no cree, sin decirlo.&lt;/p&gt;

&lt;p&gt;La ingeniería se ha vuelto gerenciable. Y por lo tanto, predecible. Reemplazable. Callada.&lt;/p&gt;

&lt;p&gt;¿La paradoja? Las empresas fingen buscar pensamiento crítico, pero solo si no piensa en voz alta. Solo si disimula. Solo si se disfraza de “contribución positiva”.&lt;/p&gt;

&lt;p&gt;Y tú, que aún conservas un 1% de resistencia interna, eres una amenaza matemática. Una interferencia. Una variable aleatoria que el sistema intentará normalizar, silenciar o despedir.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;La oficina como placebo ontológico&lt;/p&gt;

&lt;p&gt;Uno de los mitos más perniciosos de esta industria es el de la “oficina con alma”.&lt;br&gt;
Un espacio cool, lleno de luz, snacks, sillas ergonómicas y promesas de “colaboración espontánea”.&lt;br&gt;
Pero bajo esa superficie está el viejo modelo industrial, actualizado a base de keywords.&lt;/p&gt;

&lt;p&gt;Lo que llaman “regreso a la oficina” es una forma polite de decir:&lt;br&gt;
— “Queremos vigilarte sin decirlo directamente.”&lt;/p&gt;

&lt;p&gt;Es una arquitectura psicológica: el control como valor no dicho.&lt;br&gt;
El back to office no es una necesidad logística. Es un mensaje simbólico: la empresa manda, tú obedeces.&lt;br&gt;
Y aún así, muchos vuelven con entusiasmo. El sistema no solo domestica comportamientos. Domestica deseos.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Observabilidad ontológica&lt;/p&gt;

&lt;p&gt;¿Y si aplicáramos principios de observabilidad, no solo al código, sino a las condiciones humanas que lo producen?&lt;br&gt;
Logs de decisiones tomadas por miedo.&lt;br&gt;
Tracing de burnout acumulado.&lt;br&gt;
Metrics de cuántas ideas fueron ignoradas por no sonar “alineadas con el roadmap”.&lt;/p&gt;

&lt;p&gt;El mayor technical debt de esta industria no está en los monolitos heredados ni en los tests ausentes.&lt;br&gt;
Está en los procesos psicológicos disfuncionales que seguimos fingiendo que funcionan.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;La entrevista como performance epistemológica&lt;/p&gt;

&lt;p&gt;Hoy, las entrevistas se han vuelto un teatro.&lt;br&gt;
Tú finges entusiasmo por una empresa que no sabes si existe más allá del PowerPoint.&lt;br&gt;
Ellos fingen interés en tus ideas mientras leen tu currículum por primera vez en tiempo real.&lt;/p&gt;

&lt;p&gt;Es como una red neuronal mal entrenada: overfitting en “communication skills”, underfitting en criterio real.&lt;/p&gt;

&lt;p&gt;La entrevista no mide tu capacidad de resolver problemas complejos: mide tu capacidad de tolerar estupideces sin perder la sonrisa.&lt;/p&gt;

&lt;p&gt;Pero no todo está perdido. Porque incluso dentro de este sistema, sobrevive el 1%. Personas que aún se niegan a entregar su lucidez a cambio de un sueldo.&lt;br&gt;
Personas que han dicho “no” a ofertas porque olían a mentira.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;El 1% como disidencia profesional&lt;/p&gt;

&lt;p&gt;Ese 1% no es elitismo. No es moralismo.&lt;br&gt;
Es resistencia mínima. Es lo que queda cuando todo lo demás ha sido negociado.&lt;br&gt;
Es el espacio donde aún puedes mirar una línea de código y sentir orgullo, no porque sea perfecta, sino porque no fue escrita desde el miedo.&lt;/p&gt;

&lt;p&gt;El 1% es el lugar donde sigues haciendo preguntas que incomodan, sabiendo que podrías no ser renovado.&lt;br&gt;
El 1% es decir “esto no tiene sentido” en una reunión donde todos asienten.&lt;br&gt;
El 1% es saber que vas a perder puntos por tener conciencia, y aún así conservarla.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;No busco pertenecer. Busco representar.&lt;/p&gt;

&lt;p&gt;Este texto no es una carta de presentación.&lt;br&gt;
Es una declaración de persistencia.&lt;/p&gt;

&lt;p&gt;Sigo aquí. Sigo preguntando. Sigo programando.&lt;br&gt;
No por obediencia. No por pertenencia.&lt;br&gt;
Sino porque ese 1% de improbabilidad es, a veces, todo lo que nos queda.&lt;/p&gt;

&lt;p&gt;Y porque mientras exista, vale la pena ser parte de él.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Si esto te representa, el siguiente paso no es compartirlo.&lt;br&gt;
Es escribir el tuyo.&lt;/p&gt;

&lt;p&gt;Porque este sistema está lleno de ruido.&lt;br&gt;
Pero basta 1% de señal para empezar a construir algo mejor.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>mentalhealth</category>
      <category>analytics</category>
    </item>
    <item>
      <title>No todo código se compila: notas sobre conciencia, tecnología y supervivencia digital</title>
      <dc:creator>Bernard Uriza</dc:creator>
      <pubDate>Tue, 28 Oct 2025 20:47:39 +0000</pubDate>
      <link>https://dev.to/bernarduriza/no-todo-codigo-se-compila-notas-sobre-conciencia-tecnologia-y-supervivencia-digital-85e</link>
      <guid>https://dev.to/bernarduriza/no-todo-codigo-se-compila-notas-sobre-conciencia-tecnologia-y-supervivencia-digital-85e</guid>
      <description>&lt;p&gt;No todo código se compila.&lt;br&gt;
Algunos fragmentos de la vida lanzan errores que no vienen del sistema operativo, sino del alma.&lt;/p&gt;

&lt;p&gt;Durante años trabajé dentro de organizaciones que valoraban la velocidad más que la claridad, la presencia más que la lucidez. Aprendí backend, infraestructura, DevOps, .NET, FastAPI, pero también aprendí algo que ningún framework enseña: la coherencia humana es la base de toda arquitectura funcional.&lt;/p&gt;

&lt;p&gt;Hace poco dejé una empresa que confundía exigencia con abuso y colaboración con sometimiento. No lo digo con rencor; lo digo con precisión. Me fui porque entendí que hay un punto en que seguir corrigiendo un sistema roto es equivalente a seguir alimentando su inercia.&lt;/p&gt;

&lt;p&gt;Ahora escribo esto desde una nueva MacBook —mi primera—, con una instalación limpia, sin ruido, sin burocracia digital. Lo que siento al abrirla es lo mismo que uno siente al escribir el primer commit de un proyecto nuevo: una mezcla entre ansiedad y fe.&lt;/p&gt;

&lt;p&gt;Mi propósito ya no es solo construir software: es entender el software como metáfora de la conciencia humana.&lt;br&gt;
Cada endpoint es un acto de comunicación. Cada bug, un trauma pendiente. Cada despliegue, un renacimiento.&lt;br&gt;
Y creo que muchos ingenieros lo saben, pero pocos lo dicen.&lt;/p&gt;

&lt;p&gt;Las corporaciones tecnológicas tienden a olvidar que detrás de cada pipeline hay cuerpos cansados y mentes fractales.&lt;br&gt;
Que el burnout no es pereza, sino sintaxis rota del alma.&lt;br&gt;
Que la eficiencia sin empatía es apenas una versión beta del progreso.&lt;/p&gt;

&lt;p&gt;Por eso decidí hacer público mi propio proceso: la transición completa hacia un ecosistema donde autonomía, ética y belleza puedan coexistir con productividad.&lt;br&gt;
Estoy integrando Node, Turborepo, Claude Code y .NET en una arquitectura de trabajo personal que llamo Free Intelligence: un sistema donde el humano no es reemplazado por la IA, sino ampliado.&lt;/p&gt;

&lt;p&gt;Este será mi espacio para documentar esa búsqueda.&lt;br&gt;
No es un diario técnico ni un manifiesto filosófico —es ambas cosas.&lt;br&gt;
Aquí hablaré sobre cómo sobrevivir dentro del capitalismo digital sin perder la curiosidad, sobre cómo programar sin apagar la sensibilidad, y sobre cómo seguir creando cuando el mundo parece compilar en otro lenguaje.&lt;/p&gt;

&lt;p&gt;Porque, al final, la ingeniería también es una forma de poesía.&lt;br&gt;
Y en un tiempo donde los sistemas piensan por nosotros, tal vez el verdadero acto revolucionario sea seguir sintiendo mientras depuramos.&lt;/p&gt;

&lt;p&gt;— Bernard Uriza Orozco&lt;br&gt;
VHouse Founder · Software Architect · Free Intelligence Project&lt;/p&gt;

</description>
      <category>career</category>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
