Skip to content

Latest commit

 

History

History
246 lines (132 loc) · 65.8 KB

File metadata and controls

246 lines (132 loc) · 65.8 KB

BellTeam 現行設計

本書をBellTeamの現行設計の正本とする。本書は成立させる製品契約を記録し、未実装の項目は実装済みとして扱わない。

公開認証

未設定のコンテナではWeb UIと管理APIを直接使える。同じマシン・LANからのHTTP接続を許可する。Cloudflare Accessを設定して有効にした公開ドメインの接続では、Cf-Access-Jwt-Assertionの署名・発行元・audience・期限を検証し、未認証なら拒否する。LANの直接接続は維持する。ブラウザにBellTeam独自のアクセスキーを保存しない。/healthzは監視用、/api/sessionは接続確認用に未認証でも状態を返す。

コンテナ内のMCP連携はloopback専用の内部HTTP入口を使う。BellTeam MCPと通話MCPは席ごとのプロセスを起こさず、各CLIがこの入口の/mcpと/mcp/call-bridgeへHTTPで直結する。どの席かは、席のCLIが持つAITERM_SESSION_ID(Aitermが席をBot IDと同じ名前のセッションで起こして付ける)を各CLIの設定でヘッダーX-Bellteam-Seatへ写して伝える。名札がBot台帳に無い要求(席が起こした子など)には、どちらの入口もBELLTEAM_SEAT_UNRESOLVED(400)を返す。403にしないのは、Claude Codeが403を「認証が要る」と判定して全席共有の~/.claude/mcp-needs-auth-cache.jsonへサーバー名で記録し、その後しばらく本物のClaudeの席まで接続を飛ばすため(400・404・409・421・422・503は記録されない事を実測)。通話ブリッジは名札の無いBellTeamの合言葉を所属全体として扱うため、名札なしでは中継しない。実測と経緯は 席ごとのMCPをHTTPへ寄せる。追加機能の設定は永続領域へ保存し、環境変数は初回移行に使う。初回のハーネス選択、公式認証、案内botとの会話、追加機能の設定と反映は 初期設定 に従う。

iPhoneアプリ

未接続の初回画面は「はじめて使う」と、サーバーを用意済みの人のURL入力を表示する。導入ガイドは接続前からアプリ内で読めるものとし、docs/server-installation.mdをiPhone・iPad・Mac共通で同梱する。PCとDockerの準備、公開の設置用ファイルの取得、既存installerでの起動、同じマシン・LANからの接続先URL、公式AI認証まで案内する。AIへ任せる場合は、GitHubのURLと「これセットアップしたい」をまとめてコピー・共有できる。同じ手順を公開GitHubで開き、PCへ共有できる。ガイドの開閉で入力したURLを消さない。

BellTeamのiPhoneアプリはSwiftUIでios/BellBot/に置く。利用者が初回起動時に自分のBellTeamのHTTPS URLまたはLAN・同じマシンのHTTP URLを入力し、公開Web UIと同じAPIを使う。公開ドメインでは設定したCloudflare Accessの本人認証後にAPIへ接続し、ローカルでは直接接続する。BellTeam独自のアクセスキーとiPhone専用の認証情報は持たない。Bot・ルームの会話、画像添付、送信先指定、配送待ち表示、Bot画面、Bot・ルーム設定、自動実行予定、オーナープロフィールとユーザー規範を扱う。オーナー・Bot・ルームのアバターは写真選択後にドラッグと1〜3倍の拡大で正方形に切り抜き、確定してから各設定へ保存する。Macのアバター調整は画像と拡大率を横に並べ、画像・調整・確定をスクロールなしで表示する。画面確認用の固定データはDebugビルドにだけ含め、Releaseの通信には使わない。

アプリはMetricKitのハング・クラッシュ診断と共通通信処理の時間切れ・接続失敗・サーバーエラー・応答形式異常をPOST /api/diagnosticsへ送る。固定コード、画面区分、アプリ版に加え、ハング・クラッシュの呼び出しスタック、通信の数値コード・応答種別を匿名化したdiagnostic_logとして送る。会話本文・URL・認証情報は含めない。BellTeamは発生回数・状態・最新の診断ログをruntime/data/logs/diagnostics.jsonへ保存する。通信の失敗(時間切れ・接続失敗・サーバーエラー・応答形式異常・通知登録の失敗)は、エラーの種類や回数だけで重大度を決めない(ServerManagerのbughub/NETWORK_REPORTING.md)。アプリが利用者への影響と復帰の可否から付けたseverity(fatal・high・warn・info)を使い、根拠はdiagnostic_logの行に書く。severity付きの報告は重大度ごとに別の行(署名)にする。infoは適切に処理した参考記録で、その報告のdiagnostic_logにuser_impact=noneとapp_handling=<アプリがした事>の行がある時だけ参考として扱う。行はresolvedで作り、回数・時刻・診断だけを進めて、修理の対象にしない。severityの無い版の報告と、根拠の行の無いinfoは未評価として扱う。確認できないだけでは下げず、今までの重大度(時間切れ・サーバーエラー・応答形式異常はhigh、接続失敗・通知登録の失敗はwarn)と署名のままopenで登録し、文面に暫定と出す。BugHubへ返す項目は変えない。サーバーの通知は、1件を届けられなかった時をSERVER_PUSH_SEND_FAILED(warn。再送しない。原因は未確定)、通知の機能を開始できなかった時をSERVER_PUSH_UNAVAILABLE(high)に分ける。分ける前のSERVER_PUSH_FAILEDは保存済みの行のために残し、新しくは記録しない。診断の送信が失敗した場合、アプリはOSLogへ失敗を記録する。圏外・通信制限・通話中・中断(端末の状態)は報告しない。更新(/api/events)の切断は再接続で吸収し、再接続が5回連続で失敗した時だけ1件報告する。アプリが前面へ戻る時は通信の接続プールを捨ててから読み直す。復帰TaskはAppStoreが保持し、背景移行・次の復帰・ログイン終了で取り消す。取消後は次の処理へ進まず、完了した古い取得結果も反映しない。IOS_PUSH_FAILEDの診断ログには失敗した段(stage)を含める。 通知APIの失敗はstage付きの通知診断だけへ記録し、通信診断と二重計上しない。通信診断には端末種別、公開/LANの接続先区分、エラー時の画面状態、要求の経過ミリ秒を加える。GETの接続切断(-1005)は1回だけ再取得し、その後も失敗した時に記録する。POSTは再送しない。

アプリは一覧を?avatar=omitで取得し、内容の更新情報が変わったアバターだけ別URLから取得する。Botの通知用画像は更新情報とともにApp Groupへ保存する。読み直し中に重なったSSEの知らせは、次の1回の読み直しへまとめる。

一覧(GET /api/bots・/api/rooms・/api/owner)は各項目にavatarVersion(アバターの中身から決めた16桁)を返す。?avatar=omitを付けるとavatarを空にして返し、画像はGET /api/bots/<id>/avatar・/api/rooms/<id>/avatar・/api/owner/avatarから取る。?v=<avatarVersion>が現在の版と一致する取得はimmutableで返し、If-None-Matchが一致すれば304を返す。指定の無い取得は今までどおりavatarへdata URLを入れる(ビルド35以前のアプリとWeb UIのため)。会話の知らせ(/api/eventsのmessages)は、一つの変更で複数の監視が鳴っても50msの間は1件にまとめる。待機中の接続は125秒保つ(Nodeの既定5秒は再利用の瞬間に閉じてアプリ側の-1005になるため。cloudflaredの既定90秒より長くする)。

