Kortversjonen
- AI-kodeagenter er flinke til å legge til kode og dårlige til å legge merke til at koden allerede finnes. Overlatt til seg selv gir de et repo en andre hjelpefunksjon for hver første, og enhver leser må sjekke begge for å stole på noen av dem.
- Løsningen er to vaner skrevet ned slik at en agent følger dem. Fiber: finn det nærmeste eksisterende eksempelet på samme type ting og speil formen, plasseringen og navnene. Ponytail: klatre opp en stige fra “trenger dette å eksistere” til “det minimale som fungerer”, og stopp på det første trinnet som holder.
- Begge er nå en offentlig ferdighet med MIT-lisens: grain-skill på GitHub . Den inneholder forfatterreglene, et gjennomgangspass med faste tagger, og formen på avslutningslinjen som får agenten til å navngi det den speilet.
- I hovedrepoet vårt siden august: 386 saker og pull requests bærer en plankommentar som navngir eksempelet som skal speiles, på tvers av 1 333 commits inn i et tre på rundt 18 000 filer. 35 steder i koden bærer en
ponytail:-kommentar som navngir en bevisst forenkling og dens oppgraderingsvei. - Det ubehagelige funnet: agenten påkalte ferdigheten som ferdighet null ganger. Reglene virket fordi jeg også la dem i prosjektets CLAUDE.md, i worker-agentdefinisjonene og i gjennomgangssjekklisten. Legg regelen der agenten allerede leser.
Jeg skrev om dette én gang før, og jeg likte ikke resultatet, fordi det beskrev prinsippet og ikke mekanismen. Dette er versjonen med mekanismen.
Feilen dette er ment å rette
Tre eksempler fra backend-en vår, alle ekte, alle fra agent-skrevne pull requests som besto testene sine.
En ny REST-rute som hentet applikasjonstjenesten direkte fra en fabrikk. Enhver søskenrute i repoet går rute til kontroller til tjeneste til repository, der hvert hopp injiseres. Denne kollapset kjeden til rute til tjeneste. Den virket. Den betydde også at det ene stedet der forespørselsvalidering og responsforming lever for den funksjonen, ikke fantes, og den neste agenten som rørte funksjonen, hadde to former å velge mellom.
En for stor tjeneste delt opp i _payloads.py, _protocol.py og _tokens.py, hver en modul med bare async def-er, der tjenesten kalte protocol.discover(...) via import. Fil lengde-sjekken ble grønn. Det som avhengighetsinjeksjon finnes for å forhindre, kom rett tilbake: en samarbeidspartner valgt ved importtid, umulig å bytte ut uten å patche modulen, fraværende fra konstruktøren som dokumenterer hva tjenesten er avhengig av.
En andre normaliserer. Det fantes allerede en. Agenten fant den ikke, skrev åtte nye linjer som gjorde nesten det samme, og nå hevder to funksjoner med ulik kanttilfelle-oppførsel begge å kanonisere det samme feltet.
Ingen av disse er en bug i den forstand en test fanger. Hver av dem er en agent som velger koden den kunne generere, fremfor koden som fantes. Gjør det på tvers av noen hundre pull requests, og repoet slutter å ha en form.
Fiber
Ny kode skal være umulig å skille fra koden rundt seg. Ferdigheten gjør den setningen om til fem trinn en agent tar før den skriver.
Finn eksempelet først. For et endepunkt, en tjeneste, et repository, en spørringshook, et store-slice eller en komponent: lokaliser det nærmeste eksisterende og kopier formen, filplasseringen og navngivingen. Ikke finn opp struktur når et mønster finnes. I repoet vårt er branding-slicet det navngitte eksempelet for backend-en, fordi det er lite, rent og spenner over hvert lag, og ferdighetens tabell sier, for hver type artefakt, hvilken fil som skal åpnes og hva som skal speiles.
Respekter lagene. Presentasjon kaller applikasjon kaller domene, og infrastruktur implementerer domene. En ny import på tvers av disse linjene feiler arkitektursjekken. Les lagdokumentet, ikke utled det på nytt.
Lokal fiber mot repo-fiber. Match søsknene dine. Men hvis søsknene selv avviker fra den kanoniske kjeden, følg den kanoniske kjeden og noter avviket i stedet for å kopiere driften. “Den matcher de andre fem rutene i denne filen” er ikke nok hvis alle seks hopper over et lag som det kanoniske slicet bruker. Du sammenlignet for lokalt.
Gjenbruk den kanoniske hjelpefunksjonen. Hvis en hjelpefunksjon, type eller et mønster allerede finnes i repoet, bruk det. To normaliserere tvinger enhver leser til å diffe begge.
Navn bærer mening. Gjenbruk vokabularet som allerede finnes i koden. Et nytt navn på et eksisterende begrep er en bug som venter på en leser.
Testen til slutt er den jeg gir anmeldere: hvis du kan se hvilke linjer agenten skrev, bare ut fra formen deres, gikk den mot fibrene.
Ponytail
Lat betyr effektiv, ikke uforsiktig. Den beste koden er koden som aldri ble skrevet. Forstå problemet fullt ut først, hver fil endringen berører, den virkelige ende-til-ende-flyten, og klatre så opp denne stigen og stopp på det første trinnet som holder.
To regler følger med stigen.
En bugfiks er en rotårsak, ikke et symptom. En sak navngir et symptom. Grep hver kallende part av funksjonen du er i ferd med å røre. Én vakt i den delte funksjonen er en mindre diff enn en vakt i hver kallende part, og å bare patche stien saken navngir, etterlater søskenkallerne ødelagte.
Hold diffen minimal. Diffen er det gjennomgangen leser. Ingen drive-by-refaktoreringer, ingen omdøpinger, ingen omformatering, ingen “mens jeg først er her”-forbedringer, ingen berøring av filer oppgaven ikke krevde. Hver av dem begraver den virkelige endringen. Hvis diffen er større enn problemet, kutt den ned. Forbedringer du oppdaget, er en oppfølgingssak, ikke ekstra commits på denne grenen.
Og grensene, fordi “lat” er ordet som lettest misforstås. Forenkle aldri bort inputvalidering ved en tillitsgrense, feilhåndtering som forhindrer datatap, sikkerhet, grunnleggende tilgjengelighet, eller noe brukeren ba om. Vær aldri lat med å forstå: stigen forkorter løsningen, aldri lesingen. En liten diff på feil sted er en andre bug. Ikke-triviell logikk etterlater seg én kjørbar sjekk, det minste som feiler hvis logikken bryter.
Når agenten med vilje tar et trinn og kjenner takhøyden, sier den det i koden:
# ponytail: global lås, låser per konto hvis gjennomstrømning betyr noe
Det finnes 35 av dem i repoet vårt nå. Hver av dem er en beslutning en fremtidig leser ellers måtte rekonstruere, og en markør for hvor man skal se når taket nås.
Å få en agent til faktisk å gjøre det
Et prinsipp i et dokument er ikke en atferd. Tre mekanismer gjorde disse to om til atferd, og jeg vil rangere dem i denne rekkefølgen.
Kortet bærer eksempelet. I arkitektmønsteret skriver hovedmodellen et kort til en worker, og kortets Design-felt navngir det nærmeste eksisterende mønsteret som skal speiles og den minimale rotårsak-endringen. Workeren trenger ikke å finne fiberen. Den får den overlevert. På tvers av repoet vårt er det dette de 491 plankommentarene på 386 saker og pull requests er: et planleggingstrinn som navngir hva som skal speiles, før noen skriver.
Agenten avslutter med det den speilet. Hver implementerers rapport slutter med én linje, mirrored: <path>, eller navngir det nye mønsteret og hvorfor ingen søsken passet. Dette er billig, og det er den enkeltstående beste gjennomgangshjelpen jeg har lagt til. En mirrored:-linje som peker på feil type fil, forteller deg at diffen er feil før du åpner den. En som mangler, forteller deg at agenten ikke så seg om.
Gjennomgangen håndhever samme standard, i en fast rekkefølge. Gjennomgangsferdigheten vår har to pass med faste tagger, og det er taggene som gjør funn sammenlignbare på tvers av anmeldere og på tvers av agenter.
Struktur først. Avviker dette fra det nærmeste eksempelet, dupliserer en eksisterende hjelpefunksjon, krysser et lag, ligger på feil sted, griper inn i en annen moduls indre, bruker inkonsistente navn, tar eierskap til noe det burde injisere, eller bryter den kanoniske kallkjeden? Deretter kutt. Hva kan slettes, erstattes av standardbiblioteket, erstattes av en innebygd funksjon, droppes som spekulativt eller krympes?
Rekkefølgen er en regel, ikke en preferanse. Konsistens slår kutt. Et kutt som etterlater koden avvikende fra søsknene sine, er ingen seier, fordi leseren nå må lære to former for å spare åtte linjer. Kjør struktur først, og kuttpasset berører bare kode som allerede matcher.
Hva som skjedde i repoet, inkludert delen som ikke virket
Siden 1. august har hovedrepoet vårt tatt imot 1 333 commits, de fleste agent-skrevet under gjennomgang. 386 av sakene og pull requestene i den perioden bærer en plankommentar som navngir eksempelet som skal speiles, 491 slike kommentarer totalt. Den kanoniske kjeden er standardformen for et nytt endepunkt, og de to eldre rutene som hopper over kontrolleren, er listet i dokumentet som et avvik under avvikling, ikke som en andre stil.
De 35 ponytail:-kommentarene er et lavere tall enn jeg forventet, og jeg tror det er riktig. De fleste forenklinger er ikke verdt en kommentar. De som er det, er de som har en takhøyde.
Nå delen jeg helst ikke vil rapportere. Jeg sendte ut fiber og ponytail som en Claude Code-ferdighet: en beskrivelse i ferdighetsindeksen, en tekst som lastes ved behov, og en linje i CLAUDE.md som sier at den skal påkalles før kode skrives. På tvers av fem uker med transkripter påkalte agenten den ved navn null ganger. Avslutningslinjen mirrored: dukket opp i sju svar.
Reglene landet likevel. De landet fordi den samme teksten også sto på tre steder agenten leser uten å velge det: prosjektets CLAUDE.md, som er i konteksten fra første tur; worker-agentdefinisjonene, som sier “speil formen, plasseringen og navngivingen til det nærmeste eksisterende eksempelet” og “grip etter den lateste løsningen som faktisk fungerer” som regler, ikke som en ferdighet å vurdere; og gjennomgangssjekklisten, hvis tagger feller en pull request som ignorerer dem. Ferdighetsteksten ble den kanoniske referansen de tre peker på. Som utløser gjorde den ingenting.
Så leksjonen jeg vil gi videre til alle som pakker konvensjoner for en agent, er denne. En ferdighet er et godt sted for den lange versjonen av en regel. Det er et dårlig sted for selve regelen, fordi agenten må bestemme seg for å laste den, og stort sett vil den ikke. Legg én-avsnitts-versjonen i filen som alltid er i konteksten, legg imperativversjonen i definisjonen av agenten som skriver koden, og legg sjekken i gjennomgangen som styrer merge. Pek så alle tre mot ferdigheten for detaljene.
Den offentlige versjonen på github.com/LivinTribunal/grain-skill er lagt opp slik: en kort SKILL.md med reglene en agent kan holde i hodet, det fulle dokumentet med stigen og anti-mønstrene, gjennomgangstaggene, og en README som sier hvilket avsnitt som skal i din CLAUDE.md og hvilket som skal i implementerens definisjon. Bytt ut det gjennomarbeidede eksempelet med ditt eget kanoniske eksempel, og du er ferdig.

