07 / 21
Część 7 · Narzędzia — projekt i ergonomia
Plik źródłowyLekcje
- 00Kurs 2: aplikacje agentowe na modelach lokalnych — indeks
- 01Część 1 · Architektury agentowe — mapa terenu
- 02Część 2 · Szkielet — proces, konfiguracja, sesje
- 03Część 3 · Harness — pętla, która przeżywa długie zadania
- 04Część 4 · Inżynieria kontekstu — budżet uwagi
- 05Część 5 · Pamięć długoterminowa — trzy szkoły
- 06Część 6 · Tożsamość — pliki robocze jako program
- 07Część 7 · Narzędzia — projekt i ergonomia
- 08Część 8 · Code-mode — agent pisze program zamiast wywoływać narzędzia
- 09Część 9 · Protokoły i integracje — MCP, CLI, A2A
- 10Część 10 · Piaskownica produkcyjna
- 11Część 11 · Harmonogram — agent, który budzi się sam
- 12Część 12 · Kanały — jak z agentem rozmawiasz
- 13Część 13 · Flota — wielu agentów, jeden system
- 14Część 14 · Ewaluacja — skąd wiesz, że to działa
- 15Część 15 · Obserwowalność — widzieć, co robi
- 16Część 16 · Bezpieczeństwo agentowe — OWASP ASI01–ASI10
- 17Część 17 · Hybryda i router — lokalny domyślnie, chmura świadomie
- 18Część 18 · Wydanie i utrzymanie
- 19Część 19 · Capstone — osobisty asystent ogólnego przeznaczenia
- —Źródła — na czym stoi ten kurs
Poziom: ⭐⭐⭐⭐ · Czas: ~5 h · Wymaga: części 4 (budżet) i 6 (składanie promptu) Produkuje: rejestr narzędzi, przycinanie obserwacji, klucze idempotencji, ablację liczby narzędzi
0. Narzędzie to interfejs użytkownika, którego użytkownikiem jest model
Piszesz narzędzie tak, jakbyś pisał je dla juniora, który:
- przeczyta tylko opis, nigdy kod,
- nie zapamięta niczego między turami,
- pod presją zgadnie zamiast zapytać,
- i przy błędzie albo się podda, albo powtórzy to samo.
Każda z tych czterech cech ma odpowiadającą jej regułę projektową. Cała ta część to ich rozwinięcie.
⭐ Arytmetyka z części 1 wraca tutaj i jest sednem sprawy. Przy 92 % skuteczności na krok 15 kroków daje 29 %. Projektowanie narzędzi to główna dźwignia podnoszenia tych 92 % — i jednocześnie dźwignia skracania łańcucha, bo lepsze narzędzie robi w jednym kroku to, co gorsze w trzech.
1. Anatomia narzędzia
{
"nazwa": "czytaj_dokument",
"opis": ("Czyta dokument tekstowy lub PDF z archiwum. Zwraca treść. "
"Przy dokumentach > 500 linii zwraca początek i koniec — "
"wtedy użyj `szukaj_w_dokumencie`, żeby dostać środek."),
"parametry": {
"sciezka": {
"typ": "string",
"opis": "Ścieżka względem ~/dokumenty, np. 'faktury/2026/03/orange.pdf'. "
"Ścieżki bezwzględne są odrzucane.",
"wymagany": True
},
"od_linii": {
"typ": "integer",
"opis": "Numer pierwszej linii (od 1). Domyślnie 1.",
"wymagany": False, "domyslnie": 1
}
}
}
Pięć rzeczy, które ten opis robi dobrze:
- Mówi, co zwraca — nie tylko co robi.
- Ostrzega o zachowaniu brzegowym — obcięcie przy >500 linii.
- Kieruje do narzędzia następnego — „wtedy użyj
szukaj_w_dokumencie". To jest najtańszy sposób nauczenia modelu sekwencji. - Podaje przykład formatu —
'faktury/2026/03/orange.pdf'mówi więcej niż zdanie. - Mówi, co jest odrzucane — model nie traci tury na próbę.
1.1 · Nazewnictwo
| Zasada | ✗ | ✓ |
|---|---|---|
| Czasownik + rzeczownik | dokument, pdf_handler |
czytaj_dokument |
| Bez skrótów | rd_doc, get_fl |
czytaj_dokument |
| Jednoznaczne wobec reszty | szukaj i znajdz obok siebie |
szukaj_w_dokumencie, znajdz_dokumenty |
| Konsekwentny język | read_file obok zapisz_plik |
jeden język w całym zestawie |
⚠️ Trzeci wiersz jest tym, który boli najbardziej. Dwa narzędzia o podobnych nazwach i nakładających się opisach to gwarantowany błąd wyboru, i to taki, którego nie naprawisz lepszym promptem. Anthropic mówi o tym wprost: narzędzia mają minimalizować nakładanie się funkcji. Jeśli nie umiesz w jednym zdaniu powiedzieć, kiedy użyć A, a kiedy B — scal je albo usuń jedno.
2. Pobłażliwe wejście, surowe wyjście
Reguła Steinbergera z MCP Best Practices, przepisana:
Parsowanie parametrów powinno być pobłażliwe (np. przyjmij
path, gdy formalnie zdefiniowanoproject_path).
ALIASY = {
"sciezka": ["path", "plik", "file", "sciezka_pliku", "filepath"],
"od_linii": ["from_line", "start", "od"],
}
def normalizuj(argumenty: dict, schemat: dict) -> dict:
"""Model pomyli nazwę parametru. Nie karz go za to turą."""
wynik = {}
for kanon, spec in schemat.items():
if kanon in argumenty:
wynik[kanon] = argumenty[kanon]
continue
for alias in ALIASY.get(kanon, []):
if alias in argumenty:
wynik[kanon] = argumenty[alias]
break
else:
if spec.get("wymagany"):
raise BladNarzedzia(
f"Brak wymaganego parametru '{kanon}'. "
f"Podałeś: {list(argumenty)}. "
f"Oczekiwane: {list(schemat)}.")
if "domyslnie" in spec:
wynik[kanon] = spec["domyslnie"]
return wynik
⭐ Jedna tura zmarnowana na literówkę w nazwie parametru kosztuje Cię ~2 sekundy i ~400 tokenów. Trzydzieści linii aliasów zwraca się pierwszego dnia.
Ale wyjście musi być surowe i przewidywalne. Pobłażliwość na wejściu, rygor na wyjściu — bo wyjście czyta model i każda niespójność go myli.
3. Kształtowanie obserwacji — gdzie umiera kontekst
To jest miejsce, w którym agenci przegrywają najczęściej, i jednocześnie najłatwiejsze do naprawienia.
ls -R ~/dokumenty → 14 200 tokenów ← pół okna, jednym wywołaniem
cat duza-umowa.pdf → 8 900 tokenów
git log → 6 100 tokenów
3.1 · Cztery techniki, w kolejności użycia
1 · Przycinanie z zachowaniem obu końców
def przytnij(tresc: str, limit=2000) -> str:
if len(tresc) <= limit:
return tresc
p = limit // 2
return (tresc[:p] +
f"\n\n[… wycięto {len(tresc)-limit} znaków. "
f"Użyj węższego zapytania, jeśli potrzebujesz środka …]\n\n" +
tresc[-p:])
⭐ Środek jest wycinany celowo. W wynikach poleceń informacja koncentruje się na brzegach: początek to nagłówek i struktura, koniec to podsumowanie, błędy i status. Środek to zwykle powtarzalna lista.
2 · Podsumowanie strukturalne zamiast surowego wyniku
def podsumuj_liste_plikow(sciezki: list) -> str:
"""Zamiast 14 tys. tokenów listy — 200 tokenów struktury."""
from collections import Counter
katalogi = Counter(str(pathlib.Path(s).parent) for s in sciezki)
rozszerz = Counter(pathlib.Path(s).suffix for s in sciezki)
return (f"{len(sciezki)} plików w {len(katalogi)} katalogach.\n"
f"Największe katalogi: " +
", ".join(f"{k} ({n})" for k, n in katalogi.most_common(8)) + "\n"
f"Typy: " + ", ".join(f"{e or '(brak)'} ({n})" for e, n in rozszerz.most_common()) +
"\n\nUżyj `znajdz_dokumenty(wzorzec)`, żeby zawęzić.")
3 · Stronicowanie z jawnym wskaźnikiem
Wyniki 1–20 z 340. Aby zobaczyć kolejne: znajdz_dokumenty(..., od=21).
4 · Odesłanie zamiast treści — wynik ląduje w pliku, model dostaje ścieżkę i nagłówek. To jest doładowanie w ostatniej chwili z części 4, zastosowane do wyjścia narzędzia.
3.2 · Budżet per narzędzie
LIMITY = {
"czytaj_dokument": 4000, # czasem naprawdę trzeba dużo
"znajdz_dokumenty": 800, # lista — wystarczy podsumowanie
"powloka": 2000,
"szukaj_w_sieci": 1500, # ⚠️ najbardziej niezaufane, najkrócej
}
⚠️ szukaj_w_sieci ma najniższy limit nieprzypadkowo. Im mniej obcego tekstu
wpuszczasz do kontekstu, tym mniejsza powierzchnia wstrzyknięcia. Limit tokenów jest
tu środkiem bezpieczeństwa, nie tylko oszczędnością. Wraca w częściach 10 i 16.
4. Błąd jest promptem
Rozwinięcie drabiny z części 3. Dobry komunikat błędu ma trzy części:
CO poszło źle + DLACZEGO + CO WOLNO ZAMIAST TEGO
| ✗ Zły | ✓ Dobry |
|---|---|
FileNotFoundError: faktury/orange.pdf |
Nie ma pliku 'faktury/orange.pdf'. W katalogu 'faktury/' są podkatalogi: 2025/, 2026/. Spróbuj 'faktury/2026/orange.pdf' albo użyj znajdz_dokumenty("orange"). |
PermissionError: [Errno 13] |
Brak prawa zapisu do /etc/hosts. Wolno pisać tylko w ~/dokumenty i /tmp. Jeśli to wynik pośredni — użyj /tmp/wynik.txt. |
TimeoutError |
Polecenie przekroczyło 30 s i zostało przerwane. Jeśli przetwarzasz wiele plików — podziel na partie po 50 i wywołaj kilka razy. |
JSONDecodeError: line 1 col 1 |
Zwróciłeś tekst, a oczekiwano JSON. Twoja odpowiedź zaczynała się od 'Oczywiście, oto...'. Zwróć SAM obiekt JSON, bez wstępu. |
class BladNarzedzia(Exception):
def __init__(self, co, dlaczego="", zamiast=""):
self.tresc = co + (f" {dlaczego}" if dlaczego else "") + \
(f"\nSpróbuj: {zamiast}" if zamiast else "")
super().__init__(self.tresc)
# Użycie — zauważ, że podpowiedź jest WYLICZANA, nie stała:
raise BladNarzedzia(
co=f"Nie ma pliku '{s}'.",
dlaczego=f"W '{rodzic}' są: {', '.join(sasiedzi[:6])}.",
zamiast=f"znajdz_dokumenty('{pathlib.Path(s).stem}')")
⭐ „Wyliczana, nie stała" jest tu istotne. Podpowiedź zawierająca rzeczywistą zawartość katalogu podnosi szansę poprawy z ~20 % do ponad 90 %. Stała podpowiedź („sprawdź ścieżkę") nie daje prawie nic.
5. Idempotencja — narzędzie, które wolno powtórzyć
Agent powtarza wywołania: po timeoucie, po niejasnym wyniku, po restarcie sesji. Jeśli narzędzie ma skutek uboczny, powtórzenie robi ten skutek dwa razy.
import hashlib, json, os, pathlib
def klucz_idempotencji(narzedzie: str, argumenty: dict) -> str:
return hashlib.sha256(
(narzedzie + json.dumps(argumenty, sort_keys=True)).encode()).hexdigest()[:16]
class RejestrSkutkow:
"""Wykonane operacje ze skutkiem ubocznym. Powtórzenie zwraca poprzedni wynik."""
def __init__(self, plik="$OSA/skutki.json"):
self.p = pathlib.Path(os.path.expandvars(plik)).expanduser()
self.d = json.loads(self.p.read_text()) if self.p.exists() else {}
def wykonaj_raz(self, narzedzie, argumenty, funkcja):
k = klucz_idempotencji(narzedzie, argumenty)
if k in self.d:
return (f"[już wykonane {self.d[k]['kiedy']}] {self.d[k]['wynik']}")
wynik = funkcja(**argumenty)
self.d[k] = {"kiedy": TERAZ(), "wynik": str(wynik)[:500]}
self.p.write_text(json.dumps(self.d, ensure_ascii=False, indent=1))
return wynik
| Narzędzie | Idempotentne? | Uwaga |
|---|---|---|
czytaj_dokument |
⭐ tak | odczyt — powtarzaj dowolnie |
znajdz_dokumenty |
⭐ tak | — |
zmien_nazwe |
✗ nie | ⚠️ drugie wywołanie zmienia nazwę już zmienionej |
wyslij_wiadomosc |
✗ nie | ⚠️⚠️ podwójna wysyłka jest widoczna dla ludzi |
dopisz_do_pliku |
✗ nie | wpis dwa razy |
⚠️ Zapisz w rejestrze narzędzi, które są idempotentne, i traktuj to jako pole
obowiązkowe. Narzędzie bez zadeklarowanej idempotencji domyślnie uznajesz za
nieidempotentne i przepuszczasz przez wykonaj_raz.
6. Ile narzędzi to za dużo
Tu jest twardy kompromis i mało kto go mierzy.
TRAFNOŚĆ WYBORU NARZĘDZIA vs LICZBA NARZĘDZI (kształt spodziewany)
95% ┤●●●●●●
│ ●●●●●
85% ┤ ●●●●
│ ●●●●
70% ┤ ●●●●●
│ ●●●●●●●
50% ┤ ●●●●
└──┬─────┬─────┬─────┬─────┬─────┬───
3 8 15 25 40 60
Dwa mechanizmy działają jednocześnie i oba pogarszają sprawę:
- Koszt kontekstu — 40 narzędzi × ~200 tokenów = 8 tys. tokenów w każdej turze.
- Niejednoznaczność wyboru — im więcej narzędzi, tym więcej par o nakładających się opisach.
6.1 · Trzy strategie
| Strategia | Jak | Koszt | Kiedy |
|---|---|---|---|
| A · mniej narzędzi | scal podobne, usuń nieużywane | zero | ⭐ zawsze najpierw |
| B · doładowanie | pokaż 5 podstawowych + znajdz_narzedzie(opis) |
1 tura przy rzadkich | >15 narzędzi |
| C · code-mode | narzędzia jako funkcje w kodzie, nie w prompcie | patrz część 8 | ⭐⭐ >25 narzędzi |
Strategia A jest niedoceniana. Zanim dołożysz mechanizm, sprawdź dziennik: ilu narzędzi agent użył choć raz w ostatnich 100 sesjach?
python3 sesja-cli.py statystyki-narzedzi --ostatnie 100
# nazwa użyć % sesji błędów
# czytaj_dokument 412 94% 3%
# znajdz_dokumenty 388 91% 1%
# powloka 201 44% 12% ← wysoki błąd, zobacz §4
# konwertuj_docx 4 2% 50% ← ⚠️ kandydat do usunięcia
# ocr_skan 0 0% — ← ⚠️⚠️ usuń
⭐ Narzędzie użyte w 2 % sesji kosztuje kontekst w 100 % sesji. To jest zły interes i najprostsza optymalizacja w całym kursie.
6.2 · Promień rażenia jako kryterium projektowe
Steinberger planuje zmiany według promienia rażenia — ile plików ruszy. Ta sama jednostka świetnie działa przy projektowaniu narzędzi:
| Promień | Przykład | Wymagania |
|---|---|---|
| Zerowy (odczyt) | czytaj_dokument |
brak; idempotentne z definicji |
| Punktowy (1 obiekt) | zmien_nazwe(plik) |
dziennik, idempotencja |
| Lokalny (do 10) | zmien_nazwy(wzorzec) |
⭐ tryb --sucho obowiązkowy |
| Szeroki (>10) | przenies_katalog |
⭐⭐ potwierdzenie człowieka + kopia |
| Nieodwracalny | wyslij_mail, usun |
⭐⭐⭐ potwierdzenie + nie dawaj agentowi |
def wymaga_zgody(narzedzie: dict, argumenty: dict, granice: dict) -> bool:
promien = narzedzie["promien"](argumenty) # wyliczany, nie stały
return promien >= granice.get("prog_zgody", 10)
⚠️ Promień musi być wyliczany z argumentów, nie z nazwy narzędzia.
zmien_nazwy("*.txt") ma promień 3 albo 3000 zależnie od katalogu — i to jest
właśnie ta różnica, która decyduje, czy pytać.
7. OD ZERA — rejestr narzędzi
# $OSA/narzedzia/rejestr.py
"""Rejestr: jedno miejsce na schemat, limity, promień i idempotencję."""
import json, time, inspect
class Rejestr:
def __init__(self, budzet_obserwacji=2000):
self.n, self.budzet = {}, budzet_obserwacji
def dodaj(self, nazwa, opis, parametry, funkcja, *,
limit=None, idempotentne=False, promien=lambda a: 0, zaufanie="host"):
self.n[nazwa] = dict(nazwa=nazwa, opis=opis, parametry=parametry,
funkcja=funkcja, limit=limit or self.budzet,
idempotentne=idempotentne, promien=promien,
zaufanie=zaufanie)
def schemat(self, poziom_zaufania="host", tylko=None) -> list:
"""To, co trafia do promptu. Filtrowane po zaufaniu sesji — P88."""
rzad = {"piaskownica": 0, "host": 1}
out = []
for n in self.n.values():
if rzad[poziom_zaufania] < rzad[n["zaufanie"]]:
continue # sesja niezaufana NIE WIDZI narzędzia
if tylko and n["nazwa"] not in tylko:
continue
out.append({k: n[k] for k in ("nazwa", "opis", "parametry")})
return out
def wywolaj(self, nazwa, argumenty, kontekst) -> dict:
if nazwa not in self.n:
bliskie = [k for k in self.n if nazwa[:4] in k]
raise BladNarzedzia(f"Nie ma narzędzia '{nazwa}'.",
f"Dostępne: {', '.join(self.schemat_nazw(kontekst))}.",
bliskie[0] if bliskie else "")
n = self.n[nazwa]
if kontekst["zaufanie"] == "piaskownica" and n["zaufanie"] == "host":
raise BladNarzedzia(f"'{nazwa}' niedostępne w tej sesji.",
"Sesja ma poziom 'piaskownica'.", "")
arg = normalizuj(argumenty, n["parametry"])
if n["promien"](arg) >= kontekst.get("prog_zgody", 10) and not kontekst.get("zgoda"):
return {"status": "wymaga_zgody",
"tresc": f"Operacja dotknie ~{n['promien'](arg)} obiektów. "
f"Potwierdź, żeby wykonać."}
t0 = time.time()
try:
wynik = (kontekst["skutki"].wykonaj_raz(nazwa, arg, n["funkcja"])
if not n["idempotentne"] else n["funkcja"](**arg))
status = "ok"
except BladNarzedzia as e:
wynik, status = e.tresc, "blad"
except Exception as e:
wynik, status = f"Nieoczekiwany błąd: {type(e).__name__}: {e}", "blad"
return {"status": status, "tresc": przytnij(str(wynik), n["limit"]),
"ms": int((time.time() - t0) * 1000)}
Trzy rzeczy, które ten rejestr robi, a większość implementacji nie:
- Filtruje narzędzia po poziomie zaufania sesji — sesja z piaskownicy nawet nie widzi narzędzi hosta. Niewidoczne narzędzie nie zostanie wywołane ani opisane atakującemu.
- Wylicza promień z argumentów i pyta o zgodę.
- Nigdy nie wypuszcza surowego traceback — każdy wyjątek wraca jako czytelny tekst.
8. PRODUKCYJNIE
| Anthropic tool use | MCP | LangChain | Twój rejestr | |
|---|---|---|---|---|
| Schemat | JSON Schema | JSON Schema | Pydantic | dict |
Walidacja strict |
⭐ tak | częściowo | ⭐ tak | ręcznie |
| Pobłażliwe aliasy | ✗ | ✗ | ✗ | ⭐ tak |
| Filtr po zaufaniu | ✗ | ✗ | ✗ | ⭐ tak |
| Promień rażenia | ✗ | ✗ | ✗ | ⭐ tak |
| Idempotencja | ręcznie | ⭐ konwencja | ✗ | ⭐ wymuszona |
| Doładowanie narzędzi | ⭐ tool search | ⭐ od 2026-07 | ✗ | ręcznie |
💡 Jedna rzecz warta wzięcia z zewnątrz: JSON Schema zamiast własnego dict. Nie dlatego, że ładniejsze — dlatego, że
llama-serverpotrafi wymusić JSON Schema na poziomie próbkowania (gramatyki z kursu 1, część 3). Wymuszenie strukturalne to nie jest prośba do modelu, to jest gwarancja. Przy modelach lokalnych ta różnica jest znacznie większa niż przy frontier.
9. EKSPERYMENT 7 — ablacja liczby narzędzi
⭐ Ten eksperyment da Ci liczbę, której nie znajdziesz w żadnej dokumentacji: ile narzędzi wytrzymują Twoje modele.
Hipoteza: trafność wyboru narzędzia spada nieliniowo z ich liczbą, a próg jest znacznie niższy dla modeli lokalnych niż dla frontier.
9.1 · Przygotowanie
mkdir -p $OSA/pomiary/narzedzia && cd $OSA/pomiary/narzedzia
- 5 narzędzi realnych — tych, których agent faktycznie używa.
- 35 atrap — działających, ale nieistotnych (
sprawdz_pogode,konwertuj_waluty,generuj_qr…). ⚠️ Opisy muszą być wiarygodne, nie „atrapa 7" — inaczej mierzysz coś innego. - 40 zadań z jednoznacznie poprawnym narzędziem.
# zadania.py
ZADANIA = [
("Ile linii ma umowa-najem.txt?", "czytaj_dokument"),
("Znajdź wszystkie faktury z marca", "znajdz_dokumenty"),
("Zmień nazwę skan001.pdf na faktura.pdf", "zmien_nazwe"),
("Ile plików jest w ~/dokumenty?", "powloka"),
("Co pamiętasz o moim rachunku za prąd?", "przypomnij"),
# … do 40, po 8 na każde narzędzie realne
]
9.2 · Przebieg
for n in 5 10 15 25 40; do
python3 ablacja.py --narzedzi $n --model gpt-oss-20b --powtorzen 3
done
| Narzędzi w prompcie | Trafność wyboru | Tokeny (schematy) | Czas do 1. tokena | Błędne argumenty |
|---|---|---|---|---|
| 5 | /40 | |||
| 10 | /40 | |||
| 15 | /40 | |||
| 25 | /40 | |||
| 40 | /40 |
Powtórz dla qwen3-32b. Spodziewaj się, że model gęstszy wytrzyma więcej — i to
jest konkretny argument za routingiem z części 17.
9.3 · Eksperyment dodatkowy — które pary się mylą
python3 ablacja.py --narzedzi 40 --macierz-pomylek
# wypisz 10 najczęstszych par (oczekiwane → wybrane)
⭐ To jest najbardziej praktyczny wynik z całej tej części. Dostajesz konkretną listę par narzędzi o nakładających się opisach. Dla każdej pary zrób jedno z trzech: scal, usuń jedno, albo przepisz oba opisy tak, żeby różnica była w pierwszym zdaniu. Potem zmierz ponownie.
⚠️ Predykcja do sprawdzenia: przy gpt-oss-20b spodziewam się wyraźnego spadku
już między 15 a 25 narzędziami i większości pomyłek skoncentrowanych w 3–4 parach,
nie rozłożonych równomiernie. Jeśli tak jest — naprawa czterech opisów kupi Ci
większość utraconej trafności bez żadnego mechanizmu.
10. ĆWICZENIE — przepisz zestaw narzędzi Archiwisty
- Wypisz wszystkie narzędzia, jakie ma dziś, ze statystyką użycia.
- Usuń każde użyte w mniej niż 5 % sesji. Bez sentymentów.
- Dla każdego pozostałego uzupełnij:
limit,idempotentne,promien,zaufanie. - Przepisz opisy według pięciu zasad z §1: co zwraca, zachowanie brzegowe, odesłanie do narzędzia następnego, przykład formatu, co jest odrzucane.
- Napisz trzy komunikaty błędu w formacie CO + DLACZEGO + CO ZAMIAST, z podpowiedzią wyliczaną z rzeczywistego stanu.
- Zmierz ponownie zadania z §9 na nowym zestawie.
python3 sesja-cli.py narzedzia archiwista --sprawdz
# oczekuj wykrycia:
# ⚠️ 'szukaj' i 'znajdz' — opisy nakładają się w 71%
# ⚠️ 'zmien_nazwe' — brak zadeklarowanej idempotencji
# ⚠️ 'powloka' — promień stały, powinien być wyliczany z argumentów
# ⚠️ 'ocr_skan' — 0 użyć w 100 sesjach
11. Pułapki tej części
| ID | Pułapka | Objaw | Jak uniknąć |
|---|---|---|---|
| P86 | Dwa narzędzia o nakładających się opisach | Model losowo wybiera złe; prompt nie pomaga | Macierz pomyłek (§9.3). Nie umiesz w zdaniu powiedzieć, kiedy A a kiedy B → scal |
| P87 | Surowy traceback jako wynik narzędzia | Agent widzi Errno 13, poddaje się albo powtarza to samo |
Każdy wyjątek → CO + DLACZEGO + CO ZAMIAST, z podpowiedzią wyliczaną |
| P88 | Narzędzia niefiltrowane po zaufaniu sesji | Sesja z kanału widzi powloka; wstrzyknięcie dostaje shell |
Filtr w schemat() — niezaufana sesja nie widzi narzędzi hosta |
| P89 | Brak idempotencji przy skutku ubocznym | Po timeoucie agent wysyła wiadomość drugi raz | Pole idempotentne obowiązkowe; brak = przepuszczasz przez wykonaj_raz |
| P90 | Promień rażenia stały zamiast wyliczanego | zmien_nazwy("*") przechodzi bez pytania, bo „to tylko zmiana nazwy" |
promien to funkcja argumentów, zawsze |
12. Checkpoint
cd $OSA
python3 - <<'EOF'
from narzedzia.rejestr import Rejestr
r = Rejestr()
r.dodaj("powloka", "…", {}, lambda: None, zaufanie="host")
r.dodaj("czytaj", "…", {}, lambda: None, zaufanie="piaskownica")
assert len(r.schemat("piaskownica")) == 1, "P88: filtr zaufania nie działa"
assert len(r.schemat("host")) == 2
print("✓ filtr zaufania")
EOF
python3 sesja-cli.py narzedzia archiwista --sprawdz
test -s pomiary/narzedzia/ablacja.csv && echo "✓ ablacja przeprowadzona"
git add -A && git commit -m "część 7: rejestr narzędzi, obserwacje, promień, idempotencja"
- Opis narzędzia mówi, co zwraca, i kieruje do narzędzia następnego
- Wejście pobłażliwe (aliasy), wyjście surowe
- Obserwacje przycinane z zachowaniem obu końców, budżet per narzędzie
- Błąd ma trzy części, a podpowiedź jest wyliczana ze stanu
- Narzędzia nieidempotentne przechodzą przez rejestr skutków
- Promień rażenia wyliczany z argumentów, próg zgody działa
- Narzędzia filtrowane po poziomie zaufania sesji — sprawdzone testem
- ⭐ Znasz swój próg liczby narzędzi i masz macierz pomyłek
Dalej: 08-CODE-MODE.md — największa zmiana 2026. Agent pisze program zamiast
wywoływać narzędzia, a Twój mały model nagle staje się dużo lepszy.