Registrer deg Dashbord
21. september 2026

Hvordan vi hindrer at slop havner i produksjon

Explore with AI

Du bygger sannsynligvis enten en programvarefabrikk eller leier en fra en leverandør. Du har et system som tar deg fra en prompt til en pull request. Det håndterer tester, linting, formatering og automatiserte kodegjennomganger.

Men du har fortsatt ikke svar på det viktigste spørsmålet: er denne endringen trygg å sende til produksjon?.

graph TB
    W["Agent writes the code"] --> T["Types and tests"]
    T --> L["Linter and formatter"]
    L --> R["Code review"]
    R --> Q(["Is this okay for prod?"])
    Q --> D["Deploy"]
    style Q fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

Alle sjekkene du har i dag ser på diffen, men ingenting i fabrikken din vet noe om produksjonssystemet diffen skal inn i. Ingenting kan egentlig hindre at slop havner i produksjon.

Vi bygde denne funksjonen inn i Polylane, og dette innlegget handler om de tekniske detaljene i hvordan vi implementerte den.

Slik fungerer det

Det ene spørsmålet dette systemet skal svare på, er:

Ville denne endringen, når den er slått sammen og deployet, hatt en negativ innvirkning på produksjonen?

Vi bryr oss egentlig ikke om de typiske tingene en kodegjennomgangsagent ville sjekket, som stil, navngiving, testdekning osv. Og vi bestemte oss for å svare på dette spørsmålet på pull request-stadiet, side om side med alle testene dine fra før.

Til slutt kommenterer Polylane på pull requesten med en enkel «go» / «no-go»-melding, sammen med belegget fra undersøkelsen.

Flyten er ganske enkel:

  • Rører denne pull requesten filer som kan påvirke produksjonen?
  • Hvilke skyressurser er potensielt påvirket?
  • Samle kontekst om nåværende tilstand i produksjon for disse ressursene
  • Vurder flere potensielle feilmodus denne endringen kan introdusere
  • Forutsi hvordan produksjonen kan endre seg når disse endringene er deployet
  • Varsle utviklere om de sannsynlige potensielle feilmodusene
polylane bot commented 2 minutes ago ···
Caution

Merging this pull request may degrade production (high impact).

Merging this blocks every write to orders while the index builds. migrations/0114_order_search_trgm.sql:3 adds CREATE INDEX … USING gin (search_text gin_trgm_ops) without CONCURRENTLY, and a plain CREATE INDEX takes a full write lock on orders for the whole build. Checkout sustains ~38 writes/s on that table; each one queues behind the lock until the build finishes.

To make this safe: build the index with CREATE INDEX CONCURRENTLY outside the transactional migration.

orders-db · writes per second · last 48h
0 20 40
-48h -24h now
every one of these writes blocks while the index builds
En no-go-konklusjon: mekanismen i første linje, beviset under den, og hva som ville gjort endringen trygg.

Alt dette er avhengig av kontekstgrafen vi kontinuerlig bygger, som kobler sammen alle skyressursene i de ulike skykontoene dine.

graph TB
    P["Open pull request"] --> Q(["Meaningful code change?"])
    Q -->|"docs, tests, comments"| C["Conclude the check"]
    Q -->|"yes"| R["Find affected resources<br/>in the context graph"]
    R -->|"none"| C
    R -->|"yes"| E["Gather context"]
    E --> X["Build failure trajectories"]
    X --> F["Forecast the affected series"]
    F --> V["Verdict"]
    V --> C
    style X fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style F fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style V fill:#d1fae5,stroke:#6ee7b7,color:#065f46

Sette sammen konteksten

Kontekstgrafen vår er nøkkelen til at dette fungerer. Den bygger et register over alle skyressursene dine, alle repositoriene dine, team og så videre. En compute-node som en Lambda-funksjon er for eksempel koblet til databasen den leser fra og køen som trigger den. Vi legger også repositorier til grafen. Dette gjøres ved å se på de typiske manifestfilene i repositoryet, for eksempel terraform-filer, Cloudformation-filer eller Wrangler-filer. Dette gjør det mulig å koble repositorier til skyressurser.

graph LR
    Q["SQS queue<br/>orders-events"] -->|"triggers"| A["Lambda function<br/>checkout-api"]
    R["Repository<br/>checkout-edge"] -->|"deploys_to"| A
    A -->|"connects_to"| D["RDS instance<br/>orders-db"]
    style R fill:#d1fae5,stroke:#6ee7b7,color:#065f46

Når en pull request sendes inn til repositoryet, følger vi stiene i kontekstgrafen for å samle inn alle skyressursene som potensielt er påvirket av endringen. Vi bruker en liten modell til å filtrere gjennom ressursene, siden det kan være et stort antall ressurser deployet fra samme repository. Vi sender denne konteksten sammen med diffen, PR-beskrivelsen og commitene i PR-en til agenten.

graph LR
    R["Repository"] -->|"deploys_to"| C["Candidate resources"]
    C --> F(["Small model filters"])
    F --> A["Agent"]
    D["Diff, description, commits"] --> A
    style A fill:#d1fae5,stroke:#6ee7b7,color:#065f46

