前提
ここでは「Pi がモデル選択・agent loop・tool lifecycle・session・usage・cancellation を所有する」ことを、Pi をハーネスにする意味として考える。
長大な入力は Pi の user message に入れない。/rlm -- のような短い入口から外部ストアへ渡す。
既存理解
現行 RLM は次の3層になっている。
- RLM の反復ループ
- LMHandler のモデル呼び出し
- LocalREPL の永続 Python 実行環境 docs/architecture.md:21-41
重要な契約は次のとおり。
- 長大な入力はモデル履歴ではなく context 変数に置く
- REPL コードから llm_query() / rlm_query() を呼ぶ
- answer["ready"] = True で終了する rlm/utils/prompts.py:125-142
- 子 RLM は独立した環境を持ち、残り timeout/budget を継承する rlm/core/rlm.py:706-864
- stdout は切り詰め、完全な中間値は REPL 側に残す rlm/utils/prompts.py:138-142
Pi 側には必要な材料が揃っている。
- createAgentSession() によるインメモリ子セッション
- system prompt の完全置換
- customTools
- tool allowlist
- token/cost 集計、abort、dispose
- custom tool からの sub-agent 起動 Pi SDK (https://pi.dev/docs/latest/sdk)
- extension command、tool lifecycle、terminate: true Pi Extensions (https://pi.dev/docs/latest/extensions)
ローカルでも確認済み。
- Pi: 0.79.10
- Node: v24.18.1
- createAgentSession、defineTool、DefaultResourceLoader、SessionManager を実際に import 可能
- Pi の要求は Node >=22.19.0 ~/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/package.json:1-18,94-96
設計案
┌─────────────────────────────────┐ │ │ │ /rlm context.txt -- question │ │ │ └────────────────┬────────────────┘ │ │ │ │ ▼ ┌─────────────────────────────────┐ │ │ │ Pi extension command │ │ │ └─────────────────────────────────┘ │ │ ├───────────────────────────────────────┐ │ │ ▼ ▼ ┌─────────────────────────────────┐ ┌────────────────────────────────┐ │ │ │ │ │ │ │ │ │ PromptStore │ │ PiRlmRunner depth=0 │ │ raw context stays outside model │ │ │ │ │ │ │ └────────────────┬────────────────┘ └────────────────┬───────────────┘ │ │ │ │ ├───────────────────┐ │ │ │ │ ▼ │ ▼ ┌─────────────────────────────────┐ │ ┌────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ Persistent Python REPL worker │ ├──┤ Pi AgentSession │ │ │ │ │ metadata + short question only │ │ │ │ │ │ └─────────────────────────────────┘ │ └────────────────────────────────┘ │ │ │ │ ├───────────────────┼───────────────────┬──────────────────────────────────────┬───────────────────────────────┐ │ │ │ │ │ ▼ │ ▼ ▼ ▼ ┌─────────────────────────────────┐ │ ┌────────────────────────────────┐ ┌─────────────────────────────────┐ ┌─────────────────┐ │ │ │ │ │ │ │ │ │ │ rlm_exec tool │ ├──┤ llm_query() │ ┌──┤ rlm_query() │ │ answer variable │ │ │ │ │ │ │ │ │ │ │ └────────────────┬────────────────┘ │ └────────────────────────────────┘ │ └─────────────────────────────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ ├───────────────────┘ ┌──────────────────┘ │ │ │ │ ▼ ▼ │ ┌─────────────────────────────────┐ ┌────────────────────────────────┐ ┌─────────────────────────────────┐ │ │ │ │ │ │ │ │ │ One-shot Pi AgentSession │ │ PiRlmRunner depth+1 │ │ Final result returned by handle │◄─────────────┘ │ │ │ │ │ │ └─────────────────────────────────┘ └────────────────────────────────┘ └─────────────────────────────────┘
各 RLM node を一つの AgentSession として生成する。
- SessionManager.inMemory()
- RLM 専用 system prompt
- active tool は rlm_exec だけ
- AGENTS、skills、通常の Pi extensions、context files は読み込まない
- root prompt に渡すのは以下だけ
- context ID -型 -文字数・行数 -短い prefix -短い質問 -depth、残り budget、iteration limit
生の context は session.messages に入れない。
Pi の通常の bash をそのまま使うのではなく、永続 REPL を一つの構造化ツールにする。
rlm_exec({
code: `
chunks = ...
results = llm_query_batched(prompts)
answer["content"] = combine(results)
answer["ready"] = True
`
})内部動作:
- Python worker にコードを送る
- namespace を turn 間で保持
- stdout/stderr は上限付きで Pi に返す -完全な変数は worker 内に保持
- answer.ready が立ったら terminate: true -最終回答本文は tool result に流さず、外部 handle から runner が取得
これなら、1回の tool call 内の for や条件分岐から任意個の sub-call を起動できる。論文の「symbolic recursion」を維持できる。
- llm_query():
- tools なしの Pi AgentSession -一つの prompt → 一つの最終 assistant text
- rlm_query():
- PiRlmRunner(depth + 1) を生成
- maxDepth 到達時は llm_query() にフォールバック
- batched variant:
- Promise.all ではなく共有 semaphore を通す -入力順で結果を返す -親の AbortSignal、deadline、token/cost budget を共有する
既存 Python core に Pi 依存を混ぜず、独立パッケージにするのが安全。
integrations/pi-rlm/
├── package.json
├── src/
│ ├── runner.ts
│ ├── prompt-store.ts
│ ├── repl-tool.ts
│ ├── subcall-runtime.ts
│ ├── budget.ts
│ └── extension.ts
├── worker/
│ └── repl_worker.py
└── test/
extension.ts は薄い /rlm UI。中心は SDK からも直接呼べる PiRlmRunner に置く。
採らない案
最小変更ではあるが、Pi はモデル gateway にしかならない。外側のハーネスは依然として Python RLM。今回の目的とは違う。
簡単だが、モデルが sub-call を一件ずつ言語化する必要がある。ループ、分岐、大規模 fan-out をコードとして実行できず、論文 (https://www.alphaxiv.org/pdf/2512.24601v3) の Algorithm 2 に近い。
検証方針
PoC の合格条件は明確にできる。
- Context isolation 1MB以上の sentinel 入力が Pi の session messages/provider payload に混入しない。
- Symbolic recursion 一つの rlm_exec 内のループから N 個の sub-call が発生する。
- Persistent REPL turn 1 で作った変数を turn 2 で参照できる。
- Depth semantics maxDepthで one-shot call に切り替わる。
- Batch semantics 順序維持、並列実行、concurrency 上限を確認する。既存契約は tests/test_rlm_query.py:76-254 が参考になる。
- Shared limits 親子合算の token/cost、deadline、abort が子へ伝播する。
- External final answer root model の一回の出力上限を超える回答を、REPL の変数から返せる。
- 実モデル smoke direct Pi が context limit で扱えない synthetic S-NIAH を Pi-RLM が解けることを確認する。
論点
最大のリスクは REPL の安全性。現行 LocalREPL も security boundary ではないと明記されている(docs/architecture.md:112-131)。最初は trusted local PoC、外部入力を扱う段階で worker を Docker に隔離するのが妥当。
結論として、実装可能で、Pi native にする価値もある。最小PoCは「外部 context・Pi root session・永続 Python REPL・one-shot/recursive Pi sub-session」の4点に絞るのが正しい。