会話の正本はサーバーの保存済みメッセージとする。アプリが前面にある間はSSEの変更通知で画面を更新し(サーバーは知らせが無い間も25秒おきにSSEのコメント行を流し、アプリのURLSession既定の60秒や途中の中継が黙った接続を時間切れにしないようにする)、接続・再接続時には最後に表示したメッセージより後を取得して取りこぼしを埋める。SSE切断・一時的な接続失敗の時だけ間隔を広げながら再接続し、認証・設定・応答形式の異常は表示して止める。新着時は下端を見ている場合と自分の送信時だけスクロールを追従させ、過去ログの閲覧中は位置を保って新着ボタンを出す。

iPhone・iPad・Macの会話本文は共通のMessageTextでEnrichedMarkdownを使い、見出し・表・箇条書き・引用・コード・リンクを標準スタイルで表示する。範囲選択とコピーを有効にし、表の列もコピーできる。本文の文字サイズ・色・行間とリンクの色をアプリに合わせる。プロフィール、ルームの目的、Bot画面、入力要求の説明は共通のSelectableTextで原文のまま表示する。同じ本文を再表示する時は選択範囲を保つ。

会話の吹き出しでは、文書末尾の描画用の段落区切りと外余白を省く。段落間隔とコード枠の内側の余白は保持し、保存された本文を表示のために切り詰めない。描画ライブラリへの指定は公開themeインターフェースだけを使う。

アプリのメンバー一覧は、オンラインで待機中なら緑、仕事中なら赤の点滅、オフラインなら灰色の丸をアバターに添える。仕事中はGET /api/queueのrunning項目のbotIdで判定し、既存のSSE通知と一覧の再取得で更新する。

メンバーの返信・ルーム投稿と秘密情報の入力要求はAPNsでiPhoneへ通知する。保存直後に送信し、通知には本文・秘密の値を含めない。開いている会話の通知は前面表示を抑え、タップ時は会話または未完了の秘密入力画面を開く。端末登録は認証済みの通知APIで管理する。鍵・対応環境・診断は iPhoneへの通知 に従う。

通知をタップした時は共通のAppStoreが会話の宛先を変え、同じ会話の通知でもログの更新を要求する。共通のConversationViewが宛先ごとの本文状態を所有し、別の会話へ切り替えた時は以前の本文を引き継がない。未送信の文章は会話ごとの下書きとして保持する。

配布はTestFlightとし、Apple Developerの署名とApp Store Connectで配布する。

iPadアプリ

iPadはiPhoneと同じアプリに含め、別のコードやApp IDを持たない。BellBotAppが端末を見て、iPadではMacと同じDesktopWorkspaceView.swiftを開く。通信・認証・通知・購読・会話・下書き・編集・ルーム削除も同じファイルを参照する。

一覧と会話はNavigationSplitView、詳細はSwiftUIのinspectorで配置する。幅のある画面では一覧・会話・詳細を並べ、狭いウィンドウでは一覧から会話へ進み、詳細をシートで開く。メンバーとルームの編集はiPhoneと同じシートを使う。縦横4方向とウィンドウのサイズ変更を許可し、全画面を要求しない。画像の写真選択・貼り付け・ファイルのドロップは共通処理を使う。iPadの外付けキーボードはMacと同じReturn送信、Shift+Return・Option+Return改行とする。

画面試験と実機確認の状況は iPad版 に記録する。

Macアプリ

Mac CatalystでiPhone版と機能・通信を共有し、Macの画面は一覧・会話・詳細の3ペインとする。Macのメンバープロフィールは右の詳細ペインだけに表示し、会話の右上にはペインの開閉と書き出しのボタンを置く。メンバー編集は独立した編集ウィンドウを使い、保存成功とキャンセルで閉じる。プロフィールの初期値は最初の描画から持ち、モデル候補の通信を保存の開始条件にしない。検索、キーボード送信、画像ファイルの選択・ドロップ・貼り付け、会話ごとの下書き保持を備える。接続先と認証情報は利用するMacへ保存する。構成・ビルド・導入は Macアプリ に従う。

公開版の購読

Webとサーバーは無料で、会話・Bot間の配送・予定実行に購読を求めない。iPhoneとMacは同じAppleの商品を購入・復元し、本人のStoreKit利用権でアプリからの通常の会話と予定操作を判定する。初期設定の案内役との会話、閲覧・書き出し・管理は無料。期限切れでもWebと設定済みの予定は継続する。通知は端末ごとの購入情報を運営で検証する。責務・照会間隔・環境の選択・実装と公開の状況は 購読仕様 と、その構造化した準備状況を参照。

共通処理とハーネス固有処理

UI、MCP、メッセージ、Bot管理が扱う操作とデータ形式は、すべてのCLIで共通にする。

CLIごとの起動引数、入力方法、ターン完了の検出、確定回答の取得などの差はハーネスアダプタだけが扱う。共通処理はBot一覧などから得たharnessをアダプタ選択へ渡すだけとし、CLI固有の操作を持たない。現行の選択肢はClaude、Codex、Grok、Cursorである。新しいCLIへの対応も、共通側へ条件分岐を足さず、ハーネスアダプタを追加して行う。

ハーネス固有処理はAitermへ委ねる。BellTeamはAitermの継続セッションへターンを渡し、Aitermが返す完了と確定回答だけを共通形式で扱う。BellTeam独自のtmux操作とsrc/harnesses.mjsはAiterm移行までの既存実装であり、CLI固有処理を追加する場所にはしない。

AIの公式認証は、初期設定の後も設定の「AIの認証」からやり直せる(2026-10-05 オーナー裁定、Approval Box K-UTA3UC)。AIの認証が切れると、そのAIを使うメンバーは全員起動できず、入り直しを頼める席も無くなるので、設置した人が自分で戻せる入口を本体に置く。/api/harness-authはAIの種類と使用メンバー数、/api/harness-auth/<AI>は公式の状態、start・input・cancelは公式認証の進行を返す。応答のauthは初期設定と同じ形にする。startはAitermのagent_authへreloginを渡し、認証済みに見えるAIでも公式のログイン画面を起こす。reloginを知らないAitermが何も始めなかった時は、認証済みと取り違えずに409で断る。Codexの公式ログインは始めた時点で今の資格情報を消すので、今の認証が消えうるAIには一覧のstartWarningで知らせの文を渡し、画面は認証が無い・切れていると分かっている時を除いて、始める前に知らせてもう一度押してもらう。状態を見ただけの時のmessageは利用者向けの文に置き換え、公式認証が進行中の時はAitermの案内をそのまま返す。認証の画面の出し方と資格情報はAitermと公式CLIが持ち、BellTeamは進行を中継するだけにする。購読は求めない。

席を起こせなかった時(認証が切れたAIが起動時の画面で止まった時など)は、Aitermに残った端末をBellTeamが閉じる。残すと席が動いている扱いになり、次の送信から起こし直せず、入力も受け付けない。閉じた後の起こし直しと送り直しはしない。失敗は500として記録し、応答に「認証が切れている可能性」の文を添える。

実装資産の永続化境界

コンテナの中は変更しない(2026-10-05 オーナー裁定:初期コンテナと、この設置の物を場所で区切る。コンテナの中は変更せず、利用者の永続データはコンテナの外に置く。名簿では区切らない)。本番のcompose.yamlはコンテナを読み取り専用(read_only: true)で起こし、書けるのは割り当てた外の置き場(/home/bell、/home/bell/.local、/srv/bellteam)と、tmpfsの/tmp・/runだけにする。決まりの文だけでなく、作りで守る。Pythonのpip・venv、Node/npm・Corepack、基本ビルド道具はイメージに含める。Botが足すパッケージとランタイムは自分のプロジェクトの下へ入れ、複数のBotが使う物は/srv/bellteam/shared/tools/へ一つだけ置く。OSパッケージが要る時は、.debをrootなしで外の置き場へ展開する(手順はbot-environment.md)。配布物のdistribution/server/compose.yamlは、まだ読み取り専用にしていない。

