Wroc do Bloga
|15 min czytania|Ostatnia aktualizacja: 13 mar 2026|AI

Jak Przyspieszyłem Hooki Claude Code 73x (Dispatcher Pattern)

Jak Przyspieszyłem Hooki Claude Code 73x

Jeśli budujesz z Claude Code i używasz hooków, prawdopodobnie zacząłeś tak samo jak ja: jeden hook na jeden problem. Działa świetnie — dopóki nie przestaje.

To historia jak mój system 115 hooków zjadał 4.7 sekundy overhead na każdej interakcji z agentem i jak naprawiłem to jedną zmianą architekturalną.

Problem: Śmierć Przez Tysiąc Hooków

Hooki w Claude Code to skrypty, które uruchamiają się automatycznie na eventach — przed użyciem narzędzia (PreToolUse), po zakończeniu agenta (SubagentStop), gdy użytkownik wysyła wiadomość (UserPromptSubmit) i inne.

Każdy hook rozwiązuje realny problem:

  • master-enforcer blokuje edycję plików bez autoryzacji
  • verify-build-success sprawdza czy kod się kompiluje po zmianach
  • mandatory-routing kieruje poranną wiadomość do odpowiedniego workflow

Pułapka jest w tym, że każdy hook to osobny proces. Gdy SubagentStop się odpala, Claude Code uruchamia nowy interpreter Python dla każdego zarejestrowanego hooka. Przy 80ms na cold start, 34 hooki to 2.7 sekundy czystego overhead — zanim jakakolwiek logika sprawdzająca w ogóle się uruchomi.

Najgorsze? Odkryłem, że 30+ z tych 34 hooków natychmiast wychodziło. Sprawdzały nazwę agenta, widziały że to nie ich cel, i kończyły pracę. Trzydzieści procesów Python uruchomionych żeby nie zrobić nic.

Liczby (Przed)

Event Hooki Overhead
SubagentStop 34 ~2.7 sekundy
PreToolUse 14 ~1.1 sekundy
UserPromptSubmit 11 ~880ms
Razem na cykl agenta 59 ~4.7 sekundy

4.7 sekundy podatku na każdą interakcję z agentem. Przy systemie który robi setki wywołań agentów dziennie, to się sumuje do godzin zmarnowanego czasu.

Rozwiązanie: Dispatcher Pattern

Zamiast rejestrowania 34 osobnych skryptów, rejestrujesz jednego dispatcher'a który:

  1. Czyta jaki agent właśnie skończył pracę
  2. Sprawdza jakie checky dotyczą tego agenta
  3. Importuje i uruchamia tylko potrzebne checky in-process (bez spawnowania subprocesów)
  4. Zwraca jedną scaloną odpowiedź

Diagram Architektury

PRZED (34 osobne procesy):
┌──────────────┐
│ SubagentStop  │
│   Event       │──→ hook_1.py (spawn process) → exit
│               │──→ hook_2.py (spawn process) → exit
│               │──→ hook_3.py (spawn process) → check → exit
│               │──→ ...
│               │──→ hook_34.py (spawn process) → exit
└──────────────┘
Razem: 34 cold starty × ~80ms = 2,720ms

PO (1 dispatcher process):
┌──────────────┐     ┌─────────────────────┐
│ SubagentStop  │     │  dispatcher.py       │
│   Event       │──→  │  czytaj agent name   │
│               │     │  znajdź bundle       │
│               │     │  importuj check_3()  │
│               │     │  uruchom in-process  │
│               │     │  zwróć wynik         │
└──────────────┘     └─────────────────────┘
Razem: 1 cold start + wywołania in-process = ~25ms

Krok 1: Agent Tagging

Na SubagentStart, mały hook zapisuje nazwę agenta do pliku:

# agent-tagger.py (SubagentStart hook)
def main(input_data=None):
    agent_name = input_data.get('agent_name', '')
    with open('/tmp/.last-agent-name', 'w') as f:
        json.dump({'agent': agent_name}, f)

Krok 2: Konfiguracja Bundle'i

Plik JSON mapuje każdego agenta do jego bundle'i z checkami:

