かなえ Discord Bot — C4 Architecture

LLM エージェント型 Discord Bot のソフトウェアアーキテクチャを、C4 モデル(Context → Container → Component)と動的ビュー・デプロイビューで整理したもの。

走査対象: main @ 2979f7c / 生成: 2026-10-10 / 公開版のため認証情報・ID・ホスト名・ポート・個人に関する情報は省略しています

概要

単一の Python asyncio プロセスが Discord とのやり取りからエージェント実行までを担い、その周りに共有 MCP デーモン・ベクトル DB・スケジューラ・Cloudflare 上のエッジ機能が配置される構成。

~76k本体コード行数(Python/TS、テスト・スクリプト除く)
~66kテストコード行数
2モデルプロバイダ経路(Claude 直 API / Codex app-server)
~20MCP サーバー(共有 HTTP + ターン毎 stdio)
1+4Mem0 の正本(Chroma)と派生索引(BM25 / Graph / Fact / History)
Person Software System Container / Store Component External

L1System Context

Bot を一つのシステムとして見たときの利用者と外部依存。admin と一般ユーザーは同じ Discord 経路から入るが、権限モデルで扱いが完全に分かれる。

メンション / slash command

メンション

イベント受信 / 返信投稿

推論・ツール呼び出し

検索 (MCP)

相場取得 (MCP)

アーカイブ・描画・アラーム・監視

SSH トンネル越しに検索

MCP (HTTP)

pull 型デプロイ

Owner / Admin
[Person]
全機能を利用。コード実行・調査・記憶の読み書き

一般ユーザー
[Person]
restricted モードで会話のみ

開発者の Claude Code CLI
[外部クライアント]
MCP 経由で同じ長期記憶を共有

かなえ Discord Bot
[Software System]
LLM エージェント。会話・ツール実行・長期記憶・定期ジョブ・投資シグナル通知

Discord
[Gateway / REST]

LLM プロバイダ
[External]
Anthropic (Claude) / OpenAI (Codex・GPT・Embeddings) / Google Gemini / DeepSeek

Web・ソーシャル検索
[External]
Brave / Tavily / Context7 / X / 論文検索

市場データ
[External]
国内株価・為替・指数・オーダーブック・予測市場

Cloudflare
[External Platform]
R2 / Workers / Browser Rendering / Tunnel

外部 VM
[External]
ベクトル知識検索サービス

GitHub
[External]
リポジトリ・Actions CI・リリース承認

SVG を別タブで開く ↗

L2Container

VPS 上のプロセスとデータストア、Cloudflare 側のエッジ機能。サービスごとに専用の Unix ユーザーで分離されている。

Cloudflare

Ubuntu VPS(4 vCPU / 8GB)— サービス毎に専用 Unix ユーザー

共有 MCP デーモン(streamable HTTP, localhost)

Bot プロセス群(同一コードベース)

Messages API (stream)

MCP HTTP

MCP HTTP

MCP HTTP

MCP HTTP

MCP HTTP

spawn / stdio

正本の読み書き

派生索引

表の描画

非同期アップロード

MCP HTTP

health probe

知識検索

MCP

Unix socket 経由で検索

Owner / Admin

Discord

Claude Code CLI
(開発マシン)

Discord Bot(本番)
[Container: Python asyncio / discord.py]
composition root・メッセージ処理・エージェント実行

Discord Bot(ステージング)
[Container]
dev リリースツリーで稼働

Codex 系 Bot(別 identity)
[Container: provider=codex]
codex app-server に委譲

mem0 MCP

second-opinion MCP
GPT / Gemini / DeepSeek

oracle MCP
supergateway で stdio→HTTP

x-scraper MCP

学習ノート vault MCP

ターン毎の stdio MCP
[子プロセス]
brave / playwright / 株価 / 指数 / FX / 予測市場 / 論文 / IoT 等

スケジューラ
[systemd timer / coroutine]
VI シグナル判定・為替介入スコア

