almdev Технический блог

Агент читает чужой Symfony-проект
96 тысяч строк, пятнадцать вопросов и карта, в которой три ответа оказались выдумкой.

Первый заход лаборатории: как заставить агента построить карту незнакомого кода, почему «прочитай весь проект» не работает и как проверить, что из полученного описания правда.

Подкатегория: Лаборатория

Чтение
5 мин
Технологии / версии
Symfony · Coding agent · PHP · Контекст
Серия
Лаборатория · часть 1 из 9
2 июн 2026 · 5 мин · AI
Исследовательский аппарат изучает связи незнакомого программного проекта
Лаборатория 06/2026

Первое, что делает разработчик на новом проекте, — читает код. Первое, что предлагают делать с агентом, — просить его этот код объяснить. Звучит логично, работает плохо, и я хочу разобраться, почему.

Это первый заход серии, где один и тот же проект пройдёт за лето десять инженерных задач вместе с агентом. Начинаем с самой безобидной: просто понять, что там внутри.

Стенд

Все статьи серии работают с одним проектом и стартуют от одного зафиксированного коммита.

  • Внутренний трекер задач, Symfony 8.0 на PHP 8.4
  • 1180 PHP-файлов, около 96 000 строк
  • PostgreSQL 16, Doctrine ORM 3, Messenger, Redis
  • Twig, Turbo, Stimulus
  • 784 теста, покрытие 61 процент
  • PHPStan на шестом уровне с базовой линией на 2100 записей
  • Модули Core, Tasks, Docs
  • Полный прогон CI — 8 минут

Проект я знаю хорошо. Это принципиально: чтобы оценить ответы агента, нужен кто-то, кто знает правильные.

Это наблюдение за двумя заходами в одной рабочей среде, а не воспроизводимый benchmark. Я не зафиксировал в заметках модель, её версию, лимит контекста и commit SHA стенда. Поэтому числа ниже описывают этот случай, а не качество всех агентов. В следующих заходах эти параметры должны записываться до первого запроса.

Symfony 8.0 и PHP 8.4 — версии зафиксированного стенда. Поддержка Symfony 8.0 завершится в июле 2026 года; повторять опыт на новом проекте надо на поддерживаемой ветке, не обновляя исходный стенд задним числом.

Что не работает

Заход первый, наивный: «изучи проект и опиши архитектуру».

Агент потратил четыре минуты, прочитал около семидесяти файлов и выдал полторы страницы текста. Текст выглядел прекрасно. Модули перечислены верно, слои названы правильно, стек определён точно.

Дальше я начал проверять детали и настроение испортилось.

Он написал, что аутентификация построена на JWT. В проекте сессии. Взялось это, судя по всему, из наличия пакета для подписи токенов — он там есть, но используется для ссылок в письмах.

Он написал, что для очередей используется RabbitMQ. Используется Redis. В конфигурации есть закомментированная строка с другим транспортом, оставшаяся с прошлого года.

Он описал модуль Chat как работающий. В src/Module/Chat/ лежат три файла-заготовки и ни одного маршрута.

Три ошибки из пятнадцати проверяемых утверждений: 20 процентов в этой выборке. Это не доля вымысла во всём документе, но для текста, который выглядит как справка, и такой выборки достаточно, чтобы ему не доверять без проверки.

Почему так получается

Не потому что модель плохая. Потому что задача поставлена так, что провоцирует именно этот результат.

«Опиши архитектуру» — это просьба выдать связный текст. Связность подталкивает заполнять пробелы. Здесь агент достроил картину по нерелевантным следам: пакету для подписи токенов и старой закомментированной строке транспорта. Этого было недостаточно для выводов про JWT и RabbitMQ.

Второе: агент читал файлы выборочно, по своему усмотрению. Сводка инструментов показала около семидесяти открытых файлов из 1180, но их список я не сохранил. Это ещё одно ограничение эксперимента.

Третье: у ответа не было формы, в которой ошибку видно. Полторы страницы прозы проверять неудобно, и это само по себе часть проблемы.

Что работает

Заход второй: не просить описание, просить ответы на конкретные вопросы со ссылками на код.