{
  "agent_bundles": {
    "web-developer": ["universal", "code"],
    "chief-of-staff": ["universal", "health"],
    "impl-planner": ["universal", "planning"],
    "DEFAULT": ["universal"]
  },
  "bundles": {
    "universal": ["verify-agent-protocol.py", "knowledge-evaluation.py"],
    "code": ["verify-build-success.py", "verify-no-dev-servers.py"],
    "health": ["verify-morning-output.py", "verify-weight-gate.py"],
    "planning": ["verify-confidence-scoring.py"]
  }
}

Web-developer uruchamia 8 checków (universal + code). Agent planujący uruchamia 8 (universal + planning). Nieznany agent uruchamia tylko 5 (universal). Zamiast wszystkich 34 za każdym razem.

Krok 3: Dispatcher

Dispatcher czyta tag, ładuje config i importuje każdy check jako moduł Python:

# dispatch_subagent_stop.py
from dispatcher_utils import (
    load_config, read_agent_name, import_hook,
    run_check_safe, merge_responses
)

def main():
    input_data = json.load(sys.stdin)
    agent_name = read_agent_name(input_data)
    config = load_config()

    bundle_names = get_bundles_for_agent(agent_name, config)
    check_files = get_checks_for_bundles(bundle_names, config)

    results = []
    for hook_file in check_files:
        module = import_hook(hook_file)
        exit_code, stdout, stderr = run_check_safe(module, input_data)
        results.append((hook_file, (exit_code, stdout, stderr)))

    final_exit, outputs, errors, blocks = merge_responses(results)
    sys.exit(final_exit)

Krok 4: Bezpieczny Wrapper