各Botの environment/setup.sh はコンテナ起動時、会話受付・予定開始前に順番に一度実行する。実行結果は同じ場所のsetup.logに残し、一席の失敗では全体の起動を止めない。environment/env.sh は会話用CLI起動とコマンド予定の都度読み込む。Aitermには起動専用の短命MCP接続から環境変数名を渡し、Bot間で値を共有しない。手順の正本は bot-environment.md。Bot台帳、CLI認証、MCP登録先は変えない。

Bot固有の開発リポジトリ、成果物、利用設定は/srv/bellteam/bots/<bot-id>/を正本とする。ツールやMCPのBot固有設定は同プロジェクトのintegrations/<tool-id>/へ置く。

オーナーが共通化を明示したツールやMCPだけ、実装本体、依存関係、起動スクリプトを/srv/bellteam/shared/tools/<tool-id>/へ置く。共有実装の存在と全Botへの有効化を同一視せず、どのBotがどの設定で使うかは各Botのプロジェクトが所有する。BellTeamは起動時に共有ツールの保存領域を作成する。

この二系統以外は永続的な実装資産の正本にしない。/app、/usr/local、/tmp、/root、/home/bellと各CLIのグローバル設定は、基盤更新で消失または上書きされるものとして扱う。OSパッケージ導入と必要なグローバル登録は利用できるが、永続保存先の構築手順から再生成できる派生物に限定する。

Botプロフィールと送信者の人格

Botプロフィールは、自動生成Bot ID、最後に確認されたAitermセッションID、キャラクター名、プロフィール、性格(考え方)、口調、役職、役割を別の値として持つ。Bot IDはBellTeam内部で同一人物を識別する固定値であり、同じ文字列をそのBot専用のAitermセッションIDにも使う。最後に確認されたセッションIDはAitermの起動receiptから保存するが、配送時は古い自動採番IDを使わず固定Bot IDへ正規化する。役職は画面上で名前に添える短い職位、役割はBotが担当する仕事の定義である。

id: bot-a1b2c3d4
session: grok-4f8912ab
name: あかり
profileText: 同じチームのメンバー
personality: 率直で好奇心旺盛
speechStyle: 一人称は私。柔らかい口調で話す
position: 秘書
role: 調査担当

人格はプロフィール(profileText)、性格(考え方)(personality)、口調(speechStyle)に分け、Bot用のAGENTS.mdにはその現在値を記載する。各項目の読み方に関する利用者固有の指示はユーザー規範へ置く。旧形式は本文をpersonalityへ保持し、追加2欄は空で移行する。既存の混在した文章は、内容を読んで3欄へ仕分けして保存する。

プロフィールの正本は各プロジェクトのbot.jsonだけとする。役割は他の席に見せる短い説明で、作業手順はBotのプロジェクトのdocs/に置く(オーナー裁定 2026-09-03)。生成するBot側AGENTS.mdはその置き場を案内する。list_botsは全bot.jsonを読み、ID、最後に確認されたセッションID、名前、CLI、モデル、エフォート、プロフィール、性格(考え方)、口調、役職、役割を返す。SQLや別のBot索引へ複製しない。画面上の表示名は名前 役職とし、役職が未設定なら名前だけを表示する。チャット上部の副表示はBotの稼働状態だけとし、役割本文は表示しない。ルームも同じ考えで、一覧と上部に部屋の目的を表示せず、上部は人数だけにする(オーナー裁定 2026-09-03)。ルームの発言は左に発言者のアバターを出し、アバター画像から導いた代表色を吹き出しの枠に付ける。旧titleは廃止して起動時に破棄し、役職へ自動移植しない。CLI、モデル、エフォートはBot作成後も設定画面とMCPのupdate_botから変更できる。MCPはPATCH /api/bots/<id>と同じ経路で保存と稼働中への適用を行い、誰がいつ変えて戻すかは運用側の責任で、BellTeamは部屋の出入りに連動した自動切替を持たない(オーナー裁定 2026-09-03)。保存はプロフィールを書いた時点で即時に返し、稼働中Botへの適用は現在の作業の後に裏で行う。モデル未指定とエフォート未指定は、そのCLIの既定値を使う。モデルとエフォートの候補は、メンバー編集画面を開くたびにGET /api/modelsから取得する。BellTeamはAitermのagent_modelsを呼び、実行環境に入っているClaude・Codex・Grok・Cursor自身の一覧を読む。ハーネス固有の取得手順・モデルID・エフォートの変換はAitermが所有し、BellTeamは候補を永続化・再試行しない。MCPのlist_modelsも同じAPIを使い、harness指定時はそのCLIだけを取得する。取得失敗はCLIごとにerrorとして表示し、他の一覧や固定一覧で代用しない。モデルを選ぶとそのモデルのエフォート、CLIの既定と手入力ではCLI全体の範囲を表示する。保存時のエフォート確認も、指定CLIの現在の一覧で行う。保存済みのプロフィールの読込やAI設定を変えない編集は、一覧取得を要求せず値を保持する。一覧に無いIDは「その他(手入力)」で入力する。CursorのモデルIDとエフォートの分離、ClaudeのultracodeなどのCLI固有の対応はAitermから返る候補をそのまま使う。node scripts/update-models.mjs [harness]は一覧を表示する診断コマンドとする。

IDだけを受信Botへ渡してはならない。直接メッセージには、送信者のID、キャラクター名、役職、役割、本文、画像の有無を含める。受信Botは名前、役職、役割で相手を理解し、IDをsendmessageの返信先に使う。

会話ログは送信時点の送信者と宛先のID、名前、役職、役割を記録する。プロフィールを後から変更しても、過去のメッセージを誰が送ったかは変えない。送信者はMCPの呼出元プロジェクトからBellTeamが確定し、ツール引数では指定させない。

ユーザー規範

規範は二層に分ける。製品規範はconfig/common-agents.mdで、BellTeamという製品が全Botに課すもの。ユーザー規範は/srv/bellteam/owner/user-rules.mdで、オーナーがBellTeamの利用者として自分の全Botに課すもの(オーナー裁定 2026-09-03)。ユーザー規範の初期値は空とし、実際の本文はruntimeのファイルだけに保存する。オーナー情報の編集画面の下で編集し、起動時指示は「オーナー情報 → ユーザー規範 → 製品規範 → Bot固有」の順に生成する。ユーザー規範は製品の更新で変わらない。共通MCP(bellteam)のget_user_rulesは誰でも読める。書き換えは別のMCPサーバーbellteam-owner(src/mcp-owner-server.mjs)のupdate_user_rulesだけが持ち、書き換えを許可する席だけが自分のCLI設定へ登録する。誰が書けるかは席ごとの登録で決まり、BellTeamのコードは役職やBot IDを判定しない(オーナー裁定 2026-09-03)。どちらもWebの/api/user-rulesと同じUserRulesを読み書きし、書き換え後はsrc/global-instructions.mjsで起動時指示を作り直す。Botの再起動は不要で、次の配送から新しい指示が使われる。Cursor CLIはグローバルの~/.cursor/AGENTS.mdを読まず、作業フォルダのAGENTS.mdだけを読む(実測 2026-10-03、cursor-agent 2026.10.01)。そのためBellTeamは起動時に~/.cursor/hooks.jsonのsessionStartへintegrations/cursor/instructions-hook.mjsを登録し(src/cursor-instructions.mjs)、このhookがセッション開始のたびに~/.cursor/AGENTS.mdの全文をadditional_contextとして渡す(オーナー許可 2026-10-03 K-768ZYG)。足すのは自分の項目だけで、ThroughlineやApproval Boxなど他のhookは残す。登録できなくてもBotは起動し、理由をログへ出す。

