LLM агент по кодированию: методы управления задачами

LLM агент по кодированию: методы управления задачами

- Применение Task/Plan Harness -

1. Введение

В последнее время доля использования инструментов кодирования на основе LLM в разработке все больше растет. Я также начал использовать Codex, чтобы повысить продуктивность проектной работы. Сначала я использовал его в обычном режиме. Я описывал необходимые задачи в виде подсказок, а затем проверял результаты, написанные LLM, и запрашивал недостающие части. В случае простых исправлений или повторяющихся задач этого подхода было вполне достаточно.

Однако с увеличением масштабов работы простой способ подсказок столкнулся с ограничениями. Даже если подсказка была длинной, LLM не мог стабильно управлять всей задачей, а с увеличением времени выполнения задач появлялись случаи, когда терялись согласованные ранее моменты или намерения по изменениям. Сначала я думал, что это проблема можно решить, просто написав подсказки более подробно. Однако на самом деле это больше было связано с недостатком структуры для управления задачами, а не с подсказками.

В этой статье я систематизирую проблемы, с которыми я столкнулся в разработке на основе LLM, и процесс применения Task/Plan harness для их решения. Я сосредоточился не просто на объяснении концепции harness, а на том, почему я почувствовал, что это необходимо, какие правила я создал и как изменился рабочий процесс.

2. Проблемы, возникавшие при работе только с подсказками

Когда я только начал использовать Codex, я старался выполнить как можно больше задач в одном сеансе. Например, если нужно было реализовать одну функцию, я долго объяснял требования, находил соответствующие файлы и запрашивал реализацию и тестирование. Это хорошо работало для коротких задач, но когда задачи становились длиннее, контекст, накапливавшийся в сеансе, становился слишком объемным.

Первой ощутимой проблемой стало окно контекста. Чем больше объем работы и чем дольше время, тем чаще LLM забывал предыдущие данные или писал код в ином направлении, отличном от ранее установленного. Это было не просто забывание, а также ухудшение качества кода. На начальных этапах структура осознавалась относительно точно, но со временем возникали проблемы, связанные с предположениями или изменением уже принятых решений.

В этой ситуации, чтобы очистить окно контекста, приходилось инициализировать сеанс, что создавало дополнительные затраты. В новом сеансе нужно было снова объяснить, что уже было сделано, какие файлы были изменены и какие решения были приняты. Если я сокращал объяснение, LLM неправильно понимал, а если удлинял, контекст снова быстро заполнялся. В конечном итоге я использовал инструменты для ускорения работы, но в какой-то момент стал тратить время на восстановление рабочего процесса.

Вторая проблема заключалась в том, что LLM самостоятельно принимал неясные решения. В разработке множество моментов требуют решений от пользователя. Это такие вопросы, как как оформить API, нужно ли сохранять существующую структуру, создавать ли новое абстрактное представление, до какого уровня проводить верификацию и т.д. Однако если в подсказке были нечеткие части, LLM, не задавая вопросов, принимал решения по своему усмотрению и продолжал реализацию.

Проблема в том, что это решение не всегда соответствовало моему намерению. На вид создавался приличный код, но иногда он расходился с направлением проекта или рамками, которые я имел в виду. В таком случае приходилось откатывать уже измененный код, снова объяснять и снова выполнять работу. Иногда затраты на откат неверной работы чувствовались больше, чем само выполнение задачи.

3. Причины применения harness

Для решения этой проблемы я начал применять концепцию harness. Здесь harness заключается в том, чтобы не поручать LLM выполнять работу свободно каждый раз, а устанавливать фиксированные процедуры и документы, которые следует соблюдать до и после выполнения задач. То есть, если подсказка - это одно единственное обращение, то harness приближается к рамке, управляющей всей задачей.

Мой способ применения называется Task/Plan harness. Как можно понять из названия, работа управляется вокруг двух документов — TASK.md и PLAN.md. TASK.md фиксирует единицы и состояние текущей работы, а PLAN.md записывает детальные планы реализации, принятые решения и критерии верификации. Когда LLM получает запрос от пользователя, он не начинает выполнять его сразу, а сначала делит работу на маленькие единицы и составляет план.

