Ugrás a tartalomra

Két AI-ügynök, egy repó, és egy GitHub issue-tábla, ami összetartja az egészet

Hogyan futtattam két AI kódoló ügynököt párhuzamosan ugyanazon a kódbázison merge-konfliktusok és félbehagyott branchek nélkül: GitHub Issues mint munkatábla, egyetlen egyszerű fájltulajdonlási szabály, és egy ember a merge-kapunál.

Aierizer Samuel6 perc olvasás
Illusztráció egy issue-tábláról teendő, folyamatban, átnézés alatt és blokkolt oszlopokkal; minden feladaton a prioritása és a hozzárendelt AI-modell címkéje

Volt egy Rails alkalmazásom egy problémalistával. Egy kódellenőrzés a szokásos keveréket hozta felszínre: néhány valóban ijesztő biztonsági rést, pár lassú lekérdezést, egy halom elnevezési következetlenséget, és azt a fajta halott kódot, amit mindenki kikerül. Semmi egzotikus. Csak több annál, mint amennyit kézzel át akartam rágni, és pont az a fajta munka, amit könnyű elkezdeni és nehéz befejezni.

AI-ügynököket akartam ráengedni. Az aggodalom kézenfekvő volt: engedj rá néhány ügynököt ugyanarra a kódbázisra, és merge-konfliktusokat, félbehagyott brancheket kapsz, meg két botot, amelyek magabiztosan írják át ugyanazt a fájlt ellentétes irányba. Így mielőtt egyetlen sor kódot is írtam volna, egy délutánt azzal töltöttem, amit általában kihagynak: azzal, hogy a munkát hogyan hangoljuk össze, nem pedig azzal, hogy hogyan végezzük el.

Leírom, hogyan raktam össze. Nem keretrendszer, de elég jól működött ahhoz, hogy érdemes legyen leírni.

A munkatábla egyszerűen a GitHub Issues

Az első döntés az volt, hogy nem építek orchesztrátort. Csábító megírni egy ütemezőt, ami feladatokat oszt ki, állapotot követ és ügynököket indít. Egy behatárolt rendbetételhoz ez önmagában egy projekt, és tovább tartott volna, mint maguk a javítások.

Ehelyett az igazság forrása a GitHub Issues. Egy issue munkaegységenként. Egy kis címkekészlet végzi az összehangolást:

  • prioritás (p0-tól p3-ig): mi a tényleg sürgős
  • állapot (todo, wip, review, blocked): a folyamat melyik szakaszában jár
  • modell (opus, sonnet, glm): melyik ügynök vegye fel
  • sáv (fix, feature): mennyire nehéz folyamat kell hozzá

Egy feladat akkor felvehető, ha todo, a függőségei le vannak zárva, és egyetlen nyitott pull request sem érinti a fájljait. Ez az egész szabály. Nincs szerver, ami figyeli a táblát és osztja a feladatokat. Én olvasom a címkéket és indítom a megfelelő eszközt. Primitívnek hangzik. Ez az a rész is egyben, amit megtartanék, ha minden mást eldobnék, mert a táblát ember és bármelyik ügynök is el tudja olvasni, és az állapot mindig megfelel a valóságnak.

A munka statikus térképe (melyik egység melyik fájlt birtokolja, mi mitől függ) egy egyszerű markdown fájlban él a repóban. Az aktuális állapot a GitHubon van. Ez a két dolog szétválasztása többet számított, mint gondoltam. A térkép nem változik munka közben; a tábla igen.

Az egyetlen szabály, ami biztonságossá teszi a párhuzamosságot

Minden más egyetlen szabályon múlik: minden forrásfájlt egyszerre legfeljebb egy nyitott pull request birtokol.

Ha két ügynök sosem nyúl ugyanahhoz a fájlhoz, nem tudnak konfliktusba kerülni, és tetszőleges sorrendben átnézhetem a PR-jeiket. Így az előzetes munka az volt, hogy a problémalistát olyan egységekre vágtam, amelyek nem fednek át fájlokban. A tömeges törlés controllereinek biztonsági javítása egy egység. Az események jogosultságkezelése egy másik. A keresőszolgáltatás egy harmadik. Mindegyik megnevezi a pontos fájlokat, amelyekhez hozzányúlhat, és ellenőriztem, hogy ezek a halmazok ne metsszék egymást, mielőtt bármit párhuzamosan futtattam volna.

Erre épül egy függőségi gráf. Egy átnevezést elsőként kellett integrálni, mert olyan metódusneveket változtatott meg, amelyeket a kódbázis fele hívott, így minden más mögötte várt. Ezután egy sereg egymástól független javítás egyszerre futhatott. A gráf megrajzolása talán húsz percbe telt, és megmentett minden „miért van tele konfliktussal ez a branch“ beszélgetéstől.

Claude Code és opencode, ugyanarra a táblára állítva

A két eszköz sosem beszél egymással, és ez szándékos.

