# 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 ```mermaid 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_GLOBS` blokuje 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_verification` z 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.jsonl` z 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ć.