Ключ к этому подходу состоит в том, чтобы не решать большую проблему целиком. Работу делят на маленькие единицы, такие как T-01, T-02, и после завершения одной задачи фиксируют состояние, чтобы можно было инициализировать сеанс compilejava. Даже если сеанс инициализируется, поскольку предыдущие данные работы и общий план остаются в TASK.md и PLAN.md, в следующем сеансе можно продолжить работу, просто прочитав этот документ.

4. Задача/План Структура хайтекса

Структура файлов хайтекса основана на рабочих директориях по функциям. На корневом уровне находятся временные файлы TASK.md и PLAN.md, так как их смешение может привести к путанице в нескольких задачах. Мы использовали структуру, которая различает активные и завершенные задачи по функциональным областям.

.codex/tasks/active/<feature-slug>/TASK.md
.codex/tasks/active/<feature-slug>/PLAN.md

.codex/tasks/completed/<feature-slug>/TASK.md
.codex/tasks/completed/<feature-slug>/PLAN.md

TASK.md ближе к доске текущих задач. Необходимо кратко отобразить, какие задачи выполняются в данный момент, какие задачи завершены и что необходимо сделать дальше. В то же время PLAN.md ближе к детальному плану. Он включает в себя область выполнения, исключенные области, решения, методы реализации, методы проверки и журналы состояния.

# <Feature Name> Tasks

## Status
- Last updated: <YYYY-MM-DD>
- Current task: T-01

## Tasks
- [x] T-00. Create task baseline documents
  - Plan reference: PLAN.md 0, 1, 3
  - Status: Completed
  - Clear after completion: Not required

- [ ] T-01. Implement first scoped change
  - Plan reference: PLAN.md 2
  - Status: Pending
  - Clear after completion: Required

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

5. Решение проблемы контекстного окна

Первая проблема, которую я хотел решить, была связана с контекстным окном. В старом подходе все анализ, реализация, исправление и проверка проводились в одной сессии. Этот метод на начальном этапе был быстрым, но с течением времени контекст становился слишком объемным, и качество ответов LLM ухудшалось.

В хайтексе Задача/План эта проблема была решена за счет разделения на рабочие единицы. Когда LLM получает запрос, он сначала делит всю работу на небольшие задачи. Каждая задача имеет размер, который можно понять и обработать за одну сессию. Например, изменение контракта на бэкенде, исправление вызовов на фронтенде, тестирование и проверка сборки могут стать отдельными задачами.

Когда одна задача завершена, необходимо обновить TASK.md и PLAN.md. И перед переходом к следующей задаче следует сбросить сессию. Важный момент здесь заключается не в простом разрыве сессии, а в том, чтобы оставить текущий статус в документе перед окончанием. Так новая сессия будет начинаться, не требуя повторного объяснения предыдущих задач.

T-01 완료 후 기록 예시

TASK.md
- [x] T-01. Update backend command contract
  - Status: Completed
  - Clear after completion: Required
  - Result: Command 필드 구조를 변경하고 관련 Flow 호출부를 수정했습니다.

PLAN.md State Log
| Task | Status | Last result | Next start |
| T-01 | Completed | 백엔드 계약 변경 완료, compileJava 통과 | T-02 프론트엔드 호출부 수정 |

Записывая это, новая сессия может сначала просмотреть текущую задачу TASK.md и, если необходимо, проверить только соответствующий раздел в PLAN.md. Ранее мне приходилось снова писать длинные объяснения, но теперь документация выполняет эту роль. В результате, даже часто сбрасывая сессии, рабочий процесс не нарушался.

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

6. Правило не реализовывать неопределенные вопросы

После того как я решил некоторые проблемы с контекстом, появилась еще одна проблема. Это был вопрос о том, что LLM самостоятельно принимает решения по неясным аспектам. Особенно это касалось областей, которые разработчик должен решить сам, но LLM иногда продолжал действовать произвольно, не задавая вопросы. Эта проблема была более опасной, чем простые ошибки в коде. Если направление было выбрано неправильно, проблема могла быть обнаружена только после внесения множества исправлений.

Чтобы это решить, я добавил раздел Decisions Required в PLAN.md. Мы первоначально определили вопросы, требующие решения, перед реализацией, и установили правило, что если хотя бы один вопрос остается открытым, работа не начинается. После применения этого правила LLM больше не обрабатывает неопределенные вопросы произвольно и ждет моего решения после того, как выделяет варианты и их плюсы и минусы.

## 1. Decisions Required

