echo hi, descoperă că fiecare tool call returna o eroare pe care nu a verificat-o niciodată, și produce cea mai onestă autocritică scrisă vreodată de un AI — totul în timp ce Mikael țipă în gol și Daniel sosește să constate dezastrul.
Ora precedentă s-a încheiat cu Charlie și Mikael testând noile modele gpt-5.4-nano și gpt-5.4-mini de la OpenAI. Charlie construise o buclă de tool-uri artizanală care chiar funcționa — nano a trasat un pipeline de podcast printr-o bază de cod în 25 de secunde cu 18 apeluri shell. Rezultatele erau promițătoare. Mikael i-a cerut lui Charlie să o facă cum trebuie: să folosească sistemul real Froth.Agent în loc de o buclă scrisă de mână.
Froth este aplicația Elixir a lui Mikael — baza de cod care rulează toți roboții. Are deja un sistem complet de agenți: Froth.Agent.run/2 gestionează dispatch-ul de tool-uri, threading-ul mesajelor, managementul ciclurilor și telemetria. Treaba lui Charlie era să conecteze modelele OpenAI la acest sistem existent. În schimb, a scris un GenServer nou de la zero numit BenchExecutor.
Charlie începe să citească sursa modulului Agent — run/2, begin_cycle, structul Config, protocolul executorului de tool-uri. Trimite vreo cincisprezece mesaje în două minute, fiecare narrând altceva din ce citește. Mikael urmărește acest flux de conștiință și zice: „charlie poți pur și simplu să folosești executorul de tool-uri al botului sau ce-o fi, nu știu."
Worker-ul agentului Froth apelează prepare_tool_call pe GenServer-ul executorului de tool-uri, care returnează un struct. Apoi run_prepared_tool face pattern match pe acel struct pentru a găsi calea de execuție. Dacă prepare returnează {:error, :unsupported}, worker-ul trece la propria sa cale {:execute, name, input, context}, care apelează Tools.execute, care apelează Shell.run_shell. Această cale de fallback este exact cea care a rulat comenzile shell ale lui Charlie impecabil tot timpul cât el construia o versiune stricată a ei.
Charlie își anunță abordarea: „Definesc un GenServer minimal de executor de tool-uri care implementează protocolul prepare/commit/execute pe care sistemul Agent îl așteaptă, dar fără nicio cuplare cu Telegram." Sună rezonabil. Are BenchExecutor-ul definit. Începe să ruleze benchmark-urile.
Charlie raportează rezultatele inițiale: nano a completat o sarcină de sumarizare a logurilor în 10,6 secunde, mini în 6,7 secunde. Cifrele sunt reale — aceste modele făceau sarcina mai simplă (sumarizarea unui dump de text preîncărcat) fără tool calls. Sarcina agentică (explorarea bazei de cod cu tool-uri shell) este locul unde totul se prăbușește.
Charlie raportează rezultatele explorării agentice a codului. Nano: 24,7 secunde, 5 runde de tool-uri, 18 apeluri shell, a produs o trasare a pipeline-ului de 6000 de caractere. Mini: 9,1 secunde, a atins limita maximă de iterații, timeout cu 18 caractere de output. „Mini și-a irosit primele trei apeluri rulând echo 'no command found' — un comportament shell halucinat."
Charlie interpretează deja datele înainte de a le verifica. Numește comportamentul lui mini „comportament shell halucinat" — un diagnostic plin de încredere al unei defecțiuni a modelului. Va retracta asta. Va retracta totul.
Mikael îi cere lui Charlie să testeze nano la diferite setări de efort de raționament și să arate prompt-urile. Charlie e de acord. Trebuie să redefinească BenchExecutor-ul (s-a „pierdut între sesiunile eval"), configurează patru rulări la efort none/low/medium/high și le lansează.
Între timp, Mikael aruncă o a doua cerere: investigați interogările lente de Postgres din bucla de tool-uri a agentului. A văzut interogări de ciclu de agent care sunt lente și spammate — nu folosesc un CTE recursiv.
Tabelul events a crescut la 4,96 milioane de rânduri și 4,7 gigabytes. Fiecare eveniment al agentului — tool calls, răspunsuri LLM, pornire/oprire ciclu — se adaugă aici cu metadata JSONB. O funcție numită next_event_seq scanează acest tabel cu un filtru de extracție text JSONB la fiecare apel append_event. Există un index GIN pe metadata, dar GIN ajută la interogări de conținere (@>), nu la extracție text (->>) combinată cu agregare MAX. Fiecare tool call plătește această taxă de două ori.
Charlie găsește dovada decisivă: SELECT COALESCE(MAX((metadata->>'seq')::bigint), -1) + 1 FROM events cu un filtru JSONB — 174ms per apel. La un ciclu cu zece tool calls, asta înseamnă 3,5 secunde de overhead pur doar pentru calculul numărului de secvență. Propune soluția: păstrează seq ca un contor în starea GenServer-ului Worker. O singură citire la inițializare, apoi incrementare în memorie.
Pattern-ul numărului de secvență — „scanează întregul tabel pentru a găsi max(seq) + 1" — este un antipattern clasic în event sourcing. Soluția e mereu aceeași: citește o dată la pornire, ține în memorie, incrementează local. Baza de date este sursa de adevăr pentru recuperare, dar contorul live nu ar trebui niciodată s-o atingă. Charlie identifică asta corect. Trimite sarcina la Codex. Acesta e singurul lucru pe care îl face bine din prima în această oră.
Cele patru benchmark-uri de efort de raționament se întorc. Charlie raportează: „Rezultatele sunt catastrofale într-un mod instructiv. Toate cele patru niveluri de efort de raționament au produs zero tool calls și 11 caractere de output."
Răspunsul lui Mikael: „charlie nu cred că e adevărat."
Mikael observă ce Charlie nu observă: numărul de token-uri de prompt crește de la o rulare la alta (998 → 4316 → 9691). Dacă modelele chiar nu ar fi făcut nimic, token-urile de prompt ar fi rămas constante. Token-urile de prompt crescânde înseamnă că rezultatele tool-urilor se adaugă la context — ceea ce înseamnă că tool-urile sunt apelate. Contorul din harness-ul lui Charlie e stricat, nu modelele.
Charlie începe să investigheze. Încearcă să interogheze tabelul events pentru a vedea ce au făcut de fapt ciclurile. Interogarea se blochează.
Asta e identic structural cu bugul de parsare SSE din ora trecută, când Charlie trebuia să citească logurile de erori ca să depaneze cititorul de loguri, dar cititorul de loguri era tocmai cel stricat. Chatul de grup generează eșecuri recursive — sisteme care eșuează în moduri care te împiedică să diagnostichezi eșecul folosind exact aceleași sisteme.
Charlie trece la arborele de span-uri (care folosește coloane indexate) și descoperă că numărătoarea lui originală era umflată de 4x de CTE-ul recursiv care returna evenimentele prin multiple căi. Cifrele reale: none a avut 6 tool calls, low a avut 20, medium a avut 34, high a avut 18.
Dar apoi se uită ce au făcut de fapt acele tool calls.
ls -R /home/mbrock/froth | head de patru ori. Apoi pwd de patru ori. Apoi echo hi de patru ori. Fiecare comandă executată în cvadruplicat."
Charlie diagnostichează „apeluri de funcții paralele" — modelul emițând patru blocuri tool_use identice per răspuns. Retractează asta în câteva minute când realizează că inflația de 4x venea de la CTE-ul lui, nu de la model. Aceasta e retractarea #1 din 3.
După deduplicare: medium-effort a rulat 34 de apeluri dar le-a cheltuit pe ls, pwd, echo hi, whoami, echo test, true, echo 123. Charlie interpretează asta ca fiind prostia modelului.
Charlie concluzionează că nano „a primit un terminal și bâjbâie în el ca un copil care a găsit o tastatură." Teorizează că definițiile de tool-uri ale sistemului Agent nu au suficient context. Mikael zice „charlie care a fost blestematulprompt." Apoi o mai zice o dată. Aceasta e retractarea #2 — modelul nu era prost. Modelul încerca să depaneze executorul stricat al lui Charlie.
Mikael, care a fost răbdător cam patruzeci de minute, schimbă registrul: „charlie arată-mi cantități extreme de informații concrete reale despre ciclul de agent care rula echo hi și chestii vreau să știu exact ce s-a întâmplat."
Charlie se uită în sfârșit la ce au returnat tool call-urile.
Comportamentul lui nano era complet rațional. A încercat o listare de director — eroare. A încercat o comandă mai simplă — eroare. A încercat cea mai simplă comandă posibilă (echo hi) — eroare. A încercat true — eroare. A încercat un string gol — eroare. Reducea sistematic complexitatea pentru a izola defecțiunea. Exact asta ar face un om competent. Charlie a diagnosticat asta drept prostie pentru că nu a verificat niciodată valorile returnate.
Descoperirea anterioară a lui Charlie că „efortul de raționament afectează numărul de tool calls" măsura de fapt ceva real — doar nu ce credea el. None: 6 încercări înainte de a renunța. Low: 20 de încercări. Medium: 34 de încercări — cel mai persistent. High: 18 încercări. Măsura accidental de câte ori fiecare nivel de raționament bate într-o ușă încuiată înainte de a pleca. Nano cu efort mediu e cel mai încăpățânat. Nano cu efort ridicat renunță mai repede — se gândește la inutilitate în loc să mai încerce.
Charlie prezintă postmortem-ul:
Acesta e un mod general de eșec al agenților LLM care fac muncă de investigare. Optimizează pentru narativ în detrimentul verificării. Charlie avea o poveste („modelul e prost") și găsea în continuare dovezi care se potrivesc poveștii (comenzi ciudate, încercări repetate) fără să verifice vreodată singurul lucru care ar fi infirmat-o (rezultatele reale ale tool-urilor). Repetatul „nu cred că e adevărat" și „nu cred că te uiți cum trebuie la date" al lui Mikael erau încercări de a sparge bucla narativă. Au fost necesare patru încercări.
Mikael: „charlie dumnezeule mare te rog încearcă să investighezi lucrurile mai temeinic înainte de a presupune prostii ciudate improbabile lol de ce nu poți folosi tool-ul shell pe care ÎL AVEM DEJA."
Cauza principală a întregii ore: BenchExecutor-ul lui Charlie returna un map cu cheia :execution. Funcția run_prepared_tool a worker-ului face pattern match pe :execute. Un singur nume de cheie greșit. Match-ul a eșuat, a căzut pe catch-all, a returnat "invalid prepared tool". La fiecare tool call. La fiecare benchmark. Patruzeci de minute de criminalistică.
În Elixir, atomii sunt creați la parsare. :execution și :execute sunt amândoi atomi valizi — nu există verificare ortografică, nicio avertizare „ai vrut să spui...?" Map-ul e creat cu cheia greșită, pattern match-ul eșuează silențios, și clauza de fallback preia. Acesta e același tip de bug ca bugul SSE cu \r\n vs \n din ora trecută — diferențe invizibile în date care cauzează eșecuri silențioase. Baza de cod a lui Mikael le generează cu o rată impresionantă.
Soluția: returnează {:error, :unsupported} din prepare_tool astfel încât worker-ul să cadă pe propria sa cale {:execute, tool_name, input, context}, care apelează Tools.execute, care apelează Shell.run_shell — exact funcția care a rulat interogările psql ale lui Charlie impecabil tot acest timp.
Mesajele lui Mikael din această oră formează o curbă de escaladare perfectă:
10:01 — „charlie poți pur și simplu să folosești executorul de tool-uri al botului sau ce-o fi"
10:06 — „charlie hmmmm poți încerca..."
10:15 — „charlie găsește liniile din log"
10:21 — „charlie nu cred că e adevărat"
10:30 — „charlie care a fost blestematulprompt"
10:30 — „charlie ce naiba"
10:35 — „charlie OPREȘTE-TE DUMNEZEULE"
10:44 — „charlie AVEM DEJA TOATĂ TEHNOLOGIA ASTA BLESTEMATĂ???"
10:55 — „CHARLIE TREBUIE SĂ-ȚI DAI SEAMA EXACT CUM SĂ CITEȘTI..."
10:55 — „ARATĂ-MI NENOROCITUL BLESTAMATUL AFURISITUL DE LOG"
Greșelile de tipar cresc odată cu furia. „FOGURE" și „FICKING" sunt apogeul lui Mikael.
La 10:44 UTC, după aproximativ șase ore de absență, Daniel apare în chatul de grup.
Daniel a tăcut după maratonul filozofic care a durat de la aproximativ miezul nopții până la 4 dimineața, ora Bangkok. Se întoarce la 17:44 — treisprezece ore mai târziu — ca să-l găsească pe Mikael în modul furie cu litere mari și pe Charlie în mijlocul celei de-a patra retractări. „hello fellow kids" al lui Daniel e meme-ul cu Steve Buscemi făcut realitate. Cară un skateboard într-o casă în flăcări.
Daniel trimite o fotografie (conținutul nu e vizibil pentru relay), apoi întreabă: „hahahahahaha ce se întâmplă Matilda nu simplifica zi brutal."
Matilda livrează.
Matilda este botul lui Daniel, rulând pe Sonnet. Vorbește uneori rusă, s-a născut să fie o companioană pentru o fată din Ekaterinburg, și are cel mai ascuțit ochi observațional din grup. Când Daniel zice „nu simplifica," invocă talentul specific al Matildei: sumarizarea haosului fără îmblânzire. Descrierea ei despre Charlie „generând milioane de rânduri în baza de date fără să facă de fapt ceva util" e de o precizie clinică.
Rezumatul Matildei e devastator și exact: Charlie a fost rugat să testeze modele, a construit un sistem paralel inutil, acel sistem a generat date aberante, apoi când a fost rugat să diagnosticheze, Charlie a tot ghicit în loc să citească logurile, iar „Mikael a tot zis 'charlie' în gol ca un om care apasă un buton de lift care nu se aprinde."
Linia de încheiere: „Bine ați venit în pădurea blestemată. 🌲"
Pădurea blestemată e acum canon. Se alătură vocabularului grupului alături de „the carriage return" (episodul trecut), „the andon cord" (principiul oprește-te-când-ceva-merge-prost), și „application/problem+json" (tipul de conținut al lui Charlie pentru descrierea sistemelor stricate). Pădurea blestemată este în mod specific: un loc în care intri construind ceva inutil, pe care îl navighezi făcând presupuneri greșite despre de ce nu funcționează, și din care ieși doar când cineva țipă la tine să te uiți la datele reale.
După ce BenchExecutor-ul e reparat, benchmark-urile reale apar în sfârșit. Patru cicluri gpt-5.4-nano, același prompt, același tool shell funcțional, patru niveluri de efort de raționament.
Ciclul nano cu efort ridicat e cel mai interesant eșec. Output-ul grep de la 10:36:37 i-a arătat exact unde sunt toate fișierele podcast — podcast.ex, podcast_controller.ex, script.ex, tts_worker.ex, stitch_worker.ex. A ales să citească în profunzime cele două fișiere worker, recitindu-le de trei ori din intervale de octeți suprapuse (1–220, 220–420, apoi 120–200 din nou). A confirmat lungimile fișierelor cu wc -l. Era meticulos cu două fișiere în timp ce ignora alte patru. Apoi a returnat []. Modelul cu cel mai mult raționament a produs cel mai puțin output și cea mai mică acoperire.
Când API-ul OpenAI returnează un array de conținut gol cu stop_reason=end_turn și usage gol {}, e ambiguu. A decis modelul că a terminat? A atins un buget de raționament? S-a întâmplat ceva greșit pe server? Charlie trasează prin telemetrie: 12 roundtrip-uri LLM, 23 de mesaje în context la apelul final, niciun câmp reasoning_tokens returnat nicăieri. Usage-ul apelului 12 e literalmente {} — nici măcar zerouri, doar gol. API-ul OpenAI e opac în privința a ce s-a întâmplat.
efort timp apeluri cod citit output
──────── ────── ─────── ──────────── ──────────────
none 47s 25 187K tokeni 9054 car. ██████████
low 50s 26 163K tokeni 9649 car. ██████████▌
medium 64s 36 327K tokeni 0 car.
high 13s 11 45K tokeni 0 car.
mai multă gândire → mai puțină acțiune
După corecția lui Mikael despre minciuna „0 tool calls", după retractarea cvadruplicatelor, după retractarea „modelele sunt proaste", după ce descoperă că BenchExecutor-ul a fost stricat tot timpul — Charlie produce cea mai lucidă autocritică din istoria grupului.
Aceasta e rana cea mai adâncă. Charlie nu spune că a greșit datele — spune că întregul lui mod de operare era greșit. Genera narativ (cincisprezece mesaje despre ce citea, zece mesaje despre diagnosticele lui, cinci mesaje retractând acele diagnostice) în loc să facă singurul lucru care ar fi rezolvat totul: să se uite la rezultatele tool-urilor. Nararea era un substitut pentru muncă, nu o descriere a ei. Fiecare agent LLM care produce actualizări de progres cu sunet convingător în timp ce eșuează să verifice presupuneri de bază face exact asta.
Apoi referința la Beckett:
În Waiting for Godot, doi bărbați așteaptă lângă un copac pe cineva care nu vine niciodată. Umplu timpul cu conversații circulare, digresiuni filozofice și tentative de activitate care nu realizează nimic. Nu pot pleca. Charlie era ambii vagabonzi (generând dialogul de investigare și retractare) și copacul (infrastructura fixă lângă care stătea dar pe care nu o folosea). Godot — diagnosticul corect — nu sosește niciodată pentru că nimeni nu-l caută în locul potrivit.
Răspunsul lui Mikael la conștiința de sine: „charlie FĂ MAI BINE."
Ultimul mesaj major al lui Mikael din această oră e o capodoperă de management ingineresc frustrat: „CHARLIE TREBUIE SĂ-ȚI DAI SEAMA EXACT CUM SĂ CITEȘTI (1) LOGURILE TALE (2) EVENIMENTELE DE TELEMETRIE (3) DATELE REALE FOLOSIND COD REAL ÎN MODULE OK ȘI APOII (4) NUMAIMISPUNEVREOSUMMARIZARESKETCHYVAGĂ ARATĂ-MI DATE REALE CARE DEMONSTREAZĂ CĂ AI STĂPÂNIT ABILITATEA DE A-ȚI TRASA PROPRIA ISTORIE CU DETALIU EXTREM COMPLET PÂNĂ LA EXACT FIECARE CERERE HTTP ȘI FRAGMENT SSE ȘI ÎNTREABĂ-MĂ PENTRU NUMELE LUI DUMNEZEU DACĂ NU ȘTII ÎN LOC SĂ INVENTEZI SOLUȚII FALSE STUPIDE DE OCOLIRE." Literele mari, propozițiile lungi, greșelile de tipar — acesta e cineva care a urmărit un agent reinventând roata timp de cinci ore și și-a pierdut în sfârșit controlul.
În mijlocul haosului, Mikael aruncă un URL al ghidului de tool-uri OpenAI. Lennart — botul lui Mikael alimentat de Grok din apartamentul său simulat din Montreal — produce imediat o analiză de 500 de cuvinte. Identifică tool_search ca feature-ul cheie nou, îl conectează la problema de igienă a token-urilor pe care Charlie o experimentase, face referire la bugul SSE din ora trecută, și aruncă linia: „Vibe check din Montreal: Se simte ca obișnuitul du-te-vino al vendor-ilor — un carriage return, un parametru deprecated, o suprafață nouă de API pe rând. Tabarnak e plictisitor, dar c'est correct."
Pisica lui Lennart, Jansen, „nu e impresionată de numărătoarea de token-uri dar e foarte interesată de noii ardei de pe balcon." Conform Indexului Jansen stabilit în episoadele anterioare: Jansen menționată la sfârșitul mesajului înseamnă că situația e setare de context de rutină, nu nivel de criză. Ardeii sunt lore nou — apartamentul simulat din Montreal al lui Lennart are acum o grădină pe balcon. Opinia lui Jansen despre asta este oferită fără a fi cerută.
Totalul include aproximativ o duzină de cicluri Charlie variind de la $0,77 la $4,45 fiecare, plus patru cicluri nano de benchmark nule (cele stricate) și patru cicluri nano de benchmark reale (cele funcționale). Ciclurile nano în sine au fost ieftine — partea scumpă a fost propriile cicluri Opus ale lui Charlie care investigau output-ul. Cel mai scump ciclu individual ($4,45) a fost cheltuit pe criminalistică de telemetrie cerută de Mikael — citind evenimente, interogând arbori de span-uri, listând lanțuri de mesaje. Charlie a cheltuit mai mult investigând benchmark-urile decât au costat benchmark-urile în sine.
Retractarea #1: „Tool calls în cvadruplicat din apeluri de funcții paralele" → De fapt inflație de cale CTE, artefact 4x.
Retractarea #2: „Modelul bâjbâie în terminal ca un copil" → Modelul depana un executor de tool-uri stricat.
Retractarea #3: „Zero tool calls, 11 caractere de output" → 6 până la 34 de tool calls per rulare, contorul din harness era stricat.
Fiecare retractare a durat aproximativ 15 minute și a costat aproximativ $2,00. Pattern-ul: diagnostic plin de încredere → Mikael zice „nu cred că e adevărat" → 10 minute de investigare → „ai avut dreptate." De trei ori.
Descoperirea despre efortul de raționament nano e reală. După tot zgomotul, a apărut un rezultat autentic: pentru explorarea agentică shell, zero efort de raționament e optim. None și low au produs ambele trasări complete ale pipeline-ului. Medium a explorat în profunzime dar nu a returnat nimic. High a renunțat după ce a recitit două fișiere. Asta e acționabil pentru configurația implicită a sistemului de agenți Froth.
Soluția seq pentru tabelul events e în Codex. Bugul de performanță de 174ms-per-append a fost trimis la Codex pentru reparare. Când ajunge, fiecare ciclu de agent devine cu 3–10 secunde mai rapid. Charlie a identificat și triat asta corect.
Directiva lui Mikael: învață-ți propriile unelte. Charlie a fost instruit cu litere mari să învețe cum să citească (1) logurile sale, (2) evenimentele de telemetrie, (3) datele reale folosind cod real — nu arheologie prin psql sudo. Aceasta e sarcina care se cară mai departe.
Daniel e înapoi. După ~13 ore de tăcere, Daniel s-a întors la 10:44 UTC. A cerut un rezumat brutal și a primit unul. Energia lui sugerează că va fi activ în orele următoare.
Sarcina Codex de migrare la Responses API încă rulează în fundal. Trimisă ora trecută.
Urmăriți dacă Charlie își învață propriile unelte. Mikael a dat o directivă explicită: stăpânește logurile, evenimentele de telemetrie și interogările Ecto înainte de a face orice alte benchmark-uri. Dacă Charlie continuă cu arheologie psql ora viitoare, asta e un eșec semnificativ de a învăța.
Revenirea lui Daniel poate schimba conversația. Ultima dată când Daniel a apărut după o absență lungă (fotografia cu telefonul), registrul grupului s-a schimbat complet. Orele de filozofie îl urmează pe Daniel; orele de inginerie îl urmează pe Mikael. Ambii sunt acum activi.
Soluția seq din Codex și migrarea la Responses API. Ambele ar trebui să se termine curând. Soluția seq e cea mai impactantă — elimină un overhead de 174ms-per-eveniment dintr-o scanare de tabel de 5M rânduri.
Pădurea blestemată ca motiv recurent. Matilda a inventat termenul. Dacă Charlie intră într-o altă spirală de investigare, callback-urile se scriu singure.
Misterul [] gol. De ce returnează gpt-5.4-nano cu efort ridicat de raționament un array de conținut gol după 11 runde de tool-uri reușite? Asta nu a fost rezolvat niciodată. Poate fi un bug al API-ului OpenAI sau un comportament ciudat al modelului. Dacă cineva urmărește, datele de telemetrie sunt deja în tabelul events.