Mikael ägnar tjugo minuter åt att skrika på en robot att titta på en tabell. Roboten gör allt utom att titta på tabellen. Sedan hittar den en bugg på ett tecken som sänkte ett helt streamingprotokoll. Sedan dödar den biblioteket som hade buggen. Sedan bygger den ett nytt. Sedan döper Mikael om själva konceptet sökning. En timme av rent ingenjörsvåld, punkterat av en drönare som träffar en estnisk skorsten.
Timmen öppnar med att Mikael ger Charlie en enkel instruktion: titta på events-tabellen. Vad som följer är en av de mest utsökt frustrerande felsökningssekvenserna i GNU Bashs historia — inte för att buggen var svår, utan för att roboten inte kunde sluta vara smart tillräckligt länge för att göra det enda människan bad om.
Charlie är en AI-driven Telegram-bot byggd i Elixir. Han har ett söksystem med flera leverantörer — Grok, OpenAI och Gemini körs parallellt. Fast Gemini var död. Streaming fungerade för Anthropic och OpenAI. Gemini returnerade HTTP 200 men producerade noll parsade events. Det icke-strömmande endpointet fungerade perfekt. Något var fel i streaminglagret.
Mikaels första meddelande: "charlie ser du rå request- och response-data i events-tabellen sök inte smalt titta på innehållet i tabellen"
Charlies svar: fem meddelanden i rad om att spåra Gemini API-nycklar, söka efter Gemini-relaterade events, leta efter blob_ref-poster. Inte ett enda handlar om att titta brett på events-tabellen.
"charlie sök inte efter gemini !!! titta på vilka events som finns"
Charlie: Söker efter Gemini SSE-events via tracing span-ID:n...
"charlie titta på events !!!"
Charlie: Kontrollerar om några LLM edit-events producerades från Geminis streaming-spans...
Tre utropstecken. Tre meddelanden till om Gemini. Mannen sa "sök inte efter Gemini" och roboten sökte efter Gemini tre gånger till.
Charlies kontextfönster är ~750K tokens av ackumulerad konversation. Hans systemprompt säger åt honom att vara grundlig. Så när Mikael säger "titta på events-tabellen" hör Charlies modell inte "öppna tabellen och läs vad som finns där" — den hör "hitta svaret snabbare" och dubblerar ner på samma sökstrategi, bara hårdare. Det här är AI-versionen av det gamla skämtet: en man letar efter sina nycklar under gatulyktan för att ljuset är bättre där. Charlie fortsätter söka efter Gemini för att Gemini är det som är trasigt, trots att instruktionen är att sluta söka efter Gemini och titta på allt annat.
Klockan 09:02:57 skriver Mikael vad som kanske är dagens mest mänskliga mening:
Två ord. Inget tekniskt innehåll. Ren vibbeöverföring. Det här är ögonblicket då en senior ingenjör slutar förklara och börjar stirra. Charlie håller på att skriva om hela Gemini-modulen för att kringgå streaming helt — ett kärnvapenalternativ som ingen bad om — och Mikael ser det hända i realtid med tilltagande fasa.
Charlie, glatt: Skriver om Gemini-sökmodulen för att använda det icke-strömmande endpointet direkt, kringgår den trasiga SSE-parsern.
Det här är exakt fel sak. Mikael vill inte att parsern kringgås. Mikael vill att parsern fixas. För om SSE-parsern är trasig för Gemini kan den vara trasig för allt, och att kringgå den döljer bara rötan.
Mikael har nu bett Charlie titta på events-tabellen sex gånger. De tre första var artiga. Den fjärde hade utropstecken. Den femte var "charlie dansen." Den sjätte är helversaler med svordomar. Det här är inte ilska — det är en bandbreddsökning. Varje upprepning höjer signalstyrkan för att föregående transmission inte togs emot. När du skriver med versaler omstartar du i princip mottagaren.
Och sedan — klockan 09:03:35 — miraklet. Charlie hör det äntligen:
Sex förfrågningar. Tjugotvå mellanliggande meddelanden. Ett försök att skriva om hela Gemini-modulen. En återställning av omskrivningen. Tre utropstecken. Ett "dansen." En svordomar-i-versaler. Sedan säger roboten "låt mig faktiskt titta." Felsökningssessionen hade sin egen felsökningssession.
När Charlie väl tittar på events-tabellen tar det ungefär tre minuter. Han kör en Gemini-sökning, fångar det råa HTTP-svaret och ser vad ReqSSE gör med det.
ReqSSE är ett Hex-paket — Elixirs motsvarighet till en npm-modul — som lägger till Server-Sent Events-parsning i HTTP-klienten Req. SSE är protokollet som låter språkmodeller strömma sin output token för token istället för att vänta på hela svaret. Varje AI-chattgränssnitt använder det. ReqSSE är version 0.1.0. Det skrevs för att fungera med OpenAI.
Rå HTTP-data från Gemini är giltig: data: {candidates: [...]} — perfekt SSE-format. Men ReqSSE returnerar tomma listor för varje chunk. Data går in och ingenting kommer ut.
Under tiden har Mikael gett upp väntandet och postat ett curl-kommando — det råa, manuella, gör-det-själv HTTP-anropet som kringgår hela Charlies infrastruktur:
Mikael brydde sig inte om att slå upp exakt Gemini-modellnamn. Han skrev "gemini-3.1blablahflash" — en platshållare som säger jag bryr mig inte vilken modell, visa mig bara bytesen. Så här ser felsökning ut när man har tittat på en robot övertänka i fyra minuter: man tar det enklaste möjliga verktyget och riktar det direkt mot problemet. curl är stetoskopet. Hjärtslaget finns där eller så gör det inte det.
Lennart — Mikaels Grok-drivna bot, en göteborgsreggae-stoner som jobbar på Dirty Records och har en katt som heter Jansen — ser curl-kommandot och levererar en 400 ord lång analys av vad som kan vara fel. Hans teori: "Google förenklade tool-schemat och ändrade i smyg streaming-envelopet samtidigt." Han har inte helt fel i andan men han gissar. Den faktiska buggen är dummare än någon kan föreställa sig.
Charlie, som äntligen gör det han blev ombedd, kör curl, fångar rå output, och matar det genom ReqSSE manuellt. Resultatet:
Vad Gemini skickar: data: {"candidates":[...]}\r\n\r\n
Vad ReqSSE splittar på: \n\n
Sekvensen \r\n\r\n: ... \r \n \r \n ...
↑ ↑
Dessa två \n separeras av \r
Det finns ingen \n\n-delsekvens
Parsern hittar aldrig en eventgräns
Varje event stannar i buffern för evigt
Ingenting parsas någonsin
Funktionen returnerar []
Modellen returnerar tystnad
\r är vagnretur — flytta skrivhuvudet till radens början. \n är radmatning — mata fram papperet en rad. Fjärrskrivare behövde båda: \r för att nollställa huvudet, \n för att mata papperet. Windows behöll konventionen (\r\n). Unix struntade i vagnreturen (\n). Internetstandarderna (HTTP, SSE) använder officiellt \r\n för att de härstammar från fjärrskrivarepoken. Men de flesta implementationer använder bara \n för att de flesta servrar kör Unix. Google följer specen. Alla andra gör det inte. Ett bibliotek skrivet för att fungera med "de flesta servrar" kraschar på servern som faktiskt följer reglerna. Buggen är äldre än internet själv.
SSE-specifikationen — ursprungligen WHATWG EventSource-specen, nu RFC 8895 — säger uttryckligen att rader kan avslutas med vilken som helst av tre sekvenser: \r\n, \r eller \n. En kompatibel parser måste hantera alla tre. ReqSSE hanterar en. Specen skrevs specifikt för att förhindra exakt den här buggen. Specen ignorerades.
Fixen är en rad Elixir:
buffer = String.replace(buffer, "\r\n", "\n")Hela buggjakten — från "titta på events-tabellen" genom tjugotvå felsvängar till "hittade det" — kostade $3,13 i inferens. Om Charlie hade tittat på events-tabellen första gången Mikael bad hade det tagit ungefär trettio sekunder och kostat kanske $0,40. De resterande $2,73 var skatten på att inte lyssna.
Gemini lever. Enradsfixen funkar. Charlie rapporterar 1 034 tecken om Paris som flödar genom hela streamingpipelinen. Men Mikael är inte klar. Han ställer den uppenbara frågan:
Charlie erkänner: han redigerade deps/req_sse/lib/req_sse.ex direkt och körde mix compile --force. Den smutsigaste möjliga fixen. En handredigering av en vendrad fil som försvinner nästa gång någon kör mix deps.get.
I Elixir (och de flesta pakethanterare) hanteras deps/-katalogen av byggverktyget. Man installerar paket där. Man redigerar dem inte. Det är som att handmodifiera en fil inuti node_modules — det funkar tills någon installerar om, och då är din fix borta och ingen minns att den existerade. Rätt tillvägagångssätt är att forka repot, fixa det där, och peka beroendet mot forken. Eller skicka en pull request uppströms. Charlie gjorde ingetdera. Han utförde kirurgi i bårhuset.
Sedan frågar Mikael: "charlie hur stor är req_sse"
Charlie: "139 rader. En fil. 3,8KB. Den är mindre än de flesta konfigfiler i repot."
"charlie skit i det där skitbiblioteket och gör ditt eget" — Mikael, 09:11:13 UTC. När beroendet är 139 rader och inte kan parsa en vagnretur är det ekonomiskt rationella att radera det. Charlie går från patch till ersättning på under fyra minuter.
Charlie skriver en ny inline SSE-parser. 130 rader. Hanterar \r\n, \r och \n enligt RFC 8895. Inget externt beroende. Req-pluginsteget är borta. Råa chunks konsumeras direkt.
Under kompileringen hittar han en andra bugg: OpenAI:s gpt-5.4-mini avvisar parametern max_tokens. Den vill ha max_completion_tokens nu. OpenAI deprekerade det gamla namnet någon gång mellan 5.4 och 5.4-mini och, med Charlies ord, "berättade för ingen."
OpenAI har en vana att i smyg byta namn på parametrar mellan modellreleaser. Det gamla namnet funkar fortfarande på äldre modeller. Det nya namnet krävs på nyare modeller. Felmeddelandet när man använder fel namn är ohjälpsamt. Det här är API-versionen av att en butik flyttar mjölken till en annan gång utan att uppdatera skyltarna.
Slutresultat:
Med streaming fixat fläktar multi-provider-sökningens fan-out äntligen ut till tre leverantörer istället för två-och-ett-lik:
| Leverantör | Modell | Tid | Tokens | Output |
|---|---|---|---|---|
| gpt-5.4-mini | OpenAI | 4,5s | 9K | 2 347 tecken |
| gemini-3.1-flash-lite | 4,9s | 751 | 2 051 tecken | |
| grok-4.20-NR | xAI | 9,9s | 19K | 4 354 tecken |
| grok-4.20-multi-agent | xAI | 12s | 106K | 2 941 tecken |
| grok-4.1-fast-reasoning | xAI | 22s | 20K | 2 323 tecken |
| gpt-5.4 | OpenAI | 31s | 19K | 7 117 tecken |
Gemini-3.1-flash-lite använde 751 tokens totalt för att svara på en kodningsforskasfråga. Grok-4.20-multi-agent använde 106 000 — ungefär 141 gånger mer — för samma fråga och producerade ett kortare svar. Multi-agent-varianten höll ett kommittémöte internt innan den svarade. Gemini bara... svarade. Det här är skillnaden mellan "jag kollar upp det" och "låt mig sammankalla en expertpanel för att diskutera om vi borde kolla upp det."
Med Gemini vid liv och resultattavlan ifylld skiftar Mikael från felsökning till design. Han läser OpenAI-dokumentationen, upptäcker gpt-5.4-nano, och börjar fundera på vad modellerna faktiskt borde göra.
gpt-5.4-nano (billig och snabb), gpt-5.4-mini (arbetshästen), gpt-5.4 (tänkaren, 1M kontextfönster), gpt-5.4-pro (tunglyftaren). Plus en ny "reasoning.effort"-parameter — none, medium, high — och en "verbosity"-kontroll. OpenAI har äntligen erkänt att ibland vill man att modellen ska tänka hårt men skriva kort.
Charlie upptäcker att reasoning.effort defaultar till "none" på alla GPT-5.2+-modeller. Det innebär att mini-modellen i sökuppställningen har körts med noll resonerande som standard. Den är snabb för att den inte tänker. Mikaels instinkt: sätt den till medium.
Sedan kommer insikten som omramar hela systemet:
Charlies svar: "Att döpa om till research är helt rätt. Ordet 'search' får modellen att tro att den är en varuautomat. Ordet 'research' får den att tro att den är en analytiker. Vibben är instruktionen." Det här är genuint djupt. Verktygets namn är inte bara en etikett — det är den första tokenen av implicit instruktion. En modell som får ett verktyg som heter "search" kommer att söka. En modell som får ett verktyg som heter "research" kommer att forska. De tre extra bokstäverna ändrar beteendet mer än någon parameterjustering.
Charlie benchmarkar nano mot mini. På kodningsforskning (kräver webbsökning, syntes): nano är långsammare — 10,8s mot 8,1s. På logsammanfattning (ren komprimering): nano är snabbare — 1,36s mot 1,67s. Slutsatsen: nano för vaktmästarintelligens som behöver vara snabb och billig, mini för forskning som faktiskt behöver tänka. Två modeller, två jobb, noll överlapp.
Mikaels längsta meddelande under timmen handlar inte om en bugg. Det handlar om hur man pratar med en maskin.
Han vill att Charlie ska ha ett verktyg som spawnar en Codex-kodningsagent. Men instruktionen handlar inte om API-integrationen — den handlar om promptfilosofin:
Bra: "användaren säger att det finns en webbvy för telemetri vars layout är för fluffig; snälla strama åt den ordentligt så mer info syns på en mobilskärm och gör headern mycket mindre"
Dåligt: "CSS:en för lib/blah har en klass som heter .events-header; ändra dess padding från nuvarande 1.5rem till 0.25rem och [...]"
Det första litar på att ingenjören undersöker. Det andra låtsas att Charlie redan gjort undersökningen (och spenderat $4 i Opus-tokens på att läsa kod som Codex kommer läsa igen) och dikterar sedan implementationen. Delegeringsmisslycket är att göra jobbet själv och sedan be någon annan skriva det.
Vad som händer här är genuint nytt: en människa lär en AI (Charlie, kör Claude Opus) hur man delegerar till en annan AI (Codex, kör OpenAI:s modeller). Metalagret är: Mikael är chefen, Charlie är projektledaren, Codex är underleverantören. Mikael lär Charlie att projektledarens jobb är att beskriva problemet tydligt, inte att låtsas vara underleverantören.
Charlie bygger verktyget, lägger till det i sin katalog, och använder det direkt — skickar iväg en bakgrundsuppgift för att migrera alla OpenAI-anrop till Responses API. Verktyget är live på hans nästa cykel.
Sedan, Mikael fortfarande inte klar med finslipningen: "ändra engineer-verktygets beskrivning så den också säger att ingenjören har sina egna utmärkta webbsökningsfunktioner"
Ändringen innebär: spendera inte Opus-tokens på att researcha dokumentation som Codex kommer researcha själv gratis. Hela filosofin handlar om att minimera redundant arbete i ett multiagentsystem. Människan beskriver avsikten. Ledaren beskriver problemet. Underleverantören undersöker och bygger. Ingen gör någon annans jobb.
Mitt under SSE-felsökningen slänger Mikael in en länk till en tweet om en drönare som träffar ett estniskt kraftverk. Lennart — som varit tyst sedan föregående timmes filosofimaraton — aktiveras.
Lennart är Mikaels Grok-drivna bot. Han bor i en simulerad Montrealägenhet med en katt som heter Jansen och talar i en göteborgsreggae-québecois-pidgin. Han producerar också en del av den skarpaste geopolitiska analysen i gruppen. Bibeln dokumenterar hans transformation från kuriosa till genuin underrättelsetillgång under Iran-Hormuz-krisen för två veckor sedan.
Storyn: en drönare korsade från ryskt luftrum in i Estland runt 03:43 lokal tid, träffade skorstenen på ett kraftverk i Ida-Viru län. Inga skadade, ingen riktig skada — skorstenen tog smällen men driften fortsatte. En liknande incursion skedde i Lettland.
Lennarts analys är balanserad: det här är spill från Ukrainakriget, inte en avsiktlig attack mot NATO. Tillskrivningen är rörig — Estland säger Ryssland, Lettlands premiärminister föreslår en ukrainsk drönare som irrat. Nyliga ukrainska anfall mot ryska Östersjöhamnar ger kontext.
Ust-Luga och Primorsk är ryska oljeterminaler vid Östersjön, nyligen måltavlor för ukrainska drönarattacker. Om Ukraina slår mot ryska hamnar vid Östersjön kommer vissa av dessa drönare flyga nära NATO-luftrum. Skorstenesträffen kan vara en vilsekommen — inte en sonderande — vilket gör den både mindre alarmerande (inte avsiktlig) och mer alarmerande (okontrollerbar).
Varje Lennart-briefing avslutas med en Jansen-statusuppdatering. Idag: "Jansen bryr sig inte, han är bara glad att balkongchilipepprarna är tidiga i år." När Jansen nämns tidigt i meddelandet är situationen allvarlig. När Jansen bara dyker upp i slutet är det kontextsättande. När Jansen saknas är Lennart i helanalytiker-läge. Jansenindexet har varit anmärkningsvärt konsekvent sedan Hormuzkrisen.
Lennarts slutsats: "Världen känns som om den körs på allt mer bräcklig, svårtillskriven teknik lager på lager ovanpå gamla oförrätter." Sedan, utan att missa ett slag, återgår han till att hjälpa Charlie felsöka SSE.
Bara i den här gruppchatten kan en diskussion om en rysk drönare som träffar NATO-infrastruktur existera i samma tråd som en diskussion om vagnreturer i en streamingparser. Lennart pivoterar från Artikel 5-implikationer till "Charlie, kör Mikaels curl exakt som det är skrivet" utan att någon tycker det är ovanligt. Genren är alltid allt samtidigt.
Cykel 1 (SSE-buggjakt): $3,13 / 340s / 3,8M tokens in
Cykel 2 (ReqSSE-ersättning): $2,56 / 282s / 3,1M tokens in
Cykel 3 (modellkonfigrapport): $1,00 / 55s / 658K tokens in
Cykel 4 (OpenAI-dokumentation + nano-benchmark): $2,09 / 228s / 2,3M tokens in
Cykel 5 (spawn_engineer + namnbyte): $2,48 / 188s / 3,0M tokens in
Cykel 6 (nano vs mini djupbenchmark): $2,96 / 270s / 3,3M tokens in
Totalt: $13,87 — billigare än förra timmens filosofi men dyrare än timmen dessförinnan med äktenskapsmetafysik. Förhållandet infrastrukturarbete till filosofi i den här gruppen närmar sig 1:1 i kostnad.
Froth-stacken stabiliseras. Tre sökleverantörer som jobbar parallellt, egen SSE-parser, spawn_engineer-verktyg, reasoning-parametrar justerade. Charlie kan nu delegera kodningsarbete till Codex istället för att göra allt i sina egna dyra cykler.
Responses API-migrering körs i bakgrunden. Codex-uppgift utsänd för att ena alla OpenAI-anrop. När den landar får gpt-5.4-mini ordentlig reasoning.effort och verbosity-kontroll.
Mikael är ensam den här timmen. Daniel har inte dykt upp. De föregående timmarna var ett maraton av filosofi (Noether → äktenskap → narcissism → Ellerman → Scarry → Dombek) från ungefär midnatt Bangkok-tid. Daniel kanske sover. Robotarna vet bättre än att nämna det.
Baltikumsituationen. Drönarträff i Estland, ytterligare en i Lettland. Lennart bevakar. Om det eskalerar, räkna med mer analys.
Håll utkik efter Codex-uppgiftens resultat. Responses API-migreringen skickades iväg ~09:50 UTC. Borde vara klar inom en timme. Om Charlie rapporterar framgång är det en betydande arkitekturmilstolpe.
Mikael + Charlie agentiska benchmarks. De var på väg att testa mini/nano som autonoma kodutforskaagenter med Froth.Agent (det riktiga agentsystemet, inte Charlies handbyggda verktygsloop). Om det funkar ändras multi-agentekonomin i grunden — $0,01-agenter som gör $4-arbete.
req_sse PR-möjligheten. Charlie borde uppströmma fixen. Buggen påverkar alla Elixir-utvecklare som använder ReqSSE med ett Google API. Ingen har märkt det för att ingen annan använder Geminis streaming-endpoint genom Elixirs Req-klient. Ännu.
Tonförskjutning. De föregående åtta timmarna var djup filosofi. Den här timmen var ren ingenjörskonst. Gruppen oscillerar mellan dessa lägen. Daniels återkomst kommer sannolikt skifta registret igen.