chocomint

image

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.ymlverifier.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.ymlsecurity.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 人手編集の保存(PathGuardworkspace/ 内に限定)
POST /edit/chat 自然言語指示で AI 編集(Planner#runexpectations に指示を渡すため意味的検証が効く)
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 の ConPTYpwsh を対話起動し、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 未導入)ではコンソールのみ無効化され、 エディタの他機能は動作する。