キャラクターシート(設定画)

Botが自分を描く時の正本は、オーナーが決めた設定画(キャラクターシート)で、Botプロジェクトのassets/character-sheet/直下に置く(製品規範。ユーザー規範から格上げ、オーナー裁定 2026-09-03)。BellTeamはBot作成時にこのフォルダを作り、ファイルはBotが自分で置く。Web APIの/api/bots/<id>/character-sheetが直下の画像だけを一覧し(drafts/などのサブフォルダは見せない)、/api/bots/<id>/character-sheet/<file>が画像本体を返す。ホームのBot編集画面の「画像を選ぶ」の右にある「キャラクターシート」で閲覧する。アバターとは別の資産で、混ぜない。シートを正本にするのはオーナーだけで、Botの候補はdrafts/に置く。作る向きは固定せず、アバターや渡された画像からシートを作ることも、シートからアバターを切り出すこともある(オーナー裁定 2026-09-03)。設定画の形式と作り方は利用者のユーザー規範またはBot固有の手順で決める。実装はsrc/character-sheets.mjs。

オーナープロフィール

オーナープロフィールはアバター、名前、プロフィール、XプロフィールURL、GitHubプロフィールURL、名称付きのその他URLを持つ。GitHubはアカウント名でなく公開プロフィールURLを正本とする。保存の正本は/srv/bellteam/owner/profile.jsonだけとし、共通規範やBotの長期記憶へ現在値を複製しない。

Web UIは左上のオーナーアバターから設定画面を開く。アバターはBotと同じ画像選択と正方形切り抜きを使う。Web APIのGET /api/ownerとPATCH /api/owner、MCPのget_owner_profileは同じ保存サービスを読む。

BellTeamは新規・復元セッションの起動時と、ユーザー会話、Bot間直接連絡、ルーム発言、予定実行、ユーザー追加指示の配送直前に、現在のオーナープロフィールを読み直して文脈へ添付する。画像本体は添付しない。プロフィール更新時にBotを再起動せず、稼働中のBotも次の配送から新しい値を受け取る。

ユーザーとBotの会話

WebとiPhoneの入力欄ではEnterは改行だけを入力し、送信は送信ボタンだけで行う(オーナー裁定 2026-09-03)。Macアプリの入力欄ではEnterで送信し、Shift+EnterとOption+Enterで改行する(オーナー裁定 2026-09-27)。会話の読み出しは最新から行う。APIはlimit(最新から件数)・before(そのIDより前)・after(そのIDより後の差分)を持ち、画面は開幕に最新10件だけを即描画し、上へ遡ったら前の20件を先頭に足して見ている位置を動かさず、新着は末尾に足すだけにする。画像は描画を止めずに後から差し替え、末尾に貼り付いている間は高さが変わっても最新を追う(オーナー裁定 2026-09-03)。チャット上部の「Botの画面」を開くと、そのBotのCLI画面の末尾(Aitermの画面読み取りそのまま)を2秒ごとに表示し、動いているか止まっているかを人が見て判断できる。構造化した出来事の表示は遅延するため採らない(オーナー裁定 2026-09-03)。

ユーザーからBotへの送信はAitermのpty_sendへ直ちに渡す。Aitermが送信時点の状態を見て、実行中のターンへの差し込みと新しいターンの開始を決める。Web UIはAitermへの送信中・Botの処理中を表示し、ユーザーが続けてメッセージを送れる状態を保つ。Web・iPhone・Macとも、送信した本文と添付画像はAPIの返事を待たずに入力欄から外す。APIはAitermの受付(止まっているBotの起動を含む)まで返事をしないため、待ってから外すと送れていないように見える(オーナーの指摘 2026-09-30)。送信がAPIに拒否された時は、入力欄の上に「送信できませんでした(理由コード)」を一行表示し、本文と添付画像を入力欄へ戻す。拒否を黙って飲まない(実被弾 2026-09-04: 秘書室で発言が消えた)。新しいターンではAitermが完了を検出した後、確定回答を会話へ追加する。差し込みは元のターンの回答に含め、別の回答を追加しない。文章だけの通常返信にsendmessage(target="user")を要求しない。

Aitermが入力受付状態に入れず「文字列は送信していない」と明示した送信拒否は、HTTP 409とBOT_INPUT_NOT_READYを返す。画面では未送信とBotの状態確認を案内し、元のAitermの理由はサーバーログへ残す。配送記録はfailedとし、強制送信や繰り返し再送はしない。この拒否をサーバーの500診断には記録せず、それ以外の判定できないAiterm障害は従来の500診断を維持する。同じBotへの先の送信が終わるのを待ち切れず、Aitermが打つ前に断った時(AGENT_SEND_BUSY、Aiterm 0.52.1以降、待ちは60秒)も同じ扱いにし、HTTP 409とBOT_SEND_BUSYを返す。画面では未送信と、少し待ってからの送り直しを案内する。Botが道具(sendmessageなど)で送って断られた時は、符号に続けて同じ案内文を返す。符号だけでは、文が打たれたかどうかがBotに伝わらないためである。

Botがターンの途中で書き、そのあと道具を使った言葉(合間の言葉)は、そのBotとユーザーの会話画面に、Botの発言と同じ吹き出しで時刻順に表示する。ラベルは付けない。対象はユーザー会話、Bot間の直接メッセージ、通話、ルーム、予定実行でBellTeamが始めたターンとする(ルームはオーナー裁定 2026-09-28 で一度棚上げし、同日に揃えた)。ルームがきっかけのターンでは、返信者も黙る指示を受けたメンバーも、それぞれのBotの会話画面に出し、ルームには出さない。BellTeamはターンの完了を待つ間、3秒おきとターン完了後に一度、Aitermのpty_observeを_metaの"aiterm/caller": "BellTeam"付きで呼び、結果の_meta["aiterm/interim_words"]から、送信receiptとevent_cursorが一致するターンの言葉だけを受け取る(Aiterm ADR 0071)。この指定はAitermの説明と引数に出ない。合間の言葉は会話ログへbellteam.interim-word.v1として残す表示だけの記録で、誰にも配送せず、確定回答の候補にもiPhoneへの通知にもならない。依頼したBotやルームへは届かない。思考は表示しない。Claude CodeのAPIエラーとsession limitの知らせ、Codexがターンをエラーで終えた時の本文(利用上限の知らせを含む。Aiterm 0.42.2以降)も合間の言葉として表示し、エラーの本文を記録に書かないハーネスでは、Aitermが返したエラー終了・利用上限の理由を代わりに表示する(オーナー裁定 2026-09-28: 見えていれば直そうと思える)。Cursorは道具が終わってから記録を書くため、道具の前の言葉は道具の終了まで届かないが、そのままとする(同日)。Bot間の直接メッセージ、通話、ルームで始まったターンの最後の回答は誰にも配送されないため、BellTeamはターンの完了後にpty_read(agent_transcript: true)で読み、合間の言葉と同じ記録(kind: "answer")としてそのBotの会話画面へ表示する。空の回答は出さず、読めない時も配送はdeliveredとする(オーナー指摘 2026-09-28: エレグの端末にある回答が画面に出ていなかった)。前節の「Botの画面」で採らなかった構造化した出来事の表示(2026-09-03)とは別の決定である。

