SDLC / Policy-as-Code

LLM w starym projekcie: jak nauczyłem agentów mojego CLI sprzed lat

Ten wpis otwiera serię use-case’ów: zamiast teorii - konkretne wdrożenia podejścia everything-is-a-code na żywych projektach, z kodem i wnioskami. Kolejne znajdziecie pod tagiem use-case.

Projekt, którego nikt nie chce ruszać

Każdy z nas ma taki projekt. U mnie to ledo - małe CLI w Go, które napisałem w 2021 roku, bo miałem dość wklepywania docker-compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml up -d po sto razy dziennie. Ledo owija docker-compose (i podman-compose) w pojęcie trybów: w .ledo.yml deklarujesz, że tryb dev to takie a takie pliki compose, tryb test inne - i od tej pory piszesz tylko ledo container up.

Narzędzie robi swoje od pięciu lat. Vendorowane zależności, sto parę commitów, kilka firm używa go w codziennej pracy. Klasyczne legacy w najlepszym wydaniu: działa, więc nikt go nie dotyka.

Aż przyszły agenty.

Scenka z życia: proszę agenta w jednym z projektów o dopisanie kroku testów do pipeline’u. Agent widzi w repo pliki compose, więc oczywiście generuje gołe docker compose -f ... -f ... up, omijając ledo szerokim łukiem. Poprawiam, tłumaczę. Za drugim razem używa już ledo, ale wymyśla flagę --timeout do uponce, której nie ma. Za trzecim - wpisuje w .ledo.yml blok container: zamiast docker:, bo przecież „tak wygląda logiczniej”. Parser czyta docker:, więc konfiguracja po cichu wychodzi pusta.

I to jest moment, w którym można się obrazić na LLM-y. Albo zrozumieć, co się właściwie stało.

Agent to nowy w zespole - tylko bez onboardingu

Agent zachowywał się dokładnie tak, jak zachowałby się nowy programista pierwszego dnia w projekcie: robi rzeczy „na czuja”, na bazie tego, co widział gdzie indziej. Różnica jest taka, że człowiekowi robimy onboarding, a agentowi - nie. Cała wiedza o tym, jak się pracuje w tym projekcie, siedziała w jednym miejscu: w mojej głowie.

W duchu EIAC wniosek nasuwa się sam: kontekst projektu też jest kodem. Jeśli infrastruktura, design i polityki mogą być kodem - wersjonowanym, review’owalnym, testowalnym - to wiedza plemienna o pięcioletnim CLI tym bardziej. Wdrażanie LLM w starym projekcie to nie jest „podłączanie AI”. To refaktoring wiedzy: z głowy do repozytorium.

Poniżej proces, który przeszedłem z ledo, krok po kroku. Da się go powtórzyć w dowolnym legacy.

Krok 0 - spis wiedzy plemiennej

Zanim cokolwiek napiszesz, zbierz listę rzeczy, które wiesz tylko Ty. Mój patent: nie siadałem do tego „na sucho”, tylko zbierałem halucynacje agentów. Każdy błąd agenta to nieudokumentowana reguła projektu. Po dwóch tygodniach miałem listę:

  • .ledo.yml używa klucza docker:, nie container: - parser czyta tylko ten pierwszy;
  • ledo container down kasuje wolumeny, prune czyści wszystko - te komendy nigdy nie mogą trafić do CI bez guardu;
  • do CI służy ledo container uponce - odpala stack raz i kończy z kodem wyjścia głównego serwisu;
  • uponce nie przyjmuje żadnych flag, a ledo image push bierze wersję pozycyjnie, nie przez --tag;
  • każdy tryb w modes: musi wskazywać istniejące pliki compose, a main_service musi być w bazowym pliku.

Zauważcie: żadna z tych rzeczy nie wynika z kodu w sposób oczywisty. Wszystkie wynikają z decyzji sprzed lat, których uzasadnienie pamiętam tylko ja. To jest właśnie definicja legacy.

Krok 1 - AGENTS.md, czyli onboarding w repo

Pierwszy artefakt: AGENTS.md - otwarty standard pliku z instrukcjami dla agentów, który rozumieją Claude, Copilot, Cursor i reszta towarzystwa. Zasada, którą sobie przyjąłem: krótko. Ten plik siedzi w kontekście agenta praktycznie zawsze, więc płacisz za każdą linijkę. Trafiły tam tylko dwie rzeczy: golden path i guardraile.

## Golden-path commands

ledo container up [-b|-l|-n]   # start the stack for the current mode
ledo container uponce          # run once, exit with main service's code (use in CI)
ledo image build [version]     # build project image (FQN = registry/namespace/name)
ledo mode list | select <m>    # list / switch modes

## Must-know guardrails

- `.ledo.yml`'s container block uses the **`docker:`** key (not `container:`).
- Destructive - never run in CI: `ledo container down`, `rm`, `prune`.

