9.9 KiB
Architektura sieci agentowej modyfikującej kod
1. Problem
Zmiany typu "podnieś wersję SDK w 60 repozytoriach" są mechanicznie proste, ale kosztowne: każde repozytorium ma inny układ, inne miejsca użycia biblioteki i inny poziom pokrycia testami. Klasyczna automatyzacja (skrypt + sed) załatwia 70% przypadków i zostawia najgorsze 30%. Agent z LLM załatwia pozostałe 30%, ale wprowadza trzy nowe problemy: niepowtarzalność, brak audytowalności i nieograniczony zakres zmian.
Ten projekt jest odpowiedzią na pytanie: jak wpuścić agenta do repozytorium tak, żeby dało się to pokazać audytorowi.
2. Trzy warstwy i jedna zasada
┌───────────────────────────────────────────────────────────────────┐
│ KONTEKST — APM (Microsoft Agent Package Manager) │
│ apm.yml + .apm/{instructions,skills,prompts,agents,context} │
│ Wersjonowany, pinowany lockfile'em, audytowany polityką. │
└──────────────────────────────┬────────────────────────────────────┘
│ apm install → apm_modules/
┌──────────────────────────────▼────────────────────────────────────┐
│ RUNTIME — agno Workflow │
│ Kompilacja prymitywów APM → Agent/Skills/Steps. │
│ Deterministyczne kroki + sandboxowane narzędzia. │
└──────────────────────────────┬────────────────────────────────────┘
│ agentic-codemod run
┌──────────────────────────────▼────────────────────────────────────┐
│ EGZEKUCJA — GitLab CI │
│ Efemeryczny job, bramka manualna, artefakty audytowe, MR. │
└───────────────────────────────────────────────────────────────────┘
Zasada nadrzędna: kontekst jest artefaktem, nie kodem. Zmiana zachowania sieci agentowej (nowa reguła migracji, ostrzejszy guardrail, inny model dla roli) to podbicie wersji pakietu APM, a nie merge request do Pythona. Dzięki temu zespół właściciela SDK może dostarczyć wiedzę migracyjną, nie mając dostępu do silnika pipeline'u.
3. Przepływ
flowchart TD
T[Trigger: issue z labelką / MR / harmonogram / formularz] --> C[apm install + apm audit]
C --> I[intake: deterministyczny wsad zadania]
I --> R[recon: fakty o repozytorium]
R --> P[plan: ChangePlan z kryteriami akceptacji]
P --> G{jest co wdrażać?}
G -- nie --> M[manifest + komentarz w issue]
G -- tak --> B[bump wersji: deterministyczny]
B --> L[pętla: implement → verify]
L -- czerwono, iteracja < limit --> L
L -- zielono --> V[review: recenzja diffa]
V --> S[scribe: opis merge requesta]
S --> H{bramka manualna w CI}
H -- zatwierdzone --> MR[branch + commit + push + MR]
H -- nie --> A[artefakty do wglądu, zero zmian zdalnych]
Role w sieci
| Agent | Model | Narzędzia | Wyjście |
|---|---|---|---|
scout |
planner | odczyt, wyszukiwanie | RepoProfile |
planner |
planner | odczyt, wyszukiwanie | ChangePlan |
coder |
coder | odczyt, edycja, weryfikacja | zmieniony kod |
reviewer |
reviewer | odczyt, diff | ReviewVerdict |
scribe |
scribe | diff | MergeRequestDraft |
Definicje ról leżą w .apm/agents/*.agent.md. Kod nie zna nazw agentów - czyta je z kontekstu.
4. Co jest deterministyczne, a co należy do modelu
To jest najważniejsza decyzja projektowa. Model dostaje wyłącznie tę część pracy, której nie da się zrobić inaczej.
| Krok | Wykonawca | Dlaczego |
|---|---|---|
| Wykrycie ekosystemu, plików zależności, komendy testowej | kod (adapters/) |
jednoznaczne, sprawdzalne |
| Podbicie deklaracji wersji | kod (adapters/) |
zero powodów, by ryzykować halucynację w pliku zależności |
| Migracja objęta regułami codemod | kod (workflow/codemod.py) |
reguły z pakietu APM, w pełni powtarzalne |
| Nietypowe użycia API, kontekst biznesowy | model (coder) |
tu klasyczna automatyzacja się kończy |
| Ocena zakresu i ryzyka zmiany | model (reviewer) + kontrola mechaniczna |
dwa niezależne spojrzenia |
| Uruchomienie testów | kod (tools/verification.py) |
agent nie dostaje powłoki |
| Branch, commit, push, MR | kod (workflow/runner.py) |
jednoznaczny autor i format historii |
Konsekwencja: pipeline ma dwa tryby o identycznej topologii. Tryb --offline używa
wyłącznie reguł (RuleBrain), tryb domyślny dokłada agentów (LlmBrain). Tryb offline jest
bramką jakości na każdym MR - sprawdza cały przepływ bez kosztu GPU i bez dostępu do modelu.
5. Model bezpieczeństwa
Guardraile istnieją w dwóch warstwach, bo prompt nie jest zabezpieczeniem.
Warstwa promptowa (.apm/instructions/security.instructions.md) - dla modelu:
zakaz zmian w plikach pipeline'u, zakaz sekretów, zakaz rozszerzania zakresu,
zakaz usuwania testów, zakaz nowych zależności.
Warstwa egzekucji (tools/workspace.py, config.py) - dla audytora:
- każda ścieżka rozwiązywana względem korzenia repozytorium; wyjście przez
..i dowiązania niemożliwe, - lista
DEFAULT_DENY_GLOBSblokuje zapis do.gitlab-ci.yml,.git/,Dockerfile*,*.pem,.env*, manifestów APM, - budżety: liczba zmienionych plików, rozmiar pliku, liczba wywołań narzędzi, limit czasu weryfikacji,
- agent nie ma powłoki - jedyna operacja wykonawcza to
run_verificationz komendą ustaloną przez adapter, - środowisko weryfikacji jest czyszczone ze zmiennych zawierających
TOKEN,SECRET,PASSWORD,API_KEY, - każde wywołanie narzędzia trafia do
trace.jsonlz redakcją sekretów.
Warstwa dostawy kontekstu (apm-policy.yml): dozwolone źródła pakietów, zakaz prymitywu
hooks (wykonuje kod na runnerze), wymagane piny po tagu, wymagany lockfile, apm audit
wykrywający ręczną edycję zainstalowanego kontekstu.
6. Artefakty przebiegu
Każde uruchomienie zostawia w --run-dir komplet dowodów:
| Plik | Zawartość |
|---|---|
run.json |
manifest: zadanie, profil, plan, weryfikacje, werdykt, modele, hash lockfile'a APM |
plan.json |
plan zmian z kryteriami akceptacji i pozycjami requires_human |
profile.json |
ustalone fakty o repozytorium |
review.json |
werdykt recenzenta z findingami |
merge_request.md |
tytuł i opis MR |
changes.patch |
pełny diff |
trace.jsonl |
każde wywołanie narzędzia z czasem i wynikiem |
To jest odpowiedź na pytanie audytora "na jakiej podstawie ta zmiana weszła do repozytorium".
7. Backend LLM
Silnik zna wyłącznie jeden kontrakt: endpoint zgodny z API OpenAI pod CODEMOD_LLM_BASE_URL.
Cała wiedza o dostawcy jest w llm.py (fabryka modeli) i llm/models.yml (adresy, modele per rola).
Docelowo jest to vLLM na OpenShifcie. Lokalnie nie ma jednej ścieżki dla obu systemów -
Docker na macOS nie ma dostępu do Metala - więc task llm:up wykrywa platformę:
| Backend | Kiedy | Mechanizm |
|---|---|---|
vllm-gpu |
Linux + NVIDIA + Docker | vllm/vllm-openai przez docker compose |
vllm-metal |
macOS Apple Silicon | plugin vllm-project/vllm-metal (MLX), natywny vllm serve |
ollama |
awaryjnie, obie platformy | /v1 zgodne z OpenAI, natywnie lub w kontenerze |
mock |
testy i CI | llm/mock_server.py - stdlib, zero pobierania |
Każdy backend serwujący realny model wymusza --enable-auto-tool-choice z parserem hermes.
Bez tego vLLM zwraca opis wywołania w treści odpowiedzi zamiast tool_calls, agent coder
nie tknąłby żadnego pliku, a przebieg kończyłby się "sukcesem" bez jednej zmiany - awaria cicha,
czyli najgorszy rodzaj.
Atrapa jako test double
mock nie jest symulatorem modelu. Odpowiada z góry ustalonymi wywołaniami narzędzi
i strukturami zestrojonymi z fixture'em acme-app, a rozpoznaje pytającego po nazwie agenta,
którą agno wstrzykuje do komunikatu systemowego. Dzięki temu tests/test_llm_path.py
przepuszcza pełny przebieg przez LlmBrain - budowę agentów z definicji APM, tool-calling,
parsowanie strukturalnych wyjść i pętlę weryfikacji - na każdym MR, bez GPU i bez sieci.
Podział odpowiedzialności testów jest więc taki:
| Co jest sprawdzane | Czym |
|---|---|
| reguły, adaptery, sandbox, workflow | tryb --offline (RuleBrain) |
| agenci, narzędzia, schematy, pętla | tryb llm na atrapie (LlmBrain + mock) |
| jakość migracji na nietypowym kodzie | realny model, ręcznie |
8. Granice rozwiązania
- Bez agenta-nadzorcy. Topologia jest jawnym przepływem (agno
Workflow), a nie swobodną delegacją między agentami. W zamian za mniejszą elastyczność dostajemy przewidywalny koszt, powtarzalny ślad i możliwość wstawienia bramki między krokami. - Bez AgentOS. Runtime to efemeryczny job CI. Nie utrzymujemy usługi z pamięcią sesji - stan przebiegu żyje w artefaktach.
- Bez dostępu do sieci z poziomu agenta. Cała wiedza migracyjna przychodzi w pakiecie APM.
Jeśli notatki migracyjnej nie ma, pipeline zatrzymuje się i zgłasza
requires_human, zamiast migrować "z pamięci modelu". - Nie każde repozytorium się nadaje. Bez działającej komendy testowej weryfikacja nic nie znaczy, a bez weryfikacji ten pipeline nie ma prawa niczego publikować.