chocomint
Tool Execution Supervisor for Ruby — LLM のツール呼び出しをソフトウェアで厳格に監督し、 機械的検証・意味的検証・自動再試行・監査ログを行うオーケストレータ。
設計仕様は DESIGN.md を参照。設計原則:
LLM は提案者と監査者に限定し、実行・判定・再試行・記録はソフトウェアで管理する。
必要環境
- Ruby 3.4 (RubyInstaller with MSYS2/DevKit)
- LLM backend: Anthropic Messages API 互換エンドポイント
(Ollama が
/v1/messagesに対応。Claude API / OpenRouter でもbase_url差し替えで動作)
セットアップ
# Ruby 3.4 (未導入の場合)
winget install --id RubyInstallerTeam.RubyWithDevKit.3.4 -e
# 依存 gem
bundle install
設定
config/config.yml を編集する(DESIGN §11)。実機確認済みの構成:
llm:
base_url: http://localhost:11434/v1 # Ollama
model: gemma4:e4b # 提案者(primary)。tool_use 対応が必須
verifier:
enabled: true
# モデルは指定しない。verifier は llm.model を共有する(下記「モデルは 1 つ」を参照)
環境変数でオーバーライド可能: CHOCOMINT_LLM_BASE_URL, CHOCOMINT_LLM_MODEL。
モデルは 1 つ(primary と verifier で共有): VRAM 節約のため verifier は primary と同じモデルを使う。
factory.rbが verifier にllm.modelを渡すため、config.ymlにverifier.modelを書く必要はない(書いても無視される)。 単一モデルなら Ollama へのロードも 1 つで済み、モデルスワップが起きない。
モデル選定の注意: verifier は出力を必ず
1/0のみにする必要があるため、 primary/verifier を兼ねるモデルは tool_use 対応かつ 指示追従性が高い ものを選ぶ。qwen3等の thinking 系は余計な出力をして verifier が 不正判定 (RETRY) になりやすいので注意。
実行
# ollama serve を起動しておく
ruby bin/chocomint "<ユーザー要求>" ["<期待条件>"]
# 例
ruby bin/chocomint "workspace/hello.txt に hello と書いて" "内容が hello であること"
出力例:
status: PASS
trace_id: 8af69708-...
attempts: 1
proposal: {"tool":"write_file","arguments":{"path":"workspace/hello.txt","content":"hello"}}
result: {"exit_code":0,"stdout":"wrote 5 bytes to workspace/hello.txt", ...}
監督プロキシサーバー
chocomint を Anthropic Messages API 互換の HTTP サーバーとして起動できる。
POST /v1/messages で受けた要求を内部で Orchestrator#run に委譲し、
ツール実行・機械検証・意味検証・再試行・監査ログまで完結させてから結果を返す
(「監督完結型」)。通常の LLM API と違い、返るのは提案ではなく実行が保証された結果。
# 起動 (Ollama backend が別途必要)
ruby bin/chocomint-server # 既定 127.0.0.1:9210 (使用中なら 9211, 9212... と自動で空きポートを探す)
# CHOCOMINT_HOST / CHOCOMINT_PORT または config.yml の server: で変更可
リクエスト例:
$body = @{
messages = @(@{ role="user"; content="a.txt に hello と書いて" })
expectations = "a.txt が存在し内容が hello であること"
} | ConvertTo-Json -Depth 6
Invoke-RestMethod -Uri http://127.0.0.1:9210/v1/messages -Method Post `
-ContentType "application/json" -Body $body
レスポンス(Anthropic 形式 + chocomint 監督メタ情報):
{
"type": "message", "role": "assistant", "model": "chocomint-supervisor",
"chocomint": { "status": "PASS", "trace_id": "...", "attempts": 1 },
"content": [
{ "type": "tool_use", "name": "write_file", "input": { "path": "...", "content": "hello" } },
{ "type": "tool_result", "content": [{ "type": "text", "text": "{\"exit_code\":0,...}" }] }
]
}
| エンドポイント | 内容 |
|---|---|
POST /v1/messages |
監督実行。200=PASS / 422=提案不正 / 502=リトライ上限 / 400=リクエスト不正 |
GET /health |
ヘルスチェック |
テスト
bundle exec rspec
監査ログ
全試行が logs/execution.db (SQLite) の executions テーブルに記録される(DESIGN §12)。
ruby -e "require 'sqlite3'; SQLite3::Database.new('./logs/execution.db').execute('SELECT attempt,status,verifier_output FROM executions ORDER BY id').each{|r| p r}"
セキュリティ
- ファイルアクセスは
security.allowed_dirs(既定.= サーバーを起動した場所)配下のみ。 パストラバーサル (../) はPathGuardが拒否する(DESIGN §13)。 - 出力・ファイルサイズ制限、実行タイムアウトを config で制御。
対応ツール
初期スコープ (DESIGN §15) に加え、基本的なファイル/コマンドツールを実装。
ファイル系(すべて PathGuard で許可ディレクトリ内に制限)
| ツール | 引数 | 概要 |
|---|---|---|
write_file |
path, content |
ファイルを書き込む(親ディレクトリ自動作成) |
read_file |
path |
ファイル内容を stdout に返す |
list_dir |
path |
ディレクトリのエントリ一覧を返す |
append_file |
path, content |
ファイル末尾に追記(無ければ新規作成) |
delete_file |
path |
ファイルを削除(ディレクトリは対象外) |
make_dir |
path |
ディレクトリを作成(親も作成) |
file_info |
path |
存在・種別・サイズを JSON で返す |
検索・編集系(PathGuard で許可ディレクトリ内に制限)
| ツール | 引数 | 概要 |
|---|---|---|
edit |
path, old_string, new_string, replace_all? |
文字列置換。既定では old_string が一意である必要あり |
grep |
pattern, base?, glob?, ignore_case? |
正規表現検索。file:line:text 形式で返す |
glob |
pattern, base? |
glob パターン(例 **/*.rb)でファイル列挙 |
ls |
path |
エントリを name/type/size 付き JSON 配列で返す |
コマンド系
| ツール | 引数 | 概要 |
|---|---|---|
run_command |
command, args[] |
whitelist のコマンドのみ実行。shell を介さず引数配列で spawn(DESIGN §13) |
bash |
script |
bash スクリプトを実行 ⚠️ |
powershell |
script |
PowerShell (pwsh) スクリプトを実行 ⚠️ |
run_command の許可コマンドは config.yml の security.allowed_commands で指定する。
security.allow_all_commands: true にすると whitelist を無効化し任意コマンドを実行できる
(⚠️ 信頼できるローカル環境でのみ使うこと)。
⚠️
bash/powershellについて: shell を介すため 任意コマンド実行が可能で、 DESIGN §13 の「shell 展開禁止」原則からは外れる強力なツール。cwd 固定・timeout・ 出力サイズ制限は適用されるが、信頼できる環境でのみ使うこと。
AI エディタ (/edit)
chocomint-server は ブラウザ上の AI エディタを /edit で提供する。
Monaco Editor でのファイル編集、AI チャットによる編集(内部で Planner が
提案→実行→検証→再試行を完結)、本物の対話コンソール(xterm.js)を 1 画面に統合する。
ruby bin/chocomint-server
# → http://127.0.0.1:9210/edit をブラウザで開く
構成: メニュー / ファイル一覧(workspace/ 配下)/ Monaco エディタ / AI チャット /
コンソール(ConPTY による本物の tty)。
| エンドポイント | 内容 |
|---|---|
GET /edit |
エディタ画面 |
GET /edit/files |
workspace/ のファイル一覧 (JSON) |
GET /edit/file?path= |
ファイル内容 (JSON) |
POST /edit/save |
人手編集の保存(PathGuard で workspace/ 内に限定) |
POST /edit/chat |
自然言語指示で AI 編集(Planner#run。expectations に指示を渡すため意味的検証が効く) |
GET /edit/assets/* |
Monaco / xterm.js のローカル同梱アセット |
追加依存
ffi— Windows の擬似コンソール (ConPTY) を FFI で叩く(プリビルド gem、DevKit 不要)。websocket-driver— コンソール (xterm.js) の WebSocket。- Monaco / xterm.js は
public/vendor/に同梱(CDN 不要・オフライン動作)。
コンソール(本物の対話 tty)
コンソールは Windows の ConPTY で pwsh を対話起動し、WebSocket 経由で
xterm.js と双方向に繋ぐ。tty ワーカーは新規コンソール付きで spawn される
(ConPTY はコンソールを持つプロセスからしか正しく動かないため)。
WebSocket は既定で HTTP ポート +1(8081)。ポート・トークンは以下で変更可能:
$env:CHOCOMINT_WS_PORT = "8081"
$env:CHOCOMINT_WS_TOKEN = "<共有トークン>" # 未設定ならローカル限定運用として認証なし
⚠️ コンソールのセキュリティ: 生の対話 tty は
PathGuard/allowed_commandsの 監督外で任意コマンドを実行できる(DESIGN §13 の shell 展開禁止原則の例外)。bind は127.0.0.1固定。信頼できるローカル環境で、 必要ならCHOCOMINT_WS_TOKENを設定して使うこと。ConPTY 非対応環境(非 Windows /
ffi未導入)ではコンソールのみ無効化され、 エディタの他機能は動作する。