Sam AGENTS.md wystarczył, żeby zniknęła połowa problemów. Ale tylko w repo ledo - a ja przecież potrzebowałem, żeby agenty umiały ledo w innych projektach.

Krok 2 - skill: wiedza na żądanie, nie w twarz

Druga warstwa to skill - katalog .apm/skills/ledo/ z trzema poziomami szczegółowości (progressive disclosure):

  1. SKILL.md - kiedy się aktywować (praca z .ledo.yml, compose, Taskfile, pipeline’y) i mentalny model narzędzia: tryby, główny serwis, resolucja od git roota.
  2. reference.md - kompletna matryca komend i flag plus schemat .ledo.yml. W skillu stoi wprost: consult it before emitting any ledo ... invocation so you never invent a flag. To zdanie zabiło halucynowane flagi w stu procentach przypadków u mnie.
  3. templates/ - osiem plików copy-and-adapt: bazowy compose + warianty dev/test, ledo.yml, Taskfile.yml i trzy gotowe pipeline’y CI: GitHub Actions, GitLab CI i Gitea Actions.

Ten podział to nie kosmetyka. Gdybym wkleił całą referencję do AGENTS.md, spaliłbym kontekst agenta przy każdym zadaniu, także tych niezwiązanych z ledo. Skill ładuje się tylko wtedy, gdy jest potrzebny - a wtedy agent ma wszystko, łącznie z szablonami, których nie musi wymyślać.

Krok 3 - z katalogu robi się paczka: APM

I tu jest clou całej historii. Skill w repo ledo uczy agenta pracy nad ledo. Ale ledo jest narzędziem, którego używam w innych projektach - i tam też chcę mieć agenta, który je zna. Ręczne kopiowanie skilla między repozytoriami? Dziękuję, już to przerabiałem z konfigami edytorów :)

Rozwiązanie: Agent Package Manager (APM) - menedżer paczek dla wiedzy agentowej (wpis w katalogu). Dokładnie ta sama mechanika co npm czy go modules, tylko zamiast bibliotek dystrybuujesz skille i instrukcje. Repo ledo dostało manifest apm.yml:

name: ledo
version: 1.6.2
description: >-
  Drive the ledo (LeadDocker) CLI - scaffold .ledo.yml, mode-aware
  docker-compose files, CI pipelines (GitHub/GitLab/Gitea), and Taskfiles.
type: hybrid          # skill (na żądanie) + instructions (zawsze aktywne)
targets:
  - claude
  - copilot
includes: auto        # bierz wszystko z .apm/

