世界を動かす技術を、日本語で。

LLM=真実

概要

  • AIコーディングエージェント の文脈汚染問題について解説
  • Turbo などのビルドツール出力がLLMのコンテキストを圧迫
  • 環境変数 や設定ファイルでノイズ削減の工夫
  • NO_COLORCI=true など既存の環境変数の役割を再考
  • LLM=true 環境変数導入の提案とその意義

AIコーディングエージェントとノイズ問題

  • AIコーディングエージェント は人間の友達の犬のような存在
  • 犬がトリックを覚えるには 集中 が必要で、環境のノイズが邪魔
  • LLMも同じく 余計な出力 でコンテキストウィンドウが汚染される問題
  • 具体例として Turbo を使ったビルド出力が大量にstdoutに流れる
  • ビルド成功時 の出力はLLMには不要な情報が多い
  • Turbo出力には「update block」「ビルド対象パッケージ一覧」「各パッケージのビルド出力」の3つのノイズ要素

Turboの出力最適化手法

  • turbo.jsonで outputLogs を" errors-only "に設定し、不要なビルド出力を削減
  • TURBO_NO_UPDATE_NOTIFIER=1 でアップデート通知も非表示
  • Claude Codeでは .claude/settings.json で環境変数を簡単に設定可能
  • しかし、 パッケージ名一覧 は現状除去不可で、LLMのコンテキストに残り続ける

LLMの自動ノイズ回避とその限界

  • Claudeは tailコマンド で出力末尾だけをLLMに渡してノイズ回避を試みる
  • しかし、ビルド失敗時は スタックトレース が切れてしまい、tailの数値を変えて再試行する羽目に
  • これは「 犬が自分の尻尾を追いかける」状態と同じで根本解決にならない

環境変数によるノイズ削減の拡張

  • Turbo以外にも ノイズを出すツール が多く、settings.jsonのenvセクションが肥大化
  • 例: AIKIDO_DISABLESAFE_CHAIN_LOGGINGNO_COLOR などを追加
  • すべてのツールが環境変数に対応しているわけではなく、 --silent--quiet のようなフラグも併用が必要
  • NO_COLOR=1 はANSIカラーコードを抑制し、コンテキスト汚染を緩和
  • CI=true はCI/CD環境で自動的にセットされ、スピナーや色付け、ログ量を削減
  • NO_COLOR は命令的、 CI=true は宣言的なアプローチの違い

LLM=true環境変数の提案

  • AIエージェントによる コード自動生成 の普及に伴い、LLM用の最適化が必要
  • LLM=true 環境変数で、LLM向けにノイズの少ない出力を標準化する提案
  • トークン消費削減で コスト・品質・環境負荷 の三方良し
    • コスト削減(トークン消費減)
    • コンテキスト品質向上
    • 電力消費削減による環境負荷低減

人間とAIエージェントの役割逆転

  • 将来的に「人間がコードを書く」が例外となり、 AIエージェントが標準 になる可能性
  • その時は HUMAN=true をセットするのが新しいデフォルトかもしれない

まとめと呼びかけ

  • LLM=true 環境変数の導入を Claude Code などにデフォルトで設定するよう提案
  • X(旧Twitter)で Boris Cherny (@bcherny) に広めてほしいとの呼びかけ

Hackerたちの意見

この記事に関係あるけど、AIとは関係ない話ね。コーディングの趣味プロジェクトが大好きだけど、プロのソフトウェアエンジニアじゃない私にとって、これらの設定ファイルや環境変数を維持するのは本当に疲れる。特に、ちょっとしたプロジェクトでもどんどん増えていくし、どれがどれだか覚えるのも大変だし、特定の設定を見つけるのも難しい。メカニズムも謎めいていることが多くて、実際に動くかどうかを手動でテストしないといけないこともある。実際のコードは、流れが明確だから読んでいるだけでロジックが理解できるのにね。他の人の設定ファイルを何も考えずにコピーするなんてできないし、各スイッチが何をしているのかも分からないから、プロジェクトを作るのが特に他の人のプロジェクトを真似するのがフラストレーション溜まる経験になってしまう。みんなはどうやってこれをうまく対処してるの?