Ответь на вопросы ниже. На каждый ответ дай ссылку file:line, где это видно.
Если ответа в коде нет — напиши "не найдено", не предполагай.

1. Как аутентифицируется пользователь? Где настроен firewall?
2. Какой транспорт у Messenger в проде? Где это задано?
3. Какие модули реально имеют маршруты? Перечисли по routes.yaml.
4. Где проверяется принадлежность к организации?
5. Какие сущности используют мягкое удаление?
...

Требование ссылки меняет всё. Утверждение без file:line теперь выглядит подозрительно, а с ним — проверяется за пять секунд в зафиксированном commit. Номера строк поплывут после правок, поэтому в постоянной документации я оставляю путь и имя класса или метода, а строку использую как подсказку для текущей ревизии.

Разрешение ответить «не найдено» снимает давление на связность. В первом заходе агент не мог сказать «не знаю», потому что его просили описать, а не ответить.

Результат второго захода: 15 вопросов, 13 точных ответов со ссылками, 2 честных «не найдено». Ни одного выдуманного факта. На проверку ушло 12 минут.

Что вообще стоит спрашивать

Список вопросов важнее формулировки запроса. За несколько проектов у меня сложился набор, который даёт максимум понимания за минимум чтения.

Границы. Где точки входа: контроллеры, консольные команды, обработчики сообщений, подписчики событий. Это скелет — всё остальное вызывается отсюда.

Данные. Какие таблицы самые большие, где составные индексы, какие сущности связаны с какими. Схема рассказывает о продукте честнее, чем код.

Правила доступа. Где проверяются права, есть ли места, где не проверяются.

Асинхронное. Что уходит в очередь, что по расписанию. Это то, что не видно при чтении контроллеров, и то, что ломается тише всего.

Странности. Что в этом проекте сделано не так, как принято. Вот этот вопрос даёт больше всего пользы и хуже всего формулируется — агент отвечает на него плохо, но сами попытки подсвечивают нестандартные места.

Карта, которая остаётся

Разовое исследование быстро теряет ценность: в новой сессии контекста уже нет, а код продолжает меняться. Результат должен превратиться в файл в репозитории и обновляться в том же review, где меняется описанная архитектура.

У меня это AGENTS.md — не сгенерированный, а собранный руками из проверенных ответов. Для экспериментального снимка рядом нужен commit SHA; в живом файле долговечнее ссылки вида «путь + класс или метод», чем голые номера строк:

## Архитектура

Модульный монолит. Активные модули: Core, Tasks, Docs.
Chat, Planning, Helpdesk — заготовки без маршрутов, не трогать.

Слои внутри модуля: Domain / Application / Infrastructure.
Core — единственный модуль, от которого можно зависеть.

## Ключевые решения

- Аутентификация: сессии, firewall в config/packages/security.yaml
- Очереди: Redis, транспорты heavy / default / notifications
- Идентификаторы: UUID v7, трейт HasUuidIdentity
- Мультиарендность: фильтр Doctrine + явное условие в репозиториях

## Чего здесь нет

- REST API наружу. Есть только внутренние маршруты для Turbo.
- Событий Doctrine. Журнал активности пишется явным вызовом.

Раздел «чего здесь нет» я добавил после первого захода. Он напрямую бьёт по механизму, который породил выдумку про JWT: агент достраивает типичное, а этот раздел говорит, чего в проекте нет вопреки типичному.

Замер

ЗаходВремя агентаПроверкаОшибок
«Опиши архитектуру»4 мин25 мин3 из 15
15 вопросов со ссылками6 мин12 мин0 из 15

Второй заход дольше по работе агента и вдвое короче по моей. Разница именно в проверке: утверждение со ссылкой проверяется взглядом, утверждение без ссылки — поиском по проекту.

И главное число, которого нет в таблице: после первого захода у меня остался красивый текст, после второго — файл в репозитории, который работает на всех следующих задачах.

Что дальше

В следующем заходе — баг. Там появится вещь, которой в исследовании не было: способ проверить результат машинно, а не глазами. Посмотрим, насколько это меняет поведение агента.

Серия

Лаборатория

#symfony #coding-agent #php #kontekst #lab