A Claude Code-ot arra a munkára használtam, ahol drága tévedni: a jogosultsági résekre, az adatvesztéses hibákra. Az opencode-ot egy olcsóbb modellel a kis tétű finomításra: néhány keresési joker karakter escape-elésére, halott gyorsítótár-kód törlésére, időbélyegek egységesítésére. Az útválasztás egy címke az issue-n, semmi több. A Claude nem tudja elindítani az opencode-ot, az opencode sem a Claude-ot, és nem is akartam, hogy tegyék. Mindkettő ugyanazokat az issue-kat olvassa, ugyanazt az életciklust követi, és ugyanabba a repóba nyit pull requestet.

Minden feladat a saját git worktree-jében fut, saját teszt-adatbázissal, így két ügynök, amely egyszerre futtatja a teljes tesztkészletet, nem rontja el egymás adatait. A worktree-k a git beépített részei, és folyton elfelejtem, hogy léteznek; ehhez viszont pont ez teszi rendetlenség helyett kezelhetővé a „három branch egyszerre folyamatban“ helyzetet.

Az életciklus, amin minden feladat átmegy, szándékosan unalmas: vedd fel az issue-t, ágazz le, olvasd el a kódot és írj egy bukó tesztet, ami a helyes viselkedést állítja, tedd zöldre, refaktorálj, futtasd a lintert és a biztonsági szkennert, futtasd a teljes CI-t helyben, nyiss egy pull requestet, állítsd az issue-t review-ra. Aztán megáll. Az ügynök nem merge-el.

A zöld tesztek hazudnak, és más dolgok, amiket újratanultam

A leghasznosabb elv az lett, hogy egy zöld tesztkészlet nem jelenti azt, hogy a kód helyes. Több biztonsági hibának végig zöld volt a tesztje. A tesztek a hibás viselkedést kódolták: azt állították, hogy egy törlés sikeres volt, anélkül, hogy megkérdezték volna, szabad volt-e egyáltalán annak, aki törölt. Így a biztonsági javítások szabálya az volt, hogy az új tesztnek előbb buknia kell, méghozzá azért, mert a régi viselkedés hibás volt. Ha nem tudod megbuktatni, akkor még nem érted a hibát.

A folyamat pár olyan dolgot is felszínre hozott, aminek semmi köze nem volt a javításokhoz, csak a csővezetékhez. A lefedettségi kaput beállították, de sosem érvényesítették ténylegesen, így az első őszinte futás elbukott olyan fájlokon, amelyeket hónapok óta senki nem tesztelt. A biztonsági szkenner burkoló szkriptje kikényszerített egy „ellenőrizz újabb verziót“ kapcsolót, ami azért buktatta a CI-t, mert létezett egy javítókiadás fentebb, nem azért, mert talált valamit. Egy böngészőteszt pedig talán az esetek felében bukott, de csak a teljes készletben, önmagában sosem. Kiderült, hogy egy pillanattal azelőtt kattintott egy gombra, hogy az azt működtető JavaScript betöltődött volna.

Egyik sem volt izgalmas munka, de mindegyik elpazarolta volna minden jövőbeli ügynök idejét, mert mindegyiknek újra fel kellett volna fedeznie: „ez a bukás az enyém, vagy már eleve el volt törve?” Az alap egyszeri rendbetétele többet ért, mint bármelyik önálló funkciójavítás.

Az ember továbbra is a szűk keresztmetszet, szándékosan

Én nézek át és merge-elek minden pull requestet. Ez az egész szándékos felső korlátja. A párhuzamosság nem segít azon a ponton túl, ameddig át tudom nézni. Három ügynök csak annyit jelent, hogy hosszabb a rám váró PR-sor. Így egyszerre kettőt-hármat futtatok, nem tízet. A felállásban, amit használok, nincs branch védelem, ami azt jelenti, hogy a merge-kapu szó szerint az, hogy nem merge-elek semmit, ami piros vagy átnézetlen. Ez rendben van. A folyamat feladata nem az, hogy engem kiiktasson; hanem az, hogy amikor a munka hozzám ér, már tesztelt, szkennelt, és olyan fájlokra korlátozott legyen, amelyeket egy ültő helyemben át tudok látni.

Mennyibe került

Alig valamibe a modellhívásokon túl. A GitHub Issues, a címkék és a pull requestek ingyenesek. A gh CLI már fent volt. A git worktree-k a git részei. A tesztfuttató, a linter és a biztonsági szkenner már be volt drótozva a projektbe. Az egyetlen új költség a tokenek, és az útválasztás a drága modellt arra a munkára tartja, ami megéri, míg az olcsó intézi a takarítást.

Röviden: mielőtt elkezded, döntsd el, melyik fájl melyik pull requesthez tartozik, tartsd az állapotot olyan helyen, amit te és az ügynökök is látnak, és a merge-nél maradj te a döntő. A kódírást az ügynökök könnyen elvégezték. Az összehangolásukon kellett igazán gondolkodni.

Van egy elmaradása, amire sosem jut idő?

Egy elöregedett kódbázis rendbetétele, egy biztonsági és takarítási lista lezárása, vagy elköltözés egy olyan platformról, ami lassítja. Pontosan ez az a munka, amiben segítek a kis- és középvállalkozásoknak. Őszintén felmérem, végig tájékoztatom, és csak azt vállalom el, amit tényleg jól meg tudok csinálni.

Meséljen a projektjéről