まずはできるだけシンプルでミニマルな設定から始めるけど、年月が経つにつれて小さな追加や調整を重ねていくうちに、結局は自分だけがその理由を理解する巨大なものになっちゃうんだよね。

ツールやビルドプロセスはできるだけ少なくして、設定ファイルが少ない(または全くない)ツールを選ぶのがいいよ。やってることによるけど、仕事じゃないなら設定地獄は多分オプションだよね。

正直なところ… AIエージェントに更新を頼んでみて。彼らはドキュメントを読むのが得意で、自分が気にするかもしれない設定を選んでフィルタリングしてくれるから。自分で何十年もメンテナンスしてきたけど、これは本当に新鮮な風を感じたよ。

ソフトウェアの人たちは、物事を過剰に設計するのが好きだよね。数年前のウェブコーディングのブームを見てみると、人々はツールの上にさらにツール(フレームワーク、ビルドパイプライン、リンティング、ジェネレーターなど)を積み上げて、ゼロコンフィグでもできることにして、シンプルなプロジェクトにはほんの少しのファイルで済むものを作ってた。深く入り込みすぎると、ツールの維持にかかるオーバーヘッドが利点を上回ることを忘れちゃうんだよね。これが私たちの職業の呪いだと思う。私たちは物を作って自動化するから、やっていることのためのツールを作って自動化したくなるのは自然なことなんだ。

まず第一に、設定しようとしているツールのドキュメントを読むよ。これってすごく20世紀的だけど、全体がどうつながっているかを理解するのに役立つし、複雑なスタックの中で各ツールが何をしているのかを覚えるのにも助かる。ドキュメントは完璧でも完全でもないけど、設定ファイルのパラメータを見つけやすくなって、どれを調整すればいいのかも分かりやすくなる。そして、ドキュメントが不十分なときは、古い格言が当てはまるよね。「ルーク、ソースを使え。」

「JSエコシステム」の罠にハマらないで、まともなツールを使おう。もしフルーバーグルーブがプロジェクトのルートに floobergloob.config.js を追加することを要求するなら、それはフルーバーグルーブが時間の無駄だっていう良いサインだよ。JSリポジトリのルートに必要なのは、gitignore、package.json、package-lock.json、そしてオプションでTSを使っているならtsconfigだけ。node.jsプロジェクトはビルドステップを必要としないはずだし、ほとんどのウェブサイトは、バンドラー(esbuild)を呼び出して静的ファイルをdist/にコピーするだけの単一のbuild.jsで済むよ。

コーディングのペットプロジェクトが大好きだけど、職業としてはソフトウェアエンジニアじゃない私にとって、これらの設定ファイルや環境変数を維持するのは疲れる。 だったらやめればいいじゃん。 > どうやってこれをうまく処理してるの、プロの皆さん? やらないことだよ。ほら、これは君のプロジェクトなんだから。なんで自分をイライラさせるの? 環境や設定、必要なもの、理解できること、好みを整えて、それで終わり。何が必要かは進めながらわかるから。必要なら、追加するたびに各行をドキュメント化すればいい。複雑にしないで。

LLM=trueよりも、静か/冗長な設定を標準化する方がいいと思う。これは冗長性の問題で、LLMは通常は静かにしておきたい場面が多いけど、常にそうとは限らないからね。次に、出力をキャッチしてキャッシュするためのヘルパーが必要だし、正直言って、普通のシェルやbashツールに出力をキャッシュして、フィルタリングして取得できるオプションがあればいいなと思う。コンテキストやトークンよりも、パターンに対するフラストレーションは、エージェントが異なる出力の行を取得するために時間のかかるタスクを再実行することが多いから。時には冗長な出力でツールを実行するのがベストな場合もあるけど、ツールが最初の実行時に必要なものを体系的にフィルタリングしやすい方法で出力を提供してくれたら嬉しいな。