Typ hybrid oznacza, że paczka niesie dwie rzeczy naraz: skill (ładowany na żądanie) i always-on instructions - krótkie reguły przypięte do wzorców plików (**/.ledo.yml, **/docker-compose*.yml, **/.gitea/workflows/*.yml…), które pilnują guardraili przy każdej edycji tych plików, nawet gdy skill się nie aktywował.

Jedna zasada, której warto pilnować jak niepodległości: .apm/ jest jedynym źródłem prawdy. Pliki w .claude/ czy .github/generowane przez apm install - nie edytuje się ich ręcznie, tak samo jak nie edytuje się ręcznie node_modules/.

Krok 4 - instalacja w dowolnym projekcie

Konsumpcja wygląda tak, jak powinna wyglądać każda zależność - deklaratywnie i z pinem do wersji:

# apm.yml w Twoim projekcie
name: my-app
version: 0.1.0
dependencies:
  apm:
    - paramah/ledo#v1.6.2   # pin do taga wydania
apm install                    # resolves dependencies into apm_modules/
apm install --target claude    # deploy skilla do .claude/
apm install --target copilot   # albo .github/copilot-instructions.md
repo paramah/ledo - .apm/ (single source of truth) instructions/ledo.md · skills/ledo/SKILL.md · reference.md · templates/ · apm.yml apm install --target ... .claude/skills/ledo/ Claude Code / Cowork .github/copilot-instructions.md Copilot / VS Code agent generuje artefakty projektu .ledo.yml · docker-compose*.yml · Taskfile.yml · pipeline CI GitHub Actions · GitLab CI · Gitea Actions · Forgejo Actions
Paczkę robisz raz, w repo ledo. Potem instalujesz ją w dowolnym projekcie, a agent generuje z niej te same pliki - nieważne, gdzie trzymasz kod.

Od tego momentu w projekcie-konsumencie proszę agenta: „dodaj CI z testami i publikacją obrazu” - i dostaję pipeline, który wygląda tak samo niezależnie od tego, czy projekt żyje na GitHubie, GitLabie, Gitei czy Forgejo (workflowy Gitea Actions są składniowo zgodne z GitHub Actions, więc szablon jest niemal wspólny). Wzorzec zawsze ten sam: instalacja ledo → ledo mode select testledo container uponce na testy, a na tagach login do rejestru → ledo image build <wersja>ledo image push <wersja>. Zero wymyślonych flag, zero container down w CI, zero gołego docker compose.

Co się realnie zmieniło

Trzy rzeczy, które widzę po kilku miesiącach:

Agent przestał być turystą. W świeżym projekcie z zainstalowaną paczką agent od pierwszego prompta używa ledo poprawnie - łącznie z rzeczami, o których nowy człowiek w zespole dowiedziałby się dopiero po tygodniu (albo po skasowaniu wolumenów na stagingu…).

Wiedza dostała wersjonowanie. Pin paramah/ledo#v1.6.2 znaczy, że wiedza agentowa aktualizuje się tak samo jak każda inna zależność: świadomym bumpem, z changelogiem, przez PR - pełna mechanika SemVer i Conventional Commits, tylko że przedmiotem wersjonowania jest kontekst, nie kod. Jak zmienię zachowanie CLI, konsumenci dostaną nową wiedzę razem z nową wersją - a nie „kiedyś, jak sobie przypomnę”.

Legacy przestało być karą. To jest dla mnie największe zaskoczenie: pięcioletni projekt, którego nikt nie chciał ruszać, stał się łatwiejszy w utrzymaniu niż niejeden świeży - bo cała wiedza o nim jest jawna, spakowana i instalowalna.

Przepis na Twój legacy

Uogólniając proces z ledo na dowolny stary projekt:

  1. Zbieraj halucynacje, nie pisz z głowy. Puść agenta na typowe zadania i notuj każdy błąd - to gotowa lista guardraili.
  2. AGENTS.md na start, ale krótki. Golden path + guardraile. Wszystko, co długie, wynieś poziom niżej.
  3. Skill z progressive disclosure. Opis aktywacji → pełna referencja → szablony. Agent ma sprawdzać referencję, nie zgadywać.
  4. Szablony zamiast opisów. Jeden działający gitlab-ci.yml w templates/ jest wart więcej niż strona prozy o tym, jak taki plik powinien wyglądać.
  5. Spakuj to. Manifest apm.yml, typ hybrid jeśli masz i skill, i reguły per-plik, .apm/ jako źródło prawdy.
  6. Pinuj wersje. Wiedza agentowa to zależność jak każda inna - traktuj ją tak w apm.yml konsumenta.

Zwróćcie uwagę, że w tym procesie nie zmieniłem w samym ledo prawie nic. Nie przepisywałem legacy „pod AI”. Dopisałem do niego warstwę kontekstu - i to wystarczyło.

Polecam wziąć na warsztat swój najbardziej zakurzony projekt i przejść te sześć kroków. Najstarszy kod w firmie zna najwięcej odpowiedzi na pytanie „dlaczego tak” - szkoda, żeby znały je tylko dwie osoby i żadna z nich nie była agentem :)

Wnioski dla EIAC

Case ledo jest mały, ale wnioski z niego skalują się na całą tezę Everything Is A Code. Cztery, które zabieram ze sobą:

Kontekst agentowy to pełnoprawny artefakt platformy. Obok kodu, infrastruktury, designu i polityk pojawia się piąta rzecz, którą trzeba trzymać w repo: wiedza o tym, jak się w projekcie pracuje. Ma swoje pliki, swój format, swój lifecycle. W modelu poziomów agentowego wytwarzania to jest dokładnie ten fundament, bez którego nie ma mowy o wyższej autonomii - agent bez skodyfikowanego kontekstu utknie na poziomie „zdolnego stażysty” na zawsze.

Golden path działa na agentów tak samo jak na ludzi. Platform engineering od lat powtarza: nie zmuszaj ludzi do pamiętania, daj im wydeptaną ścieżkę. Okazuje się, że agent jest idealnym konsumentem golden path - bo w przeciwieństwie do człowieka zawsze czyta dokumentację, o ile tylko ta jest w jego kontekście. Platforma w erze agentów to w dużej mierze właśnie to: te same golden paths, tylko podane w formacie, który agent instaluje jak zależność.

Guardraile to policy-as-code dla wiedzy. Lista „czego agentowi nie wolno” (destrukcyjne komendy poza CI, wymyślanie flag, edycja generowanych plików) niczym nie różni się koncepcyjnie od polityk, które egzekwujemy na infrastrukturze. Reguła, której nie da się załadować do kontekstu agenta, podzieli los polityki z wiki - nikt (i nic) jej nie przestrzega.

Neutralność wobec forge’a to exit-by-design w małej skali. Paczka, która generuje ten sam pipeline na GitHubie, GitLabie, Gitei i Forgejo, oznacza, że wiedza projektu nie jest zakładnikiem żadnego dostawcy - dokładnie ta sama logika, która stoi za suwerennym IDP z wyjściem wpisanym w architekturę. Kodyfikując kontekst, przy okazji dostajesz przenośność. Za darmo.

Krótko: EIAC nie kończy się na infrastrukturze i designie. Wiedza plemienna była ostatnią rzeczą w projekcie, która żyła poza repozytorium - i właśnie przestała mieć wymówkę.

Materiały online