xangiのアーキテクチャと設計思想について説明します。
xangiは「AI CLI(Claude Code / Codex CLI / Gemini CLI)やローカルLLM(Ollama等)をチャットプラットフォームから使えるようにするラッパー」です。
User → Chat (Discord/Slack) → xangi → AI CLI → Workspace
| レイヤー | 役割 | 実装 |
|---|---|---|
| Chat | ユーザーインターフェース | Discord.js, Slack Bolt |
| xangi | AI CLIの統合・制御 | index.ts, agent-runner.ts |
| AI CLI | 実際のAI処理 | Claude Code, Codex CLI, Gemini CLI, Local LLM |
| Workspace | ファイル・スキル | skills/, AGENTS.md |
メインのオーケストレーター。以下を統合:
- Discord/Slackクライアントの初期化
- メッセージ受信とルーティング
- AI CLIの呼び出し
- スケジューラーの管理
- コマンド処理(
!discord,!schedule等)
AI CLIを抽象化するインターフェース:
interface AgentRunner {
run(prompt: string, options?: RunOptions): Promise<RunResult>;
runStream(prompt: string, callbacks: StreamCallbacks, options?: RunOptions): Promise<RunResult>;
}xangiがAI CLIに注入するシステムプロンプトを管理:
- チャットプラットフォーム情報 — Discord/Slack経由の会話であることを伝える短い固定テキスト
- XANGI_COMMANDS.md —
prompts/XANGI_COMMANDS.mdからDiscord操作コマンド・スケジューラー等の仕様を読み込み
AGENTS.md / CHARACTER.md / USER.md 等のワークスペース設定は、各AI CLIの自動読み込み機能に委譲:
| CLI | 自動読み込みファイル | 注入方法 |
|---|---|---|
| Claude Code | CLAUDE.md |
--append-system-prompt(一回限り) |
| Codex CLI | AGENTS.md |
<system-context> タグで埋め込み |
| Gemini CLI | GEMINI.md |
CLI側で自動読み込み(xangi側の注入なし) |
| Local LLM | AGENTS.md, MEMORY.md |
システムプロンプトに直接埋め込み(CLAUDE.md は通常 AGENTS.md のシンボリックリンクのため除外) |
| ファイル | 対応CLI | 特徴 |
|---|---|---|
| claude-code.ts | Claude Code | ストリーミング対応、セッション管理 |
| persistent-runner.ts | Claude Code(常駐) | --input-format=stream-json で常駐プロセス化、キュー管理、サーキットブレーカー |
| codex-cli.ts | Codex CLI | OpenAI製、0.98.0対応、cancel対応 |
| gemini-cli.ts | Gemini CLI | Google製、セッション管理、ストリーミング対応 |
| local-llm/runner.ts | Local LLM | Ollama等のローカルLLMを直接呼び出し、ツール実行・ストリーミング対応 |
定期実行とリマインダーを管理:
┌─────────────────────────────────────────────────────┐
│ Scheduler │
├─────────────────────────────────────────────────────┤
│ - schedules: Schedule[] # スケジュールデータ │
│ - cronJobs: Map<id, CronJob> # 実行中のcronジョブ │
│ - senders: Map<platform, fn> # メッセージ送信関数 │
│ - agentRunners: Map<platform, fn> # AI実行関数 │
├─────────────────────────────────────────────────────┤
│ + add(schedule): Schedule │
│ + remove(id): boolean │
│ + toggle(id): Schedule │
│ + list(): Schedule[] │
│ + startAll(): void │
│ + stopAll(): void │
└─────────────────────────────────────────────────────┘
スケジュールの種類:
cron: cron式による定期実行once: 単発リマインダー(指定時刻に1回実行)
永続化:
- JSONファイル(
${DATA_DIR}/schedules.json) - ファイル変更を監視して自動リロード(debounce付き)
タイムゾーン:
- サーバーのシステムタイムゾーン(
TZ環境変数)に従う - Docker環境では
TZ=Asia/Tokyo等を設定推奨
ワークスペースの skills/ ディレクトリからスキルを読み込み、スラッシュコマンドとして登録。
skills/
├── my-skill/
│ ├── SKILL.md # スキル定義
│ └── scripts/ # 実行スクリプト
└── another-skill/
└── SKILL.md
1. ユーザーがメッセージ送信
↓
2. Discord/Slackクライアントが受信
↓
3. 権限チェック(allowedUsers)
↓
4. 特殊コマンド判定
- !discord → handleDiscordCommand()
- !schedule → handleScheduleMessage()
- /command → スラッシュコマンド処理
↓
5. AI CLIに転送(processPrompt)
↓
6. レスポンス処理
- ストリーミング表示
- ファイル添付抽出
- SYSTEM_COMMAND検出
- !discord / !schedule 検出・実行
↓
7. ユーザーに返信
1. cron/タイマーがトリガー
↓
2. Scheduler.executeSchedule()
↓
3. agentRunner(prompt, channelId)
- AI CLIでプロンプト実行
↓
4. sender(channelId, result)
- 結果をチャンネルに送信
↓
5. 単発の場合は自動削除
xangiは1人のユーザーが使う前提で設計されています:
- 認証は
ALLOWED_USERによる単純なID照合 - セッションはチャンネル単位で管理
- マルチテナント機能は意図的に省略
AI CLIの実装詳細を隠蔽し、交換可能に:
// 設定でバックエンドを切り替え
AGENT_BACKEND=claude-code # or codex or gemini or local-llm将来的に新しいAI CLIが登場しても、アダプターを追加するだけで対応可能。
AIが出力する特殊コマンドを検出して自動実行:
| コマンド | 動作 |
|---|---|
SYSTEM_COMMAND:restart |
プロセス再起動 |
!discord send ... |
Discordメッセージ送信 |
!schedule ... |
スケジュール操作 |
これにより、AIが自律的にシステムを操作可能。
| データ | 保存先 | 形式 |
|---|---|---|
| スケジュール | ${DATA_DIR}/schedules.json |
JSON |
| ランタイム設定 | ${WORKSPACE}/settings.json |
JSON |
| セッション | ${DATA_DIR}/sessions.json |
JSON(チャンネルID→セッションID) |
| トランスクリプト | logs/transcripts/YYYY-MM-DD/{channelId}.jsonl |
JSONL(送信プロンプト・応答・エラー) |
チャンネルごとのAI会話ログをJSONL形式で自動保存する機能。デバッグ・障害分析に使用。
ディレクトリ構成:
logs/transcripts/
2026-03-08/
1469606785672417383.jsonl # チャンネルごとのログ
1477591157423734785.jsonl
2026-03-09/
...
記録される内容:
prompt: ユーザーから送信されたプロンプト(タイムスタンプ・チャンネルトピック注入後)response: Claude Code の最終応答(result メッセージ)error: タイムアウト、API エラーなど
注意事項:
- ログは
.gitignoreで除外されている - 自動ローテーション(日付ごとにディレクトリ分割)
- ログ書き込み失敗は無視(本体の動作に影響させない)
src/
├── index.ts # エントリーポイント、Discord統合
├── slack.ts # Slack統合
├── agent-runner.ts # AI CLIインターフェース
├── base-runner.ts # システムプロンプト生成、XANGI_COMMANDS.md読み込み
├── claude-code.ts # Claude Codeアダプター(per-request)
├── persistent-runner.ts # Claude Codeアダプター(常駐プロセス)
├── codex-cli.ts # Codex CLIアダプター
├── gemini-cli.ts # Gemini CLIアダプター
├── local-llm/ # Local LLMアダプター
│ ├── runner.ts # メインランナー(セッション管理・ツール実行ループ)
│ ├── llm-client.ts # LLM APIクライアント(Ollama native + OpenAI互換)
│ ├── context.ts # ワークスペースコンテキスト読み込み
│ ├── tools.ts # ビルトインツール(exec/read/web_fetch)
│ └── types.ts # 型定義
├── scheduler.ts # スケジューラー
├── schedule-cli.ts # スケジューラーCLI
├── skills.ts # スキルローダー
├── config.ts # 設定読み込み
├── settings.ts # ランタイム設定
├── sessions.ts # セッション管理
├── file-utils.ts # ファイル操作ユーティリティ
├── process-manager.ts # プロセス管理
├── runner-manager.ts # 複数チャンネル同時処理(RunnerManager)
└── transcript-logger.ts # トランスクリプトログ
prompts/
└── XANGI_COMMANDS.md # xangi専用コマンド仕様(AI CLIに注入)
┌─────────────────────────────────────────┐
│ xangi container │
├─────────────────────────────────────────┤
│ - Node.js 22 │
│ - Claude Code CLI / Codex CLI / Gemini │
│ - GitHub CLI (gh) │
│ - (xangi-max) uv + Python 3.12 │
└─────────────────────────────────────────┘
│
├── /workspace (bind mount)
├── /home/node/.claude (volume)
├── /home/node/.codex (volume)
└── /home/node/.config/gh (volume)
- 非rootユーザー(node)で実行
- ワークスペースのみマウント
- 認証情報はvolumeで永続化
| 変数 | 説明 | 必須 |
|---|---|---|
DISCORD_TOKEN |
Discord Bot Token | ✅ |
DISCORD_ALLOWED_USER |
許可ユーザーID(1人のみ) | ✅ |
AUTO_REPLY_CHANNELS |
メンションなしで応答するチャンネルID(カンマ区切り) | - |
DISCORD_STREAMING |
ストリーミング出力(デフォルト: true) |
- |
DISCORD_SHOW_THINKING |
思考過程を表示(デフォルト: true) |
- |
INJECT_CHANNEL_TOPIC |
チャンネルトピックをプロンプトに注入(デフォルト: true) |
- |
INJECT_TIMESTAMP |
現在時刻をプロンプトに注入(デフォルト: true) |
- |
| 変数 | 説明 |
|---|---|
SLACK_BOT_TOKEN |
Slack Bot Token(xoxb-...) |
SLACK_APP_TOKEN |
Slack App Token(xapp-...)※Socket Mode用 |
SLACK_AUTO_REPLY_CHANNELS |
メンションなしで応答するチャンネルID(カンマ区切り) |
SLACK_ALLOWED_USER |
Slack用の許可ユーザーID |
SLACK_REPLY_IN_THREAD |
スレッド返信するか(デフォルト: true) |
SLACK_STREAMING |
ストリーミング出力(デフォルト: true) |
SLACK_SHOW_THINKING |
思考過程を表示(デフォルト: true) |
| 変数 | 説明 | デフォルト |
|---|---|---|
AGENT_BACKEND |
AI CLI(claude-code / codex / gemini / local-llm) |
claude-code |
AGENT_MODEL |
使用するモデル | - |
WORKSPACE_PATH |
作業ディレクトリ(ホストのパス) | - |
SKIP_PERMISSIONS |
デフォルトで許可スキップ | false |
TIMEOUT_MS |
タイムアウト(ミリ秒) | 300000 |
PERSISTENT_MODE |
常駐プロセスモード(高速応答) | true |
MAX_PROCESSES |
同時実行プロセス数の上限 | 10 |
IDLE_TIMEOUT_MS |
アイドルプロセスの自動終了時間(ミリ秒) | 1800000(30分) |
DATA_DIR |
データ保存ディレクトリ | /workspace/.xangi |
| 変数 | 説明 | デフォルト |
|---|---|---|
LOCAL_LLM_BASE_URL |
LLMサーバーURL(Ollama等) | http://localhost:11434 |
LOCAL_LLM_MODEL |
使用するモデル名 | - |
LOCAL_LLM_API_KEY |
APIキー(vLLM等で必要な場合) | - |
LOCAL_LLM_THINKING |
Thinkingモデルの推論を有効にするか | true |
LOCAL_LLM_MAX_TOKENS |
最大トークン数 | 8192 |
対応モデル例(Ollama):
nemotron-3-nano— NVIDIA Nemotron 3 Nano(24GB)軽量・高速nemotron-3-super— NVIDIA Nemotron 3 Super(86GB)高精度・エージェント向けqwen3.5:9b— Qwen 3.5 9B(9GB)Thinking対応- その他Ollamaで利用可能なモデル
API対応:
- Ollama native API(
/api/chat)—think:false対応、ストリーミング - OpenAI互換API(
/v1/chat/completions)— vLLM等にも対応
| 変数 | 説明 |
|---|---|
GH_TOKEN |
GitHub CLIトークン(gh auth tokenで取得) |
| ホスト | コンテナ | 説明 |
|---|---|---|
${WORKSPACE_PATH} |
/workspace |
作業ディレクトリ |
~/.gitconfig |
/home/node/.gitconfig |
Git設定 |
xangi_claude-data volume |
/home/node/.claude |
Claude認証 |
xangi_codex-data volume |
/home/node/.codex |
Codex認証 |
xangi_gh-data volume |
/home/node/.config/gh |
GitHub CLI認証 |
- クライアント初期化コードを追加
- メッセージハンドラを実装
scheduler.registerSender()で送信関数を登録scheduler.registerAgentRunner()でAI実行関数を登録
AgentRunnerインターフェースを実装config.tsにバックエンド設定を追加index.tsで初期化処理を追加