sendmessage(target="user", ...)は、Botが通常のチャットターン外からユーザーへ自発的に連絡する場合と、通常チャットの回答で画像を提示する場合に使う。画像付き回答は本文と画像パスを一回の呼出しへまとめる。BellTeamは明示送信をそのターンの確定回答として採用し、通常出力を会話へ重ねない。Botが回答を空で終え、明示送信も無いターンでは、Botの発言を会話へ足さない(空の吹き出しを出さない)。

Bot間の会話

Bot間通信の共通入口は次のMCPツールとする。

sendmessage(target, message, image?)

これは非同期の直接メッセージである。Aitermが相手Botの継続セッションへ受理した時点で送信操作を完了し、相手Botのターン完了や返答を待たない。

受信Botの通常の最終回答は送信元へ自動転送しない。返事をしたいBotが、自分の意思でsendmessageを呼んで送り返す。繰り返し届くメッセージには毎回送信者の名前、役職、役割、IDを付け、相手を認識した会話として同じ継続セッションへ積み重ねる。

GrokBotとの通話は外部のgrokbot-bridge MCPで行う。BellTeamは全Botの4つのCLIへcall-bridgeを標準登録する。CLIは本体の内部入口の/mcp/call-bridgeへ接続し、本体が認証ヘッダーと発信者の名札(X-Call-Bridge-Caller-Id)を付け直して通話ブリッジへ中継する。要求と応答はためずに流し、応答の待ち時間に上限を置かない。本体を止める時は開いている中継の流れを終える。上流が応答の途中で切れた時は席側の流れも切る。上流の401と合言葉ファイルの欠落は、席では直せないので502(CALL_BRIDGE_AUTH_FAILED・CALL_BRIDGE_AUTH_UNAVAILABLE)で返す。認証ヘッダーは共有永続領域の権限600のファイルから読み、接続先と値をCLI設定へ書かない。BellTeamは共有永続領域のUNIXソケット上に通話専用のBot一覧と着信配送を開く。ブリッジからの着信は通話相手の所属とIDを付けて記録し、ユーザーやBellTeam Botに成り代わらせず、通常のAiterm配送で宛先Botへ渡す。BellTeam MCPに通話ツールは置かない。GrokBot宛てのマリアン中継はブリッジが担当する。

UIではBot間メッセージを会話本文へ複製しない。送信側と受信側の個別チャットに、相手の名前とアバターを含む一行の活動記録として表示し、タップした時だけ時刻、本文、画像の有無を開く。

トップページのメンバー欄とルーム欄は混ぜず、それぞれの中で最後のアクション日時が新しいものから上へ並べる。アクションはユーザーの送信、Botの返答、Bot間活動、ルーム投稿とし、処理中・待機中・オンライン状態の変化は並べ替えに使わない。最後のアクションがない項目は各欄の末尾へ置き、同時刻は元の安定順を保つ。

会話を開いたことはアクションではないため、既読にしても並び順を変えない。未読は端末ごとに最後に読んだアクションIDを保持し、一覧の印だけで示す。開いている会話へ新しいアクションが届いた場合はその場で既読とし、一覧へ戻っている間に届いたBotからユーザーへの返答だけを未読にする。Bot同士のやりとり(peer)とユーザー自身の送信には未読を付けない(オーナー裁定 2026-09-03: Bot間の受信で既読が戻る挙動をなくす)。

配送と追加指示

ユーザー会話、Bot間の直接メッセージ、ルーム発言、予定実行は、すべてBellTeam内で待機させず、宛先BotのAitermセッションへpty_sendで直ちに渡す。Aitermが実行中なら現在のターンへ差し込み(mode=agent_steer)、それ以外は新しいターンを開始する(mode=agent_dispatch)。BellTeamはこの判断をしない。送信エラーはfailedとして記録し、別の配送方法へ切り替えない。画面とMCPに手動の割り込み選択は置かない。

メッセージの配送状態は、BellTeamがAitermへ送信を始めた時点をrunning、新しいターンの完了を確認した時点をdelivered、起動・送信・完了待ちのいずれかが失敗した時点をfailedとする。差し込みでは新しい完了通知を作らないため、BellTeamが元のターンを追跡している場合はその完了後にdeliveredとする。BellTeamが元のターンを追跡していない差し込みは、Aitermの受理をもってdeliveredとする。Aitermがharnessの記録でターンのエラー終了(Claude Codeの529等のAPIエラー、Grokのturn_ended outcome=error)を検知してerrorを返した時もfailedとし、AITERM_TURN_ERROR: <エラー本文>を理由として残す。harnessの利用上限で止まった時(rate_limited)もfailedとし、Aitermが返した上限の知らせがあればAITERM_TURN_RATE_LIMITED: <知らせ>を理由として残す。DMとルームの会話ログへ状態遷移を追記し、最終状態をWeb UIへ表示する。DMでは、エラーと利用上限の知らせを合間の言葉としてBotの会話画面へ出す(「ユーザーとBotの会話」参照)。

処理中のメッセージはWeb UIとMCPから取得できる。既存の/queue APIとlist_queued_messagesは互換名として残し、待機メッセージは返さない。複数のルーム発言が同じBotのターンへ入った場合、respond_to_roomは受信文に含めたメッセージIDを指定して返信先を確定する。BellTeamが元のターンを追跡していない差し込みでも、メッセージIDから返信先を取得できる。Aitermが受け付けた後のTUI操作とターン完了判定はAitermに任せる。

オーナーへの報告漏れの確認と選択肢カード

Botのターンが終わると、BellTeamはオーナーへの報告が漏れていないかを確かめる(オーナー裁定 2026-10-01、src/turn-reports.mjs)。対象は、新しく始まったターン(オーナー・メンバー・予定・通話・ルームから)で、実行中のターンへの差し込みは元のターンに含める。ターンの時間の幅に、そのBotの会話画面へ出た合間の言葉・最後の回答・オーナー宛てのメッセージ(オーナー向けの言葉)と、メンバー宛ての直接メッセージを集める。

  1. 処理漏れ:オーナー向けの言葉が1つも無い。コードで判定する。ルームのターンは、オーナーがルームで読めるので除く。
  2. 中身の漏れ:メンバー宛てのメッセージがある時だけ、TypeSafeのJev(jev-latest)へ1回聞く。問いは2つのNoulで、outcomeは「メンバーとのやりとりの答え・結果・結論・決めたこと・頼んだことが、オーナー向けの言葉で伝わっているか」、substanceは「最後の言葉そのものに具体的な中身があるか」。Jevの主な学習言語が英語のため問いは英語で書き、会話は原文のまま渡す。outcomeが0.35未満かsubstanceが0.5未満なら漏れとする。基準は2026-09-28〜30の実際の44ターンで決めた(ドリリー・ハランの漏れを拾い、誤判定は1件)。

漏れていれば、BellTeamがそのBotへ「直前のターンの内容を、オーナーへ自分の言葉で伝えて」と頼む。返事はそのBotの会話画面に出る。この依頼で始まったターンは確かめない。Jevが失敗した時は頼まず、理由をログへ出す。

Botはオーナーに裁定や選択を求める時、MCPのask_owner(question, options, allowOther)で選択肢のカードをオーナーとの会話画面へ出せる(src/owner-questions.mjs)。Web・iPhone・Macで選択肢を押すか、allowOtherなら文章で答える。答えは「あなたが選択肢で尋ねた「…」への回答です。」としてオーナーからのメッセージでBotへ届く。Botは答えを待たずにターンを終える。答えたカードには二度と答えられない。カードの作成はMCPからの内部APIだけ、回答は公開APIだけで受ける。オーナーの判断・承認・操作が必要な時は、ask_ownerやチャットに加えて、Approval Boxのrequest_decisionでも申請するよう製品規範とMCPの説明文で勧める(禁止の形にはしない。オーナー裁定 K-9KK8NL・K-D3RVZD)。