Vi sender også diffen gjennom et sett med deterministiske heuristikker for raskt å styre agentens oppmerksomhet mot ting som vanligvis er sannsynlige å ha negativ innvirkning på produksjonen:

  • en migrasjon som må kjøres for hånd, eller i en bestemt rekkefølge i forhold til deployen
  • CREATE INDEX uten CONCURRENTLY, eller ADD COLUMN ... NOT NULL uten standardverdi, som begge låser tabellen for varigheten
  • et endepunkt som fjernes mens den nåværende deployede versjonen fortsatt leser det
  • kode som begynner å lese en miljøvariabel, hemmelighet eller binding som ingenting i diffen provisjonerer

Dette er anbefalinger som styrer agenten mot sannsynlige deployrisikoer.

Feiltrajektorier

Med konteksten gitt ovenfor, kommer modellen opp med ulike feilmodus den nye diffen kan introdusere i produksjon, og undersøker hver av dem.

Resultatet er en oversikt over feiltrajektorier. En trajektorie er en kausal kjede fra en trigger, gjennom den endrede koden, til en observerbar forringelse i en bestemt metrikk, og hver lenke i den har en referanse: en fil og linje, en loggmal med telling, en metrikkverdi, en konfigurasjonsnøkkel, en kant i grafen.

Agenten prøver både å bekrefte og avkrefte hver trajektorie før den kommer til en konklusjon. Hver ender i en av tre tilstander:

  • confirmed når trajektorien er bekreftet mot produksjon.
  • plausible når kjeden er konkret, men en eller flere lenker bare kunne «gjettes» uten telemetridata som bekreftet det.
  • refuted når telemetridata fra produksjon ga nok bevis til at denne trajektorien er lite sannsynlig å skje i produksjon.

Vi tar en konservativ tilnærming, og enhver confirmed-trajektorie fører til en avvist vurdering.

export function deriveTrajectoryVerdict(trajectories: Trajectory[]): "pass" | "fail" {
  return trajectories.some((entry) => entry.status === "confirmed") ? "fail" : "pass";
}

graph LR
    D["The diff"] --> T1["Dropped index still<br/>used by checkout"]
    D --> T2["Retry change amplifies<br/>load on orders-db"]
    D --> T3["Removed binding<br/>breaks the worker"]
    T1 --> C1["confirmed"]
    T2 --> C2["plausible"]
    T3 --> C3["refuted"]
    C1 --> V["No-go"]
    C2 --> V
    C3 --> V
    style C1 fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style C2 fill:#dbeafe,stroke:#93c5fd,color:#1e3a8a
    style C3 fill:#d1fae5,stroke:#6ee7b7,color:#065f46
    style V fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

Forutsi konsekvensen

Vi gir agenten et verktøy til å forutsi tidsserier basert på historiske data, med mulighet til å injisere potensielle eksterne faktorer.

Si at en trajektorie hevder at en endring halverer timeouten på en sti som gjør nye forsøk. Om det spiller noen rolle, avhenger av trafikken, og trafikk er egentlig ikke et enkelt tall, den har en form. En kø som ligger på 60 % dybde og blir værende der, er greit. Den samme køen på 60 %, men som klatrer hver uke, er en annen situasjon, og å lese den siste timen med metrikker vil ikke fortelle deg hvilken av dem du ser på.

Så før agenten utkaster konklusjonen for en trajektorie, henter den den historiske serien for de påvirkede ressursene og forutsier dem sammen. Vi selv-hoster for øyeblikket Toto-2.0-22m-modellen.

graph LR
    A["Agent"] -->|"telemetry query"| P["Provider"]
    P -->|"aligned series"| A
    A -->|"up to 16 series"| M["Forecasting model"]
    M -->|"p10, p50, p90"| A
    style M fill:#fef3c7,stroke:#fcd34d,color:#78350f

Grunnen til at vi bruker en dedikert multivariat modell fremfor enda en prompt, er at seriene ikke er uavhengige. Forespørselsrate, feilrate, ventetid og kødybde for én ressurs beveger seg sammen, og å forutsi hver for seg kaster bort korrelasjonen som gjør prognosen verdt å ha. Hver serie i et kall deler én attention-gruppe.

Dette er den mest eksperimentelle funksjonen vi nylig har lagt til, og vi måler fortsatt innvirkningen. Men den består allerede vibes-evalen.

checkout-api request forecast
Hourly observations from the latest 64 complete buckets, followed by a 24-hour probabilistic forecast.
observed p10–p90 forecast
20,000 25,000 30,000 35,000 Forecast starts Sep 18 00:00 Sep 18 20:00 Sep 19 16:00 Sep 20 12:00 Sep 21 08:00 Time (UTC) Requests per hour
Figur 2
Et eksempel på hvordan prognosen fungerer
64 timevise observasjoner av én påvirket ressurs går inn, og 24 timer med median og p10-p90 kommer ut. Båndet blir bredere med horisonten, og agenten leser båndet i stedet for bare medianen: en p10 som holder seg over terskelen en trajektorie er avhengig av, er et annet svar enn en som krysser den.

