CRM na Markdownie, część 2: 27 godzin, 23 commity i katalog porażek
W pierwszej części pokazałem, jak postawić CRM na plikach Markdown w jeden wieczór. Tutaj — jak naprawdę powstawał: z liczbami, chronologią i, co najważniejsze, porażkami. Porażki nie są załącznikiem do tej historii, tylko jej szkieletem: niemal każdy element systemu pojawił się jako odpowiedź na konkretną awarię i bez tych awarii cała konstrukcja jest niezrozumiała.
Kontekst: robiłem outreach dla mojego generatora wideo — szukałem pierwszych użytkowników i rozmów z nimi. Pomagał Claude Code. Wszystko poniżej wydarzyło się w ciągu 27 godzin kalendarzowych, 22–23 sierpnia 2026 roku: 23 commity, 16 kart kontaktów, 12 dokumentów, 307 linii kodu. I każdy fakt w tym tekście jest potwierdzony plikiem, commitem albo dosłownym cytatem — zasada „niczego nie dopowiadać" obejmuje też artykuły o samym systemie.
Po co — i dlaczego przy trzech kontaktach
System założyłem, kiedy kontaktów było trzy. Nie trzydzieści — trzy. Sformułowanie z commita, od którego wszystko się zaczęło: „w tej chwili trzy żywe kontakty w trzech różnych miejscach (rozmowa na Telegramie, profil na LinkedIn, wiadomość głosowa) i ani jednego miejsca, w którym widać, czyj ruch i co zostało obiecane".
Od razu zastrzeżenie: to mój jedyny przypadek, nie prawidłowość. Ale ma swoją logikę. Chaos nie zaczyna się przy setce kontaktów — zaczyna się przy czwartym, tylko że zauważycie go później, kiedy nie będzie już czym odtworzyć korespondencji.
Drugi powód jest głębszy. U mnie liczą się dwa wyniki: klient albo insight. Rozmowa, z której dowiedziałem się, że bólu nie ma, to nie porażka, tylko rezultat. Dlatego w karcie jest sekcja „czego się nie spodziewaliśmy" i nie ma pola „kwota transakcji". Cel wyznacza schemat danych, nie odwrotnie — CRM sprzedażowy i CRM badawczy mają różne pola przy tym samym szkielecie.
I rzecz trzecia, o rolach. Maszyna zbiera, przygotowuje teksty i pamięta wszystko. Człowiek decyduje i wysyła. Ta granica jest postawiona twardo i, jak się okaże niżej, nie bez powodu.
Konstrukcja: tablica ani jednego dnia nie była prowadzona ręcznie
Karta, tablica i skrypt urodziły się w jednym commicie. To ważniejsze, niż się wydaje: decyzja „źródłem prawdy jest frontmatter karty, tabela jest pochodną" zapadła, zanim powstał pierwszy wiersz tabeli. Tablicy nie trzeba było „przestawiać na generowanie" — ręcznie nie była prowadzona w ogóle nigdy.
Trzy decyzje w tej konstrukcji uważam za przenośne dokądkolwiek.
Dwa progi przeterminowania, nie jeden. Mój ruch pali się po dwóch dniach, cudzy — po pięciu. Uzasadnienie jest etyczne, nie techniczne: własny dług pali się szybciej niż cudzy — człowiek nie ma obowiązku nam odpowiadać, a my mamy. Jedna kolumna „dni od ostatniego kontaktu" zlałaby te dwie metryki w jedno i schowała własną bezczynność za cudzym milczeniem.
Trzy stany hipotezy, nie dwa. Skrypt drukuje „NIE PYTALIŚMY" słowem, a nie zerem. „Nie potwierdziło się" i „nie pytaliśmy" to różne diagnozy. Sprawdziło się to na żywych danych niemal od razu: jedna hipoteza dała 0 potwierdzeń na 3 — a okazało się to pudłem targetingu, w próbce po prostu nie było ani jednej osoby właściwego typu. Zero wydrukowane jako zero czytałoby się jak obalenie.
Dwa rodzaje hipotez — dwa różne pola. Hipoteza o produkcie („cena jest u nich nieprzewidywalna") agreguje się po wszystkich kartach. Hipoteza o człowieku („myślę, że to nie on decyduje") nie agreguje się nigdy i żyje wewnątrz karty. Osobne pola sprawiają, że podmiana jednej na drugą jest fizycznie niemożliwa. Dlaczego to najcenniejszy chwyt całej konstrukcji — trochę niżej, w porażkach interpretacji: dyscyplina nie zadziałała tu nawet u tego, kto tę regułę napisał.
Granica „co jest generowane, a co pisane ręcznie" biegnie po sensie: datę przebiegu wpisuje skrypt, decyzję przebiegu — człowiek, linijką argumentu. Mechaniczne — maszynie, znaczeniowe — człowiekowi.
Porażki. Rodzaj pierwszy: dane
System zapamiętał nieprawdę albo zgubił prawdę.
Cudza fraza stała się głosem właściciela — w trzech plikach. Wypowiedź rozmówczyni trafiła do profilu mojego głosu jako moja własna. Warunek, który to umożliwił: dosłowny log korespondencji nie istniał, karta została złożona ze streszczenia. Streszczenie gubi autora — kwestie bez wyraźnego „kto to powiedział" zlepiają się w jeden głos, a zlepienie wypływa po tygodniach, kiedy źródła już nie da się odtworzyć. Zamknięte regułą „dosłownie, nie streszczeniem" i wstawką ⚠ w zepsutych plikach — nie usunięciem: po cichu wymazać błąd znaczy wymazać i lekcję.
Agent wymyślił ofertę — i zapisał ją jako moją. W polu „co proponujemy ludziom" pojawiły się „pomiary modeli wideo" — coś, czego nigdy nikomu nie proponowałem. Agent to zmyślił. Zamknięte tabelą „co mogę dać — moimi słowami" z kolumną „komu mówiłem": propozycja bez źródła to wystawa chciejstwa, nie fakt.
Kontakt bez adresu przepadł na zawsze. Linku do profilu nie ściągnięto za tego samego podejścia, a feed, który pokazał tego człowieka, nie daje się odtworzyć: to samo zapytanie dziesięć minut później zwróciło innych ludzi. Dwie punktowe próby odszukania — pudło. „Dopiszę później" w systemie zasilanym z niedeterministycznego feedu znaczy „zgubię".
Rodzaj drugi: proces
Reguła była — i nie zadziałała.
Gotowy tekst pojechał pod post obcego człowieka. Najdroższa wpadka: komentarz przygotowany pod najcenniejsze wejście w lejku został opublikowany pod postem innej osoby — w dodatku oznaczonej „NIE pisać". Mechanizm jest uczciwy i nie chodzi tu o nieuwagę: w karcie leżał link do profilu, a komentarz pisze się pod postem. To dwa różne adresy i różnicę widać dopiero w momencie wysyłki. Stąd strażnik: jeśli następnym krokiem jest wysyłka, a dokładnego adresu nie ma, przebieg pada, a na tablicy pali się „BRAK ADRESU".
Pierwsza wersja tego strażnika kłamała. Łapał po rdzeniu „odpowiedź" i odpalał się na karcie „czekamy na odpowiedź na dwa pytania" — a to jest oczekiwanie, adres nie jest tam potrzebny. Wniosek, który pojechał prosto do komentarza w kodzie: fałszywy alarm jest groźniejszy od przeoczenia — ostrzeżenie, które kłamie, człowiek uczy się ignorować.
Moje poprawki nie były zapisywane nigdzie. Decyzje produktowe szły do jednego pliku, profil głosu — do drugiego, fakty — do kart. A poprawki „jak ze mną rozmawiać" nie szły nigdzie i zostawały w czacie — i te same błędy się powtarzały. Wyszło na jaw po bezpośrednim pytaniu: „jak zapisujesz moje feedbacki, bierzesz je pod uwagę?". Odpowiedź brzmiała „nijak" i jeszcze w tej samej godzinie powstał dziennik poprawek. Jeśli kategoria wiedzy nie ma pliku, wiedza żyje w korespondencji i umiera wraz ze zmianą sesji — albo pracownika.
Stan procesu nie żył nigdzie. Stan każdego kontaktu był przechowywany, ale stan samego cyklu — nie. Pytanie „jutro otworzę nową sesję — czy zrozumie, że tydzień jeszcze nie minął?" miało odpowiedź „nijak". Tak powstał dziennik przebiegów: proces ma stan tak samo jak rekord i ten stan wymaga własnej tabeli.
Reguła unieważniła tekst, a tekst został. Szkic wiadomości, odrzucony przez nowe reguły, przeleżał w karcie trzy dni jako roboczy, a pole „następny krok" wciąż mówiło „wysłać". Znalazło się to dopiero przy pełnej inwentaryzacji. Reguła, która unieważnia artefakt, musi unieważniać i sam artefakt — inaczej zakaz i przedmiot zakazu spokojnie mieszkają w jednym pliku.
Rodzaj trzeci: interpretacja
Z poprawnych danych — błędny wniosek. Najdroższy rodzaj.
Milczenie użytkownika wyjaśnione produktem. Człowiek się przeprowadzał. Po śladzie na produkcji — zapłacił, trzyma saldo, sześć obciążeń w dwie minuty, potem cisza — wywnioskowałem: motywacja jest, pieniądze są, dostęp jest, więc zatrzymuje go produkt. Wniosek został nazwany najczystszym przypadkiem w próbce. Wyjaśnienie okazało się prozaiczne: przeprowadzka. Cofnięte dwie minuty po wypowiedzi właściciela wiedzy — czyli mnie samego, który o przeprowadzce dowiedział się z życia, nie z danych.
Ten mechanizm warto rozebrać do końca. W moim własnym pliku z insightami stoi czarno na białym: zachowanie z produkcji wyjaśnia „co", a nie „dlaczego". Złamałem własną regułę tego samego dnia, w którym ją zapisałem. Ślad behawioralny bez kontekstu człowieka kłamie pewnie i przekonująco — i właśnie dlatego rozdzielenie „hipotezy o człowieku" i „hipotezy o produkcie" jest strukturalne, osobnymi polami, a nie dyscyplinarne. Dyscyplina nie utrzymała nawet autora reguły.
Wniosek przepisany po cichu — dwa razy jednego dnia. Wniosek poprawiony po cichu miesiąc później wygląda tak, jakby nigdy nie był rewidowany, i cena błędu ginie razem z lekcją. Teraz zderzenie utrwala się jako zapis: co twierdzono, co przyszło przeciw temu i co dokładnie rozstrzygnęło spór.
Ankieta zbierała potwierdzenie nas samych. Pierwsza wersja kwestionariusza — skale od 1 do 5 według naszej własnej listy bólów. Człowiek wybierze z naszej listy, my dostaniemy potwierdzenie siebie i nazwiemy to badaniem. Wyłapane przed wysyłką — a wysyłać mieliśmy po cudzej sieci kontaktów, więc ceną błędu była cudza reputacja. Lista wariantów odpowiedzi to wasze hipotezy; agregat po niej mierzy was, a nie rynek.
Ból 9 na 9 o mało nie stał się ofertą. Dziewięć osób na dziewięć potwierdziło problem — a trzy z dziewięciu już rozwiązały go za darmo lokalnym narzędziem i raportowały sukces, nie ból. Licznik potwierdzeń bez oznaczenia „u kogo już rozwiązane" sprzedaje cudzy sukces jako wasz popyt.
Cztery cofnięcia własnych decyzji
Tego w artykułach typu „jak zbudowałem X" zwykle nie ma, a dla czytelnika jest cenniejsze od sukcesów.
| Co cofnięto | Po jakim czasie | Czym obalone |
|---|---|---|
| Wniosek „zatrzymuje go produkt" | 2 minuty | fakt z życia człowieka: przeprowadzka |
| „W sporach priorytet ma właściciel" | 45 minut | sam właściciel: „moim celem jest efektywność, a nie próby dogadzania mi" |
| Rezygnacja z osobnego skilla do wyszukiwania | 7 godzin | kryterium było błędne: skille dzieli się nie według częstotliwości, lecz według pytania, które zadaje człowiek |
| Poprawka frazy wyszukiwania | jeden przebieg | pomiar: było 1 na 3, zrobiło się 0 na 3 |
Ostatni wiersz to osobna lekcja. Naprawiałem nie tę usterkę, a poprawka pogorszyła sprawę. Wyszło to na jaw tylko dlatego, że pudło zostało zapisane: bez zapisu naprawiałbym to samo jeszcze raz. Zapis negatywnego wyniku to właśnie mechanizm ulepszania; poprawka bez zmierzonego „przed" jest nieodróżnialna od rytuału.
Do tego dochodzi arytmetyka, która zakazuje A/B przy moich wolumenach: wzór n = 16σ²/δ² przy bazowym odzewie 20% i pożądanym wzroście o 10 punktów procentowych wymaga 256 kontaktów na wariant. Na czterdziestu–sześćdziesięciu łapie się tylko efekt dwukrotny. Ważne zastrzeżenie: cała ta baza ilościowa pochodzi z cudzych kanałów, e-maili B2B i amerykańskich cold calli; przenoszę mechanizmy, nie liczby. Zamiast A/B — rundy po pięć rozmów na jednym sformułowaniu, z progiem decyzyjnym ogłoszonym z góry.
Reguła bez strażnika nie przeżywa
Główny wniosek całej tej historii — wypracowany, nie wymyślony.
Za każdym razem, kiedy reguła była zapisana prozą, ktoś ją łamał. Wliczając przypadki, gdy łamał ją ten sam człowiek, który ją napisał — w tym samym pliku, dwa akapity niżej. Przeżyły tylko te reguły, pod które napisano skrypt kładący przebieg. Pięciu z ośmiu strażników to bezpośrednia odpowiedź na awarię, która już się wydarzyła; żaden nie powstał „na przyszłość, a nuż się przyda".
Strażnika przy tym sprawdza się mutacją: psujesz kartę celowo — przebieg musi poczerwienieć dokładnie na niej; przywracasz — zzielenieć. Zielony test, którego nie da się zrobić czerwonym, to dekoracja. Uczciwa liczba z oryginału: mutacją sprawdziłem trzech strażników z ośmiu, reszta żyła na słowo honoru. W opublikowanej wersji md-crm ten dług jest spłacony — wszyscy strażnicy są pokryci 24 autotestami, a sam zestaw testów sprawdzono mutacją: wyłączasz strażnika — czerwienieją dokładnie jego testy.
I finałowy akcent, który potwierdził regułę na samym narzędziu. Jedyny bug, który po cichu psuł dane, mieszkał w jedynym miejscu skryptu niepokrytym testem: magiczne przesunięcie „+24" przy rzeczywistej długości separatora 22 znaki. Pierwszy przebieg szedł inną gałęzią kodu i wyglądał na sprawny, a każdy kolejny wrzynał się dwa znaki w głąb poprzedniego wiersza dziennika. Przez osiem iteracji skrypt obrastał kontrolami kart — i ani jedną kontrolą samego siebie.
Co z tego to ustawienia, a co szkielet
Szkielet: jeden plik na człowieka, stan we frontmatterze, tablica jako pochodna, strażnicy kładący przebieg, dziennik dosłowny, stan cyklu osobno od stanu kontaktów. To przenosi się dokądkolwiek — włącznie z rekrutacją: ATS różni się od CRM słownikiem etapów, nie architekturą.
Ustawienia: progi 2 i 5 dni, punkty kontrolne 5/12/25 rozmów, kody hipotez. Moje wartości to punkty odniesienia ze wskazanymi źródłami, nie stałe; każdy dobierze własne.
Postawienie tego wszystkiego u siebie to dwie komendy — instrukcja w pierwszej części. Katalog porażek w wersji zanonimizowanej jedzie razem ze skillem — jako plik references/failures.md: wydaje mi się słuszne, żeby narzędzie woziło ze sobą historię własnych awarii.