Kluczowy element — każdy check uruchamia się w wrapperze który łapie SystemExit (żeby hooki wywołujące sys.exit() nie zabiły dispatcher'a):

def run_check_safe(hook_module, input_data, hook_name="unknown"):
    stdout_capture = io.StringIO()
    stderr_capture = io.StringIO()
    exit_code = 0
    try:
        with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
            hook_module.main(input_data)
    except SystemExit as e:
        exit_code = e.code if e.code is not None else 0
    except Exception:
        exit_code = 0  # Nigdy nie blokuj przy crashu hooka
    return exit_code, stdout_capture.getvalue(), stderr_capture.getvalue()

Krok 5: Adaptacja Istniejących Hooków

Każdy hook potrzebuje jednej zmiany żeby akceptować przygotowane dane wejściowe:

# Przed
def main():
    input_data = json.load(sys.stdin)

# Po
def main(input_data=None):
    if input_data is None:
        input_data = json.load(sys.stdin)

To zachowuje pełną kompatybilność wsteczną — hooki dalej działają standalone gdy dostaną JSON przez stdin.

Liczby (Po)

Event Przed Po Przyspieszenie
SubagentStop ~2.7s (34 procesy) 23ms (1 proces) 117x
PreToolUse ~1.1s (13 procesów) 18ms (1 proces) 61x
UserPromptSubmit ~880ms (10 procesów) 23ms (1 proces) 38x
Razem na cykl ~4.7s ~64ms 73x

Ta sama egzekucja. Te same sprawdzenia. Zero zmian w zachowaniu. Tylko mądrzejsza architektura.

Drugi Zysk: Konsolidacja Reguł

Przy optymalizacji hooków, zastosowałem tę samą zasadę do reguł instrukcyjnych — plików markdown które Claude Code ładuje przy każdej sesji.

Nagromadziłem 1,824 linie reguł w 13 plikach. Wiele zduplikowanych, część sprzeczna ze sobą. Badania nad compliance instrukcji AI pokazały:

  • Optymalna ilość zawsze ładowanych instrukcji: 50-200 linii — powyżej tego compliance spada
  • Efekt "Lost in the Middle" — instrukcje zakopane w długich plikach są ignorowane przez model
  • Progressive disclosure jest lepsze — lekkie jądro ładowane zawsze, szczegóły ładowane na żądanie

Po audycie: 1,824 → 248 linii (-86%). Trzynaście plików zarchiwizowanych, pięć kluczowych zachowanych. Zachowanie agentów faktycznie się poprawiło bo pozostałe instrukcje były jasne, niesprzeczne i wystarczająco krótkie żeby model je w pełni przetworzył.

Kluczowe Zasady

1. Mierz Przed Optymalizacją

Zakładałem że moje hooki są wydajne bo każdy z nich był mały. Nigdy nie zmierzyłem skumulowanego kosztu spawnowania 34+ procesów na event. Zawsze mierz.

2. Przyrostowe Dodawanie Komplikuje System Wykładniczo

Każdy hook był racjonalny indywidualnie. 115-ty hook niczym się nie różnił od pierwszego. Ale koszt systemowy rósł liniowo podczas gdy wartość per-hook nie.

3. Dispatcher Pattern Ma Zastosowanie Wszędzie

Jeden punkt wejścia który routuje warunkowo jest prawie zawsze lepszy niż wiele niezależnych punktów wejścia. To dotyczy hooków, middleware, event handlerów i routów API.

4. Mniej Kontekstu = Lepsza Compliance AI

Dla systemów AI agentów specyficznie: model przetwarza twoje reguły lepiej gdy jest ich mniej. Duplikacja nie dodaje bezpieczeństwa — dodaje szum.

5. Architektura Ponad Dodawanie

Gdy twój system wydaje się wolny lub zawodny, odpowiedzią rzadko jest "dodaj kolejny check". Zwykle to "przeorganizuj jak checky są zorganizowane."

Jak To Zaimplementować

  1. Policz swoje hooki: sprawdź settings.json
  2. Zmierz overhead: dodaj timing do jednego hooka żeby zobaczyć koszt cold startu
  3. Zidentyfikuj marnotrawstwo: ile hooków wychodzi natychmiast dla niepassujących agentów?
  4. Stwórz dispatcher_config.json: zmapuj agentów do bundle'i z checkami
  5. Zaadaptuj hooki: dodaj parametr input_data=None do każdego main()
  6. Zbuduj dispatchery: jeden na high-frequency event (SubagentStop, PreToolUse)
  7. Zaktualizuj settings.json: zamień N wpisów na 1 wpis dispatcher'a
  8. Trzymaj backupy: stary settings.json to twój 2-minutowy plan rollbacku

Dispatcher pattern zamienił mój najwolniejszy komponent systemu w najszybszy. To samo podejście zadziała dla każdego setupu Claude Code z więcej niż 10 hookami.

FAQ

Ile hooków to za dużo?

Nie ma twardego limitu, ale jeśli masz więcej niż 10 hooków na SubagentStop, powinieneś rozważyć dispatcher'a. Overhead rośnie liniowo z ilością hooków.

Czy dispatcher pattern zmienia co jest egzekwowane?

Nie. Te same checky uruchamiają się dla tych samych agentów. Jedyna zmiana jest architekturalna — checky są importowane jako moduły Python zamiast spawnowane jako osobne procesy.

Co jeśli dispatcher crashnie?

Dispatcher opakowuje wszystko w top-level try/except który wychodzi z kodem 0. Crash dispatcher'a nigdy nie zablokuje twojego agenta. Indywidualne hooki można przywrócić w niecałe 2 minuty.

Czy mogę użyć tego z hookami nie-Pythonowymi?

Skrypty shell pozostają jako osobne rejestracje (nie da się ich importować jako moduły). Dispatcher pattern działa najlepiej gdy większość hooków jest w tym samym języku.

Jak dodać nowy check po implementacji dispatcher'a?

Dodaj plik z checkiem, dodaj go do odpowiedniego bundle'a w dispatcher_config.json i dodaj parametr input_data=None do jego main(). Żadna zmiana w settings.json nie jest potrzebna.

O Autorze

DG

Dawid Gac

Edukator E-commerce & Przedsiebiorca

Dawid Gac to polski przedsiebiorca, edukator e-commerce i wspolzalozyciel EcomBrain. Pomaga przedsiebiorcom budowac i skalowac biznesy online przez swoj kanal YouTube, spolecznosc i coaching 1:1.

Powiazane artykuly