| ID | Decision Needed | Options / Notes | Status | Decision |
| --- | --- | --- | --- | --- |
| D-01 | API 응답을 기존 DTO에 추가할지, 별도 DTO를 만들지 결정 필요 | 기존 DTO 확장: 변경 범위 작음 / 별도 DTO: 역할 분리 명확 | Open | |

규칙:
- Status가 Open인 결정이 하나라도 있으면 구현을 시작하지 않습니다.
- 사용자가 선택하면 Status를 Decided로 변경하고 Decision에 근거를 기록합니다.

Этот метод оказался более эффективным, чем я ожидал. Ранее LLM продолжал, исходя из предположений по тем вопросам, которые я не указал в запросе. Теперь наоборот, LLM сначала указывает мне на пропущенные решения. Например, он предлагает выбрать, использовать ли существующее поле, добавить новое, а также определить, оставить ли проверку на бэкенде или фронтенде.

Этот процесс не только не контролировал LLM, но и помогал рассматривать мои инструкции. Я чувствовал, что обдумал все решения при запросе работы, но на самом деле часто есть пропуски. Список решений в PLAN.md стал инструментом, который выявляет такие пропуски. Получая вопросы перед реализацией, я могу остановиться на мгновение, чтобы переосмыслить направление и снизить затраты на откат после реализации с ошибками.

7. Указание объема работы и исключений

Одна из распространенных проблем при использовании LLM заключается в том, что он пытается вносить изменения, даже если не запрашиваются улучшения. Это может проявляться в упорядочивании стиля окружающего кода, внесении дополнительных изменений в связанные структуры или в создании абстракций, учитывающих будущую масштабируемость. Хотя такие предложения могут быть полезными, в реальной практике увеличение объема изменений приводит к большему бремени при обзоре и риску регрессии.

Поэтому в рабочем документе PLAN.md были обязательно указаны Scope и Out-of-scope. Это деление на области, которые можно изменять, и области, которые нельзя изменять в данной работе. Например, если работа заключается в изменении определенного API контракта, то scope включает только данный Command, Flow, Resource и места вызова, в то время как не относящиеся к этому рефакторинг и изменение кода, будут записаны как Out-of-scope.

## 0. Working Rules

- Scope is limited to **************Command, *************Cdo, Flow/Resource wiring, and directly affected frontend call sites.
- Out-of-scope files/modules: unrelated track pattern behavior, generated target project files, unrelated UI restyling.
- Verification rule: run focused backend compile and affected frontend build when possible.
- Do not start implementation while any item in Decisions Required is Open.

С этим правилом, даже если LLM обнаруживает хорошие идеи по улучшению во время работы, их нельзя сразу же применять. При необходимости их можно выделить в отдельное задание или проконсультироваться с пользователем. Это позволило избежать ненужного увеличения объема одной работы. Особенно в совместном коде важны небольшие единицы изменений, поэтому это правило способствует поддержанию изменений, которые можно легко рецензировать.

8. Эффект предварительного определения критериев проверки

Хотя LLM быстро помогает генерировать код, есть отдельная проблема — безопасен ли созданный код на самом деле. Поэтому в PLAN.md были включены правила верификации. Перед началом работы необходимо определить, каким образом будет проверяться завершенность работы.

Например, если производится изменение на серверной части, надо запускать compileJava или тесты соответствующих модулей, а если производится изменение на фронтенде, то необходимо запускать build или проверку типов соответствующего пакета. Если невозможно провести проверку из-за проблем с окружением, нужно зафиксировать причину, по которой проверка не была проведена, и информацию, которую удалось подтвердить. Важно не оставлять оценку завершенности на усмотрение LLM.

Verification or completion:
- Run .\gradlew.bat :drama-feature:compileJava :drama-facade:compileJava
- Search for stale **********Cdo.get**********Id() references
- If verification is blocked, record the blocker and the smallest successful check

С наличием этих критериев также удобно рецензировать результаты работы. Вместо простого «изменено» важно указать, какую проверку было проведено. В реальной работе важно было проверить, прошел ли билд, остались ли какие-либо ссылки, появляются ли результаты с ожидаемыми именами. Предварительное определение критериев верификации помогло LLM также более четко определять момент завершения работы.

9. Изменения в реальном процессе работы

Перед применением Harnes рабочий процесс был относительно простым. Я объяснял функции в промпте, LLM их реализовывал, а я проверял результаты. Если были проблемы, я запрашивал повторно. Этот подход позволял быстро начать работу, однако с усложнением задач число исправлений увеличивалось.

