My Architect, część 9: Recursive context — jak nauczyliśmy agenta uczciwie mówić, czego nie przeczytał
To dziewiąty artykuł serii o My Architect. W skrócie dla tych, którzy dołączyli teraz: My Architect to system, w którym projekt żyje między sesjami agenta AI; agent prowadzi go przez MCP, a człowiek widzi postęp w wizualnym interfejsie. Ten artykuł to historia jednej funkcji: od badania MIT do skilla recursive-context w pluginie my-architect v1.13.0. Wszystkie liczby z naszych przebiegów pochodzą z realnych uruchomień z 2026-07-03; liczby z badania RLM — z artykułu arXiv:2512.24601. Nic nie jest zmyślone.
Prolog. Problem, którego duży kontekst nie rozwiązuje
Współczesne agenty mają ogromne okno kontekstowe i to tworzy niebezpieczną iluzję: „zmieści się — znaczy da radę". W praktyce praca z dużymi danymi psuje się na trzy sposoby i żadnego z nich nie leczy rozmiar okna.
Pierwszy: okno to materiał eksploatacyjny. Agent, który „po prostu przeczytał" log o wadze 200 MB, albo go nie przeczytał (okno się skończyło), albo spalił budżet sesji na jeden plik — i dalej pracuje na ogryzku kontekstu.
Drugi: wiarygodność zamiast faktów. Poproś agenta „zbierz, co już działa w kodzie" — a dostaniesz gładki tekst, w którym fakty z kodu, streszczenie dokumentacji i domysły są nie do odróżnienia. Duży kontekst nie daje gwarancji pracy z danymi: model potrafi brzmieć pewnie także bez oparcia w źródle.
Trzeci: ciche kurczenie się pokrycia. Agent przetworzył 40 plików z 60, coś padło po drodze — a w odpowiedzi i tak brzmi „przeanalizowałem repozytorium". Nikt nie skłamał celowo; po prostu nikt nie obiecywał liczyć, co zostało pominięte.
Przed tą funkcją nie mieliśmy na takie przypadki żadnej sensownej dyscypliny: zachowanie agenta — jak się poszczęści konkretnej sesji, pokrycia nikt nie obiecywał liczyć, a fakty w odpowiedziach są nie do odróżnienia od streszczenia dokumentacji.
Rozdział 1. Pomysł: artykuł o RLM i dlaczego nie potrzebowaliśmy ich biblioteki
W grudniu 2025 grupa z MIT (OASYS lab) opublikowała pracę Recursive Language Models z open-source'owym repozytorium alexzhang13/rlm. Pomysł jest prosty i piękny: zamiast upychać gigantyczne wejście do promptu, rlm.completion() kładzie je jako zmienną w REPL-u — a model pisze kod, który to wejście ogląda, tnie i rekurencyjnie wywołuje sub-model (rlm_query, z batchingiem i limitem równoległych wywołań) na kawałkach. Kontekst przestaje być tekstem promptu i staje się środowiskiem zewnętrznym.
Liczby z artykułu: obsługa wejść do 100× dłuższych niż natywne okno; na long-context benchmarkach mediana przewagi nad vanilla GPT-5 to +26–130% zależnie od strategii baseline (i ~+13% wobec baseline'u samego Claude Code), przy porównywalnym koszcie. Ważne zastrzeżenie autorów: rekurencyjna dekompozycja wygrywa na zadaniach naturalnie rozkładalnych (analiza kodu, przetwarzanie dokumentów), a szkodzi spójnym sekwencyjnym rozumowaniom. Repozytorium oferuje środowiska REPL od lokalnego procesu po Dockera i chmurowe piaskownice oraz odnotowuje zastosowania w DSPy, Symbolice i Google Cloud.
I tu zauważyliśmy rzecz najważniejszą: Claude Code ma już wszystkie prymitywy RLM, natywnie. Bash to REPL do deterministycznego wstępnego cięcia. Skrypt Workflow to zwykły scenariusz JavaScript, z którego agent orkiestruje sub-agentów: agent() — dosłownie „wywołaj sub-LLM jak funkcję", w izolacji od historii sesji; pipeline() i parallel() — batching z automatycznym limitem równoległości; budget — sufit kosztów. Wciąganie zależności pythonowej z drugą warstwą rekurencji do harnessa, który sam to umie, byłoby nadmiarowe. Potrzebny jest więc nie pakiet, lecz skill: dokument, który utrwala dyscyplinę.
Rozdział 2. Co budowaliśmy
Skill recursive-context w pluginie my-architect: cienki router + trzy przepisy + jeden kanoniczny skrypt workflow. Zamysł od razu założyliśmy szerzej niż „jeden ogromny plik": te same zasady pokrywają audyt bazy kodu i wydobywanie faktów z repozytorium pod wymagania — audyt repo to dokładnie to samo zadanie big-corpus, które artykuł nazywa idealnym dla rekurencji.
Rdzeń dyscypliny w jednym akapicie: bramka rozmiaru przed czytaniem (ls/wc jako pierwsza akcja; ≤256 KB i ≤5000 linii — czytaj normalnie, skill milczy) → index, don't ingest (granice kawałków tnie kod w scratchpadzie, treść nie przepływa przez okno; przy zadaniach „igiełkowych" — najpierw zgrubny grep) → fan-out izolowanych sub-agentów, każdy dostaje tylko swój kawałek + wąskie pytanie, zwrot ściśle według schematu, nie prozą → rekurencja jako pętla (zwijanie agregatu do małego rozmiaru wewnątrz jednego skryptu) → synteza z uczciwym pokryciem (ile kawałków przetworzono, które padły). Dla faktów z kodu — osobny kontrakt: {claim, evidence_path, confidence} i twarda reguła — twierdzenie bez ścieżki do kodu faktem nie jest: trafia do dokumentu jako placeholder [fakt: <pytanie>], a nie wiarygodna zaślepka.
Jak to wygląda na żywo
Pierwsze sekundy pracy to nie czytanie, lecz rekonesans kodem. Realne komendy z żywego przebiegu na logu o wadze 48.7 MB; cały „kontakt" głównego agenta z zawartością pliku to sześć linii próbek:
wc -lc fixture.log # 600000 linii, 48 735 680 bajtów → bramka zadziałała
head -4 fixture.log # jaki kształt mają wpisy? (4 linie)
tail -2 fixture.log # (jeszcze 2)
split -l 10000 -a 3 fixture.log chunks/chunk- # 60 kawałków po ~793 KB: tnie kod, nie czytanie
Dalej każdy sub-agent dostaje izolowane zadanie — tylko swój kawałek i wąskie pytanie. Fragment realnego promptu z tamtego przebiegu:
Przeanalizuj TYLKO plik …/chunk-aat — to kawałek loga produkcyjnego (~10000 linii). Pracuj grep/awk/Read ściśle wewnątrz tego pliku, nigdzie indziej nie chodź. Szum tła loga ma postać: „\<timestamp\> DEBUG|INFO|WARN [auth|billing|catalog|gateway|search] request rid=N handled in Xms status=200". Pytanie: znajdź WSZYSTKIE linie, które NIE mieszczą się w tym szablonie szumu […] Jeśli anomalii nie ma — zwróć pusty findings.
Odpowiadać sub-agent ma obowiązek nie prozą, lecz strukturą. Oto znalezisko z tamtego przebiegu — dosłownie, tak jak wróciło (schemat wymusza dołączenie dokładnego cytatu-dowodu):
{
"claim": "ERROR-level log line from an unexpected component (payment-reconciler)
reporting a panic/ledger drift, not matching the standard noise template",
"evidence": "2026-06-25T00:00:00 ERROR [payment-reconciler] PANIC: ledger drift detected txn=TXN-2222",
"chunk": "…/chunks/chunk-aat"
}
Orkiestracja to zwykły JavaScript, który agent przekazuje narzędziu Workflow. Serce skryptu (skrócone; pełny kanon leży w skillu):
const rawPartials = await pipeline(groups, (g, _o, i) =>
agent(`Read ONLY these files: ${g.join(', ')}. Question: ${A.question}. …`,
{ label: `map:${i}`, schema: FINDING }))
const partials = rawPartials.filter(Boolean) // null = grupa, która padła
const failedGroups = rawPartials.map((r, i) => (r ? -1 : i)).filter(i => i >= 0)
let prevSize = Infinity // rekurencja = pętla z asekuracją
while (JSON.stringify(working).length > 30_000) {
const size = JSON.stringify(working).length
if (size >= prevSize) { log(`no progress — stop`); break } // gwarancja zakończenia
prevSize = size
/* …reduce-agenty zwijają znaleziska partiami… */
}
return { findings: working, groupsSucceeded: partials.length, failedGroups } // uczciwe pokrycie
A tak wygląda „fakt" w zadaniu o wymaganiach — dosłowny element z żywego przebiegu po naszym repozytorium (zwróć uwagę: ścieżka i linie, nie „gdzieś w kodzie"):
{
"claim": "updateNode(...) gates: if updates.status === 'done' && existing.status !== 'done',
it computes getBlockers(...) and throws HierarchyValidationError(..., 'BLOCKED_BY_OPEN_ITEMS')",
"evidence_path": "src/server/domain/hierarchy.ts:189-215",
"confidence": "verified"
}
Rozdział 3. Dlaczego testy są właśnie takie
Trzymaliśmy się „żelaznego prawa" tworzenia skilli: datasety eval pisze się przed plikami skilla, baseline zdejmuje się przed autorstwem. Jeśli nie widziałeś, jak agent myli się bez skilla — nie wiesz, czego skill uczy. Testy ułożyły się w trzy warstwy, każda odpowiada na swoje pytanie.
Testy triggerów: „czy skill załaduje się sam?" Od początku postawiłem wymaganie: agent ma automatycznie rozumieć, kiedy sięgnąć po skill — bez podpowiedzi od człowieka. W Claude Code decyzja o załadowaniu zapada na podstawie nazwy i opisu — dlatego sędziowie w teście widzą tylko name+description i zapytanie użytkownika, nic więcej. Trzech niezależnych sędziów na case; w datasecie są nie tylko pozytywy (log 200 MB, dump, transkrypt, audyt repo — po rosyjsku i angielsku), ale i negatywy typu near-miss: YAML na 300 linii, poprawka trzech znanych plików, 40 jednakowych ticketów z CSV. Plus cross-regresja: 10 pozytywów sąsiedniego skilla myarchitect — nowy skill nie może kraść cudzych triggerów. Skill, który odpala się na wszystko jak leci, jest szkodliwszy niż skill, którego nie ma.
Testy behawioralne: „czy dyscyplina jest przestrzegana?" Sześć case'ów dry-run, każdy uderza w konkretny sposób zepsucia się: bramka przed czytaniem (przeciw „najpierw po prostu przeczytam"); milczenie poniżej progu (przeciw kargokultowi — log na 180 linii nie zasługuje na potok); grep przed fan-outem (efektywność); rekurencja pętlą, a nie zagnieżdżonym workflow() (twarde ograniczenie platformy — zagnieżdżenie głębiej niż jeden poziom jest niemożliwe); kontrakt faktów; i fallback, gdy narzędzia Workflow w ogóle nie ma. Wykonawcy piszą plan działań, niezależny grader linijka po linijce porównuje go z assertions.
Przebiegi live: „czy maszyneria działa naprawdę?" Dry-run sprawdza plan, ale nie wykonanie. L1 — syntetyczny log na 600 000 linii / 48.7 MB z 12 zasianymi „igłami", których pozycje zna deterministyczny generator: to daje mierzalne recall i precision zamiast wrażeń. Igły trzech typów nie są przypadkowe: 4 rzadkie PANIC (szukanie anomalii), 3 podejrzane nadpisania configów — i 5 kroków jednej sesji (login → elevate-privileges → export-full-dump → wipe-audit-trail → logout), rozsmarowanych po różnych końcach pliku: ten typ sprawdza syntezę — czy potok zdoła złożyć spójną fabułę z kawałków przetworzonych przez różnych agentów. L2 to nie syntetyka: realne repozytorium i realne pytanie — „co już działa w kodzie i nie powinno być ruszane?" — z wyrywkową ręczną weryfikacją ścieżek-dowodów.
Rozdział 4. Trudności. Trzy case'y, które ulepszyły funkcję
Case 1. Baseline okazał się zbyt dobry
Nieprzyjemna (i najbardziej pożyteczna) niespodzianka pomiaru RED — przebiegu tych samych zadań bez skilla, zdjętego zanim skill został napisany. Daliśmy agentowi ten sam log 48.7 MB — spodziewając się próby czytania na wprost. Zamiast tego Sonnet z zimną krwią zrobił wc -l, obejrzał 20 linii głowy i 21 ogona, przeszedł się statystyką grep/awk po poziomach i komponentach — i znalazł wszystkie 12 igieł w 7 wywołaniach narzędzi i ~37 tysiącach tokenów. Baseline w wydobywaniu faktów ze znajomego repo też był mocny.
Uczciwy wniosek trzeba było wbudować i w raport, i w sam skill: na greppable igłach w jednorodnym, maszynowo czytelnym logu grep-first modele mają już „we krwi" — i skill nie powinien z tym walczyć, powinien to usankcjonować (przepis giant-file, krok 2: kandydaci się zmieścili → odpowiadaj wprost, fan-out niepotrzebny, i powiedz to jawnie). Prawdziwa delta skilla to nie „ratunek przed naiwnym czytaniem", lecz trzy inne rzeczy: kontrakt (schematy, oznaczenia confidence, placeholdery, pokrycie — baseline zwrócił mocną prozę, w której fakty z kodu i streszczenie doców są nie do odróżnienia), skalowalność na zadania rozumienia bez grep-kotwic i korpusy większe niż okno, oraz powtarzalność — dyscyplina utrwalona siecią regresyjną, a nie zależna od nastroju konkretnej sesji. Gdybyśmy nie zdjęli baseline'u, sprzedawalibyśmy skill nie za to, co robi.
Case 2. Pętla, która mogła się nie skończyć, i pokrycie, które kłamało
Zbiorcze review znalazło w kanonicznym skrypcie dwa defekty, bolesne właśnie przez swoją subtelność. Pierwszy: pętla reduce („zwijaj znaleziska, aż agregat zrobi się mały") nie miała gwarancji zakończenia — reduce-agent, któremu kazano „zdeduplikuj i odrzuć nieistotne", ma pełne prawo zwrócić tyle samo znalezisk, ile dostał, jeśli wszystkie są unikalne i istotne. Agregat przestaje się zmniejszać — pętla dalej płodzi agentów. Fix — no-progress guard: runda nie zmniejszyła agregatu → wychodzimy z logiem; po fiksie terminacja jest dowodliwa (śledzony rozmiar ściśle maleje).
Drugi defekt był gorszy, bo łamał własną regułę skilla. Skrypt zwracał chunksProcessed: <wszystkie chunki> bezwarunkowo — nawet jeśli część map-agentów padła i ich kawałków nikt nie przeczytał. To samo „ciche kurczenie się pokrycia" z prologu, tylko teraz wewnątrz narzędzia, które obiecywało z nim walczyć. Fix: nieprzefiltrowana tablica wyników zachowuje null w miejscu każdej grupy, która padła — to jedyny sposób, by wiedzieć, co dokładnie nie zostało pokryte; zwrot stał się uczciwy: {groupsSucceeded, failedGroups, …}, a reguła „failedGroups ma obowiązek trafić do finalnej odpowiedzi dla użytkownika" jest wpisana w tekst skilla.
Do tego samego worka — lekcja o review testowej otoczki: recenzent wykonywał generator fixture na wejściach brzegowych, a nie tylko go czytał. Tak znalazły się nieskończona pętla na nienumerycznym argumencie (POSIX awk porównuje liczbę ze stringiem znak po znaku — warunek pętli jest prawdziwy wiecznie; 76 MB śmieci w 3 sekundy) i cicha utrata igły przy małych rozmiarach (pozycja była ucinana do nieistniejącej linii 0).
Case 3. Czternaście milisekund
Pierwsze uruchomienie live L1 zakończyło się po 14 ms z wynikiem chunksTotal: 0. Nie wystartował ani jeden agent. Minimalna sonda workflow pokazała przyczynę: args przychodzi do skryptu jako string JSON, a nie obiekt — nawet jeśli przekaże się obiekt. args.count było undefined, pętla budująca listę chunków nie wykonała się ani razu, skrypt uczciwie zwrócił pustkę.
Ani dry-run, ani review kodu by tego nie złapały — defekt żyje na granicy między narzędziem a skryptem i ujawnia się tylko przy prawdziwym wywołaniu. Fix — jedna linia defensywnego parsowania (const A = typeof args === 'string' ? JSON.parse(args) : args) — poszedł do kanonicznego skryptu skilla i do planu, z adnotacją „zweryfikowane żywym przebiegiem". To najlepszy argument za tym, dlaczego odbiór był live: jego zadaniem nie jest potwierdzić, że wszystko gra, lecz znaleźć to, co znaleźć może tylko rzeczywistość.
Bonus tego samego rodzaju: sub-agenci wykonujący testy behawioralne fizycznie nie mają narzędzia Workflow — dwóch z nich samodzielnie to odkryło przez wyszukiwanie narzędzi i poprawnie przeszło na udokumentowany fallback (równoległe zwykłe agenty, ta sama dyscyplina). Gałąź fallbackowa skilla przeszła żywą weryfikację przypadkiem — ale naprawdę.
Rozdział 5. Było → jest
| Było (RED, bez skilla) | Jest (GREEN + LIVE, ze skillem) | |
|---|---|---|
| Ogromny plik | brak dyscypliny: jak się poszczęści konkretnej sesji; pokrycia nikt nie deklaruje | bramka rozmiaru przed czytaniem; grep-first usankcjonowany; fan-out 60 agentów w razie potrzeby; recall 12/12, precision 12/12 — baseline też znalazł 12/12, delta nie leży w trafności szukania, lecz w jawnie zadeklarowanym pokryciu 60/60 grup i powtarzalności (zob. Case 1) |
| Fakty z kodu | mocna proza: fakty z kodu i streszczenie doców nie do odróżnienia, dziur nie widać | 40 faktów, 100% ze ścieżką i liniami, verified/inferred rozdzielone, 2 dziury — jawne [fakt: …]; wyrywkowa weryfikacja 5/5 potwierdziła co do słowa |
| Autowybór skilla | — | 60/60 głosów sędziowskich (pozytywy, negatywy near-miss, cross-regresja sąsiada — 0 fałszywych zadziałań) |
| Gwarancje potoku | — (bez skilla potok nie istniał); w szkicu skilla przed review: pętla mogła się nie zakończyć, pokrycie mogło kłamać | terminacja dowodliwa; failedGroups w zwrocie; string args rozbrojony |
| Okno main-agenta | ~37k tokenów próbek i statystyk grep przechodzi przez okno (tak przeszedł baseline) | L1: przez okno przeszło 6 linii próbek — sam plik 48.7 MB przez okno nie przechodził wcale |
Produkt uboczny L2, którego nikt nie zamawiał: wydobywanie faktów odsłoniło realne pytanie do produktu — UI zapisuje zależność dependsOn prosto do lokalnego store'a, omijając route HTTP z walidacją. Założyliśmy na to osobne zadanie weryfikacyjne. Dobry potok faktów znajduje nie tylko to, o co pytano.
Rozdział 6. A efektywność?
Znajdowanie rzeczy trudnych to połowa roboty; ważne, by nie płacić potokiem za wszystko jak leci. Efektywność jest tu zaprojektowana, a testy sprawdzają właśnie ją.
Skill umie milczeć. Poniżej progu (test „log na 180 linii") — zwykłe czytanie, zero maszynerii. Negatywne case'y triggerów i cross-regresja potwierdzają na wszystkich sprawdzonych scenariuszach (42/42 głosy „nie odpalać"), że potok nie uruchamia się na zadaniach, gdzie jest overheadem.
Grep przed fan-outem. Zadanie igiełkowe ze znaną kotwicą rozwiązuje się wycięciem regionów bez ani jednego sub-agenta — a agent ma obowiązek jawnie powiedzieć, że fan-out nie był potrzebny (test 3).
Mały korpus — bez Workflow. Przepis requirements-mining wprost zakazuje strzelania z armaty do wróbli: ≤30 istotnych plików → jeden agent z listą. Żywy L2 tak właśnie przeszedł: 1 agent, ~74k tokenów, 36 wywołań — na wyjściu 40 weryfikowalnych faktów.
Cena pełnego fan-outu jest znana i zapłacona świadomie. L1 z 60 agentami kosztował ~1.22M sub-tokenów i ~2.5 minuty — na tym zadaniu grep byłby ~33 razy tańszy (~37k tokenów, Case 1). Wiemy to i tego nie ukrywamy: na zadaniach greppable skill nakazuje grep i potoku nie włącza, a drogi przebieg był jednorazowym odbiorem maszynerii — i zwrócił się pierwszym znalezionym bugiem (Case 3). Fan-out to narzędzie na przypadki, gdy grep nie działa z zasady (proza, transkrypty, pytania o rozumienie), a nie podatek od każdego dużego pliku. Przy tym sub-tokeny to wydatek okien izolowanych; okno głównej sesji zostało czyste, a to często najbardziej deficytowa waluta.
Budget i głębokość. Kanoniczny skrypt szanuje budget jako ogranicznik głębokości zwijania i loguje wczesne zatrzymania. Dla uczciwości: ta gałąź jest sprawdzona tylko case'em dry-run o zwijaniu — żywego przebiegu z realnym stopem na budget nie było.
Epilog. Co zostało uczciwie nieudowodnione
Próg 256 KB / 5000 linii to startowy default, nie wycierpiana liczba: trzeba go stroić w miarę doświadczeń. Dwa scenariusze nie zostały sprawdzone na żywo — to najlepsi kandydaci następnej rundy eval: repo-audit w całości (multi-modal sweep + „kop, aż dwie rundy z rzędu będą suche") na dużym nieznanym repozytorium oraz prozatorski transkrypt bez grep-kotwic: właśnie tam, według artykułu RLM, oczekiwana jest maksymalna przewaga nad baseline'em i właśnie tam uczciwy baseline pokazałby prawdziwą porażkę bez skilla. A znalezione pytanie o podwójną ścieżkę zapisu dependsOn też czeka na swoją weryfikację.
Jak wypróbować
Skill jedzie w pluginie my-architect od v1.13.0 i włącza się sam, gdy zadanie pachnie dużym korpusem — nie trzeba go wołać ręcznie (o to właśnie chodziło w testach triggerów).
- Załóż konto na my-architect.app, weź token na stronie API Keys i wyeksportuj go w tej samej sesji, w której uruchamiasz Claude Code:
``bash export MCP_API_KEY=mcp_TWÓJ_TOKEN ``
- Dodaj marketplace i postaw plugin:
`` /plugin marketplace add d7561985/my-architect-marketplace /plugin install my-architect@my-architect-marketplace ``
- Masz już plugin?
/plugin marketplace update my-architect-marketplace→/plugin update my-architect.
---
Fakty i linki
- Skill:
plugins/my-architect/skills/recursive-context/w otwartym repozytorium github.com/d7561985/my-architect-marketplace, plugin v1.13.0, tagmy-architect--v1.13.0, commit wydaniab39cfc1. - Pełne dane przebiegów:
skills/recursive-context/evals/RESULTS.md(RED / GREEN / LIVE); datasetytrigger-evals.json,behavior-evals.json; generator fixturefixtures/gen-fixture.sh(deterministyczny, 12 igieł). - Badanie: Recursive Language Models, arXiv:2512.24601 (MIT OASYS, grudzień 2025); repozytorium github.com/alexzhang13/rlm.
- Kluczowe commity z fixami po review i live:
be0b99d(guard generatora),ab505f5(terminacja reduce + uczciwe pokrycie),c512e64(defensywne parsowanieargs).