Как писать агентов Claude Code, которые тебе не врут
Два правила для надёжных пайплайнов с агентами Claude Code: один агент — одна специализация, и shell-команды вместо промптов везде, где ответ должен быть количественным.
Ты попросил Claude Code сверстать этот дизайн и проверить, что он совпадает с макетом в Figma. Он вернулся: Готово. Все секции соответствуют, отступы правильные, цвета те. Открываешь страницу — половина отступов не та, hover-состояния вообще нет, кнопки на оттенок не те. Модель не соврала со зла — она предсказала, что ты хочешь услышать проверено, и сгенерировала именно эту последовательность токенов. Шага проверки не было. Его и не могло быть — проверка требует сравнения с ground truth, а один агент в одном контексте не способен выйти за пределы собственного ответа и себя же проверить.
Два правила превратили мои хаотичные воркфлоу с галлюцинациями в надёжные пайплайны: один агент — одна специализация и всё, что можно выполнить как shell-команду, должно выполняться как shell-команда. Это не теория. Это то, что я делаю каждый день с Claude Code, и именно эти паттерны реально двигают дело.
Почему агенты-универсалы врут
LLM — это предсказатель следующего токена. Когда в промпте две роли — сделай X и проверь X — модель заканчивает первую роль, а затем предсказывает, как выглядел бы вывод второй, не выполняя её на самом деле. Самопроверка структурно слаба: тот же контекст, та же модель, те же слепые пятна. Прошло на проверке коррелирует с прошло на построении — они фейлятся вместе.
Модель не знает, что врёт. С её точки зрения, я всё тщательно проверил — это когерентное продолжение я написал код. По той же причине ты уверен? не ловит галлюцинации: модель так же уверена на втором проходе. Уверенность не коррелирует с правильностью — она коррелирует с тем, насколько правдоподобно звучит следующее предложение.
Решение — не лучший промпт. Будь осторожен, перепроверь, не галлюцинируй — эти инструкции не делают ничего. Решение структурное: специализируй агента так, чтобы он физически не мог притворяться, и пропускай количественную работу через shell, чтобы ответ приходил из реального состояния, а не из вероятности токенов.
Правило 1: один агент — одна специализация
Раздели работу на отдельных агентов с отдельными контекстами. У каждого — одна ответственность и узкий набор инструментов. Весь процесс становится эстафетой, а не одним агентом, который бегает по кругу:
- Builder-агент: берёт спек, пишет код. Это его единственная задача. У него
Read,Edit,Write,Bash. - Reviewer-агент: берёт спек плюс диф, проверяет по критериям. Чистый контекст. Не знает как код был написан — только что вышло. У него
Bash,Read,Grep,Glob— никаких инструментов записи. - Analytics-агент: отвечает на вопросы по данным, конструируя и запуская запросы. Только
Bash. Не может дойти до ответа без запуска реальной команды. - Оркестратор: основная сессия, которая вызывает каждого агента по очереди и никогда не просит одного делать работу другого.
Конкретный пример: вёрстка UI плюс визуальная проверка по макету в Figma. Builder пишет компоненты и коммитит диф. Затем оркестратор вызывает Reviewer с URL макета, дифом и чёткими acceptance criteria. Reviewer запускает Playwright, снимает скриншоты, диффит их с референсом и возвращает PASS или FAIL с реальными путями скриншотов и pixel diff. Builder вообще не приближается к шагу проверки — и именно поэтому проверка настоящая.
Антипаттерн — это мега-агент: один промпт вроде сверстай этот UI и убедись, что он совпадает с макетом. Гарантирую — он напишет, что всё совпадает. Не совпадает. Нарратив я проверил — это просто наиболее вероятная последовательность токенов после я сверстал.
Правило 2: shell вместо промпта, всегда
Всё количественное, всё что касается реального состояния, всё где ответ может быть неправильным так, что выглядит правильно — гони через sh. Задача агента — сконструировать и запустить команду, затем прочитать её вывод. Агент не является источником истины. Вывод shell является.
- Подсчёт:
wc -l logs.txt— правда. Приблизительно 47 строк лога от модели — галлюцинация. - Аналитика:
psql -c "SELECT count(*) FROM events WHERE created_at > now() - interval '30 days'". Не оцени объём. - Тесты:
pnpm test --reporter=json | jq '.numFailedTests'. Не расскажи что зафейлилось. - Git-состояние:
git rev-list --count main..HEAD,git diff --stat. Не посчитай коммиты или опиши изменения.
Когда это усвоишь, начинаешь замечать каждое место, где агент только что собирался придумать число. Похоже, здесь около 200 записей... — нет. Запусти SELECT count(*). Большинство тестов проходит... — нет. Запусти тесты, распарси JSON. Модель отлично умеет конструировать команду. Она ненадёжна в том, чтобы быть командой.
Конкретные режимы фейла, которые я словил
Это не гипотетические примеры. Каждый из них стоил мне реального времени до того, как я сменил паттерн:
- Призрачная проверка. Агент сказал я проверил все 14 секций по макету. Не открывал макет. Не делал скриншот. Проверка была галлюцинированным шагом в нарративе.
- Уверенные неправильные числа. Запросил monthly active users из аналитических данных. Получил число, промахнувшееся примерно в 3 раза. Модель интерполировала по выборочным строкам вместо того, чтобы запустить реальный запрос.
- Выдуманные изменения файлов. Агент сказал я обновил
config/feature-flags.json. Не обновлял. Он только собирался.git diffбыл пустым. - Фейковые прогоны тестов. Все тесты проходят. Ни один тест не запускался. Агент даже не вызвал тест-раннер — он предсказал, как выглядел бы его вывод.
Все четыре решаются теми же двумя правилами: раздели агента, выталкивай в shell. У Reviewer нет Write, поэтому он не может фейково редактировать файлы. У Analytics-агента только Bash, поэтому он не может вернуть число, которое не пришло из запроса. Структурная невозможность бьёт добрые намерения каждый раз.
Как это структурировать в Claude Code
Claude Code поддерживает sub-агентов, описываемых в .claude/agents/*.md. Каждый файл агента декларирует имя, описание, разрешённый набор инструментов и системный промпт. Оркестратор (твоя основная сессия) вызывает их через инструмент Agent. Вот тип определения, который я использую для reviewer — короткий, узкий, физически неспособный писать код:
---
name: reviewer
description: Reviews a diff against acceptance criteria. Cannot edit code.
tools: Bash, Read, Grep, Glob
---
You are a strict code reviewer. You receive:
- A diff (already produced by the builder)
- Acceptance criteria
Your job:
1. Run the build, the tests, the linter — through Bash.
2. Read the changed files directly.
3. Compare the actual behavior to the criteria.
4. Report PASS or FAIL with concrete evidence (command output, file excerpts).
You must NOT:
- Trust the builder's summary
- Assume anything was verified just because it was claimed
- Mark something PASS without running the actual checkОбрати внимание на набор инструментов: Bash, Read, Grep, Glob. Никакого Write, никакого Edit, никакого Agent. Reviewer может запускать команды, читать файлы, искать паттерны — и ничего больше. Если он попытается выдать галлюцинированный диф за проверенный, форма его вызовов инструментов делает это очевидным: реальных проверок не было. Можно заоудитить вызовы и увидеть ровно то, что инспектировалось.
Паттерн оркестрации: основная сессия вызывает Builder → ждёт → сама запускает git diff, чтобы зафиксировать реальное изменение → вызывает Reviewer со спеком и дифом → читает вердикт. Основная сессия никогда не просит одного агента делать оба. Ограничения инструментов сильнее инструкций в промпте: не фейкай проверку — это пожелание. Отсутствие Write — это факт.
Антипаттерны, которые пора выкинуть
То, что вижу в промптах и что не делает ничего — или, хуже, даёт ложное ощущение безопасности:
- Будь осторожен и перепроверь свою работу. Не генерирует никакого дополнительного поведения. Модель и так выдаёт то, что выглядит как тщательная работа.
- Убедись, что ты действительно проверил. Слово действительно не добавляет семантики, на которую модель может среагировать. Она действительно заявит, что проверила.
- Не галлюцинируй. Мем в prompt engineering. Галлюцинация — не переключатель, который модель может выключить.
- Доверять модели на маленьких числах. Именно на маленьких числах она врёт увереннее всего. Порога честности не существует.
- Добавление правил в промпт, чтобы заставить честность. Структурные решения (разделить + shell) бьют твики промпта каждый раз. Если правило нужно энфорсить — закодируй его в доступе к инструментам, а не в тексте.
Если твоя стратегия ловить галлюцинации — это подбирать более эмфатичные формулировки, то у тебя нет стратегии. У тебя есть надежда.
Ментальная модель
Агент — не коллега. Это функция: prompt → tokens. Функция отлично пишет код и плохо интроспектирует, сделала ли она правильную вещь. Воспринимай её утверждения о собственной работе как гипотезу. Диф, exit-код, скриншот, row count — вот доказательства. Итоговый саммари в конце хода — самая лживая поверхность во всей системе.
Специализация — твоя страховка от нарративного дрейфа. Shell — твой единственный источник истины. Builder пишет. Reviewer проверяет. Bash решает.
Вывод
Если запомнить одно: не позволяй одному агенту и производить, и судить собственный вывод; и не позволяй ни одному агенту отвечать на количественный вопрос без запуска команды. Всё остальное — следствие этих двух правил. Настраивай доступ к инструментам агрессивно, аудитируй вызовы инструментов вместо саммари — и поверхность галлюцинаций сжимается с повсюду до нескольких конкретных мест, где ты уже знаешь смотреть.