概要
単一の Python asyncio プロセスが Discord とのやり取りからエージェント実行までを担い、その周りに共有 MCP デーモン・ベクトル DB・スケジューラ・Cloudflare 上のエッジ機能が配置される構成。
L1System Context
Bot を一つのシステムとして見たときの利用者と外部依存。admin と一般ユーザーは同じ Discord 経路から入るが、権限モデルで扱いが完全に分かれる。
SVG を別タブで開く ↗L2Container
VPS 上のプロセスとデータストア、Cloudflare 側のエッジ機能。サービスごとに専用の Unix ユーザーで分離されている。
SVG を別タブで開く ↗コンテナ一覧
| コンテナ | 技術 | 責務 |
|---|---|---|
| Discord Bot(本番 / ステージング) | Python 3 asyncio, discord.py, aiohttp | メッセージ受付からエージェント実行・返信まで。同じコードベースを prod / dev のリリースツリーで別プロセスとして実行 |
| Codex 系 Bot | 同コードベース + codex app-server | 別の Discord identity。provider を codex に固定し、ツールループを Codex 側に委ねる |
| 共有 MCP デーモン | MCP streamable HTTP(一部 supergateway で stdio を変換) | 起動コストの高い MCP(mem0 / second-opinion / oracle / x-scraper / 学習ノート vault)を常駐させ、Bot と CLI で共有 |
| ターン毎の stdio MCP | 子プロセス(npx / venv python) | Web 検索・ブラウザ操作・株価・指数・FX・予測市場・論文・IoT など。ターン単位で spawn、終了時に process group ごと回収 |
| ChromaDB サーバー | Chroma HTTP | Mem0 の現行の正本。単一のコレクションを本番 Bot と mem0 MCP が共有して読み書きする。マルチプロセスからのファイル直接アクセスを避けるためサーバーモードで運用 |
| SQLite 群 | SQLite | Mem0 の派生索引(エンティティグラフ・fact store・BM25)、記憶ポリシー、work store。Mem0 の派生索引はプロセスごとの状態ディレクトリに置かれる。正本台帳 + outbox のスキーマも存在するが、現行運用では使われていない |
| 状態ディレクトリ | JSON / JSONL(原子的書き込み) | 会話ログ、セッション対応、turn outbox、Mem0 バッチ、BM25 索引、R2 アップロード待ちの spool |
| LanceDB / 外部 VM | LanceDB(組み込み)+ HTTP | 投資ナレッジ KB。重いベクトル検索は外部 VM の読み取り専用サービスへ SSH トンネル越しに委譲 |
| スケジューラ | systemd timer / 常駐 coroutine | VI シグナル判定(営業日の引け後)、為替介入スコアリング(30 分毎) |
| 運用フォワーダ | Python + Discord webhook | journal の WARNING 以上、MCP デーモンの例外、unit の状態遷移を Discord に転送 |
| Cloudflare | R2, Workers, Browser Rendering, Tunnel | 会話・状態のアーカイブ、リモート MCP(アラーム)、表の PNG 化、外部からの死活監視 |
L3Component — Discord Bot プロセス
composition root(bot.py)がパイプラインの各ステージを組み立て、Provider 層を通してエージェントランタイムを呼ぶ。
コンポーネントとモジュールの対応
| コンポーネント | 主なモジュール | 責務 |
|---|---|---|
| Composition Root | main_async.py bot.py services/runtime_bootstrap.py | 設定検証、PID ロック、状態の復元、health server と Discord クライアントの起動、SIGTERM での drain |
| Admission / FIFO | bot_pipeline/admission.py fifo.py | 送信者の権限確定と受付判定。チャンネル単位でターンを直列化(キャンセル安全) |
| Context | bot_preprocess.py bot_pipeline/context.py utils/context_gateway.py utils/attachment_* | 返信元・履歴・添付・記憶・KB を並列に取得。添付は出所と信頼レベル付きで保持 |
| Prompt | bot_pipeline/prompt.py services/prompt_builder.py services/sentinel.py | trusted / untrusted のタグで信頼境界を明示し、入力中の境界マーカーを除去 |
| Model | bot_pipeline/model.py services/provider_router.py services/providers/* | provider の選択(restricted は常に Claude)とストリーミング callback の配線 |
| Claude ランタイム | services/claude_api/entry.py tool_loop.py tools.py mcp_client.py | Messages API のストリームを自前のツールループで回す。並列度制御、リトライ、モデルフォールバック |
| Permission Gate | permissions.py restricted.py loop_guard.py | ルールベースの許可判定、非 admin 向け deny list、同一呼び出しの堂々巡り検知と予算制御 |
| MCP 選択 | services/mcp_selector.py mcp_semantic_selector.py mcp_control.py | キーワード一致と埋め込み類似度で、そのターンに使う MCP を選ぶ。それ以外は deferred tool として後から呼び出し可能 |
| 熟考パイプライン | router.py planner.py magi_lite.py verifier.py evidence.py | 依頼型の発話を deliberate tier に振り分け、計画 → 実行 → 3 つの視点での評価 → 根拠との照合を行う |
| Compaction | compaction.py microcompact.py distill.py | 長い履歴の要約、時間が空いた後の古いツール結果の圧縮、大きなツール出力の蒸留 |
| OAuth Manager | services/oauth_manager.py | トークンの更新と、複数アカウント間の failover |
| Codex Session | services/codex_app_server/* | Discord の会話と Codex スレッドの対応を永続化し、JSON-RPC 風の stdio プロトコルで通信 |
| Session Registry | services/session.py session_registry.py | チャンネルとセッションの対応、追記専用の JSONL 会話ログ |
| Publish / 描画 | bot_pipeline/publish.py utils/markdown_parser.py math_renderer.py table_renderer.py | 2000 字境界での意味単位の分割、数式(KaTeX)・表・Mermaid の画像化 |
| Record / Outbox | bot_pipeline/record.py bot_record.py turn_outbox.py | 相関 ID 単位でコミットを記録し、admin のターンだけを Mem0 バッチに積む |
| Work / Research | services/work/* services/deep_research/* | SQLite を正本とする耐久ジョブとリマインダ、専用スレッドで実行する多段の Web 調査 |
| Ops | services/health*.py structured_log.py r2_archive.py *_scheduler.py | liveness / readiness、構造化ログ、R2 への非同期アーカイブ、定期ジョブ |
L3Component — 長期記憶(Mem0 拡張)
現行は legacy モードで運用しており、Chroma のコレクションが記憶の正本。BM25・エンティティグラフ・fact store は書き込み時に追従更新する派生索引で、失敗しても正本は残る。コードには SQLite の正本台帳 + transactional outbox から全ストアを投影する canonical モードが実装されているが、本番 Bot が使う Mem0α 実装はこのモードと併用できない設計のため無効になっている。
派生索引は各プロセスの状態ディレクトリに置かれるため、インスタンス間で中身が揃う保証はない。正本(Chroma)だけが共有されている。
SVG を別タブで開く ↗取り込み
ターンをチャンネル毎に貯めてまとめて抽出し、LLM 呼び出しの回数を減らす。軽量モデルで「残す価値があるか」を判定し、category / importance / tier を付与。重複は捨てずに参照回数を加算する。
検索
既定はプレーンなベクトル検索。明示的に思い出す依頼のときだけ、クエリの書き換え・並列検索・LLM による並べ替えを行う。固有名詞の取りこぼしは BM25 と RRF で補う。
忘却と統合
tier は取り込み時に一度付けて終わりではなく、鮮度で減衰する。生の会話ログから事実への統合(replay)と、週次の compaction を cron で実行。
汚染対策
記憶への書き込みは admin のターンに限定。非 admin の発言から永続的な「事実」が植え付けられる攻撃を防ぐ。Memory Policy は見える範囲を絞るだけで、権限を広げることはない。
Dynamic1 メッセージの処理フロー
メンションを受けてから返信と記録が終わるまで。コンテキストの取得は並列化し、記憶の書き込みは返信の後に非同期で行う。
SVG を別タブで開く ↗Deployリリースとデプロイ
開発 worktree はデプロイの入力にならない。レビュー済みのコミット範囲だけが、secret を持たない専用 checkout を経由してリリースツリーに入る。
SVG を別タブで開く ↗設計判断の要点
信頼境界を prompt の構造で表す
- admin / 非 admin の入力をタグで分け、非 admin の文面は「データ」として扱う
- 入力に紛れた境界マーカーは sentinel で除去
- restricted モードは provider を Claude に固定し、ツールを deny list で二重に制限
壊れ方を先に設計する
- turn outbox で各ターンのコミットを耐久化
- R2 アーカイブはローカル spool を経由し、Cloudflare 障害が返信を止めない
- verifier 自体が失敗したら、品質低下の印を付けて通す(fail-open)
正本と派生を分ける
- work store は SQLite の正本を先に commit し、その後で派生を更新
- Mem0 は現行 legacy モードで Chroma が正本。BM25・グラフ・fact store は正本から作り直せる派生索引として扱う
- SQLite 台帳を正本にする canonical モードは実装済み・未有効
- モデルのセッションは使い捨てとして扱う
プロバイダの冗長化
- Claude 直 API と Codex app-server の 2 経路
- トークン予算の枯渇や拒否時は別モデルにフォールバック
- OAuth アカウント間の failover
プロセスと権限の分離
- サービス毎に専用 UID、credential は root 所有の EnvironmentFile
- リポジトリの dotenv は本番で読まない
- MCP 子プロセスは process group 単位で確実に回収
ツールの段階的な提示
- MCP は意味的に関連するものだけを最初からモデルに見せ、残りは deferred にする
- ループ回数の上限ではなく、反復検知と予算でループを止める
このページはコードの静的走査から作った要約です。実行時の設定(環境変数・feature flag)によって有効になる経路は変わります。