0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM Bob を“番頭”に据える — OpenRyoko を魔改造して Slack 常駐 AI 同僚「OpenBanto」を作ってみた

0
Last updated at Posted at 2026-08-09

はじめに — 「黒い画面の Bob」を、チームの Slack に住まわせたい

これまで IBM Bob Shell(bob)は基本「自分のターミナルで叩く相棒」でした。強力なんですが、チームの Slack に常駐して、空気を読んで、頼まれた仕事を最後までやり切る同僚が欲しくなります。

そこで、Slack ファーストの AI 同僚ゲートウェイ OpenRyoko(MIT / Jinn 系)をフォークし、LLM エンジンを IBM Bob に差し替えた派生版「OpenBanto」を作ってみました。名前は旅館・宿の表方を仕切る「番頭」から。表(人間・Slack)を捌き、重い仕事は裏方(=Bob)に振る、という役割そのものです。

OpenBanto の“番頭”(温泉旅館の番頭がお客をさばき、裏の仕事は職人=Bob に振る)

この記事は第1弾。この作業用に立てた検証VMで、実際に Slack で Bob が喋るところまでを、ハマりどころ込みで書きます。

  • リポジトリ: phssakaigawa/openbanto(MIT。OpenRyoko / Jinn の出典表記あり。IBM 公式ではありません)

ベースにした OpenRyoko とは

OpenRyokoJinn 系の 常駐デーモン + AI組織 + cron + Web ダッシュボード + MCP という土台に、Slack 上での振る舞いを足した設計です。

  • 空気読みトリアージ … メッセージ毎に「黙る / スタンプ / 返信」を判定
  • 自然言語での自律完遂 … 「最後までやって」で複数ターン走り切る
  • Agents View Canvas … 全セッションを Slack のタブにライブ表示

読んでいて面白いのは、これが 1人用アシスタントではなく、チームの共有チャンネルに“住む”前提だということ。Employee(役職・上長・部門)を持つ “AIの組織” モデルで、まさに「番頭とその配下」を置ける器でした。


設計 — Bob を「エンジン」として足す

OpenRyoko は claude / codex / gemini の3エンジンをプラガブルに持っています。ここに bob エンジンを足します。ポイントは2経路:

  1. ワンショットshared/oneShotCli.ts)— トリアージ等の軽い内部呼び出し用
  2. セッションエンジン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再開に使える IDsession_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 runstdin が開いたままだと入力待ちでハングします。子プロセスの stdin は即クローズ(stdioignore にするか 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 でメンションしたら、これが返ってきました。

Slack でのエラー:Failed to spawn IBM Bob CLI (claude)

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.enginebob)のに、渡ってくる binclaude。犯人はエンジン設定を選ぶ三項演算子でした:

// 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 だ」と分かる。“さわってみないと見つからない”の典型で、個人的には一番の山場でした。

修正版を入れて再起動 → もう一度メンション:

修正後:番頭「Banto」が“ご記帳”の挨拶で応答

動きました。 「いらっしゃいませ」と番頭が受付し、Slack から投げた依頼を裏で Bob が処理して返してくる。番頭の完成です。


ライセンスと出典(大事)

OpenBanto は MIT。フォーク元の権利表記は必ず残します。

  • 上流 LICENSE(OpenRyoko / Jinn の著作権)はそのまま保持
  • NOTICEJinn → OpenRyoko → OpenBanto のチェーンを明記
  • WhatsApp コネクタが使う GPL ライブラリ(baileys)は コアに同梱せず optional plugin 化(使う人だけ自分で npm i)。おかげでコアは MIT クリーンなまま tsc が通ることも確認
  • IBM / Bob は IBM の商標。OpenBanto は独立 OSS で IBM 公式ではありません

「フォークして名前を変えて公開・商用」も、MIT なら表示義務さえ守れば実務上問題なし——ここは MIT ライセンスのチェックをおこないました。


さいごに

「無ければ作る」で、IBM Bob を Slack 常駐の AI 同僚として動かすところまで来ました。

おかしなところがあったら、ソッと教えてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?