はじめに — 「黒い画面の Bob」を、チームの Slack に住まわせたい
これまで IBM Bob Shell(bob)は基本「自分のターミナルで叩く相棒」でした。強力なんですが、チームの Slack に常駐して、空気を読んで、頼まれた仕事を最後までやり切る同僚が欲しくなります。
そこで、Slack ファーストの AI 同僚ゲートウェイ OpenRyoko(MIT / Jinn 系)をフォークし、LLM エンジンを IBM Bob に差し替えた派生版「OpenBanto」を作ってみました。名前は旅館・宿の表方を仕切る「番頭」から。表(人間・Slack)を捌き、重い仕事は裏方(=Bob)に振る、という役割そのものです。
この記事は第1弾。この作業用に立てた検証VMで、実際に Slack で Bob が喋るところまでを、ハマりどころ込みで書きます。
- リポジトリ:
phssakaigawa/openbanto(MIT。OpenRyoko / Jinn の出典表記あり。IBM 公式ではありません)
ベースにした OpenRyoko とは
OpenRyoko は Jinn 系の 常駐デーモン + AI組織 + cron + Web ダッシュボード + MCP という土台に、Slack 上での振る舞いを足した設計です。
- 空気読みトリアージ … メッセージ毎に「黙る / スタンプ / 返信」を判定
- 自然言語での自律完遂 … 「最後までやって」で複数ターン走り切る
- Agents View Canvas … 全セッションを Slack のタブにライブ表示
読んでいて面白いのは、これが 1人用アシスタントではなく、チームの共有チャンネルに“住む”前提だということ。Employee(役職・上長・部門)を持つ “AIの組織” モデルで、まさに「番頭とその配下」を置ける器でした。
設計 — Bob を「エンジン」として足す
OpenRyoko は claude / codex / gemini の3エンジンをプラガブルに持っています。ここに bob エンジンを足します。ポイントは2経路:
-
ワンショット(
shared/oneShotCli.ts)— トリアージ等の軽い内部呼び出し用 -
セッションエンジン(
engines/bob.ts)— 会話の本体。中断可能&会話継続(resume)対応
bob run のヘッドレス出力を確認すると、こうなっています(-f json):
{"type":"result","status":"success",
"stats":{"task_id":"…","duration_ms":3132,"session_costs":0.0143,"tool_calls":0},
"last_message":"OK"}
last_message が回答、stats.task_id が再開に使える ID、session_costs でコストも取れる。これを EngineResult に写像します。エンジンの肝はこのあたり:
private buildArgs(opts: EngineRunOpts, prompt: string): string[] {
const args = ["run"];
if (opts.resumeSessionId) args.push("--resume", opts.resumeSessionId); // 会話継続
args.push("--format", "json", "--trust");
if (opts.cwd) args.push("--workspace", opts.cwd);
// bob run に --model は無い(モデルは team / API キーに紐づく)
args.push(prompt);
return args;
}
⚠️ 地味に重要:
bob runは stdin が開いたままだと入力待ちでハングします。子プロセスのstdinは即クローズ(stdioをignoreにするかchild.stdin.end())。これは Node-RED ノードを作ったときにも踏んだ罠でした。
認証は BOB_API_KEY(Inference 型キーなら team-id 不要)。エンジンは既定で bob にしました。
実機で ワンショット / セッション / resume を叩くと、resume で前ターンの内容をちゃんと覚えて返してきます(task_id を渡すだけ)。ここまでで「Bob をエンジンにする」部分は完成。
検証環境 — 作業用に VM を新規作成する
作業マシンを汚したくないので、この作業のために Ubuntu 24.04 LTS の VM を1台新規作成します。Node.js 22 + pnpm + git だけ入れた「素の開発VM」です。
# VM で
node -v # v22系
corepack pnpm -v # 10.6系
bob 本体は npm グローバルの bobshell。VM には公式インストーラで入れます:
curl -fsSL https://bob.ibm.com/download/bobshell.sh | bash # Node 22+ 前提
bob --version # 2.0.0
config.yaml(~/.openbanto/config.yaml)はこんな感じ。既定エンジンを bob に、Slack トークンを入れます:
engines:
default: bob
bob:
bin: /home/xxx/.npm-global/bin/bob # PATH に無ければ絶対パス
connectors:
slack:
appToken: "xapp-…" # Socket Mode
botToken: "xoxb-…"
portal:
portalName: "Banto"
BOB_API_KEY は環境変数で(config には書かない)。あとは起動:
openbanto start # 実体は node dist/bin/jimmy.js start
# → gateway listening on http://127.0.0.1:7777 / Slack connector started (socket mode)
Slack 側は Socket Mode のアプリを1つ作り、connections:write の App-Level Token(xapp-)と Bot Token(xoxb-)を発行。マニフェストから作ると scope 込みで一発です。ここまでで、Slack で @メンションすると Bob が答える…はず。
【本題】さわってみないと絶対に見つからないバグ
意気揚々と Slack でメンションしたら、これが返ってきました。
Error: Failed to spawn IBM Bob CLI (claude): ENOENT — binary not found on PATH.
Try one of:
- npm install -g @anthropic-ai/claude-code
「IBM Bob CLI (claude)」。エンジンは bob にしたのに、なぜか claude バイナリを起動しようとして落ちている。しかも案内が claude(npm)。VM に claude は入っていないので当然 ENOENT。
ログの決定打はこの1行でした:
[INFO] Bob engine starting: claude run (workspace: …)
BobEngine は選ばれている(=session.engine は bob)のに、渡ってくる bin が claude。犯人はエンジン設定を選ぶ三項演算子でした:
// manager.ts(api.ts / context.ts も同型)
const engineConfig = session.engine === "codex"
? config.engines.codex
: session.engine === "gemini"
? config.engines.gemini ?? config.engines.claude
: config.engines.claude; // ← bob の分岐が無く、claude にフォールバック!
bob の分岐が無いので既定の claude ブロックに落ち、その bin: "claude" が BobEngine.run() に渡っていた、という話。3箇所に bob 分岐を足して解決:
: session.engine === "bob"
? config.engines.bob ?? config.engines.claude
: config.engines.claude;
ついでに、エラー案内も直しました。bob が見つからないときは Bob 公式の curl インストールを案内し、絶対パス指定でも案内が出るよう basename でも引けるように:
const hints = INSTALL_HINTS[requestedBin]
?? INSTALL_HINTS[path.basename(requestedBin)] // 絶対パスでも "bob" を拾う
?? [ /* generic */ ];
// bob: curl -fsSL https://bob.ibm.com/download/bobshell.sh | bash
これ、ユニットテストでも机上でも気づけないタイプでした。ビルドは通るしエンジン単体テストも緑。実際に Slack から一発叩いて初めて「あ、bin が claude だ」と分かる。“さわってみないと見つからない”の典型で、個人的には一番の山場でした。
修正版を入れて再起動 → もう一度メンション:
動きました。 「いらっしゃいませ」と番頭が受付し、Slack から投げた依頼を裏で Bob が処理して返してくる。番頭の完成です。
ライセンスと出典(大事)
OpenBanto は MIT。フォーク元の権利表記は必ず残します。
- 上流
LICENSE(OpenRyoko / Jinn の著作権)はそのまま保持 -
NOTICEに Jinn → OpenRyoko → OpenBanto のチェーンを明記 - WhatsApp コネクタが使う GPL ライブラリ(baileys)は コアに同梱せず optional plugin 化(使う人だけ自分で
npm i)。おかげでコアは MIT クリーンなままtscが通ることも確認 - IBM / Bob は IBM の商標。OpenBanto は独立 OSS で IBM 公式ではありません
「フォークして名前を変えて公開・商用」も、MIT なら表示義務さえ守れば実務上問題なし——ここは MIT ライセンスのチェックをおこないました。
さいごに
「無ければ作る」で、IBM Bob を Slack 常駐の AI 同僚として動かすところまで来ました。
おかしなところがあったら、ソッと教えてください。


