- Task/Plan Harness 適用記 -
1. はじめに
近年、開発業務においてLLMベースのコーディングツールを使用する割合がますます高まっています。私もプロジェクト作業の生産性を高めるためにCodexを使い始めました。最初は一般的な方法で使用していました。必要な作業をプロンプトで説明し、LLMが作成した結果を確認したうえで、不足している部分を再度依頼するという方法です。簡単な修正や反復作業では、この方法だけでも十分に効果がありました。
しかし、作業規模が大きくなるほど、単純なプロンプト方式には限界がありました。プロンプトを長く書いても、LLMは作業全体を安定して管理できず、作業時間が長くなると、前に合意した内容や以前の変更意図を見失うことがありました。最初は、私がプロンプトをより詳しく書けば解決できる問題だと思っていました。しかし実際には、プロンプトの問題というより、作業を管理する構造が不足している問題に近いものでした。
この記事では、LLMベースの開発作業で私が経験した問題と、それを解決するためにTask/Planハーネスを適用した過程をまとめます。単にハーネスの概念を説明するのではなく、なぜ必要だと感じたのか、どのようなルールを作ったのか、実際の作業フローがどのように変わったのかに焦点を当てています。
2. プロンプトだけで作業するときに経験した問題
最初にCodexを使ったときは、1つのセッションでできるだけ多くの作業を処理しようとしていました。たとえば、ある機能を実装する必要がある場合、要件を長く説明し、関連ファイルを探させ、実装からテストまで続けて依頼していました。短い作業では問題ありませんでしたが、作業が長くなると、セッション内に蓄積されるコンテキストが多くなりすぎました。
最初に強く感じた問題は、コンテキストウィンドウでした。作業量が多く、時間が長くなるほど、LLMが以前の内容を忘れたり、前に決めた方針とは異なる方法でコードを書いたりすることがありました。単に記憶できないという程度ではなく、コード品質も同時に揺らぎました。序盤は比較的正確に把握していた構造を、後半になると大まかに推測したり、すでに決定した内容を再び変更したりするなどの問題がありました。
この状況で、コンテキストウィンドウを空けるためにセッションを初期化すると、別のコストが発生しました。新しいセッションでは、それまで何を行ったのか、どのファイルを修正したのか、どのような決定をしたのかを再び説明する必要がありました。説明を短くするとLLMが誤って理解し、説明を長くすると再びコンテキストがすぐに埋まりました。結局、作業を速くするためにツールを使ったのに、ある時点からは作業フローを復元するために時間がかかるようになりました。
2つ目の問題は、LLMが曖昧な内容を自分だけで決定してしまう点でした。開発作業には、ユーザーの決定が必要な場面が数多くあります。API契約をどのようにするか、既存の構造を維持するか、新しい抽象化を作るか、どこまで検証するかといった問題です。しかし、プロンプトに明確に書かれていない部分があると、LLMは質問せず、自分なりの論理で判断したうえで実装を進めることがありました。
問題は、その判断が常に私の意図と一致するわけではなかったことです。外見上はもっともらしいコードが作られていても、実際のプロジェクトの方向性や、私が想定した範囲から外れている場合がありました。その場合、すでに変更されたコードを元に戻し、再度説明して、もう一度作業する必要がありました。作業そのものよりも、誤った作業を元に戻すコストのほうが大きく感じられることさえありました。
3. ハーネスを適用するようになった理由
この問題を解決するために、ハーネスという概念を適用してみることにしました。ここでいうハーネスとは、LLMに毎回自由に作業を任せるのではなく、作業の前後に従うべき固定された手順と文書を用意する方法です。つまり、プロンプトが1回の依頼だとすれば、ハーネスは作業全体を管理する枠組みに近いものです。
私が適用した方法はTask/Planハーネスです。名前のとおり、TASK.mdとPLAN.mdという2つの文書を中心に作業を管理します。TASK.mdには現在の作業単位と状態を記録し、PLAN.mdには詳細な実装計画、決定事項、検証基準を記録します。LLMはユーザーの依頼を受けると、すぐに実装するのではなく、まず作業を小さな単位に分けて計画を作成します。
この方法の核心は、大きな問題を一度に解決しないことです。作業をT-01、T-02のような小さな単位に分け、1つの作業が終わったら状態を記録したうえでcompilejavaセッションを初期化できるようにしました。セッションを初期化しても、TASK.mdとPLAN.mdに以前の作業内容と全体計画が残っているため、次のセッションではこれらの文書を読むだけで作業を続けられます。
4. Task/Planハーネスの基本構造
ハーネスのファイル構造は、機能ごとの作業ディレクトリを基準に設定しました。ルートに一時的な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. コンテキストウィンドウ問題の解決
私が最初に解決したかった問題は、コンテキストウィンドウでした。以前の方法では、1つのセッションで分析、実装、修正、検証をすべて続けて行おうとしていました。この方法は序盤は速いものの、後半になるほどコンテキストが肥大化し、LLMの応答品質が低下する問題がありました。
Task/Planハーネスでは、この問題を作業単位の分離によって解決しました。LLMは依頼を受けると、まず全体の作業を小さなTaskに分けます。各Taskは、1回のセッションで理解して処理できる大きさに設定します。たとえば、バックエンドの契約変更、フロントエンドの呼び出し部分の修正、テストおよびビルドの検証は、それぞれ別のTaskにできます。
1つのTaskが終わったら、必ず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のCurrent taskを確認し、必要に応じてPLAN.mdの該当セクションだけを確認すれば済みます。以前は私が長い説明を再び書く必要がありましたが、今では文書がその役割を代わりに果たします。その結果、セッションを頻繁に初期化しても作業フローが途切れなくなりました。
また、大きな問題を一度に解決しようとするよりも、小さな問題を1つずつ解決したほうが、LLMの応答品質は安定していました。LLMは広い範囲を一度に扱うときよりも、明確な範囲と完了基準がある小さな作業で、より良い結果を出しました。この点は、実際に適用して最も大きく実感した変化でした。
6. 未決定の事項は実装しないルール
コンテキストの問題をある程度解決した後、別の問題が見えてきました。LLMが曖昧な内容を自分だけで決定してしまう問題です。特に、開発者が必ず決めなければならない部分を、LLMが質問せずに勝手に進めることがありました。この問題は、単純なコードエラーよりも危険でした。方針を誤ると、多くのファイルを修正した後で初めて問題に気づくことになるからです。
これを解決するために、PLAN.mdにDecisions Requiredセクションを設けました。実装前に決定が必要な事項を先に特定し、1つでもOpen状態のまま残っていれば作業を開始しないルールを作りました。このルールを適用してから、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を使うときによく起きる問題の1つは、依頼していない改善まで一緒に行おうとする傾向です。周辺コードのスタイルを整理したり、関連がありそうな構造を追加で変更したり、将来の拡張性を考慮して抽象化を作ったりします。もちろん、このような提案が役立つこともありますが、実務では変更範囲が広がるほどレビューの負担と回帰のリスクも大きくなります。
そのため、PLAN.mdのWorking RulesにはScopeとOut-of-scopeを必ず記載するようにしました。今回の作業で修正してよい領域と、修正してはいけない領域を分けるのです。たとえば、特定のAPI契約を修正する作業であれば、そのCommand、Flow、Resource、呼び出し部分までをScopeとし、unrelatedなリファクタリングや生成コードの直接修正は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が作業中に良い改善案を見つけても、すぐには適用しません。必要であれば別のTaskに分けるか、ユーザーに確認します。そのおかげで、1つの作業が不要に膨らむのを防げました。特に共同作業用のコードでは小さな変更単位が重要なので、このルールはレビュー可能な変更を維持するうえで役立ちました。
8. 検証基準を先に決める効果
LLMはコード生成を迅速に支援してくれますが、生成されたコードが実際に安全かどうかは別の問題です。そのため、PLAN.mdにはVerification ruleを含めました。作業を開始する前に、どのような方法で完了を確認するかを決めておくのです。
たとえば、バックエンドの修正であれば関連モジュールの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. 実際の作業フローの変化
ハーネスを適用する前は、作業フローは比較的単純でした。私がプロンプトで機能を説明すると、LLMが実装し、私は結果を確認しました。問題があれば再度依頼しました。この方法はすぐに始められますが、作業が複雑になるほど修正の繰り返しが増えました。
ハーネスを適用した後は、フローが変わりました。まず作業の目標と範囲を整理します。次に、LLMがTASK.mdとPLAN.mdを作成します。PLAN.mdに決定が必要な事項があれば、実装を止めて私が選択します。すべての決定が整理されたら、1つのTaskだけを実行します。Taskが終わったら結果と検証内容を記録し、必要であればセッションを初期化してから次のTaskに進みます。
작업 흐름
1. 사용자 요청 입력
2. LLM이 작업 목표, 범위, 제외 범위, 검증 기준 정리
3. TASK.md / PLAN.md 생성
4. Decisions Required 확인
5. Open 결정이 있으면 구현 중단 후 사용자 결정 대기
6. 하나의 Task만 구현
7. 검증 실행 및 결과 기록
8. 세션 초기화 후 다음 Task 진행
このフローは最初は遅く見えましたが、全体の作業時間はむしろ短くなることが多くありました。誤った方向で実装して元に戻すことが減り、セッションが長くなって品質が低下する問題も減りました。何より、作業状態が文書として残るため、途中で止めたり、別の作業をしてから戻ったりしても、続きから再開しやすくなりました。
10. 適用して感じたメリット
1つ目のメリットは、コンテキスト管理が容易になったことです。セッションを長時間維持する必要がないため、LLMの品質低下を抑えられました。以前はセッションの初期化が負担でしたが、今ではTASK.mdとPLAN.mdがあるため、初期化も作業フローの一部になりました。
2つ目のメリットは、決定にかかるコストが減ったことです。LLMが勝手に決定せず、決定が必要な事項を先に一覧化してくれるため、実装前に方針を定められました。この過程で、自分が考えていなかった選択肢も確認できました。
3つ目のメリットは、レビュー可能性が高まったことです。作業範囲、決定理由、検証結果が文書に残るため、後からコードを見たときに、なぜこのように変更したのかを追跡しやすくなりました。特に複数のファイルを同時に修正する作業では、この記録が小さな設計書の役割を果たしました。
4つ目のメリットは、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