そうそう!こういう議論をたくさん見てきて、ひとつのルールを思いついたんだ。LLMのために特別な配慮をするなら、それはa) 人間にも良いこと、または b) 手間がかかりすぎるってこと。LLMと人間の両方に、冗長なツール出力を隠しつつ、問題があったときには戻って確認できるツールがあればいいなと思う。でも実際には、私はターミナルを最小化して、スパムが終わるまで無視しちゃうんだ。もしかしたら、LLMもそういうのが必要なのかもね。常にstdoutの洪水に直結している必要はないと思う。

そうだよね、何がLLMを阻んでるの? myCommand > /tmp/out_someHash.txt ; tail out_someHash.txt して、失敗したときに /tmp/out_someHash.txt をgrepしたりtailしたりするのは?

それから、すべてのツールが無関係なコンテキストをstdoutに大量に出力して、コンテキストウィンドウを汚染していることに気づいたとき、まるでレンガが顔に当たったような衝撃を受ける。コンテキストウィンドウだけじゃない。あの無駄な出力の多くは人間にとっても完全に無意味だし、警告が無関係な出力の中に隠れていて、誰かが気づくまで何年も放置されることも珍しくない。

「成功時は何も出力しない」という古いUnix哲学は、複数のツールを内部で使うパイプやシェルスクリプトを作り始めると、めちゃくちゃに見えるよね。stdoutとstderrが分かれている理由もすぐにわかるし。

そうだね。もしかしたら、BGPルーターで route print を実行して実際に8ギガバイトのテキストを2分間出力したいときのために、BATCH=yes(デフォルトはno) --batch(デフォルトは --no-batch)があればいいかも。デフォルトの出力が「X, Y, Z ...と9千以上の似たようなエントリー」を要約してくれるのはいいかもしれない。人間用とバッチ用で別々のコマンド名があるのは面倒だし、人間用に -h を使うのは、lsやdfみたいに少しマシだけど、結局は後方互換性のためのハックで、alias が増えて人間の生活を悪化させるだけだよね。

もう一つの受け入れられる解決策は、安いモデルで「ランナー」サブエージェントを作って、コマンドを実行して重要な部分をメインエージェントに伝えることだね。

そう、これが解決策だよ。無関係な出力を整理できるエージェントが必要なんだ。

そして、すべてのツールがcrazyな量の無関係なコンテキストをstdoutに出力して、コンテキストウィンドウを汚染していることに気づいた瞬間、顔にレンガが当たる。エージェントに自分で最適化されたスクリプトを書かせることで、これが本当に助けになることがわかった。Claudeは直接gradlewを使うことを禁止されて、私たちが作ったヘルパースクリプトだけを使えるようになった。それはクリアして、再コンパイルして、ローカルに公開して、テストして… すべていくつかの追加フラグで行われる。そしてテストが失敗したときは、スタックトレースが表示される。これ以前は、Claudeはたくさんの異なる呼び出しをしなきゃいけなくて、すべてがコンテキストを混乱させてた。テストが失敗すると、gradleが生成したHTML/XMLファイルを読み始めて、コンテキストがめちゃくちゃになったんだ。だって、そこにはたくさんのインラインJavaScriptが含まれているからね。最近は、ほとんどのアプリケーションでこの「LLM=true」みたいな動作を実装してる。LLMが使っているときは、ログがあまり冗長じゃなくて、重複もなくて、同じ行が何度も表示されないようになってる。 > 何かがうまくいかないのを見て、彼はtailを使ってスタックトレースをカットしたから、もっと大きなtailを使って再試行する。見たものに満足できず、さらに大きなtailで再試行して… 問題が見えてくる。まるで犬が自分の尻尾を追いかけているみたいだ。私も同じ問題を抱えてた。Claudeは5分以上のテストスイートを何度も続けて実行してたんだ、ただ最後に違う| grep somethingを付け加えただけで。今、私が作ったスクリプトは常に全体の(簡略化された)出力をログに記録して、テンポラリファイルのパスを表示するだけ。これがすごくうまくいくんだ。