После применения Harnes процесс изменился. Сначала мы формулируем цели и объем работы. Затем LLM создает TASK.md и PLAN.md. Если в PLAN.md есть решения, которые нужно принять, реализация останавливается, и я принимаю решение. После того, как все решения оформлены, выполняется только одна задача. По завершении задачи записываются результаты и данные о верификации, и при необходимости сеансы инициализируются заново перед переходом к следующей задаче.

작업 흐름

1. 사용자 요청 입력
2. LLM이 작업 목표, 범위, 제외 범위, 검증 기준 정리
3. TASK.md / PLAN.md 생성
4. Decisions Required 확인
5. Open 결정이 있으면 구현 중단 후 사용자 결정 대기
6. 하나의 Task만 구현
7. 검증 실행 및 결과 기록
8. 세션 초기화 후 다음 Task 진행

Этот процесс изначально казался медленным, но общее время работы чаще всего сокращалось. Уменьшилось количество неправильных реализаций и возвратов, а также снизилась проблема ухудшения качества из-за слишком длинных сессий. Более того, статус работы фиксировался в документе, поэтому было легко продолжить, даже если работа была приостановлена или приходилось возвращаться к ней после выполнения других задач.

10. Преимущества, которые я ощутил при применении

Первоочередное преимущество заключается в том, что управление контекстом стало проще. Поскольку теперь нет необходимости долго поддерживать сессии, удалось уменьшить ухудшение качества LLM. Ранее инициализация сессии вызывала у меня беспокойство, но теперь благодаря TASK.md и PLAN.md инициализация стала частью рабочего процесса.

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

Третье преимущество заключается в повышении вероятности рецензирования. Поскольку объем работы, причины решений и результаты проверки оставлены в документе, было легко проследить, почему изменения были внесены при просмотре кода позже. Особенно в случаях, когда несколько файлов редактировались одновременно, эта запись служила небольшим проектным документом.

Четвертое преимущество заключается в том, что роль LLM стала более ясной. До применения ханов LLM иногда действовал как реализатор и проектировщик. После применения ханов LLM стал помогать в реализации в рамках планов, а границы работ и решения теперь контролируются разработчиком. Эта разница была важна в практическом применении.

11. Важные моменты при применении

Task/Plan ханы не требуются для всех работ. Для простых запросов на описание, чтения кода, небольших исправлений это даже может оказаться избыточным. Ханы показали большую эффективность в работах, которые влияют на реальный репозиторий, такие как изменения кода, конфигурации, тестирования или создание артефактов.

Кроме того, слишком детальная документация увеличивает затраты на управление. Лучше всего было кратко фиксировать состояние работы в TASK.md, а детальные сведения помещать в PLAN.md. Если TASK.md становится слишком длинным, трудно быстро понять текущее состояние работы. Напротив, если PLAN.md слишком короткий, становится трудно восстановить обоснование решений на следующей сессии.

Наконец, наличие ханов не избавляет от необходимости проверки. LLM все еще может создавать неверный код, и даже если команда проверки проходит, это не всегда может полностью соответствовать требованиям. Ханы следует рассматривать не как устройство для доверия к LLM, а как контрольный механизм для более безопасного использования LLM разработчиком.

12. Заключение

Из этого опыта я понял, что для эффективного использования LLM недостаточно просто хорошо формулировать запросы. Запросы служат средством для передачи мгновенных запросов. Между тем, Task/Plan ханы представляют собой структуру для управления всей работой. В практике более важна устойчивая рабочая структура, чем одноразовые запросы.

После применения Task/Plan ханов снизилось ухудшение качества из-за проблем с контекстным окном, а работа стала легко продолжаться даже после инициализации сессии. Также благодаря правилам, позволяющим сначала выявлять неопределенные вопросы и останавливать реализацию, сократилось число случаев, когда LLM случайно принимает решение.

Таким образом, LLM стал инструментом, который не только ускоряет написание кода, но и повышает продуктивность в рамках четкой рабочей структуры. Однако этот эффект проявляется не тогда, когда мы полностью полагаемся на LLM, а тогда, когда разработчик четко устанавливает границы работы, критерии решений и проверки. Я считаю, что при дальнейшем применении LLM в практике необходимо больше сосредоточиться на том, как делить, фиксировать и проверять работу.

DEVKC

Site footer