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

> Wdrożenie LLM w legacy to nie podpięcie Copilota, tylko zamiana wiedzy plemiennej w kod. Case study: moje pięcioletnie Go CLI (ledo), które dzięki paczce APM nauczyło agentów pracy w CI/CD niezależnie od forge'a.

URL: https://eiac.dev/blog/llm-w-starym-projekcie-case-ledo
Filar: SDLC / Policy-as-Code
Data: 2026-07-09
Tagi: use-case, llm, agents, apm, legacy, ci-cd, docker

---

*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](https://github.com/paramah/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](/blog/design-as-code-tokeny-od-zera) i [polityki](/blog/policy-as-code-dla-zespolow) 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](https://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.

```markdown
## 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`](/katalog/task) 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)](https://microsoft.github.io/apm/) - menedżer paczek dla wiedzy agentowej ([wpis w katalogu](/katalog/apm)). Dokładnie ta sama mechanika co npm czy go modules, tylko zamiast bibliotek dystrybuujesz skille i instrukcje. Repo ledo dostało manifest `apm.yml`:

```yaml
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/` są *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:

```yaml
# apm.yml w Twoim projekcie
name: my-app
version: 0.1.0
dependencies:
  apm:
    - paramah/ledo#v1.6.2   # pin do taga wydania
```

```bash
apm install                    # resolves dependencies into apm_modules/
apm install --target claude    # deploy skilla do .claude/
apm install --target copilot   # albo .github/copilot-instructions.md
```

<figure>
<svg viewBox="0 0 640 330" role="img" aria-label="Przepływ paczki APM: repo ledo z katalogiem .apm zawierającym instrukcje, skill, referencję i szablony jest jedynym źródłem prawdy. Komenda apm install z targetem deployuje wiedzę do harnessów .claude oraz .github/copilot-instructions.md w projekcie konsumenta. Z nich agent generuje artefakty projektu: .ledo.yml, pliki compose, Taskfile oraz pipeline CI dla GitHub, GitLab, Gitea i Forgejo.">
  <g font-family="'Poppins', system-ui, sans-serif" text-anchor="middle">
    <rect x="20" y="20" width="600" height="62" rx="6" fill="none" stroke="var(--color-rust)" stroke-width="1.5"/>
    <text x="320" y="44" fill="var(--color-rust)" font-size="13">repo paramah/ledo - .apm/ (single source of truth)</text>
    <text x="320" y="63" fill="var(--color-muted)" font-size="10" font-family="'JetBrains Mono Variable', monospace">instructions/ledo.md · skills/ledo/SKILL.md · reference.md · templates/ · apm.yml</text>
    <line x1="320" y1="82" x2="320" y2="112" stroke="currentColor" stroke-width="1.5"/>
    <path d="M320 112 l-4 -7 h8 z" fill="currentColor"/>
    <text x="320" y="103" fill="var(--color-muted)" font-size="10" font-family="'JetBrains Mono Variable', monospace" dx="118">apm install --target ...</text>
    <rect x="60" y="118" width="240" height="52" rx="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
    <text x="180" y="140" fill="currentColor" font-size="13">.claude/skills/ledo/</text>
    <text x="180" y="158" fill="var(--color-muted)" font-size="10" font-family="'JetBrains Mono Variable', monospace">Claude Code / Cowork</text>
    <rect x="340" y="118" width="240" height="52" rx="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
    <text x="460" y="140" fill="currentColor" font-size="13">.github/copilot-instructions.md</text>
    <text x="460" y="158" fill="var(--color-muted)" font-size="10" font-family="'JetBrains Mono Variable', monospace">Copilot / VS Code</text>
    <line x1="180" y1="170" x2="180" y2="200" stroke="currentColor" stroke-width="1.5"/>
    <line x1="460" y1="170" x2="460" y2="200" stroke="currentColor" stroke-width="1.5"/>
    <path d="M180 200 l-4 -7 h8 z" fill="currentColor"/>
    <path d="M460 200 l-4 -7 h8 z" fill="currentColor"/>
    <rect x="20" y="206" width="600" height="56" rx="6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="5 3"/>
    <text x="320" y="228" fill="currentColor" font-size="13">agent generuje artefakty projektu</text>
    <text x="320" y="247" fill="var(--color-muted)" font-size="10" font-family="'JetBrains Mono Variable', monospace">.ledo.yml · docker-compose*.yml · Taskfile.yml · pipeline CI</text>
    <line x1="320" y1="262" x2="320" y2="290" stroke="var(--color-rust)" stroke-width="1.5"/>
    <path d="M320 290 l-4 -7 h8 z" fill="var(--color-rust)"/>
    <text x="320" y="312" fill="var(--color-rust)" font-size="12">GitHub Actions · GitLab CI · Gitea Actions · Forgejo Actions</text>
  </g>
</svg>
<figcaption>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.</figcaption>
</figure>

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](/katalog/gitlab), [Gitei](/katalog/gitea) czy [Forgejo](/katalog/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 test` → `ledo 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](/blog/wersjonowanie-semver-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](/blog/cztery-poziomy-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](/blog/platform-engineering-normalizacja-pracy) 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](/blog/od-idp-do-adp) 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](/blog/policy-as-code-dla-zespolow). 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](/katalog/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ę](/blog/suwerenny-idp-exit-by-design). 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

- [ledo (LeadDocker)](https://github.com/paramah/ledo) - repo z kompletną paczką APM w `.apm/`
- [Agent Package Manager - dokumentacja](https://microsoft.github.io/apm/) i [quickstart](https://microsoft.github.io/apm/quickstart/)
- [AGENTS.md - otwarty standard instrukcji dla agentów](https://agents.md)
- [Gitea Actions](https://docs.gitea.com/usage/actions/overview) i [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) - zgodność składniowa z GitHub Actions
- [Compose Specification](https://compose-spec.io) - na czym to wszystko stoi