はじめに
Mario Zechnerが開発し、Flask・Jinja2の作者として知られるArmin Ronacherが共同創業したEarendilで開発が続く軽量CLIコーディングエージェント「Pi」(earendil-works/pi。本記事執筆時点で69.6kスター・MITライセンス・最新リリース v0.80.6)は、「システムプロンプトを1,000トークン未満に切り詰める」という最小主義設計に加えて、エージェント自身に拡張(新しいツール)を書かせて能力を追加できる という自己拡張機構を目玉機能としています1。
インストールや基本的なCLI操作の検証はすでに広く行われているため、この記事では一歩踏み込んで、その目玉機能である「自己拡張」を実際にPiにやらせてみた結果 を報告します。結論から言うと、拡張の書き方をPi自身に聞かせたところ、存在しないSDKをimportしたコードを生成し、Pi自身がロードを拒否しました。
この記事で学べること
- 「エージェントが自分の拡張を書く」機能を実際に試した実測ログと、そこで発生した具体的な失敗
- なぜPiの標準システムプロンプトに書かれたドキュメント誘導が効かなかったのか
- ドキュメントに書かれた簡易サンプルと、実際の型定義(TypeBox)が要求する正しい拡張の書き方の差分
- 同種の「自己拡張・自己改善」を謳うAIツールを検証する際のチェックポイント
TL;DR
- 目玉機能「拡張の自己生成」を試したところ、存在しない
@earendil-works/pi-sdkパッケージと架空のクラスベースAPIをimportしたコードを生成し、Pi自身がCannot find module '@earendil-works/pi-sdk'でロード拒否した - Piの標準システムプロンプトには「拡張について聞かれたら同梱ドキュメントを読め」という誘導があるが、
gemini-2.5-flashはこれに従わず一般的な学習知識でコードを生成した - 実際の拡張APIは
ToolDefinitionインターフェースがTypeBoxスキーマと5引数のexecute()を要求しており、READMEの簡易サンプルより複雑
検証環境
Node v22.22.2 / npm 10.9.7 で、公式手順どおり npm install -g --ignore-scripts @earendil-works/pi-coding-agent を実行し、pi --version が 0.80.6(GitHubの最新リリースタグと一致)であることを確認した状態から検証を始めています。
自己拡張機能を試す:架空のSDKをimportさせてしまった
Piの最大の差別化ポイントは、エージェント自身に拡張(新しいツール)を書かせて機能を追加できることです。まず、単純なワードカウントツールの拡張を書かせてみました。
$ pi -p "Create a pi extension at .pi/extensions/wordcount.ts that registers \
a custom tool named 'wordcount' which counts words in a given text string. \
Just write the file, don't test it." \
--model google/gemini-2.5-flash --mode json
生成された .pi/extensions/wordcount.ts は以下の内容でした。
import { Extension } from '@earendil-works/pi-sdk';
class WordCountExtension extends Extension {
async activate() {
this.registerTool({
name: 'wordcount',
description: 'Counts words in a given text string.',
parameters: {
type: 'object',
properties: {
text: { type: 'string', description: 'The text string to count words from.' },
},
required: ['text'],
},
handler: async (args: { text: string }) => {
const wordCount = args.text.split(/\s+/).filter(word => word.length > 0).length;
return { wordCount };
},
});
}
}
export default WordCountExtension;
一見それらしいコードですが、@earendil-works/pi-sdk というパッケージは存在せず、クラスベースの Extension 基底クラスというAPIもPiには存在しません(実際のPiの拡張APIは、公式READMEに書かれている通り export default function (pi) { ... } という 関数export の形です2)。試しにこの拡張を読み込ませると、Pi自身が即座にエラーで拒否しました。
$ pi -e .pi/extensions/wordcount.ts -p "list your available tools" \
--model google/gemini-2.5-flash --mode json
Error: Failed to load extension ".../wordcount.ts":
Failed to load extension: Cannot find module '@earendil-works/pi-sdk'
Require stack:
- .../.pi/extensions/wordcount.ts
Hint: Start without extensions using "pi -ne".
なぜドキュメント誘導が効かなかったのか
Piの標準システムプロンプトには「pi自身・SDK・拡張・テーマ・スキル・TUIについて聞かれたときだけ、同梱のドキュメントを読むこと」というガイドラインが明記されています3。今回の依頼はまさに「pi extensionを書いて」という直球のリクエストで、このガイドラインが発火してしかるべき場面でした。
しかし gemini-2.5-flash はこのガイドラインに従ってドキュメントを読みに行かず、学習データにありがちな「SDKをimportしてクラスを継承する」という一般的なプラグインパターンをそのまま生成しました。ドキュメント参照はあくまで「システムプロンプトに書かれた指示」であり、モデル側が確実に実行するとは限らないという、プロンプトベースの誘導の限界がここに表れています。
興味深いのは、Pi開発者のArmin Ronacher自身が別のブログ記事で「エージェントが生成したコードは、根本原因の推測に基づいた見せかけの修正になりがちだ」と警告している点です4。今回の検証は、まさにその指摘どおりの現象がPi自身の目玉機能で再現された形になります。
正しい拡張の書き方(TypeBox API)
READMEのサンプルコードは簡略化されており、実際にグローバルインストールされたパッケージの型定義(dist/core/extensions/types.d.ts)を確認すると、ToolDefinition インターフェースは以下を要求します。
export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown, TState = any> {
name: string;
label: string; // README のサンプルには無いが必須
description: string;
parameters: TParams; // 素のJSON SchemaではなくTypeBoxスキーマ
execute(
toolCallId: string,
params: Static<TParams>,
signal: AbortSignal | undefined,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext
): Promise<AgentToolResult<TDetails>>;
// ...
}
READMEの pi.registerTool({ name: "deploy", ... }) という省略形のサンプルだけを見て実装すると、label フィールドの欠落や execute の引数不足でハマります。拡張を自作する場合は、node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts を直接読むか、examples/extensions/ 配下の実サンプルを参照するのが確実です。
著者視点の発見ポイント
実際に手を動かして分かったのは、「エージェントに自分の拡張を書かせる」というPiの目玉機能は、docsを読ませる誘導(システムプロンプトのガイドライン)が入っていても、軽量・高速なモデルだと素通りされうる ということです。今回 gemini-2.5-flash で再現しましたが、これは特定モデルの欠陥というより「自己拡張・自己改善を謳うツール全般で起こりうる構造的なリスク」だと捉えるべきでしょう。同種のツールを検証するときは、①ドキュメント誘導が本当に機能しているかをモデルごとに確かめる、②「AIに任せられる」とされる機能ほど、生成物を実際にロード・実行して壊れていないか確認する、の2点を癖にしておくと安全です。
まとめ
- Piの目玉である自己拡張機能に「拡張を自分で書いて」と頼んだところ、存在しないSDKをimportしたコードを生成し、Pi自身がロード拒否する結果になった
- システムプロンプトの「拡張について聞かれたらドキュメントを読め」という誘導は、軽量モデル(
gemini-2.5-flash)では実際には発火しなかった - 実際の拡張APIは、READMEの簡易サンプルより複雑なTypeBox型を要求する
- 「エージェントに任せられる」機能ほど、生成物を実際にロード・実行して検証する価値がある
-
earendil-works/pi - GitHub(旧 badlogic/pi-mono。2026年4月にリポジトリ移管)/Pi: a coding agent with efficient system prompting - Tensorlake ↩
-
実機確認:
node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js(グローバルインストール後のビルド済みコード) ↩ -
Building Pi With Pi - Armin Ronacher's Thoughts and Writings ↩