Latensbegrensningen

Å kjøre produksjonskonsekvensvurderingen i pull request-flyten betyr at den må være rask. Ingen vil ha et steg som legger til 15 minutter i CI-pipelinen sin. På våre egne repositorier er for eksempel denne vurderingen et påkrevd CI-steg. Er den treg, stopper hele SDLC-en vår opp.

Vi har i praksis et budsjett på 2 til 3 minutter for å produsere en nøyaktig konsekvensvurdering. Alt utover det er ikke akseptabelt i en CI/CD-pipeline.

Vår første prototype feilet fullstendig på denne begrensningen. Medianen vår var på nesten 7 minutter, og det var vanlig å se kjøringer ta opptil 20 minutter.

0 s 60 s 120 s 180 s 240 s 300 s Innledning webhook, poster, diff 7.4 s Sandkasse klone head 16.5 s Gjennomgangsrunde 242.4 s Levering sjekkjøring, kommentar 3 s Ukjent 38.6 s
Figur 3
Median-latens for hvert steg i kjøringen av produksjonskonsekvensvurderingen
Produksjonsmedianer, 15. september 2026, over 325 kjøringer. Hver fase har sin egen median, så den ukjente blokken på slutten er ventetid i kø mellom fasene pluss aritmetikken i å legge sammen medianer.

Målet ble å finne ut hvordan vi kunne redusere antallet modellsteg for å få hele flyten under 3 minutter. Vi kjørte typisk 30 til 40 sekvensielle modellsteg, og i verste fall opptil nesten 600 steg, hvert forbrukende 17 sekunder av budsjettet vårt, og størstedelen av outputen deres var reasoning-tokens.

Vi gjorde en rekke endringer i løpet av de siste 3 ukene, med ulik innvirkning på ytelsen. Her er de mest betydningsfulle.

Sette et stegbudsjett og kommunisere det til agenten

Vi innførte et stegbudsjett, satt til maks 30 steg per agentrunde, og vi kommuniserer tydelig til modellen hvor mange steg den har brukt på hvert steg. Å sette antallet i prompten lar modellen planlegge rundt det, noe som viser seg å bety mer enn selve tallet.

graph TB
    A["Turn starts, 30 steps"] --> B["Model step"]
    B --> T["Tool call"]
    T --> C(["Steps left?"])
    C -->|"1"| E["Record the verdict now"]
    C -->|"more"| R["Next step, carrying<br/>the count in the prompt"]
    R --> B
    style R fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style E fill:#d1fae5,stroke:#6ee7b7,color:#065f46

Starte nye gjennomganger i stedet for å flette inn i den eksisterende

En typisk pull request fortsetter å få nye commits etter at den er åpnet. I den første prototypen flettet vi hele den nye diffen inn i den samme gjennomgangstråden som en styrende brukermelding. Denne styringen forvirret modellen kraftig og førte til at den leste filer den allerede hadde undersøkt, og kjørte telemetrispørringer på nytt som den allerede hadde fullført.

Nå avbryter en ny commit den forrige vurderingskjøringen og starter en helt ny tråd. Det virker kontraintuitivt, men det senket til slutt latensen for komplette gjennomganger.

Gjenbruke tidligere arbeid

Når en ny commit havner i pull requesten etter at en gjennomgang allerede var fullført, pleide vi naivt å starte en ny konsekvensvurdering fra bunnen av.

Vi innførte muligheten til å gjenbruke den forrige vurderingen, og utløse en ny vurdering på den mindre diffen mellom de to påfølgende commitene.

Vi deployet disse endringene gjennom september og har gradvis forbedret latensen, og latensen ligger nå komfortabelt innenfor budsjettet.

0 s 150 s 300 s 450 s 600 s 27 Aug 31 Aug 4 Sep 8 Sep 12 Sep 16 Sep 94
Figur 4
Median latens for en konsekvensvurderingsrunde

Betyr dette egentlig noe?

Hva er poenget med å brenne alle disse tokenene hvis vi ikke ser noen meningsfulle resultater? Suksessmetrikken vår er antall hendelser vi forhindrer per uke for hver av kundene våre. En forhindret hendelse er en pull request der:

  • vi flagger en potensiell risiko for produksjonen
  • en ingeniør pusher én eller flere commits
  • en ny vurdering konkluderer med at den potensielle risikoen er redusert
  • pull requesten blir slått sammen
2.3
Produksjonshendelser forhindret, per kunde, hver uke

Dette er allerede ganske betydelig, og vi forventer at det vil fortsette å vokse. Vi itererer kontinuerlig og kjører evalueringer for å forbedre ytelsen og kvaliteten på agenten vår.

Ingen burde ha vakt. Polylane følger med på infrastrukturen din, undersøker og fikser det som går i stykker.

Registrer deg

Fortsett å lese