Hukommelsesfejlen gemt i en fremskridtslabel
En aften i slutningen af september gik AI-agenten i en brugers workspace ned med en fejl om for lidt hukommelse midt i en tur. De oplagte mistanker — for mange sub-agenter kørende på én gang, eller en kontekst, der var vokset for stor — var begge forkerte. Den rigtige årsag var en statuslabel: mens modellen streamede en stor shell-kommando nogle få tegn ad gangen, sendte vi hele den kommando indtil nu igen som fremskridtslabel ved hvert stykke. En kommando på 124 KB blev til omkring 41.000 hændelser og cirka 690 MB køede strenge.
Sådan streamer agenten sit arbejde
Når du bygger på Mythex, kører agenten inde i dit projekts private cloud-sandkasse. Mens modellen skriver, streamer dens output tilbage til din browser: tekst dukker op ord for ord, og når den kører et værktøj — for eksempel en shell-kommando, der skriver en fil — ser du en label for det, der opdateres live, så du ved, hvad den laver.
Modellen streamer i små stykker. Med den model vi kørte, var stykkerne ofte kun tre tegn lange.
De første hypoteser var forkerte
Nedbruddet lignede et skaleringsproblem, så vi startede der.
- Parallelle sub-agenter? Turen havde kun brugt to, hver med omkring 1,5 MB tilstand. Ikke nok.
- Gemte spor? Vores tracing holdt et kørselstræ i hukommelsen, men det var langt fra størrelsen af heapen ved nedbruddet.
Begge var plausible historier, og begge var billige at tro på. Ingen gengav nedbruddet. Lektien, vi må genlære: en hypotese er ikke en årsag, før den gengiver fejlen.
Gengivelse for alvor
Vi byggede en midlertidig sandkasse med de samme hukommelsesgrænser som produktion og en falsk model, der streamede en stor fil i realistiske stykker på tre tegn. Vi gjorde også læseren i den anden ende af socket'en langsom, som et rigtigt netværk.
Det virkede. Heapen steg, og processen døde med samme fejl.
Mekanismen var enkel, når vi kunne se den. Mens modellen skrev en stor kommando — her en heredoc, der skrev en fil på 124 KB — opdaterede vores streaming-kode værktøjets label ved hvert stykke. Labelen var ikke de nye tre tegn; den var hele kommandoen indtil nu. Så hændelserne voksede: 3 tegn, så 6, så 9, … op til 124 KB, omkring 41.000 gange. Socket'en kunne ikke sende dem så hurtigt, som de blev produceret, så de hobede sig op i dens skrivebuffer som strenge — omkring 690 MB af dem.
Rettelsen
- Begræns labelen. En statuslabel skal bare sige, hvad der sker. Den er nu begrænset til 200 tegn, og vi begrænser, hvor ofte den delvise kommando scannes igen.
- Stop kvadratisk sammenkædning. At sætte streamede stykker sammen med gentagne strengsammenkædninger er O(n²) for store output. Vi gik over til en chunk-baseret tilgang, der tilføjer i lineær tid.
Derefter ledte vi efter andet, der kunne vokse uden grænse ved en flod af output, og satte grænser for det:
- Live kommando-output vist i chatten samles og holdes til de sidste 8.000 tegn.
- Individuelle hændelseslinjer er begrænset til 4 MB.
- Replay-bufferen, en browser bruger til at indhente efter genforbindelse, er begrænset til 8 MB pr. projekt.
- Gamle checkpoints beskæres, mens turen kører, og beholder den nyeste tilstand og de ankre, tidligere ture har brug for.
At teste floder ærligt
To ting gjorde dette sværere at teste, end det burde være:
- Rigtige modeller samarbejder ikke. Beder du en model om at skrive en enorm fil, vil den typisk nægte, opsummere eller skrive den mere fornuftigt. For at teste floder har du brug for en scriptet model, der gør præcis det, du siger, kørende gennem den rigtige agentkode.
- Scripts skal opføre sig som modeller. Vores første scriptede model genbrugte det samme svar-ID ved hvert svar, så agentrammeværket behandlede hvert svar som en erstatning for det forrige og afsluttede ture for tidligt. Det lignede en fejl i UI-opdateringen. Det var ikke — det var testharnessen, der løj for os. Vi var også nødt til at håndhæve modellens reelle kontekstgrænse i scriptet, ellers var hukommelsestallene urealistiske.
Det vi tog med
- Streamende UI'er forstærker små fejl. Alt, du sender "pr. stykke", bliver sendt titusindvis af gange. Send deltaer, eller begræns nyttelasten.
- Begræns alle buffere, der krydser en langsom grænse. Sockets, replay-logs og checkpoints har alle brug for et loft.
- Gengiv før du retter. Vores to første forklaringer lød rigtige og var forkerte.
Hvis du for nylig har set en meget lang fil dukke op i Mythex-chatten uden at agenten gik i stå, er det en del af forklaringen.