From Vibe Coding to Contract-Driven AI Development
Vibe coding działa świetnie, dopóki projekt nie zaczyna rosnąć. Zobacz, jak łączyć mocne modele AI, tańsze modele, kontrakty i generatory kodu w skalowalny workflow.
From Vibe Coding to Contract-Driven AI Development
Jak wydawać najdroższą inteligencję na niepewność, a powtarzalność przenosić do kontraktów, generatorów i tańszych modeli.
Wyobraźmy sobie pozornie zwykły pivot.
Sklep, który dotąd sprzedawał standardowe produkty, zaczyna oferować roczne licencje cyfrowe. Po zaksięgowaniu płatności użytkownik powinien automatycznie otrzymać dostęp na 365 dni. Ponowne przetworzenie tego samego webhooka nie może wygenerować drugiej licencji.
Można przekazać takie wymaganie coding agentowi.
Przeczyta repozytorium. Znajdzie checkout, produkty, zamówienia i płatności. Zaproponuje model danych. Doda migrację, service, kilka endpointów i testy. Przy dobrym modelu jest spora szansa, że powstanie działające rozwiązanie.
Tylko że „napisz kod” jest tutaj najmniej interesującą częścią zadania.
Najpierw trzeba zdecydować, czym właściwie jest licencja w tym systemie.
Czy to osobny typ checkoutu?
Czy zachowanie produktu?
Czy entitlement powstaje bezpośrednio w callbacku płatności?
Co dzieje się po awarii?
Jak retryujemy operację?
Gdzie gwarantujemy idempotencję?
Czy zamówienie powinno wskazywać aktualny produkt, czy zachować snapshot tego, co klient rzeczywiście kupił?
Dopiero odpowiedzi na te pytania określają, jaki kod ma sens.
I właśnie tutaj widzę granicę vibe codingu.
Problemem nie jest już to, czy AI potrafi pisać kod. Potrafi.
Problemem jest to, ile decyzji pozwalamy mu podejmować ponownie za każdym razem, gdy dotyka systemu.
„Plan with the best model, execute with a cheaper one” nie wystarcza
Popularna strategia pracy z modelami brzmi rozsądnie:
Plan with the best model. Execute with a cheaper model.
Mocny model projektuje. Tańszy implementuje.
Tyle że między tymi dwoma etapami musi istnieć coś więcej niż plan w Markdownie.
Jeśli przekazujemy drugiemu modelowi dokument zawierający kilkanaście decyzji, nadal musi ustalić, które z nich są wymaganiami, które sugestiami, jak odnoszą się do obecnego codebase’u i gdzie kończy się jego swoboda.
Formalnie dostał plan.
Praktycznie nadal wykonuje część pracy architektonicznej.
Dlatego przydatne jest rozdzielenie trzech typów zadań, które zazwyczaj nazywamy po prostu „codingiem”.
Decision problem występuje wtedy, gdy trzeba ustalić, jak system powinien działać.
Reasoning problem pojawia się wtedy, gdy rozwiązanie istnieje, ale jego znalezienie wymaga analizy zależności, alternatyw albo błędów.
Execution problem zaczyna się wtedy, gdy decyzja została już podjęta i trzeba poprawnie wykonać jej konsekwencje.
Wszystkie mogą kończyć się pull requestem.
Nie wszystkie wymagają tej samej inteligencji.
Lepszy model i wyższy reasoning level rozwiązują różne problemy
Warto tu rozdzielić dwie osie.
Jedna to capability modelu.
Druga to reasoning effort — ile pracy pozwalamy konkretnemu modelowi wykonać nad problemem.
Większy reasoning budget może pomóc modelowi sprawdzić więcej wariantów, przetestować hipotezy albo zweryfikować własne rozwiązanie. Nie ma jednak ogólnej reguły mówiącej, że słabszy model na max staje się odpowiednikiem mocniejszego modelu na medium.
Dobrze widać to w aktualnych evalach OpenAI.
Na SWE-Bench Pro GPT-5.6 Sol osiąga 64,6%, a Luna 62,7%. To niewielka różnica. Na SEC-Bench Pro jest już 71,2% kontra 48,9%. W wewnętrznym Research Debugging — 68,3% kontra 50,8%.
GPT-6 Astra pokazuje podobny efekt z innej strony: na Terminal-Bench 4.0 osiąga 57,9%, podczas gdy GPT-5.6 Sol 37,3%.
Nie wynika z tego prosta hierarchia „najlepszego modelu do kodowania”.
Wynika coś bardziej użytecznego:
przewaga mocniejszego modelu zależy od klasy problemu, który mu powierzamy.
Jeżeli decyzje są już podjęte i zadanie jest mocno ograniczone, tani model może być wystarczająco dobry.
Jeśli jednocześnie musi odkryć domenę, znaleźć ukryte założenia, zaprojektować architekturę i przewidzieć skutki uboczne, kupujemy capability.
Dlatego frontier intelligence warto wydawać przede wszystkim tam, gdzie nadal istnieje niepewność.
Najdroższa jest decyzja, którą podejmujemy drugi raz
Załóżmy, że aplikacja ma strony, dokumentację i blog.
Każdy z tych typów ma mieć tytuł, slug, język, opis SEO, treść, właściciela oraz możliwość publikacji w określonym czasie. Slug powinien być unikalny w ramach języka.
Możemy zaszyć te założenia osobno w kilku implementacjach.
Możemy też zapisać je raz.
W rzeczywistym modelu GOAT bazowa encja content definiuje między innymi title, slug, lang, publication_date, description, body, relację owner oraz constraint unique(lang, slug).
page, doc i blog rozszerzają tę bazę, dodając własną konfigurację SSR, SEO i CRUD.
Dla klasycznego software engineering jest to po prostu sensowna abstrakcja.
W środowisku, w którym kod modyfikują również agenci AI, pojawia się dodatkowy efekt.
Kolejny agent nie musi rekonstruować wspólnych reguł na podstawie trzech podobnych implementacji.
Decyzja została skompresowana do jednej reprezentacji.
Na potrzeby tego tekstu nazwijmy to decision compression:
jedna decyzja domenowa zostaje zapisana w takiej formie, żeby kolejne etapy procesu nie musiały jej ponownie interpretować.
To może być schema, typ, DSL, API contract, policy albo constraint.
Nie każde takie narzędzie jest równie silnym kontraktem.
Ale kierunek jest ważniejszy od konkretnej technologii.
Dokumentacja mówi agentowi, co chcieliśmy zrobić. Kontrakt powinien ograniczać to, co może zrobić
Dzisiejsze systemy agentowe są otaczane coraz większą ilością kontekstu.
Mamy AGENTS.md, architecture docs, system prompts, style guides i przykłady kodu.
To pomaga.
Ale nadal istnieje zasadnicza różnica pomiędzy zdaniem:
slug powinien być unikalny w ramach języka
a deklaracją:
unique(lang, slug)
Pierwszą agent musi zinterpretować.
Drugą może sprawdzić system.
To nie jest nowa idea. Software engineering od dawna wykorzystuje DSL-e, schematy, type systems, model-driven development i generatory.
Nowe jest środowisko, w którym taki formalny model może jednocześnie stać się interfejsem pomiędzy człowiekiem, LLM-em i generatorem.
Unmesh Joshi opisał w 2026 roku bardzo podobny wzorzec w *DSLs Enable Reliable Use of LLMs*. Jego argument jest praktyczny: niewielki, ograniczony DSL zmniejsza liczbę możliwych reprezentacji tej samej intencji, a parser, type checker czy compiler może dostarczyć agentowi deterministycznej informacji zwrotnej.
To jest dużo ciekawsze niż po prostu „lepszy prompt”.
Prompt pomaga agentowi zgadnąć poprawnie.
Kontrakt powinien sprawić, że część błędnych odpowiedzi przestaje być dopuszczalna.
Contract-Driven AI Development już istnieje — i warto to powiedzieć wprost
Sam termin nie jest naszym wynalazkiem.
Enrico Piovesana opublikował w listopadzie 2025 roku framework Contract-Driven AI Development (C-DAD). Jego punkt wyjścia jest bardzo podobny: większość codebase’ów przechowuje intencję i ograniczenia w sposób niejawny, więc agent zmuszony jest je rekonstruować. C-DAD proponuje maszynowo weryfikowalne kontrakty zawierające między innymi preconditions, postconditions i invariants.
Interesuje mnie tutaj pokrewny, ale bardziej generator-first wariant tej idei.
Nie tylko:
jak sprawić, żeby agent rozumiał kontrakt?
Ale również:
ile konsekwencji tego kontraktu możemy wykonać bez używania modelu?
GOAT jest dobrym case study właśnie dlatego, że łączy deklaratywny model z generowaniem kolejnych warstw aplikacji.
Wróćmy do licencji
Nasze wymaganie biznesowe brzmi:
Po opłaceniu produktu typu
digital_licenseużytkownik powinien otrzymać licencję na 365 dni, a retry nie może stworzyć drugiego entitlementu.
Najgorszy możliwy handoff wygląda tak:
Zaimplementuj licencje w sklepie.
Wtedy agent jest równocześnie analitykiem biznesowym, architektem, projektantem bazy danych, backend developerem i reviewerem.
Lepszym rozwiązaniem jest najpierw ustalić model domeny.
W rzeczywistym modelu GOAT produkt posiada product_type.
shop_order_item przechowuje własny product_type, dzięki czemu typ zachowania może zostać zachowany jako snapshot transakcji.
shop_fulfillment zawiera status, product_type, liczbę prób, available_at, locked_at i last_error. Komentarz domenowy opisuje tę encję jako trwałe, retryowalne zadanie wykonywane po płatności.
shop_license przechowuje entitlement użytkownika oraz jego relację z shop_order_item, razem ze statusem i okresem obowiązywania.
Po podjęciu decyzji architektonicznej przepływ może więc wyglądać tak:
business intent
roczna licencja po płatności
↓
decision
digital_license jest zachowaniem produktu realizowanym przez retryowalny fulfillment
↓
contract
product type + order snapshot + fulfillment + license + relations + permissions + constraints
↓
generator
powtarzalna struktura aplikacji
↓
cheap model
lokalna logika fulfillmentu + test
Najważniejsza zmiana nastąpiła przed wygenerowaniem pierwszej linii kodu.
Zmniejszyliśmy przestrzeń decyzji.
Tańszy model staje się użyteczny wtedy, kiedy przestajemy wymagać od niego pracy architekta
Po ustaleniu kontraktu zadanie wykonawcze może być znacznie węższe:
Zaimplementuj handler
digital_licensezgodnie z istniejącym modelem. Nie zmieniaj schematu ani API. Dla właściwych order items utwórz entitlement, ustaw daty obowiązywania, zachowaj retryability i dodaj test.
Model nie musi już zastanawiać się:
gdzie trzymać licencję,
jak reprezentować fulfillment,
czy potrzebujemy nowej encji,
jak łączyć entitlement z zamówieniem.
Te decyzje powstały wcześniej.
Pozostał ograniczony problem implementacyjny.
To jest istotniejsze niż samo porównywanie cen modeli.
Nie próbujemy zastąpić Astry Luną.
Próbujemy przekształcić problem tak, żeby nie wymagał Astry.
Generator powinien przejąć wszystko, czego nie chcemy ponownie negocjować
LLM i generator mają przeciwne zalety.
Model językowy dobrze radzi sobie z niepewnością. Potrafi analizować niepełne wymagania, porównywać opcje i adaptować rozwiązanie.
Generator powinien być nudny.
Jeśli ustaliliśmy jeden wzorzec DTO, nie chcemy nowej interpretacji przy każdej encji.
Jeśli każde pole określonego typu powinno zachowywać się identycznie, kreatywność jest tutaj źródłem driftu.
Dlatego jedna z najważniejszych zasad tego podejścia brzmi:
Use AI to decide what. Use generators to repeat how.
W GOAT pojawia się jeszcze interesujący drugi poziom: AI może pomagać tworzyć same templates generatora.
To zmienia skalę wartości.
Jeżeli mocny model zaimplementuje jeden feature, wykorzystaliśmy jego reasoning raz.
Jeżeli pomoże nam znaleźć powtarzalny wzorzec i przenieść go do generatora, ta sama decyzja może zostać wykorzystana w kolejnych dziesiątkach implementacji.
To właśnie tutaj AI zaczyna pomagać nie tylko w pisaniu systemu, ale również w ulepszaniu maszyny, która system produkuje.
Ale kontrakt jest wart tylko tyle, ile rzeczywiście wymusza
Tutaj własny model GOAT daje nam bardzo dobry kontrprzykład.
product_type, który może sterować wykonywalnym zachowaniem produktu, jest obecnie short_text.
Z punktu widzenia schematu poprawne są więc równie dobrze:
digital_license
digital-licence
license365
Jeżeli zestaw możliwych zachowań jest skończony, enum albo jawny rejestr handlerów byłby silniejszym kontraktem.
Jeszcze ciekawszy przykład znajduje się w shop_fulfillment.
Komentarz opisuje unikalną parę order/type jako mechanizm zapewniający idempotencję.
W pokazanym modelu nie widać jednak constraintu, który taką unikalność rzeczywiście wymusza.
To nie jest wada, którą warto ukrywać.
To dokładnie pokazuje granicę między intencją a kontraktem.
Komentarz może powiedzieć agentowi, że operacja ma być idempotentna.
Constraint może sprawić, że system odrzuci stan, który tę zasadę łamie.
I właśnie dlatego samo posiadanie DSL-a nie wystarcza.
Contract-driven development zaczyna się wtedy, gdy ważne invariants nie tylko opisujemy, ale potrafimy je egzekwować.
Nie wszystko powinno jednak trafić do DSL-a
Istnieje też przeciwne ryzyko.
Najpierw DSL ma kilka prostych deklaracji.
Potem potrzebujemy wyjątków.
Dodajemy warunki.
Hooki.
before.
after.
retry.
unless.
custom_handler.
Po pewnym czasie okazuje się, że zbudowaliśmy język programowania, tylko gorszy.
Joshi zwraca uwagę na dokładnie to napięcie: DSL pomaga LLM-owi wtedy, gdy pozostaje ograniczony i jasno wyznacza granice.
Dlatego praktyczna reguła jest prosta:
Formalizuj to, co powtarzalne. Koduj to, co wyjątkowe.
Dane, relacje, constraints, standardowe permissions czy CRUD-y są dobrymi kandydatami do formalizacji.
Nietypowe algorytmy i jednorazowe zachowania często nadal należą w kodzie.
Granica będzie przesuwać się wraz z dojrzewaniem systemu.
I dobrze.
Największy problem AI driftu nie musi mieć nic wspólnego z halucynacją
Wyobraźmy sobie inne wymaganie:
użytkownik może edytować swój profil.
Implementacja wygląda dobrze.
Później pojawia się doprecyzowanie:
ale nie może zmienić emaila.
Security dodaje kolejną zasadę:
zmiana emaila musi przechodzić osobny proces weryfikacji.
Panel administracyjny nadal używa starego CRUD-u.
Jedno API ma nowszy model permissions, drugie starszy.
Agent nie musi niczego zmyślić, żeby popełnić błąd.
Wystarczy, że poprawnie rozwiąże fragment problemu.
Wraz ze wzrostem codebase’u rośnie liczba miejsc, z których trzeba rekonstruować intencję.
Większy context window pomaga przeczytać więcej kodu.
Nie gwarantuje jednak, że agent odróżni decyzję biznesową od przypadkowego szczegółu implementacji.
Dlatego ważnym elementem skalowania AI coding nie będzie wyłącznie zdolność modeli do przyjmowania większej ilości kontekstu.
Będzie nim również zdolność organizacji do zmniejszania ilości kontekstu, który w ogóle wymaga interpretacji.
DECIDE → FREEZE → EXPAND → VERIFY
Cały model można sprowadzić do czterech etapów.
DECIDE — mocny model albo człowiek pracuje nad tym, czego jeszcze nie wiemy: domeną, architekturą, security, migracją, trudnym bugiem, konsekwencjami biznesowymi.
FREEZE — kiedy decyzja jest wystarczająco stabilna, przestaje istnieć wyłącznie w rozmowie. Trafia do schema, modelu domenowego, DSL-a, API contract, policy, testu albo reguły generatora.
EXPAND — generator wykonuje mechaniczne konsekwencje. Tańszy model uzupełnia małe, jasno ograniczone luki.
VERIFY — parser, compiler, schema, testy, static analysis i review sprawdzają wynik. Model może brać udział w tej pętli, ale nie powinien być jedynym sędzią własnej pracy.
To podejście nie eliminuje AI z implementacji.
Zmienia miejsce, w którym wykorzystujemy jego inteligencję.
Najciekawszą formą pamięci firmy może okazać się kod, którego nie trzeba już interpretować
Organizacje mają dużo więcej wiedzy technicznej, niż znajduje się w ich dokumentacji.
Jak działa ownership.
Które dane są snapshotowane.
Jakie role mogą zmieniać dane.
Co oznacza status zamówienia.
Jak wygląda poprawny CRUD.
Które zachowania muszą być idempotentne.
Ta wiedza żyje w kodzie, pull requestach, ticketach, dokumentacji i głowach ludzi.
LLM może próbować ją odtworzyć.
Ale odtwarzanie wiedzy przy każdej zmianie jest kosztowne.
Jeśli część tych decyzji uda się zamienić w constraint, schema albo generator, przestają być wyłącznie wiedzą zespołu.
Stają się właściwością systemu.
I właśnie dlatego warto patrzeć na generatory nie tylko jako sposób na szybsze pisanie boilerplate’u.
Mogą być również sposobem na utrwalanie decyzji organizacji w formie, którą kolejne modele dziedziczą automatycznie.
Co jest po vibe codingu?
Vibe coding odpowiada na pytanie:
Czy AI potrafi to zbudować?
Coraz częściej odpowiedź brzmi: tak.
W dużym systemie ciekawsze jest jednak inne pytanie:
Czy za pół roku kolejny agent będzie musiał od nowa odkrywać, dlaczego zbudowaliśmy to właśnie tak?
Jeżeli tak, za każdym razem płacimy za tę samą decyzję.
Modele będą lepsze.
Reasoning będzie tańszy.
Agenci będą pracować dłużej.
Context windows będą rosły.
Ale agent, który przeczyta milion linii kodu, nadal będzie musiał ustalić, które z nich reprezentują intencję, a które są przypadkową historią implementacji.
Contract-driven AI development proponuje inny kierunek.
Nie próbować sprawić, żeby AI było coraz lepsze w zgadywaniu naszego systemu.
Sprawić, żeby system wymagał od AI coraz mniej zgadywania.
Mocny model powinien pomagać podejmować decyzje tam, gdzie istnieje niepewność.
Kontrakt powinien utrwalać te, których nie chcemy ponownie negocjować.
Generator powinien wykonywać to, co już wiemy.
A tańszy model powinien dostać ograniczony problem, w którym nadal rzeczywiście potrzebujemy elastyczności.
Najdroższej inteligencji nie warto wydawać tam, gdzie powstaje najwięcej kodu.
Warto ją wydawać tam, gdzie powstają decyzje, które później będą powtarzane tysiące razy.