learn-harness-engineering Project 02 徹底解説:Agent-Readable Workspace と永続化状態ファイルによる「中断地点からの再開」
【免费下载链接】learn-harness-engineeringHarness engineering beginner tutorial, from 0 to 1项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineering
関連講義:講義 03. リポジトリを唯一の信頼できる情報源にする ・ 講義 04. 指示を複数ファイルに分ける
本プロジェクトは、AI エージェント(Claude Code や Codex など)が「プロジェクト構造」「現在の進捗」「次にやるべき作業」を、人間の口頭説明なしにリポジトリの状態だけで把握できるようにする、ハーネスエンジニアリングの入門演習です。具体的には、Electron + React 製のナレッジベースアプリに対して「ドキュメント import」「詳細ビュー」「ローカル永続化」を 2 セッションに分けて実装し、ARCHITECTURE.md・PRODUCT.md・session-handoff.mdをあらかじめ配置したワークスペースが、エージェントの「再探索コスト」をどれだけ削減できるかを実験します。この記事を読むと、リポジトリを「エージェントにとって読める状態」にするためのドキュメント構成・状態ファイル設計・IPC 境界の実装方法を、本リポジトリのソースコードを追いながら習得できます。
このプロジェクトが解く問題:エージェントは「聞けない」
エージェントは、システムプロンプト・リポジトリ内のファイル内容・ツール実行結果の 3 つしか入力を持ちません。Slack や Jira、Confluence、あるいは先輩エンジニアの頭の中にある情報は、エージェントにとって存在しないも同然です。講義 03 ではこれを「Knowledge Visibility Gap(知識の可視性ギャップ)」と呼び、ギャップが大きいほどエージェントの失敗率が上がると説明しています。
Project 02 はこの理論を実践に移します。「エージェントが読めるワークスペース(agent-readable workspace)+ 永続化された状態ファイル(persistent state files)」というハーネス・メカニズムを、実際のコードベースに対して適用するのが本プロジェクトの目的です。新しいエージェントセッションが、口頭の文脈なしに「リポジトリ状態だけで」作業を再開できるかどうかを、A/B 実験で検証します。
リポジトリ内のプロジェクト構成
プロジェクト本体は projects/project-02/ に格納されており、同一のプロダクト範囲を 2 通りの「読みやすさ」で実装したディレクトリが用意されています。
| ディレクトリ | 含まれるもの | 比較すべき観点 |
|---|---|---|
| starter/ | Project 01 のコードに、未完成のドキュメント import・詳細ビュー・永続化が入っています。ドキュメントは存在するが意図的に薄く、session-handoff.mdはありません。 | 2 回目のエージェントセッションがどれだけ「再探索」するか。 |
| solution/ | 同じプロダクト範囲が完成しており、solution/ 配下に引き継ぎ用ドキュメント、feature_list.json、session-handoff.md があります。 | 新しいセッションがリポジトリ状態だけで作業を再開できるか。 |
starter と solution は、docs/ARCHITECTURE.mdとdocs/PRODUCT.mdを持っている点では共通ですが、solution 側はこれらが拡充されており、さらにAGENTS.mdが「セッション開始時の読み込み順序」を明文化し、session-handoff.mdが前回セッションの成果・決定事項・次ステップを記録しています。つまり「ドキュメントがあるかどうか」ではなく「エージェントが最初に何を読めば作業を始められるか」が設計されているかどうかが、この 2 つの差分です。
ハーネス・メカニズムの核心:2 つのプリミティブ
本プロジェクトが示すハーネス・メカニズムは、次の 2 つの要素に集約されます。
- Agent-readable workspace(エージェントが読めるワークスペース):リポジトリ内に、エージェントが起動直後に読むべきエントリポイントとドキュメント階層を用意する。
AGENTS.md→docs/ARCHITECTURE.md→docs/PRODUCT.md→feature_list.jsonという読み順が、まさにこれです。 - Persistent state files(永続化された状態ファイル):セッションをまたいで残すべき知識を、セッション外の「頭の中」ではなくファイルに書く。
session-handoff.md(進捗の引き継ぎ)とfeature_list.json(機能の実装状態)がその役割を担います。
講義 03 の「ACID アナロジー」で言えば、これはDurability(永続性)の実践です。「頭の中にあるものは数えない。書き留めたものだけが数える」——solution ディレクトリは、この原則をファイルシステム上に具体化した見本です。
AGENTS.md が定める「起動ルール」とドキュメント階層
projects/project-02/solution/AGENTS.md は、エージェントがコードを書く前に完了すべき手順を順序付きで定義しています。
1. Read this file completely. It defines the boundaries and conventions for this project. 2. Read `docs/ARCHITECTURE.md` to understand the Electron layer structure and import flow. 3. Read `docs/PRODUCT.md` to understand the feature requirements. 4. Run `npm install && npm run check` to verify the project builds cleanly. 5. Read `feature_list.json` to see the current state of all features.ここが講義 04「一つの巨大な指示ファイルは失敗する」の実践です。全知識を 1 ファイルに詰め込むのではなく、docs/をエージェント向けに役割分担させています。
docs/ ARCHITECTURE.md -- Electron のレイヤ構造、データフロー、import パイプライン PRODUCT.md -- 機能要件とユーザー向け挙動さらに AGENTS.md は「新しい機能を追加するときは、コードを書く前に該当ドキュメントを更新する」という知識とコードの同期ルール(講義 03 の Principle 4)を明文化しています。これにより、セッション間で「何が変わったか」をエージェントが把握できます。
3 つの状態ファイルの役割を分解する
ARCHITECTURE.md —— システム構造の地図
projects/project-02/solution/docs/ARCHITECTURE.md は、Electron アプリの 4 層構造(Renderer / Preload / Main / Services)と、import フロー・コンテンツ取得フロー・データ保存レイアウトを図解しています。エージェントが「このシステムはどう組織されているか」というフレッシュセッションテストの問いに答えるためのファイルです。
PRODUCT.md —— 機能要件の地図
projects/project-02/solution/docs/PRODUCT.md は、プロダクトの機能要件(ドキュメント管理・テキストインデックス・根拠付き Q&A・永続化・ステータスバー)と制約条件を定義しています。制約には「最大ファイルサイズ 10 MB」「対応フォーマットは.txtと.md」「Q&A はモックパターンで LLM 統合なし」「すべてローカルでネットワーク通信なし」とあり、エージェントが過剰実装(overreach)しないための境界線になっています。
feature_list.json —— 機能の実装状態
projects/project-02/solution/feature_list.json は、プロジェクトの全 7 機能を ID・説明・status・エビデンス・テスト日時で管理しています。Project 01 から引き継がれた 4 機能(window-launch / document-list / question-panel />## Decisions Made - Added GET_DOCUMENT_CONTENT as a new IPC channel rather than bundling content with GET_DOCUMENT to keep payloads small for list views. - Document deletion removes both the stored content file and the original copy in the documents directory. - Import panel replaces the document detail view rather than appearing in a modal. ## Next Steps Proceed to Project 03 to add indexing, metadata extraction, and grounded Q&A features.
「なぜその実装にしたか」という決定理由までファイルに残すことで、次セッションのエージェントは設計意図を推測せずに済みます。これは講義 03 の「推測コストは地図を描くコストより常に高い」という原則の実践です。
ソースコードで読む Electron 4 層アーキテクチャ
solution のソースコードは、AGENTS.md が定めるレイヤ境界を忠実に実装しています。
src/ main/ -- BrowserWindow のライフサイクルと IPC 登録。ファイルシステム操作はすべて services 経由 preload/ -- main と renderer の唯一の橋渡し。contextBridge で型付き API を公開 renderer/ -- React + TypeScript の UI 層。window.knowledgeBase API 経由でのみ通信 services/ -- main プロセスの純 TypeScript ビジネスロジック。PersistenceService をコンストラクタ注入 shared/ -- 境界をまたぐ型と IPC チャンネル名の単一情報源IPC チャンネルの単一情報源
src/shared/types.ts は、Document・Chunk・Citation・QAResponseなどの型と、IPC チャンネル名の定義を一元管理しています。チャンネル名はnamespace:action形式(例:documents:get-content)という命名規則が AGENTS.md で定められています。
export const IPC_CHANNELS = { LIST_DOCUMENTS: 'documents:list', IMPORT_DOCUMENT: 'documents:import', GET_DOCUMENT: 'documents:get', GET_DOCUMENT_CONTENT: 'documents:get-content', DELETE_DOCUMENT: 'documents:delete', START_INDEXING: 'indexing:start', ASK_QUESTION: 'qa:ask', GET_STATUS: 'app:status', } as const;preload の型付きブリッジ
src/preload/preload.ts は、contextBridge.exposeInMainWorld('knowledgeBase', api)で、renderer からは Node.js モジュールを直接 import できない、というレイヤ境界を守りながら型付き API を公開します。
const api = { documents: { list: () => ipcRenderer.invoke(IPC_CHANNELS.LIST_DOCUMENTS), import: (filePath: string) => ipcRenderer.invoke(IPC_CHANNELS.IMPORT_DOCUMENT, filePath), get: (id: string) => ipcRenderer.invoke(IPC_CHANNELS.GET_DOCUMENT, id), getContent: (id: string) => ipcRenderer.invoke(IPC_CHANNELS.GET_DOCUMENT_CONTENT, id), delete: (id: string) => ipcRenderer.invoke(IPC_CHANNELS.DELETE_DOCUMENT, id), }, ... };main プロセスのハンドラ登録
src/main/ipc-handlers.ts は、registerIpcHandlers(ipcMain, services)でチャンネル名とサービスメソッドを 1 対 1 にマッピングします。GET_DOCUMENT_CONTENTチャンネルがdocumentService.getDocumentContent(id)に委譲される様子は、インポートフローとは独立した「コンテンツ取得専用チャンネル」の追加事例です(session-handoff.md の「Decisions Made」と一致します)。
機能実装の 3 本柱とデータフロー
① ドキュメント import
ユーザーが ImportPanel で.txt/.mdファイルを選ぶと、次のフルパスでデータが流れます(docs/ARCHITECTURE.md の Import Flow より)。
App.tsxの Import ボタン → ImportPanel がファイル入力を表示- ユーザーがファイルを選択 →
onImport(file.path)発火 App.tsxがwindow.knowledgeBase.documents.import(filePath)を呼ぶ- preload が
ipcRenderer.invoke('documents:import', filePath)を発行 ipc-handlers.tsがDocumentService.importDocument(filePath)に委譲DocumentServiceが (a) ファイル存在検証 → (b) 内容と stats の読み取り → (c)Documentメタデータ生成 → (d)PersistenceServiceで documents ディレクトリへコピー → (e) 抽出テキストをcontent/<id>.txtに保存 → (f)documents-meta.jsonに追記- 結果が IPC を逆流 →
App.tsxがrefreshDocuments()でリスト更新
src/services/document-service.ts の実装では、UUID でidを発行し、titleは拡張子を除いたファイル名、statusは初期値'imported'としてメタデータを作成します。
const doc: Document = { id: uuidv4(), title: filename.replace(/\.[^.]+$/, ''), filename, importedAt: new Date().toISOString(), size: stats.size, status: 'imported', };② 詳細ビュー(コンテンツ取得)
詳細ビューは、リスト表示のペイロードを軽く保つため、コンテンツを別チャンネルdocuments:get-contentで取得します(session-handoff.md に記録された設計判断)。DocumentDetailの「View Content」ボタン →getContent(id)→DocumentService.getDocumentContent(id)→PersistenceServiceがcontent/<id>.txtを読み、renderer のpre-wrapコンテナで全文表示されます。削除ボタンはdeleteDocument(id)を呼び、保存済みコンテンツと documents ディレクトリ内のオリジナルコピーの両方を削除します。
③ 永続化(basic persistence)
src/services/persistence-service.ts が、JSON / テキストの低レベル I/O を担います。コンストラクタでdataDirを受け取り、ensureDirectories()がdocuments/とindex/を再帰作成します。データはすべてapp.getPath('userData')/knowledge-base-data/配下に保存され、writeJsonはインデント付きでアトミックに書き込みます。
knowledge-base-data/ documents-meta.json # ドキュメントメタデータ配列 content/<doc-id>.txt # 文書ごとの抽出テキスト chunks/<doc-id>.json # 文書ごとのチャンク配列 index/index-meta.json # ドキュメントIDとチャンクIDの対応 qa-history.json # Q&A 対話ログsrc/renderer/App.tsx では、useEffectのマウント時フックでrefreshDocuments()を呼び、起動時にdocuments:list→indexing:statusを実行してドキュメントリストとステータスバーを復元します。これが「再起動後もドキュメントが残る」永続化のユーザー体験です。Document型のstatusフィールド('imported' | 'indexing' | 'indexed' | 'error')は、インデックス処理の進捗を追跡するために将来のプロジェクトで使われる設計です。
実験方法:同じ作業を 2 回実行して「再探索コスト」を測る
このプロジェクトの最も重要な部分は、実験設計です。同じ作業を 2 回実行します。
- 1 回目:チェックイン済みの starter/ をそのまま使い、薄いドキュメントだけが存在し
session-handoff.mdがない状態で、エージェントに document import・詳細ビュー・永続化を実装させます。観察点は「2 回目のエージェントセッションがどれだけ再探索するか」。 - 2 回目:solution/ の形、すなわち拡充された
ARCHITECTURE.md・PRODUCT.md・session-handoff.mdがあらかじめ配置された状態で、同じ作業を実行します。観察点は「新しいセッションがリポジトリ状態だけで、口頭の文脈なしに再開できるか」。
この A/B 比較によって、「地図(ドキュメント)の質」がエージェントの再探索量・推測・失敗に与える影響を定量的に観察できます。講義 03 の「フレッシュセッションテスト」(新規セッションにリポジトリ内容だけを与えて 5 つの基本質問に答えられるか)を、実際のタスク完了という形で検証する演習です。
完了の定義(Definition of Done)
AGENTS.md は、機能が「完了」したと言える条件を 5 つ列挙しています。
npm run checkで TypeScript がエラーなくコンパイルされる- アプリが起動し、ウィンドウが表示される
- 機能が
feature_list.jsonに"status": "pass"とエビデンス付きで記録される - コードが Electron のレイヤ境界を守っている
docs/ARCHITECTURE.mdやdocs/PRODUCT.mdが変更を反映して更新されている
特に 3 と 5 は「コードを書いたらドキュメントと状態ファイルも更新する」という知識同期の仕組みで、これが次セッションのエージェントが頼る「リポジトリをシステム・オブ・レコードにする」実装上の裏付けです。
ツールと実行環境
- エージェント:Claude Code または Codex
- Git:状態のコミットとロールバック(ACID の Atomicity に相当)に使用
- Node.js + Electron:アプリの実行環境
実行コマンドは package.json で定義されています。
{ "scripts": { "dev": "node scripts/dev.js", "build": "tsc -p tsconfig.node.json && vite build", "check": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.json" } }npm installで依存関係を導入し、npm run checkで型チェックを実行、npm run devで開発起動します。React 18・Vite 6・TypeScript 5.7・Electron 33 を利用した構成です。
次のステップ:Project 03 への接続
session-handoff.md の Next Steps が示す通り、本プロジェクトの次は「インデックス処理・メタデータ抽出・根拠付き Q&A」を追加する Project 03 です。Project 02 で確立した「エージェントが読めるワークスペース + 永続化された状態ファイル」という基盤の上に、より複雑な状態管理とマルチセッション継続性が積み上がっていきます。
学びの要点
- エージェントにとって「リポジトリにない知識は存在しない」。Project 02 は、この原則をドキュメント配置と状態ファイルという具体的な成果物に落とし込んだ実践例です。
- ハーネス・メカニズムは「Agent-readable workspace + persistent state files」の 2 プリミティブ。
AGENTS.md(読み順の定義)、docs/(構造の地図)、feature_list.json(実装状態)、session-handoff.md(決定理由と次ステップ)が四位一体となって再開を可能にします。 - レイヤ境界をコードで守る。IPC チャンネルを
src/shared/types.tsに一元化し、preload を唯一のブリッジとし、renderer はwindow.knowledgeBaseAPI 経由でのみ通信する、という規約がソースコードで検証可能です。 - A/B 実験で地図の質を測る。同じ作業を starter / solution で 2 回実行し、再探索量を比較するのが本プロジェクトの本質です。
【免费下载链接】learn-harness-engineeringHarness engineering beginner tutorial, from 0 to 1项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineering
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考