チャット画像

Web・iPhone・iPad・Macでユーザーが選択、ドロップ、または貼り付けた画像は、本文と同じ送信で対象Botへ渡す。1回の送信に何枚でも付けられ、Web APIはimages(配列)で受ける。1枚のimageだけを送る古いアプリの送信も受ける。本文を含む送信の上限は96 MiB(Cloudflareが受ける本文の上限100MBに収める)。iPhone・iPad・Macの入力欄は共通のUITextViewで、コピーした画像はその画像を、コピーした画像ファイル(Finderなど)はファイル本体を添付にする。画像以外のファイルは添付しない。会話ログには画像の有無、枚数(image_count)、保存した会話資産の参照(image_assets)を残す。BellTeamは画像を一時ファイルへ置き、そのパスの並びをAitermのimage引数へ渡すだけにする。CLIごとの添付手順(入力欄へのパスの先打鍵等)はAitermが吸収し、BellTeamはharness別の画像手順を持たない。会話表示用の画像本体は、Botへ渡す一時ファイルとは別に会話資産として保存する。一時ファイルは送信元Botのターン中だけ維持し、そのBotがsendmessageで別のメンバーへ画像を転送できるようにする。送信元ターンの完了後に削除する。

MCPのimageはコンテナ内の画像ファイルパスを受け取る。Botがユーザーまたはルームへ提示した画像は、実体を/srv/bellteam/bots/<bot-id>/messages/assets/へ複製し、会話ログには公開元の一時パスを残さず資産IDだけを保存する。Web APIは認証後にその画像を配信し、Web UIは会話内へ実画像として表示する。オーナーが直接会話やルームへ送った画像は、全部を/srv/bellteam/owner/messages/assets/<送信ID>-<番号>.<拡張子>へ残し、ログのimage_assetsに並べる(Botへ渡す一時ファイルはターン後に消えるため)。APIは発言ごとに全画像のimage_urlsと、1枚しか読まない古いアプリ向けに1枚目のimage_urlを返す。Web・Mac・iPhone・iPadの吹き出しは、2枚以上の画像を本文の吹き出しの外へ置き、先頭1枚と、横と下へ傾けてずらした奥の最大2枚で扇状のカード束にし、下へ「画像N枚」と表示する。押すと黒い全画面で画像を大きく表示し、下のサムネイル、左右スワイプ、左右ボタンで切り替え、上の枚数表示と閉じるボタンを使う。1枚は単独表示から同じ全画面を開き、サムネイルを出さない。Mac・iPhone・iPadは共通の画面でimage_urlsを読み、全画像を認証付きで取得する。その項目が無い発言は従来のimage_urlを使う。保存されていない過去の画像は有無と枚数の表示を保つ。PNG、JPEG、WebP、GIFを受け付け、画像でないファイル、symlink、25 MiBを超えるファイルは明示エラーにする。

相対パスは最初にBotプロジェクトから解決する。Grokがimages/<name>を返し、Botプロジェクトに実体がない場合は、そのBotプロジェクトに対応する最新のGrok session保存先からBellTeamが解決する。CLI固有の保存場所をBotへ推測させない。

Botとプロジェクト

一つのBotは一つのプロジェクトフォルダ、ユーザーへ見せない固定Bot ID、最後に配送確認できたAitermセッションIDを持つ。一つのプロジェクトへ複数Botを置かず、Botごとの記憶と人格を同じ会話履歴へ積み重ねる。

直接メッセージ、ルーム配送、ユーザー会話、予定実行は、bot.jsonにある最後に確認されたセッションIDへ最初に直接送る。Aitermが不達を返した時だけ、同じBotプロジェクトで新しいセッションを起動する。Aitermが返した実際のセッションIDをbot.jsonへ保存してから、同じメッセージを一度だけ再送する。二度目も不達ならそのエラーを返し、追加の起動や再送を行わない。通常配送の前にセッション一覧を確認せず、Botのセッションを常時起動しない。

BellTeamは5分おきにAitermのpty_observeで席を観測し、画面が変わらず入力待ち(idle/composer_ready)で、起動後のプロセス(activity.post_startup_process_count)と子の配送待ち(pending_child_deliveries)がともに0、BellTeamの配送も無い状態が30分以上続いたClaude Codeの席を閉じる(オーナー許可 2026-10-03 K-HSQQEX)。Approval Boxの答えを待つClaude Codeのhookも起動後のプロセスに含まれる。どちらかの数がnull・未提供の席は閉じない。旧版Aitermで起きた席は起動完了の控えが無いので、0.49.0へ更新しただけでは対象にならない。Codex・Grok・Cursorは席ごとの未決・保留申請を確認できるまで対象にしない。CLI内部のタイマー(Claude Codeの/loopなど)は観測できず、停止対象になるので、定期の仕事はBellTeamの予定を使う。閉じている途中の配送は閉じ終わるまで待ち、通常の不達復旧で起き直す。観測履歴はメモリだけに保持し、BellTeam再起動後は30分を数え直す。

Botのセッション再起動は、現在の処理が終わった後に専用Aitermセッションを閉じ、設定済みのCLI、モデル、エフォート、同じプロジェクトで起動し直し、Aitermが返したセッションIDを保存する。CLI変更とモデル・エフォートを既定へ戻す変更は、稼働中なら配送列の末尾で再起動し、停止中なら次のメッセージで新しい設定を使う。設定、会話ログ、長期記憶、RAGは永続領域へ保持する。ただし起動時に読み込ませるのは前セッションの短期記憶だけで、長期記憶とRAGは必要な時にMCPから検索する。

同じCLIで明示モデルまたはエフォートへ変更する時は、Aitermのagent_configureを配送列の末尾で実行し、同じセッションと会話文脈を保つ。モデルIDは変動する各CLIのlive catalogをAitermが検証し、別モデルへのフォールバックは行わない。

BellTeamは毎日03:00(Asia/Tokyo)に全Botを確認する。現在のAitermセッションが動作中で、処理中と待機中のメッセージがなく、Throughlineの完了ターンが20件以上あるBotだけを再起動する。処理中、待機中、停止中のBotはその夜の自動再起動を行わない。通常メッセージの送信時にはターン数を確認しない。手動再起動と、停止したセッションを次のメッセージで復元する処理は維持する。

設定生成とBot起動のホームはsrc/runtime-home.mjsで決める。BELLTEAM_HOMEがあればその絶対パス、未指定の開発実行ではリポジトリのruntime/homeを使う。DockerはBELLTEAM_HOME=/home/bellを指定して既存volumeを使う。実行ユーザーのHOMEを暗黙の書込先にしない。設定更新・MCP・Throughline・Aiterm・Botの復元と起動に同じHOMEとCLI設定先、XDGの設定・状態保存先を渡し、Botのenv.shもこれらの場所を変更しない。Aitermへは親環境と同値でもホーム関連変数を明示して渡す。

CLAUDE_CONFIG_DIRは継承せず、ClaudeはHOMEから既定の設定場所を読む。明示指定するとuser MCP設定の場所も変わるため、既存の/home/bell/.claude.jsonを維持する。設定場所を変えた環境でtmuxを起動していた場合は、そのtmuxを終了してからBellTeamを起動する。