運用フォワーダ
journal / MCP エラー / unit 状態 → Discord webhook

mem0 hook daemon
CLI のプロンプトへ記憶注入

ChromaDB サーバー
[Vector DB]
Mem0 の正本(単一コレクションを共有)

SQLite 群
Mem0 派生索引(グラフ / fact store / BM25、プロセス毎)/ 記憶ポリシー / work store

状態ディレクトリ
JSONL 会話ログ / セッション / turn outbox / Mem0 バッチ / BM25 索引 / R2 spool

LanceDB
投資ナレッジ KB

描画系
KaTeX+Puppeteer / Mermaid

cloudflared / SSH トンネル

LLM プロバイダ

検索・市場データ API

R2
会話・状態・画像アーカイブ

Archive Worker
prefix 限定・Bearer 認証

Alarm Worker
リモート MCP

死活監視 Worker

Browser Rendering
表の PNG 化

外部 VM
知識ベクトル検索(読み取り専用)

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 HTTPMem0 の現行の正本。単一のコレクションを本番 Bot と mem0 MCP が共有して読み書きする。マルチプロセスからのファイル直接アクセスを避けるためサーバーモードで運用
SQLite 群SQLiteMem0 の派生索引(エンティティグラフ・fact store・BM25)、記憶ポリシー、work store。Mem0 の派生索引はプロセスごとの状態ディレクトリに置かれる。正本台帳 + outbox のスキーマも存在するが、現行運用では使われていない
状態ディレクトリJSON / JSONL(原子的書き込み)会話ログ、セッション対応、turn outbox、Mem0 バッチ、BM25 索引、R2 アップロード待ちの spool
LanceDB / 外部 VMLanceDB(組み込み)+ HTTP投資ナレッジ KB。重いベクトル検索は外部 VM の読み取り専用サービスへ SSH トンネル越しに委譲
スケジューラsystemd timer / 常駐 coroutineVI シグナル判定(営業日の引け後)、為替介入スコアリング(30 分毎)
運用フォワーダPython + Discord webhookjournal の WARNING 以上、MCP デーモンの例外、unit の状態遷移を Discord に転送
CloudflareR2, Workers, Browser Rendering, Tunnel会話・状態のアーカイブ、リモート MCP(アラーム)、表の PNG 化、外部からの死活監視

L3Component — Discord Bot プロセス

composition root(bot.py)がパイプラインの各ステージを組み立て、Provider 層を通してエージェントランタイムを呼ぶ。

Container: Discord Bot プロセス(Python asyncio)

Claude エージェントランタイム(services/claude_api)

Provider 層

