På denna sida
Den 21 september 2026, kl. 00.33 UTC, publicerade Tobi Lütke detta: ”MCP vs CLI för LLM:er är fel diskussion. Folk debatterar på fel lager.” Den 23 oktober pekade han sedan på ett nytt repository som heter dquickts. Det är en fork av QuickJS som kan pausa ett program mitt i en for-loop, skriva hela dess heap till bytes och återuppta det i en annan process. ”Utmärkt code mode-miljö”, skrev han.
Han har rätt om lagret. Jag vill först hålla med honom, med hans egna ord, och sedan snäva in påståendet tills det säger något som en synkronisering kan använda.
Lagret han menar
Här är argumentet som han framförde det, vilket är det enda rättvisa sättet att citera ett argument man tänker böja:
Båda fungerar otroligt bra så länge de körs genom en REPL-liknande miljö som kan bevara tillstånd. Det roliga med CLI är att CLI-verktyg oftast nås via BASH, som råkar vara en REPL med beständigt tillstånd (filsystemet), och därför fungerar CLI mycket bättre än MCP. Men det här är inte ens i närheten av att vara en inneboende egenskap hos MCP. Det betyder bara att vi behöver bättre harnesses.
Det stämmer, och det förklarar en iakttagelse som många har gjort utan att förklara den. En agent som får en CLI skriver en fil, läser den igen tre steg senare, greppar i den, pipar den. En agent som får en naken MCP-server får ett resultat per anrop och måste bära allt annat i sitt kontextfönster. CLI:n vann inte på protokoll. Den vann för att bash kom med en plats att lägga saker på.
Hans vadslagning följer av det. Någon bygger en inbäddningsbar motor i SQLite-stil som gör bash, TypeScript eller tool calls till en enda plan, kontrollerad innan den körs och byggd för beständig exekvering. dquickts är ett steg mot det. Dess README beskriver en REPL ”vars funktioner och variabler överlever omstarter”. Den beskriver också ett beständigt host call: ”ett skript anropar en värdfunktion vars svar kommer timmar senare. Processen sparar skriptet och avslutas.”
Så långt full enighet. Nu inskränkningen.
Vilket tillstånd, för vilket jobb
”Bevara tillstånd” döljer en fråga: vems tillstånd, och i vilken form? För en kodagent är tillståndet repositoryt och skalet, och en REPL som överlever omstarter är ett bra svar. För en agent som bygger en synkronisering mellan två SaaS-system är tillståndet den behöver mellan stegen redan definierat av jobbet. Fyra poster bär det, och ingen av dem är en variabel i ett skript.
Cursorn. En synkronisering läser en källa i fönster och måste veta var det senaste fönstret slutade. I Tajos synkroniseringsmotor är det en sparad cursor per synkronisering och omfång, skriven efter varje batch utan misslyckade poster, och nästa körning fortsätter därifrån. En schemalagd körning sparar sin startpunkt innan den gör något annat, så att en första körning som misslyckas låter nästa läsa från samma punkt. Principen bakom, skicka deltat och aldrig tabellen, finns i förklaringen av reverse ETL. Jag lämnar cursorn där.
Det väntande förslaget. I Tajos connector-MCP-verktyg är en skrivning två anrop. brevo_propose validerar indata mot den deklarerade åtgärden och registrerar ett förslag. Ingenting skickas. Resultatet innehåller ett förslags-id och, när en person måste besluta, sidan där personen godkänner det. brevo_apply kör de godkända skrivningarna för den anslutningen i ordning och stannar vid den första som fortfarande väntar på en person. Posten däremellan är tillståndet. Den innehåller indata och en digest av dem, så att en godkänd skrivning inte kan ändras mellan beslutet och apply. Den innehåller inte leverantörens svarsvärden, eftersom en append-only-godkännandepost inte får bli ett lager av personuppgifter.
Leasen. En körning av en synkronisering håller en exklusiv lease under hela sin livstid, med en ägartoken, en tidsgräns på femton minuter som standard, och förnyelse. En inaktuell ägare kan inte släppa en nyare lease. Om lease-lagret inte går att nå vägrar körningen att starta i stället för att gissa. Vilken worker som har körningen är ett faktum i leasen, inte i någon process minne.
Planen. syncs.dryRun läser nästa fönster från cursorn, avgör infoga, uppdatera, radera eller oförändrad per post och skriver exakt en rad: planen, som sparas i en timme. Den flyttar ingen cursor och skriver ingenting till något av systemen. syncs.apply skriver den planen en gång. Den vägrar om cursorn har flyttats, eller om mer än en tolererad andel av posterna har ändrats under tiden. En upprepning returnerar det första resultatet i stället för att skriva igen.
Det är tillståndet mellan stegen: fyra poster med id:n, läsbara för varje agent som får se dem.
Varför MCP-sidan inte sparar något i en session
När tillståndet väl ligger i posterna har en session inget kvar att hålla. Därför håller Tajos MCP-endpoints ingen. Varje begäran får en ny server och en transport utan sessions-id. Läsningar är en begäran per anrop, med begränsade resultat så att ett svar inte kan dränka en tur. Skrivningar är först förslag, sedan apply, och förslagets id är det du bär med dig.
Protokollet rörde sig åt samma håll. Revision 2026-07-28 av MCP-specifikationen tog bort sessioner på protokollnivå och headern Mcp-Session-Id från Streamable HTTP-transporten. En server som behöver kontinuitet skapar nu sitt eget handtag och får tillbaka det som ett vanligt argument. Det är precis vad ett förslags-id är. Vad är en MCP-server tar upp den revisionen och hur ett tool call skiljer sig från en beständig synkronisering; den här texten handlar bara om var tillståndet hamnar.
Konsekvensen är den som spelar roll för integrationsarbete. Säg att en agent i Claude Code föreslår en skrivning och att dess session tar slut. Personen godkänner den en timme senare. En agent i en annan klient, eller samma agent i morgon, anropar apply med id:t och skrivningen landar, en gång. En agent som kraschar förlorar sitt kontextfönster och inget annat. Ingenting pausades, så ingenting behövde återupptas.
Vad vi inte har, och vad det kostar
Den ärliga invändningen kommer från REPL-sidan och lyder så här: ”Visst, era poster överlever. Men varje anrop läser om det som förra anropet redan hade. Ett skript skulle hämta kontakterna en gång, joina dem, filtrera dem och returnera ett tal. Er agent gör fem anrop och betalar för vart och ett.” Den invändningen har rätt om mekanismen, och vi har inte mätt priset. Utvärderingen som skulle räkna anrop per uppgift med och utan medburet tillstånd är inte byggd. Tills den finns står påståendet att call mode kostar fler tokens obesvarat.
Ytterligare tre saker måste sägas rakt ut, eftersom en läsare som läser klart den här artikeln inte ska föreställa sig dem:
- Ingen kod skriven av kunder körs i dag. Connector-läsningarna är en begäran per anrop, utan sandlåda i vägen. Det finns en code runner i vår stack, och den är ett internt verktyg. I produktion är den avstängd om inte två separata flaggor är satta, och den svarar bara administratörer. Varje anrop får en ny kontext, bindningar som bara tillåter läsning och en budget på två sekunder för synkron kod. Dess egen header säger med versaler att den är en korrekthetsgräns, inte en säkerhetsgräns.
- Ingen REPL. Ingenting som en agent beräknar i ett anrop är tillgängligt för nästa, om det inte skrevs som en post med ett id.
- Ingen beständig agentsession. Klienten sparar sin konversation. Tajo sparar förslagen, planerna, cursorerna och leaserna. Det finns ingen tredje sak.
Vad en beständig motor är till för
Inget av detta är ett argument mot dquickts. Dess README listar vad snapshots är till för, och listan är bra. Låt ett inaktivt skript gå i dvala och frigör dess minne. Kör en lång beräkning i delar på den worker som är ledig. Varmstarta varje instans från en snapshot i stället för att upprepa uppsättningen. Parkera ett skript på ett host call vars svar kommer senare. För en kodagents arbetsyta, eller en notebook som ska överleva en omstart, är det rätt verktyg.
En synkronisering bör ändå inte vara beroende av en, av tre skäl som README:n själv ger mig. För det första: ”En sessionsfil kan bara laddas i den qjs-build som skrev den.” En heap som skrivits av en motorversion är en död fil efter en uppgradering; en cursor är en sträng. För det andra: ”Snapshots är betrodd indata, precis som bytekod”, och ”skadliga upptäcks inte”. En sparad heap är något du måste skydda, inte något du kan granska; ett förslag är en rad som en person läser innan hen godkänner det. För det tredje innehåller en snapshot allt som skriptet hade läst, inklusive kundposter. Posterna ovan är byggda åt andra hållet: planen sparar digests, inte värden, och förslagsposten sparar aldrig leverantörens svar.
En av reglerna vi bygger efter är att deterministiskt slår smart. En heap som återupptas mitt i en loop är smart. En cursor, ett förslag, en lease och en plan är tråkiga, går att inspektera och är desamma på varje motor, i varje klient, efter varje krasch.
Lütke ställde rätt fråga. Var bor tillståndet? För en synkronisering skulle svaret aldrig ligga i processen. Det skulle alltid ligga i posterna, eftersom det är där personen som godkänner skrivningen måste kunna läsa det.
Vanliga Frågor
Är MCP eller en CLI bäst för AI-agenter?
Ingen av dem i sig. Tobi Lütkes poäng från 21 september 2026 håller: båda fungerar så snart anropen går genom något som bevarar tillstånd mellan stegen. En CLI känns bättre i dag eftersom bash sparar tillstånd i filsystemet. Skillnaden ligger inte i protokollet, utan i harnessen.
Vilket tillstånd behöver en agent mellan stegen i en synkronisering?
Integrationens egna poster: en cursor som säger var den senaste körningen stannade, ett förslag som väntar på en persons godkännande, en lease som säger vilken worker som har körningen, och en dry-run-plan som apply måste stämma överens med. Inget av det är en variabel i ett skript. Det är rader med id:n.
Varför sparar Tajos MCP-server ingenting i en session?
Varje begäran får en ny server och en transport utan sessions-id. Allt som nästa anrop behöver är en post: ett förslags-id, ett plan-id, en cursor. Därför kan en andra agent, i en annan klient, fortsätta där den första stannade, och en agent som kraschar förlorar inget annat än sitt eget kontextfönster.
Kör Tajo kod som agenter har skrivit?
Inte för kunder. Läsningar via connector-verktygen är en begäran per anrop, utan sandlåda. Den code runner som finns är ett internt verktyg bakom två flaggor, som bara administratörer når, med skrivskyddade bindningar och en ny kontext per anrop. Den säger om sig själv att den inte är en säkerhetsgräns. Det finns ingen REPL och ingen beständig session.
Vad är en beständig JavaScript-motor som dquickts bra för?
Dess README nämner användningarna: låta ett inaktivt skript gå i dvala, dela upp en lång beräkning över workers, varmstarta från en snapshot och parkera ett skript på ett host call vars svar kommer timmar senare. Allt är verkligt. En synkronisering bör ändå inte vara beroende av en: en snapshot kan bara laddas i den motorbuild som skrev den, är ogenomskinlig för en granskare och innehåller all kunddata som skriptet läste.