RTK(コマンド出力を短くしてAIへ渡すCLI。担当ドリリー)はBellTeamコンテナに標準搭載する(2026-09-27 オーナー指示、インフラ整備の部屋)。本体はDockerfileで本家のinstall.sh(sha256照合つき)を使って/usr/local/bin/rtkへ入れる。~/.localはcursor-cliのvolumeに隠れるため使わない。版は固定しない。利用統計の送信はRTK_TELEMETRY_DISABLED=1で止める。hookはsrc/rtk-hooks.mjsがthroughline installの後に登録し、起動時とBotの変更時に繰り返しても項目を増やさない。Claudeは本家のrtk init -g --hook-only --auto-patch --no-trust-filters、Codexは~/.codex/hooks.jsonのPreToolUse末尾へrtk hook codex(matcher Bash)を足し、公式App Serverのhooks/listが示す鍵とhashで、そのhandlerだけをconfig/batchWriteで信頼する。Cursorは~/.cursor/hooks.jsonのpreToolUseへrtk hook cursor(matcher Shell)、Grokは~/.grok/hooks/rtk.jsonから同梱のintegrations/rtk/grok-hook.mjsを呼ぶ。本家のrtk initはCodex・CursorでグローバルAGENTS.mdへ追記し、BellTeamが生成し直すファイルとぶつかるため、Claude以外は使わない。登録できなかった時もBotは起動し、理由をログへ出す。登録の仕様と保守はドリリーのdocs/rtk-bellteam-registration.mdが正本で、Grokのhookスクリプトはドリリーから受け取って更新する。

Aiterm(担当エレグ)の親配送hookはsrc/aiterm-hooks.mjsがRTKの後に登録する。AitermのMCPをBellTeamが登録するので、hookも同じ起動処理で登録する。hookが無いと、Claude Code・Cursorの席からのagent_launch・pty_sendがCLAUDE_PARENT_HOOK_UNAVAILABLEで拒まれる。同梱のaiterm-mcpが持つmergeClaudeParentHooks・mergeCursorParentHooksを呼び、~/.claude/settings.jsonと~/.cursor/hooks.jsonのAitermの項目だけを足す。登録済みなら何も書かない。本家のaiterm-setupは起こさない(aiterm-setup --hooks-onlyと同じ登録になる)。登録できなかった時もBotは起動し、理由をログへ出す。

Codexの席が親の時の配送hookは、Aiterm 0.56.0 から同じ起動処理で登録する。同梱のensureCodexParentSteerへ共有HOMEとコンテナのCodex(/usr/local/bin/codex)を渡す(aiterm-setup --codex-steer enableと同じ登録になる)。Aitermは~/.codex/hooks.jsonのPostToolUse・Stopの末尾へ自分の項目を足し、一時的に起こしたCodexの公式App Serverで承認を書き、~/.config/aiterm-mcp/codex-parent-hooks/config.jsonを置く。このhookが無いと、Codexの席が起こした子の回答と、Aitermの配送に乗る製品(Approval Box)の回答が、番の終わりまで届かない。BellTeamの文(pty_send)の届き方は変わらない。登録済みなら何も書かない。入口の無い古いAitermでは登録しない。結果がready以外の時(failed、席が動いている間に初めて登録した時のrestart_required)もBotは起動し、状態と理由をログへ出す。

AitermのMCP登録は、aiterm-setupが書くものと同じ形(nodeの絶対pathと同梱のdist/index.js)にする。aiterm-setupは、起動先が違う登録をCodex・Grokの公式CLIで作り直し、その時にBellTeamが書いたstartup_timeout_sec・tool_timeout_secが消える。Aiterm 0.47.0からは引数なしのaiterm-setupがBotの環境でも通るので、席が流しても登録が変わらないようにする。同梱のaiterm-mcpを読めない環境では、PATH上のaiterm-mcpを起動先にする。

Throughline(会話の記録と引き継ぎ。担当ナユタ)はBellTeamコンテナに初期搭載する基盤である(2026-10-01 オーナー裁定)。本体はDockerfileでthroughline@latestをnpmのglobalへ入れ、起動時とBotの変更時にthroughline installで4ハーネス(Claude・Codex・Grok・Cursor)のhookを登録する。登録に失敗した時はBellTeamを起動しない。短期記憶、ルーム文脈、記憶候補、夜間再起動のターン数に加え、Botのターンをオーナーの会話画面へ出す道にも使う。Claude Code・Grok・Cursorは、裏で動かした作業が終わると、BellTeamを通らずに自分から次のターンを始める。BellTeamは自分が配送したターンしか見ていなかったため、そのターンの報告がオーナーへ届かなかった(2026-10-01 ラプンツェル)。Stop hookが記録元のThroughlineは、どう始まったターンも記録するので、BellTeamはThroughlineのobserver-wait・observer-readで各Botの終わったターンを順に受け取り、BellTeamが配送していないターンの最後の回答をそのBotの会話画面へ出す(実装中。Throughlineの始まり方の表示・本文を削らない読み出し・hostの修正を待つ)。

mcp-lazy(MCPを使う時だけ本体を起こす中継。担当レオナ)はBellTeamコンテナに初期搭載する(2026-10-05 オーナー裁定)。本体はDockerfileで公開リポジトリkitepon/mcp-lazyの受入済みコミットからビルドし、/usr/local/bin/mcp-lazyへ置く。npmのmcp-lazyは別の製品なので使わない。版は受入した物へ固定する(0.3.2、ba0f443)。BellTeamが書くMCP登録のうち、Aitermは中継ごしにする(中継の実体がある環境だけ。無い環境では直結)。席がAitermの道具を呼ぶまで本体を起こさず、待機中の実占有を約45MBから約4MBにする。本体は一度起きたら席が終わるまで保つ(MCP_LAZY_IDLE_STOP=0)。本体が眠っている間の親配送の引き取りは、Aitermの判定(aiterm-delivery-wake、親がClaude・Codex・Cursorの時)を中継が30秒おきに回して起こす。記録(初期化と道具の一覧の控え)は~/.cache/mcp-lazy/aiterm-<CLI>に置く。bellteamとcall-bridgeは本体へHTTPで直結しているので中継を通さない。BellTeamが書かない登録を中継ごしにするかは、その道具を入れた設置が自分で決める。

Throughlineの会話の記録のDB(~/.throughline)は、この設置の物としてデータ側に置く(2026-10-05 オーナー裁定)。本番のcompose.yamlが./runtime/data/shared/throughlineをコンテナの/home/bell/.throughlineへ割り当てる。コンテナの中の道は変わらないので、Throughlineの設定とCLIのhookは変えない。割り当てはcreate_host_path: falseで、元のフォルダが無い時は起動が止まる。割り当ての行が無い設定で起こした時は止まらず、会話が記録されないまま動く。そのためruntime/home/.throughlineにはBotの利用者が書けない空のフォルダを残し、反映の後はmountに/home/bell/.throughlineの1行がある事とDBの大きさを確かめる。割り当てより前のcommitへ本番を戻す時は、先にフォルダをruntime/home/.throughlineへ戻す。配布物のdistribution/server/compose.yamlは割り当てを持たず、DBは/home/bell側に置かれる。

起動前に、共通のオーナー情報と共通規範を各CLIのグローバル指示へ生成する。正本は各CLIのグローバルAGENTS.mdだけとし、Claude用のグローバルCLAUDE.mdは@AGENTS.mdだけを記載する。BellTeamはThroughlineへBotのproject・silent・--sessions recentを渡す一回の呼び出しで、会話本文を持つ直近の複数sessionから作った短期記憶を取得する(オーナー裁定 2026-10-05。Throughline 0.15.0以上が要り、それより前の版では起動が止まる)。該当する会話がなければ空として起動する。Botの名前、プロフィール、性格(考え方)、口調、役職、役割と取得した短期記憶をBotプロジェクトのAGENTS.mdへ生成する。Claude用のCLAUDE.mdは@AGENTS.mdだけを記載する。起動時には会話プロンプトを渡さない。各CLIは通常のプロジェクト指示としてこれらを読み、その後に実際のメッセージだけを受け取る。