これがgradleを使ったエージェントの私の正確な体験で、本当にイライラするのを見てきた。自分の低ノイズラッパースクリプトを設定しようと思ってたんだ。この投稿が、今日これに取り組むきっかけになったよ。

長時間実行されるビルドスクリプトの問題を解決した方法は、すべての出力をファイルにリダイレクトするログスクリプトを持つことだよ。スクリプトの最初に # 出力をログファイルにリダイレクト(リダイレクションでスクリプトを再実行) source "$(dirname "$0")/common/logging.sh" を含めることができる。そうすると、スクリプトが実行されると出力がファイルに保存されて、LLMがそれを検索できるようになる。これが本当にうまくいくんだ。

うわー、これやってみたいな。具体的に ./gradlew を使ってこれを作るためのアドバイスとかある?

Claudeは直接 gradlew を使うことが禁止されていて、私たちが作ったヘルパースクリプトしか使えないんだ。それはクリアして、再コンパイルして、ローカルに公開して、テストして… すべていくつかの追加フラグでやるんだ。そしてテストが失敗したときは、スタックトレースが表示される。今のところ私の質問は、これがLLMに特有なことは何なのかってことなんだ。人間も無駄な出力を何ページも読み漁る必要はないよね。

どうして禁止されてるの? AGENTSで自分のラッパーを使うようにエージェントに言ってるけど、半分の確率で無視されてそのままツール使ってるよ。

人に何度も言わなきゃいけないのが不思議だけど、AIはあんまり変わらないよね。ツールは昔からいろんな出力があって、それをコントロールする方法もあったし、悪いツールはデフォルトでたくさん出力するけど、良いツールは "-v" のバージョンや簡単な grep の後ろに隠してる。--LLMとか追加するんじゃなくて、クリーンで一貫した冗長性コントロールを追加してほしいな。

このコンセプトには価値があると思うけど、今のLLMをターゲットにするのは短絡的だと思う。これは一時的な問題を解決するために、永続的な変更を加えようとしてる気がする。長期的に価値があるのは、簡潔で正確、かつあいまいでないオプションだと思う。これはLLMだけのために考えるべきじゃないよね。人間は時々、すぐに理解するために読みやすさを求めることがあるし、文脈を追加することで大きく助けられることもあるけど、時には正確さとあいまいさのなさが最重要になることもある(例えば監査のとき)。似たようなもののバッチを扱うとき、同じ文脈を繰り返すのは何の役にも立たないし、一度に見える量を制限しちゃう。だから、人間が直接読むためにこういう出力をリクエストできるのはメリットがあるよね。それに、今持ってる出力処理ツールの幅広さもあるし(まだawk使ってる人もいるし)。だから、これは必要だけど、数年後にはLLMには必要なくなるかもね。他の使い道は残るだろうけど。

テキストストリームを扱うツールのUNIX哲学、何かがうまくいかない限り「静か」にしておく、一つのことをうまくやる、などは、現代のAIコーディングエージェントにとても適しているよね。

なんでエージェントが出力をコンテキストに入れるべきかどうかを動的に判断できないの? LLMに確認して、出力が重要そうかどうかを判断すればいいのに。重要じゃなければ、フル出力をローカルに保存して、プロンプトには簡単な要約とユニークIDを入れればいいじゃん。そうすれば、必要なときにフル出力を取り出せるツールも用意すればいいし。人間もそんな感じでやってるよね。ターミナルをスクロールして、無視できる部分をサクッと判断して、後で「これ全部のスタックトレース読まなきゃダメかも」って思い出すこともあるし。決定を下すのにフル出力を送る必要もなくて、「npm run buildの出力が500行で成功したけど、これ読む必要ある?」って送れば、会話の流れに応じてLLMが「はい」か「いいえ」で返事できるんじゃない?

それって、ある程度サブエージェントがやってることじゃない?

ズームインするとスクロールが壊れるの、ちょっと気持ち悪い。