メッセージパイプライン(bot_pipeline/*)

Discord インターフェース

deliberate

Discord

Composition Root
bot.py / main_async.py
on_message・ライフサイクル・PID ロック・graceful drain

Commands
bot_commands/*
slash / prefix: work・watch・memory・session・model・mcp・skills

Status UI
bot_ui.py
考え中→スピナー→最終編集

Admission
admin 判定・受付可否

Channel FIFO
チャンネル単位の直列化

Context
履歴・返信元・添付・記憶を並列取得

Prompt
trusted / untrusted ラップ・sentinel 除去

Model
provider 呼び出し・ストリーム配線

Publish
2000 字分割・数式/表/図の画像化

Record
JSONL 追記・Mem0 バッチ投入(admin のみ)

Turn Outbox
相関 ID 単位の耐久コミット記録

Provider Router
provider_router / providers/*
claude / codex 選択・restricted は claude 固定

Tier Router
chat / deliberate 判定

call_with_tools
entry.py
並列度制御・リトライ・モデルフォールバック

Tool Loop
stream → tool_use → gate → 実行 → tool_result

Permission Gate
ルール判定・restricted deny list・loop guard

MCP Client Pool
stdio / HTTP・セマンティック選択・deferred tool

Compaction
auto-compact・microcompact

熟考パイプライン
planner → executor → MAGI-lite → verifier

SecondOpinion
他社モデル並列相談

OAuth Manager
トークン更新・アカウント failover

Codex App-Server Session
services/codex_app_server
JSON-RPC over stdio・LRU スレッド管理

Session Registry
チャンネル↔セッション対応・JSONL 永続化

Memory Service
services/mem0・memory_policy
検索・取り込み・明示的な記憶制御

Work Runtime
services/work
耐久ジョブ・リマインダ・条件監視

Deep Research
planner → 並列 searcher → MAGI → 引用付きレポート

Ops
health server・構造化ログ・runtime state・R2 archive・スケジューラ

LLM プロバイダ

MCP サーバー群

Chroma / SQLite / 状態ファイル

SVG を別タブで開く ↗

コンポーネントとモジュールの対応

コンポーネント主なモジュール責務
Composition Rootmain_async.py bot.py services/runtime_bootstrap.py設定検証、PID ロック、状態の復元、health server と Discord クライアントの起動、SIGTERM での drain
Admission / FIFObot_pipeline/admission.py fifo.py送信者の権限確定と受付判定。チャンネル単位でターンを直列化(キャンセル安全)
Contextbot_preprocess.py bot_pipeline/context.py utils/context_gateway.py utils/attachment_*返信元・履歴・添付・記憶・KB を並列に取得。添付は出所と信頼レベル付きで保持
Promptbot_pipeline/prompt.py services/prompt_builder.py services/sentinel.pytrusted / untrusted のタグで信頼境界を明示し、入力中の境界マーカーを除去
Modelbot_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.pyMessages API のストリームを自前のツールループで回す。並列度制御、リトライ、モデルフォールバック
Permission Gatepermissions.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 つの視点での評価 → 根拠との照合を行う
Compactioncompaction.py microcompact.py distill.py長い履歴の要約、時間が空いた後の古いツール結果の圧縮、大きなツール出力の蒸留
OAuth Managerservices/oauth_manager.pyトークンの更新と、複数アカウント間の failover
Codex Sessionservices/codex_app_server/*Discord の会話と Codex スレッドの対応を永続化し、JSON-RPC 風の stdio プロトコルで通信
Session Registryservices/session.py session_registry.pyチャンネルとセッションの対応、追記専用の JSONL 会話ログ
Publish / 描画bot_pipeline/publish.py utils/markdown_parser.py math_renderer.py table_renderer.py2000 字境界での意味単位の分割、数式(KaTeX)・表・Mermaid の画像化
Record / Outboxbot_pipeline/record.py bot_record.py turn_outbox.py相関 ID 単位でコミットを記録し、admin のターンだけを Mem0 バッチに積む
Work / Researchservices/work/* services/deep_research/*SQLite を正本とする耐久ジョブとリマインダ、専用スレッドで実行する多段の Web 調査
Opsservices/health*.py structured_log.py r2_archive.py *_scheduler.pyliveness / readiness、構造化ログ、R2 への非同期アーカイブ、定期ジョブ

L3Component — 長期記憶(Mem0 拡張)

現行は legacy モードで運用しており、Chroma のコレクションが記憶の正本。BM25・エンティティグラフ・fact store は書き込み時に追従更新する派生索引で、失敗しても正本は残る。コードには SQLite の正本台帳 + transactional outbox から全ストアを投影する canonical モードが実装されているが、本番 Bot が使う Mem0α 実装はこのモードと併用できない設計のため無効になっている。

派生索引は各プロセスの状態ディレクトリに置かれるため、インスタンス間で中身が揃う保証はない。正本(Chroma)だけが共有されている。

保守(Maintenance)

検索(Search)

正本と派生索引(現行: legacy モード)

取り込み(Ingestion)

派生索引(プロセス毎の状態ディレクトリ)

新規

重複

canonical モード時のみ outbox で投影

書き込み後に追従更新(失敗は非致命)

Batch Accumulator
チャンネル毎に 10 ターンで flush

Extract & Classify
Haiku でゲート・事実抽出
category / importance / tier 付与

Dedup / Conflict
近似重複判定・矛盾解決・supersede

Reinforce
重複は mention_count を加算(Hebbian)

Chroma コレクション
[正本]
text-embedding-3-small
本番 Bot と mem0 MCP で共有

BM25 Index
固有名詞の取りこぼし防止

Entity Graph
Obsidian 風 wikilink

Fact Store
attribute 付き fact・有効期間

History Index
生の会話ログ BM25

Canonical Ledger
SQLite + transactional outbox
実装済み・未有効(canonical モード用)

Plain
ベクトル検索(既定)

Smart
Haiku でクエリ書き換え→並列検索→LLM rerank

RRF Fusion
ベクトル + BM25 + グラフ

Memory Policy
namespace・可視範囲を絞る(付与はしない)

Decay
tier を鮮度で減衰

Replay
会話ログ→事実への睡眠的統合

Weekly Compact
cron・レポート出力

Record ステージ

Context ステージ

SVG を別タブで開く ↗

取り込み

ターンをチャンネル毎に貯めてまとめて抽出し、LLM 呼び出しの回数を減らす。軽量モデルで「残す価値があるか」を判定し、category / importance / tier を付与。重複は捨てずに参照回数を加算する。

検索

既定はプレーンなベクトル検索。明示的に思い出す依頼のときだけ、クエリの書き換え・並列検索・LLM による並べ替えを行う。固有名詞の取りこぼしは BM25 と RRF で補う。

忘却と統合

tier は取り込み時に一度付けて終わりではなく、鮮度で減衰する。生の会話ログから事実への統合(replay)と、週次の compaction を cron で実行。

汚染対策

記憶への書き込みは admin のターンに限定。非 admin の発言から永続的な「事実」が植え付けられる攻撃を防ぐ。Memory Policy は見える範囲を絞るだけで、権限を広げることはない。

Dynamic1 メッセージの処理フロー

メンションを受けてから返信と記録が終わるまで。コンテキストの取得は並列化し、記憶の書き込みは返信の後に非同期で行う。

LLMMCP / ToolsProvider / Tool LoopMemoryBot (pipeline)Discordpar[並列コンテキスト取得]loop[終了条件まで]opt[deliberate tier]Adminメンション + 本文 / 添付1MESSAGE_CREATE2Admission(admin判定)→ Channel FIFO3返信元・直近履歴4記憶検索(初回ターンのみ)5添付 DL・検証6Prompt組み立て(trusted /untrusted ラップ)7ステータス表示「考え中」8call_with_tools(tier, restricted)9Messages API(stream)10text / tool_use11PermissionGate・Loop Guard12ツール実行13tool_result(大きい結果は蒸留して-保存)14verifier が根拠と照合15最終テキスト + セッション情報162000 字分割・数式 / 表/ 図の画像化17返信投稿・ステータス削除18JSONL 追記・turnoutbox コミット19Mem0 バッチへ投入(非同期)20
SVG を別タブで開く ↗

Deployリリースとデプロイ

開発 worktree はデプロイの入力にならない。レビュー済みのコミット範囲だけが、secret を持たない専用 checkout を経由してリリースツリーに入る。

VPS

GitHub

pull 型

PID 1 が UID 切替前に読む

開発 worktree
feature ブランチ

Pull Request

CI
lint(shellcheck)/ test / security(定期)/ offload(Worker ビルド)

trusted-release
手動 dispatch・レビュー済み範囲検証
unit / characterization / integration / offline-eval

deploy ユーザー
secret を持たない専用 checkout

releases/{prod,dev}/current
原子的 symlink 切替・リリース毎 venv

root 所有の credential
サービス毎の EnvironmentFile(0600)

systemd units
サービス毎の専用 UID・dotenv 無効化

rollback / snapshot helper
root 所有

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)によって有効になる経路は変わります。