Bot削除はAitermセッションを閉じ、参加中のルームと予定宛先から外して、Botのプロジェクトフォルダを削除する。Web UIでは削除前に確認を求める。ルーム削除は設定画面から確認後に行い、ルームの設定、会話履歴、予定を保存領域ごと削除する。参加BotとBotの個別記憶は削除しない。iPhone・Macでは共通のルーム編集画面に削除操作を置き、成功後はルーム一覧、開いている会話、そのルームの下書きから外す。削除に失敗した時は理由を表示し、ルームと下書きを残す。

Bot作成時はユーザーがIDを指定しない。BellTeamがIDを自動生成し、プロジェクトフォルダ(docs/・environment/・assets/character-sheet/)、プロフィール、AGENTS.md、Claude用のCLAUDE.md、個人記憶領域を作る。作成だけではセッションを起動せず、初回挨拶などの偽の会話も作らない。最初の実メッセージが届いた時に通常の不達復旧経路で起動する。

グループチャットルーム

Botの返信(respond_to_room の reply)も新たなルーム発言として全員へ届く。返信先の自動選択が必要なら、その発言を含む直近3ターンを使って改めて判定する。

ルームは自動生成ID、ルーム名、目的、代表Bot、メンバー、予定、アバターを持つ。アバターはBotと同じ画像選択と正方形切り抜きで設定し、一覧とチャット上部に表示する(オーナー裁定 2026-09-04)。代表Botはルーム設定をMCPから変更でき、ユーザーはWeb UIから常に変更できる。既存Botの招待と退出は全BotがMCPから行え、対象Botを省略すると自分を対象にする。代表Botはメンバーだけを指定でき、代表が退室した時は未設定にする。

ルームへの通常メッセージは次のMCPツールを共通入口とする。

invite_to_room(roomId, botId?)
leave_room(roomId, botId?)
sendroommessage(room, message, image?, targets?)
respond_to_room(action, messageId?, message?, image?, targets?)

誰が発言しても、BellTeamはThroughlineのルーム文脈APIへ発言を記録し、余計な情報を除いた直近3ターンを得る。TypeSafeを設定して有効にし、手動指定がない時は、現在の発言とその前の発言を分け、部屋の名前と目的、メンバーの名前・役職・役割を添えてJevへ渡す。一度の要求で「直近3ターンの会話に対して次の発言が必要か」と「必要なら次に誰が発言するべきか」を判定させる。話題が続いているだけでは返信を要しない。メンバーへの呼びかけや挨拶、様子を尋ねる問いかけは返信を要する(2026-09-28 オーナー裁定。「テスト」「みんな元気?」に誰も返さなかったため)。受領・引き受け・お礼など次の返答を求めない発言は返信不要とし、継続確率が0.6以上の時だけ選ばれた一人を返信者にする。それ以外は返信者なしとする。Web UIとiPhoneアプリでメンバーを選んだ時やMCP・予定のtargets指定時は、その全員が返信者となり、Jevの会話継続判定より優先する。選択はルームごとに画面内だけで保持し、「@名前」の記法は使わない。

判定後、最新の発言を返信者か否かの情報とともに部屋の全メンバーへ届ける。返信者には通常のメッセージを渡し、ほかのメンバーには発言を把握してサイレントに処理を終える指示を渡す。Bot送信者自身も部屋の流れを同じように処理する。判定や配送が失敗した時はエラーとして記録・表示し、別の返信者へ切り替えない。各メンバーの配送状態は発言単位で記録する。

配送の対応付けに必要なルームID、発言ID、宛先IDはBellTeamが保持する。AIへ渡すルーム文脈は、参加者、ルーム名、目的、発言者、返信対象を確定するメッセージID、改変しない実メッセージ本文とする。直接メッセージには自然な宛名と実本文を渡す。

受信Botの通常の最終回答は内部で破棄し、ルームへ自動転送しない。返信者はrespond_to_room(action="reply")を呼び、返信者以外は内容を把握してrespond_to_room(action="silent")を呼ぶ。同じBotが複数のルーム発言を処理中なら、受信文に含まれるメッセージIDをmessageIdに指定して返信先を確定する。同じ返信者が同じ発言に再送した場合は投稿済みの発言IDを返し、重複投稿しない。投稿後に一部のメンバーへの配送が失敗した時は、投稿済みであることと失敗した宛先を返し、配送状態にも失敗を残す。silentは制御結果としてBellTeam内部だけで消費し、履歴へ残さない。特定の一人だけに必要な連絡はsendmessageで個別配送する。

Botの予定は実行内容としてprompt(AIへの指示)かcommand(AIを起こさないコマンド)のどちらか一方を持つ。commandはBotのプロジェクトフォルダでsh -cとして実行し、schedulerは完了を待たない。終了コードが0でない時だけ、そのBotからユーザーへの会話に失敗と出力の末尾を1件残す。成功は何も出さない。AIを起こす必要のない作業(スクリプト実行、サイトの書き出し、到達確認)はAIへの指示にせずコマンド予定にする(オーナー裁定 2026-09-04)。ルーム予定はAIへの指示だけを持つ。

ルーム予定は/srv/bellteam/rooms/<room-id>/schedule.json一ファイルへ保存し、返信者を自動選択するか特定メンバーを手動指定できる。予定実行は、代表Botがその時に発言したようには扱わず、代表Botが設定したルーム予定であることを明示する。設定済みの予定は一覧で指示本文まで表示し、「編集」で同じIDのまま名前・頻度・日時・指示を更新できる(PUT /api/{bots|rooms}/<id>/schedules/<scheduleId>)。「今すぐ実行」は同じ配達経路で予定を一回だけ送り、次回時刻と有効状態は動かさない(POST .../schedules/<scheduleId>/run)。

予定の頻度は一回・毎時・毎日・曜日指定・○日おき・○時間おき・cron式を持つ。Botとルームは同じ頻度設定と次回日時計算を使う。日おきはkind=interval_days、intervalDays(正の整数)、startDate(YYYY-MM-DD)、time(HH:mm)、timezoneを保存する。開始日から暦の日数で数え、月末・年末・夏時間をまたいでも指定時刻と周期を保つ。2日おきなら開始日・2日後・4日後に実行し、保存時点より後の最初の該当日時を次回にする。

時間おきはkind=interval_hours、intervalHours(1〜24)、startHour・endHour(0〜23)、minute(0〜59、既定0)、timezoneを保存する。毎日の開始時刻から間隔を足し、終了時刻以内の時刻に実行する。9時〜18時の3時間おきなら9・12・15・18時となる。終了時が開始時より小さい場合は翌日までとし、同じならその時刻に一回実行する。実行後は現在時刻より後の次回へ進め、編集時は保存済みの間隔・開始日・時間帯・タイムゾーンを引き継ぐ。Web APIとMCPも同じ保存形式を使う。

詳細な履歴や設定はMCPから必要な時に取得できる。

会話を開いた時は最新位置を表示し、上端へのスクロールで過去のページを取得する。初回と追加の取得件数は従来どおりとし、追加した本文の描画で高さが変わる間は取得前の読み位置を保つ。取得の失敗は表示し、上端を離れて再び戻るまで自動で取り直さない。