これの続きです。
4ヶ月くらい100% agentic codingで新規のプログラミング言語 & 処理系(Wado; ワドゥ)を設計・実装してきて、ある程度明確になってきたことがあるので、雑多でMECEでもないですが、現時点での記録を残します。
Claude Code on cloud の実行環境が落とされる件
ほぼ claude code on cloud で開発していますが、こいつはかなりはっきりした独特の癖があります。
まず、1 session 1 containerで動いていて、しばらくagentに動きがないとcontainerごと落とされて、何か入力があるとcontainerを立ち上げてsession再開、という振る舞いをします。
困ったことに、フルテストなどの数十分かかるbackground jobを実行しつつclaude codeが待ちの状態になると、supervisorが「アクティビティなし」と判断するらしく、containerごと落とされます。未コミットの差分はこのタイミングで消えることがありますが、消えないこともあるので「消える」というのはおそらくバグです。何にせよ数十分かかるタスクを完遂できないのは困る。
苦肉の策がこれ:
wado/.claude/skills/on-task-done/SKILL.md at main · wado-lang/wado · GitHub
The process might be killed by the supervisor if you just wait for its completion. Watch the progress periodically in order to tell the supervisor you are still working in progress.
要は定期的に何かしろ、ということ。このへんはそのうち改善すると思いますが、現状だと無駄にtokenを消費しつつ「停止していない」ことをsupervisiroに伝えないといけません。「定期的な何か」の間隔は、5分で落とされたことがあるので、3分程度にしています。
新しいプログラミング言語への適応
WadoはRustに似た言語ですが、Rustにはない構文や意味論があり、まったく新しいプログラミング言語です。
Claude Codeへはこのcheatsheetを渡してWadoを書かせています。
wado/docs/cheatsheet.md at main · wado-lang/wado · GitHub
しかしながら、Claude Codeの生成するWadoコードはいつもだいたい70点くらい。動くものを書けるという意味では適応できていますが、コードの品質は低いです。
たとえばこういうよくないコードがありがちです:
- Rustのlifetime相当があると勘違いしてRust的なlifetimeを前提としたコードを書こうとする
- thinkingをみてると「borrowを避けるために…なコードにしないとけない」みたいなことを呟いている
- matchをうまく使えずに、ifの連鎖を書いてしまう
- 標準ライブラリをうまく使えずにユーティリティを自分で書いてしまう
- これを減らすためにRustの標準ライブラリと同じ語彙を用意していますが、ある程度はしょうがない…知識として存在しないので
- ジェネリクスを使うときに型引数を明示しようとする
- その他、Rustにないデフォルト引数、デフォルトフィールド値、matches演算子(Rustのmatches!マクロを言語に組み込んだもの)などをうまく使えない
Rustのlifetimeに引きずられる一方で、Rustとほぼおなじ性能のmatchをうまく使えなかったりするのは不思議です。そして「matchを使って」と指示しなおすと、普通にmatchに書き換えられます。
しょうがないので標準ライブラリやサンプルコードはかなりしっかりめにレビューして品質を保つようにしています。人間がコードを書くことさえあります。
まとめると、チートシートによってほぼ「動くものを書ける」は達成できますが、学習量の少なさからくるぎこちなさが目につきます。それでもWadoはRustから構文と意味論の8割を拝借しているのでまだマシですが、まったく新しい構文の言語だと適応は相当難しいかもしれません。
「ad-hocな変更はしないでください。正しい設計に基づく正しいコードだけを書くべきです」
Claude Codeの癖なのか世の中にある学習データ(おそらくgit commit history)がそうなのか、普通にコードを書かせると変更量を最小にするad-hocなworkaroundをしがちです。セッションごとに「ad-hocな変更はしないでください。正しい設計に基づく正しいコードだけを書くべきです」とセッション中に何回も指示してようやくまともなコードになります。
それでもセルフレビューで「ad-hocなworkaround、品質の低いコードはありますか」と聞くとたっぷり指摘が出てくるわけですが。
どうもAGENTS.mdにこの種のことを書いても無駄なので、REVIEW.md は別途育てなければいけないかもしれないな、と思い始めています。
SDD (Spec Driven Development) は必須
Wadoは大きめの機能を実装するときは WEP (Wado Evolution Proposals) という文書をつくってから作業に取り組みます。これはこれでよいです。Claude Codeに雑に指示すると見当違いのものを作ることがよくあるので、まともなものを開発するためには仕様書は必須だなと痛感しています。
しかし注意しないといけないのが、Claude Codeは一度何かにフォーカスするとコードベースの他の部分や他の文書を読まずに議論しようとする癖があるな、ということ。このセッションで初出の機能については、「この機能について調べろ」と指示しながらでないと、Wadoに存在しない(とくにRustの)機能や構文を引き合いにだして議論を進めようとするのでかなり厄介です。
LLMのcontextサイズがあと100倍くらいになると言語仕様をすべてcontextに載せられるのでしょうが、いまはまったくそのレベルに達していないので、新しいプログラミング言語を作るためのバッドノウハウが日々溜まっていきます。そして数ヶ月後にはそれらは不要になり、それまで貯めた知見のアンラーンが必要になるというわけです。
コードを健全に保つ活動
Wado compilerはこれまで何度か大規模リファクタをしてきています。とくにcodegen (wasm生成器) は2回くらい大規模リファクタをしていて、1回目は1月下旬、2回目は2月なかばにやってました。
最初の大規模リファクタではAST -> wasmを AST -> TIR (Typed IR) -> wasm にして、2回目の大規模リファクタでは AST -> TIR -> WIR (Wasm IR) -> wasm にして、その後2ヶ月間大きな構造の変化はしていないので、IRが二層ある状態で安定しそうだなと思っています。このへんはrustcのアーキテクチャもかなり参考にしています。
WIRのときはこのWEPを作りました:
wado/docs/wep-2026-02-14-wir-layer.md at main · wado-lang/wado · GitHub
そもそもなぜコンパイラの大規模離ファクタが必要になるかというと、放っておくとcodgenでいろいろなことをやりすぎてcodegenモジュールが果てしなく肥大化するからなんですよね。WIR導入直前などはcodegen.rsが18000行くらいに肥大化していて、高レベルなIRであるTIRを低レベルなwasmに変換するために必要な様々なことを一挙に担う神モジュールと化していました。こうなると、Claude Codeが長いコードを読むのは苦手ということもあり、何をするのもものすごく時間がかってしまいます。
ここにほぼWasmの命令に一対一対応したIR(ただし構造はtree状)を導入することで、codegenはWIRをただemitするだけ(このWIRのemitは1000行の巨大なmatch文ですが、ほぼロジックがなくシンプルな状態)、それ以外のさまざまなロジックはすべてcodegen前に済ませるという形にして、構造がシンプルになり、さらにコンパイルフェーズの新設も簡単になり開発効率はかなり上がりました。
で、このWIRリファクタリングは、プロトタイピングの時間も含めると10日くらいかかってます。来る日も来る日もひたすらリファクタでつらかった…。その後この規模のリファクタはしていないので、このときがんばってリファクタした甲斐はあったのでしょう。
もっともリファクタに関していうと、Wado処理系の開発時間の6割くらいはリファクタです。3割は最適化器の改善 & 最適化器のバグ修正です。言語機能の設計と実装に使っている時間は全体の1割以下です。まあ、agentic codingに限らず、処理系の開発というのはそういうものかもしれません。振り返ればXslateの開発のときもひたすらバグ修正する毎日だった気がしますし。
所感
いろいろ大変なんですけど、なんだかんだでかなり言語機能は出来上がってきたので、そろそろwasm platformへのデプロイを試したい所存。プログラミング言語処理系開発は楽しいです🤤