Slack MCP Agent — システムアーキテクチャ¶
v2.1 / 2026-05-09
MVP のシステム構成を定義する。
先行プロジェクト caster-division-ai の思想(Custom Tool パターン、pause/resume、監査ログ)を継承しつつ、
本プロジェクト固有の要件(SQLite / シングルプロセス / 4 月 MVP)に最適化する。
1. 設計原則¶
| # | 原則 | 説明 |
|---|---|---|
| A-1 | シングルプロセスで十分 | MVP はローカル実行 × 1 人運用。Backend・MCP Server・Web を個別プロセスで起動し、make dev で一括管理 |
| A-2 | Custom Tool ファースト | セキュリティが重要な HR データ操作は Custom Tool で Backend 内完結。MCP Server は Managed Agent 経由のツール提供に使用 |
| A-3 | 状態は SQLite に集約 | Backend と Web が同一 DB ファイルを参照。Backend が書き込み、Web は読み取り主体 |
| A-4 | Slack が主 UI、Web が副 UI | ユーザー体験は Slack で完結。Web は管理・監視・設定用 |
| A-5 | マルチテナント対応を構造で準備 | 全テーブルに tenant_id。MVP はシングルテナントだがスキーマレベルで将来に備える |
| A-6 | caster-division-ai の継承 | Custom Tool パターン、承認フロー、監査ログ、DashShell レイアウトを踏襲 |
| A-7 | 二系統 Slack Bot | 既存 MCP Agent Bot(業務パイプライン直結)と会話型 Bot(先に会話・計画提示・承認後に実行)を並存。同一 Backend プロセスで動作し、DB・Custom Tool Executor を共有する |
2. システム構成図¶
flowchart TD
subgraph user ["User / PM"]
Slack["Slack\n(Channel / Agent Container)"]
WebUI["Web 管理画面"]
end
subgraph backend ["Backend (Python)"]
ExistingBolt["MCP Agent Bot\n(既存 Slack Bolt App)\n- app_mention → L1-L4直結\n- actions"]
ConvBolt["会話型 Bot\n(別 Slack App)\n- app_mention → Conversation Layer\n- plan 承認 actions"]
ConvLayer["Conversation Layer\n- LLM planner: intent/reply/plan/candidate_tools\n- plan承認 actions\n- approved_plan_context を L2 へ橋渡し"]
Pipeline["Pipeline\nL1: Msg → Task\nL2: Task → Prompt\nL3: Prompt → Steps\nL4: Steps → Exec"]
Routing["Routing Engine\nA/B/C rule decision\n- task_type\n- flag severity"]
CTE["Custom Tool Executor\n(freee API 直接)"]
HrSync["HR Employee Sync Job\nfreee employee_id × Slack UserID"]
ExistingBolt --> Pipeline --> Routing
ConvBolt --> ConvLayer
ConvLayer -->|"approved_plan_context"| Pipeline
Pipeline --> CTE
end
subgraph web ["Web (Next.js)"]
SC["Server Components → SQLite 読取\nServer Actions → SQLite 書込\n(settings, audit)"]
end
subgraph anthropic ["Anthropic Cloud"]
MAS["Managed Agent Session\n- L4 実行ランタイム\n- MCP Tool 呼出\n- tool_confirmation"]
MCP["Combined MCP Server\n(scripts/combined_mcp_server.py, port 8001)\nhr-freee 系モック + Google Sheets + Slack DM"]
MAS --> MCP
end
FreeeAPI["freee API\n(OAuth 2.0)"]
SlackAPI["Slack Web API\nusers.lookupByEmail"]
SQLite["SQLite DB\nagent.db"]
Slack -->|"Socket Mode"| ExistingBolt
Slack -->|"Socket Mode"| ConvBolt
WebUI -->|"HTTP"| SC
CTE --> FreeeAPI
HrSync --> FreeeAPI
HrSync --> SlackAPI
HrSync --> SQLite
SC --> SQLite
backend --> SQLite
MCP -->|"MCP (streamable-http)"| FreeeAPI
2.1 プロセス構成¶
MVP は開発容易性を優先し、MCP Server を 1 プロセスに統合(scripts/combined_mcp_server.py)して運用する。mcp-servers/hr-freee/ と mcp-servers/common/ のパッケージは単体起動・テスト用に残っているが、make dev では使用しない。
| プロセス | 言語 | ポート | 起動コマンド | 役割 |
|---|---|---|---|---|
| Backend | Python | 8000 (設定用、通常は Socket Mode のため外部公開不要) | python -m src.app (in backend/) |
MCP Agent Bot + 会話型 Bot + パイプライン + Custom Tool Executor + E2E control |
| Combined MCP Server | Python | 8001 | python scripts/combined_mcp_server.py |
HR モック + Google Sheets + Slack DM を単一プロセスで提供 |
| Web | Node.js | 3100 | pnpm dev (in web/) |
管理画面 (Next.js) |
| ngrok | - | - | ngrok http --domain=$NGROK_DOMAIN 8001 |
MCP Server を Anthropic Cloud に公開 |
補助(必要時のみ個別起動):
| プロセス | ポート | 用途 |
|---|---|---|
mcp-servers/hr-freee/server.py |
MCP_HR_FREEE_PORT (既定 8001) |
hr-freee 単体のユニットテスト・動作確認 |
mcp-servers/common/server.py |
MCP_COMMON_PORT (既定 8002) |
common 単体のユニットテスト・動作確認 |
2.1.1 会話型 Bot の追加設定¶
会話型 Bot は .env に以下の環境変数を追加することで有効になる。3 つすべて設定されている場合のみ起動する(ConversationSlackConfig.enabled)。設定がない場合は Backend 起動時にスキップされ、既存 MCP Agent Bot の動作に影響しない。
| 環境変数 | 説明 |
|---|---|
CONVERSATION_SLACK_BOT_TOKEN |
会話型 Bot 専用の Bot Token (xoxb-...) |
CONVERSATION_SLACK_APP_TOKEN |
会話型 Bot 専用の App-Level Token (xapp-...) |
CONVERSATION_SLACK_SIGNING_SECRET |
会話型 Bot 専用の Signing Secret |
Railway にデプロイする場合は、既存の SLACK_BOT_TOKEN / SLACK_APP_TOKEN / SLACK_SIGNING_SECRET はそのまま残し、上記 3 変数を追加する。
2.2 通信経路¶
| From | To | プロトコル | 説明 |
|---|---|---|---|
| Slack → Backend | WebSocket (Socket Mode) | Slack Events / Actions をリアルタイム受信 | |
| Backend → Slack | HTTPS | chat.postMessage, chat.update, views.open |
|
| Backend → Slack Web API | HTTPS | 人物マスタ同期時の users.lookupByEmail |
|
| Backend → Anthropic (L1-L3) | HTTPS | messages.create (Anthropic SDK) |
|
| Backend → Anthropic (L4) | HTTPS | beta.sessions.create/retrieve/events |
|
| Anthropic → MCP Server | HTTP (streamable-http) | Managed Agent がツール実行 (ngrok 経由、combined MCP の単一ポート) | |
| Backend → freee API | HTTPS | Custom Tool Executor 内で直接呼出。Lane B analysis tools もこの経路でfreee勤怠を取得する | |
| Backend Sync / Lane B/C Jobs → freee API | HTTPS | sync_hr_employees が従業員・employee group membership を取得し、Lane B jobs が勤怠を取得して hr_snapshots を作成。Lane A jobs は人物マスタと設定を使って通知対象を解決する |
|
| Backend Routing Engine → SQLite | ファイル I/O | A/B/C の最新区分を tasks.routing_tier、履歴を routing_decisions、監査を audit_logs に保存 |
|
| E2E Control → Backend | HTTPS → nginx → localhost | E2E_CONTROL_TOKEN で保護し、Railway E2E の Prompt/Process 承認を Slack Web クリックに依存せず実行 |
|
| Backend → SQLite | ファイル I/O | aiosqlite (WAL モード) |
|
| Web → SQLite | ファイル I/O | Server Components から直接読取 |
3. レイヤー構成¶
3.1 概要¶
flowchart TD
subgraph presentation ["Presentation Layer"]
P1["Slack (Block Kit cards / Agent Container)"]
P2["Web (Next.js Server Components)"]
end
subgraph application ["Application Layer"]
A1["Slack Event/Action Handlers"]
A2["Pipeline Orchestrator"]
A3["Web Server Actions"]
end
subgraph domain ["Domain Layer"]
D1["L1-L4 Pipeline (LLM 呼出 + 状態遷移)"]
D2["Custom Tool Executor (freee API)"]
D3["Approval Gate (承認/却下/再生成)"]
end
subgraph infrastructure ["Infrastructure Layer"]
I1["SQLite (aiosqlite / WAL)"]
I2["Anthropic SDK (Messages + Managed Agents beta)"]
I3["Slack SDK (slack-bolt)"]
I4["freee API Client (OAuth 2.0)"]
I5["MCP Servers (FastMCP / streamable-http)"]
end
presentation --> application --> domain --> infrastructure
3.2 各レイヤーの責務¶
| レイヤー | 責務 | 依存方向 |
|---|---|---|
| Presentation | Slack Block Kit JSON 構築、Web ページレンダリング | → Application |
| Application | ユーザーイベントの受信、パイプライン起動、状態更新の調整 | → Domain |
| Domain | ビジネスロジック(L1-L4、承認フロー、A/B/C ルーティング、ツール実行) | → Infrastructure |
| Infrastructure | 外部サービス接続、永続化、SDK ラッパー | (最下層) |
4. 4 層パイプライン詳細¶
4.1 全体フロー¶
flowchart TD
UserMsg["User Message"]
subgraph L1 ["L1: Message → Task"]
L1P["Claude messages.create (1-shot)\n→ Task JSON (title, desc, priority)\n→ DB: tasks INSERT\n→ Slack: Task Card 投稿"]
end
Routing["Rule Routing\n→ tasks.routing_tier\n→ routing_decisions\n→ audit_logs"]
FAQ{"taskType faq"}
FAQAnswer["FAQ Answer\n→ faq_system.md\n→ Slack FAQ Card"]
subgraph L2 ["L2: Task → Prompt"]
L2P["Claude messages.create (1-shot)\n→ 実行方針テキスト\n→ DB: prompts INSERT\n→ Slack: Prompt Card (承認待ち)"]
end
Gate1{"人間が承認 or 却下"}
L2Reject["却下 → 理由入力\n→ L2 再実行 (新バージョン)"]
subgraph L3 ["L3: Prompt → Process"]
L3P["Claude messages.create (1-shot)\n→ steps JSON (ツール呼出計画)\n→ DB: processes INSERT\n→ Slack: Process Card (承認待ち)"]
end
Gate2{"人間が承認 or 却下"}
L3Reject["却下 → 理由入力\n→ L3 再実行 (新バージョン)"]
subgraph L4 ["L4: Process → Execution"]
L4P["Managed Agent Session\n→ Custom Tool (freee) / MCP (common)\n→ DB: executions INSERT/UPDATE\n→ Slack: Execution Card (進捗更新)\n→ 完了/失敗/中止"]
end
UserMsg --> L1
L1 --> Routing --> FAQ
FAQ -->|"yes"| FAQAnswer
FAQ -->|"no"| L2
L2 --> Gate1
Gate1 -->|"却下"| L2Reject --> L2
Gate1 -->|"承認"| L3
L3 --> Gate2
Gate2 -->|"却下"| L3Reject --> L3
Gate2 -->|"承認"| L4
4.2 L1-L3: Messages API 呼出¶
| 層 | System Prompt | 入力 | 出力 | max_tokens |
|---|---|---|---|---|
| L1 | prompts/l1_system.md |
<user_input> ラップされたユーザーメッセージ |
Task JSON (title, description, priority, taskType) |
2048 |
| L2 | prompts/l2_system.md |
Task JSON + ツールカタログ | 実行方針テキスト (自然言語) | 8192 |
| L3 | prompts/l3_system.md |
承認済み Prompt + ツールスキーマ (JSON) | Process steps JSON (steps[]) |
8192 |
共通パターン:
- 全て anthropic.AsyncAnthropic.messages.create (1-shot)
- モデル: claude-sonnet-4-6 (環境変数 CLAUDE_MODEL で上書き可)
- System Prompt はファイルから読み込み
4.2.1 Rule-based A/B/C ルーティング¶
L1 の Task JSON は title, description, priority, taskType を維持し、orchestrator.handle_mention() が L1 完了直後に backend/src/routing/ の明示ルールを呼び出す。Phase 1 では taskType と、後続ジョブから渡される flag_kind / severity を入力に A/B/C を決定する。不明な入力は安全側の B(AI 補助 + 人レビュー)に倒す。
このルーティングは backend/src/tools/router.py の Custom Tool Executor ディスパッチとは別概念である。tools/router.py は L4 の tool_name → executor、routing/ は業務タスクの処理区分を担当する。
taskType: "faq" は社内ハウスルール FAQ の短絡フローである。Task と routing decision は通常通り保存するが、L2-L4 は生成せず、tenant settings の faq_knowledge_text と faq_system.md で一次回答を生成して FAQ カードを投稿する。ナレッジ未設定、不確実、法令・個別判断を含む場合は routing:C 人対応 に更新する。
4.3 L4: Managed Agent Session¶
Anthropic の Managed Agents API (beta) を使用。Agent のランタイムは Anthropic Cloud で実行され、ツール呼出は MCP Server 経由。
sequenceDiagram
participant B as Backend
participant A as Anthropic Cloud
participant M as MCP Server
B->>A: sessions.create(agent_id, env_id)
B->>A: events.send(user.message, steps)
A->>M: MCP tool call
M-->>A: tool result
B->>A: sessions.retrieve (polling)
A-->>B: status: processing / idle
Note over B,A: tool_confirmation が来た場合
B->>A: events.send(allow_once)
B->>A: sessions.events.list (完了確認)
Note over B: summary 抽出
| パラメータ | 値 | 説明 |
|---|---|---|
| ポーリング間隔 | 3 秒 | POLL_INTERVAL_SECONDS |
| 最大ポーリング回数 | 120 回 | MAX_POLL_ATTEMPTS (= 360 秒タイムアウト) |
| tool_confirmation | allow_once で自動承認 |
MVP では全ツール自動承認 |
4.4 Custom Tool Executor¶
freee API を扱う HR 3 ツール (list_employees, list_attendance, calculate_overtime) は Custom Tool Executor で Backend 内実行する。L4 ポーリングループが custom_tool_use イベントを検知し、Backend 内の Router → freee Adapter → freee API の順に処理する。
flowchart TD
MAS["Managed Agent Session"] -->|"custom_tool_use event"| Backend["Backend (polling で検知)"]
Backend --> CTE["Custom Tool Executor"]
CTE --> T1["list_employees\n→ freee GET /api/1/employees"]
CTE --> T2["list_attendance\n→ freee GET /api/1/employees/{id}/work_records"]
CTE --> T3["calculate_overtime\n→ list_attendance 結果を集計 (Backend 内計算)"]
T1 --> Result["events.send(tool_result)"]
T2 --> Result
T3 --> Result
Result -->|"Managed Agent Session 続行"| MAS
セキュリティ上の利点: - OAuth トークンが Anthropic Cloud に渡らない - ツール実行ログが Backend 内で完結 - Policy Engine (Post-MVP) で制御可能
4.5 HR Employee Sync Job¶
sync_hr_employees は Phase 0 の人物マスタ構築用 job。freee employees と employee_group_memberships を取得し、login_email をキーに Slack users.lookupByEmail で Slack UserID を解決して hr_employees に upsert する。
sequenceDiagram
participant J as sync_hr_employees
participant F as freee API
participant S as Slack Web API
participant DB as SQLite
J->>F: GET /companies/{company_id}/employees
F-->>J: employees
J->>F: GET /employee_group_memberships
F-->>J: employee_id + login_email
loop membership ごと
J->>S: users.lookupByEmail(login_email)
S-->>J: Slack UserID or users_not_found
J->>DB: hr_employees INSERT/UPDATE
end
MVP では make sync-hr-employees による手動実行を提供する。sync_hr_employees はスケジューラから呼び出せる job 関数として実装済みだが、自動 cron 登録は後続 issue で扱う。Slack 未登録・メール不一致の場合は slack_user_id = NULL で保存し、warning log を出して手動補正対象にする。
4.6 In-Process Scheduler¶
MVP では Backend プロセス内に APScheduler ベースの scheduler を持つ。Railway 単一コンテナ構成で Slack Bolt と同じプロセス内に起動し、日次 HR パイプラインの取得・保存・差分検出・フラグ付けの基盤を提供する。
SCHEDULER_DISABLED=true で scheduler 全体を停止できる。MOCK_MODE=true は scheduler 基盤を停止しない。将来の業務 job は MOCK_MODE の場合に外部 API 呼び出しを避ける。
hr_snapshots は日次取得データのスナップショットを保存し、hr_flags は差分検出や異常検知のフラグを保存する。Lane A 通知では hr_flags を idempotency marker としても使い、同日の重複送信を防ぐ。AWS EventBridge + Lambda + RDS/PostgreSQL への移行は Post-MVP の課題とする。
Lane A scheduler jobs:
attendance_closing_notification(締め日前営業日の本人リマインド)missing_clockin_detection(未打刻通知 + 連続閾値でエスカレーション)paid_leave_expiry_detection(60/30/10/7日前の段階通知)
Monthly report scheduler job:
monthly_hr_report_generation(毎月1日 08:00、Lane A/B 指標をmonthly_reportsdraft に保存し、monthly_report_channel_idに HR レビューカードを投稿)
monthly_reports は scheduler job または手動実行から再生成できる。同一 tenant_id + report_month は上書きし、Slack の approve_monthly_report action で delivered に更新する。
5. 承認フローアーキテクチャ¶
5.1 承認ゲート¶
stateDiagram-v2
[*] --> 生成中
生成中 --> 承認待ち
承認待ち --> 承認済み : 承認
承認待ち --> 却下 : 却下
却下 --> 生成中 : 理由入力 → 再生成
承認済み --> [*]
Prompt と Process の 2 箇所に同一パターンの承認ゲートを配置。
5.2 却下 → 再生成¶
- ユーザーが [却下] をクリック
views.openで理由入力モーダルを表示- 理由を DB に保存 (
rejection_reason,rejected_by) - 前バージョンの内容 + 却下理由を含めて L2/L3 を再実行
- 新バージョン (version + 1) を DB に INSERT
- Slack カードを新バージョンの承認待ちに更新
5.3 実行中止¶
- 実行中の Execution カードに [中止] ボタンを表示
- クリック → Managed Agent Session を停止 (
sessions.cancel) - Execution ステータスを
cancelledに更新 cancelled_by,cancelled_atを記録- Task ステータスを
cancelledに更新
5.4 再実行¶
- 失敗した Execution カードに [再実行] ボタンを表示
- クリック → 同じ Process steps で新しい L4 Session を開始
- 新しい Execution レコードを INSERT
- Task ステータスを
runningに戻す
6. 認証アーキテクチャ¶
6.1 Slack 認証¶
| 項目 | 方式 |
|---|---|
| アプリ配信 | Socket Mode (WebSocket) |
| トークン | SLACK_BOT_TOKEN (xoxb-), SLACK_APP_TOKEN (xapp-) |
| 署名検証 | SLACK_SIGNING_SECRET (Bolt が自動検証) |
| ユーザー識別 | Slack user ID (event.user / body.user.id) |
| メール突合 | users.lookupByEmail (人物マスタ同期時のみ) |
6.2 freee OAuth 2.0¶
sequenceDiagram
participant W as Web 管理画面
participant B as Backend/API
participant F as freee
W->>B: [接続] クリック
B->>F: authorize URL 生成
B-->>W: リダイレクト
W->>F: freee で認可
F-->>B: callback + auth code
B->>F: token exchange
F-->>B: access + refresh token
Note over B: DB 保存 (暗号化)
B-->>W: 接続完了リダイレクト
| 項目 | 方式 |
|---|---|
| トークン保存 | integrations テーブル (credentials JSON, 暗号化) |
| リフレッシュ | 401 応答時に自動リフレッシュ → リトライ |
| スコープ | employees:read, work_records:read |
| トークン管理 | Backend プロセス内のみ。Anthropic Cloud には渡さない |
6.3 Web 管理画面認証¶
| フェーズ | 方式 | 詳細 |
|---|---|---|
| MVP | HTTP Basic 認証 | Next.js Middleware で全ルートに適用。ADMIN_USER / ADMIN_PASSWORD 環境変数 |
| Post-MVP | Firebase Auth + Google SSO | @cast-er.com ドメイン制限。Caster 既存と同構成 |
6.4 Anthropic API¶
| 項目 | 方式 |
|---|---|
| 認証 | API Key (ANTHROPIC_API_KEY) |
| SDK | anthropic.AsyncAnthropic (Backend 内) |
| Managed Agent | agent_id + environment_id で Session を作成 |
7. 永続化アーキテクチャ¶
7.1 SQLite 設計方針¶
| 項目 | 方式 | 理由 |
|---|---|---|
| WAL モード | 有効 | 読み書き並行性。Web の読取と Backend の書込が同時可能 |
| ULID | 全テーブルの PK | 時系列ソート可能な分散 ID |
| JSON カラム | steps, results, config, credentials, metadata, details |
柔軟なスキーマ拡張 |
| FK 制約 | 有効 (PRAGMA foreign_keys = ON) |
データ整合性 |
| マイグレーション | バージョン管理 (SCHEMA_VERSION + _MIGRATIONS dict) |
インクリメンタル DDL |
7.2 テーブル一覧¶
| テーブル | 主要カラム | 用途 |
|---|---|---|
tenants |
id, slug, name, agent_id, vault_id | テナント管理 |
tasks |
id, tenant_id, title, status, channel, thread_ts | タスク (L1 出力) |
prompts |
id, task_id, version, content, status, rejection_reason | 実行方針 (L2 出力) |
processes |
id, task_id, version, steps (JSON), status, rejection_reason | ステップ計画 (L3 出力) |
executions |
id, task_id, process_id, session_id, status, results (JSON) | 実行記録 (L4) |
slack_messages |
id, task_id, card_type, channel, message_ts | Slack カード ↔ DB マッピング |
tool_permissions |
id, tenant_id, tool_name, permission, constraints (JSON) | ツール権限 (Post-MVP) |
audit_logs |
id, tenant_id, timestamp, actor_type, action, details (JSON) | 監査ログ |
settings |
id, tenant_id, config (JSON) | テナント設定 |
integrations |
id, tenant_id, provider, status, credentials (JSON) | 外部連携 |
hr_employees |
id, tenant_id, freee_employee_id, login_email, slack_user_id, status | freee 従業員 ID と Slack UserID の人物マスタ |
hr_snapshots |
id, tenant_id, target_date, snapshot_kind, payload_json | 日次 HR データのスナップショット |
hr_flags |
id, tenant_id, snapshot_id, target_date, flag_kind, severity, resolved_at | 差分検出・異常検知フラグ |
7.3 Backend ↔ Web の DB 共有¶
flowchart TD
BackendPy["Backend (Python, aiosqlite)\nINSERT/UPDATE\n(tasks, prompts, processes,\nexecutions, audit_logs)"]
WebNode["Web (Node.js, better-sqlite3)\nSELECT (全テーブル)\nUPDATE (settings)\nINSERT (audit_logs)"]
DB["agent.db (WAL)"]
BackendPy --> DB
WebNode --> DB
- Backend: 主要な書き込み担当 (パイプライン進行)
- Web: 読み取り主体 + 設定変更・監査ログ書込
- WAL モードにより同時アクセスが安全
8. MCP Server アーキテクチャ¶
8.1 MVP 構成¶
MCP Server は Managed Agent がツールを呼び出す際のインターフェース。Backend が直接呼び出すのではなく、Anthropic Cloud 経由で使用される。MVP では HR 系モックと出力系ツールを 1 プロセス(combined) に統合して運用する。
| サーバー | ポート | ツール | 備考 |
|---|---|---|---|
Combined MCP Server (scripts/combined_mcp_server.py) |
8001 | list_employees, list_attendance, calculate_overtime(モック), write_rows_to_google_sheet, send_slack_dm |
make dev で起動する本線。Google Sheets / Slack DM は GOOGLE_SERVICE_ACCOUNT_JSON / SLACK_BOT_TOKEN 設定時のみ実 API。HR 系は常にモックデータを返す(本番実行は Custom Tool Executor で行う) |
mcp-servers/hr-freee/ |
MCP_HR_FREEE_PORT |
HR モックツール | 単体テスト・動作確認用。make dev では未使用 |
mcp-servers/common/ |
MCP_COMMON_PORT |
write_rows_to_google_sheet, send_slack_dm |
単体テスト・動作確認用。make dev では未使用 |
8.2 ツール実行パスの使い分け (MVP)¶
MVP では freee HR ツールを Custom Tool Executor、出力系ツールを MCP Server で実行する。
| ツール | 実行パス | 理由 |
|---|---|---|
list_employees |
Custom Tool Executor (Backend 内) | freee OAuth トークン保護・個人情報 |
list_attendance |
Custom Tool Executor (Backend 内) | freee OAuth トークン保護・個人情報 |
calculate_overtime |
Custom Tool Executor (Backend 内) | freee OAuth トークン保護・個人情報 |
write_rows_to_google_sheet |
MCP Server common (Agent 経由) | 書込系・低リスク |
send_slack_dm |
MCP Server common (Agent 経由) | 書込系・低リスク |
| 判断基準 | Custom Tool Executor | MCP Server |
|---|---|---|
| 認証情報の扱い | Backend プロセス内で保持。Anthropic Cloud に渡さない | MCP Server プロセス内で保持 |
| 監査 | Backend ログ + audit_logs テーブル |
Agent イベントログ |
| 適用範囲 | freee API (HR データ) | Google Sheets, Slack DM |
8.3 ネットワーク要件¶
- MCP Server は localhost で起動
- Anthropic Cloud からのアクセスには ngrok トンネルが必要
setup-managed-agent.pyで Agent 定義に MCP Server URL を登録
9. 非同期処理アーキテクチャ¶
9.1 イベント駆動フロー¶
Slack Bolt は同期ハンドラ内で ack() を呼び、重い処理は asyncio.new_event_loop() で非同期実行。
def handle_approve_prompt(ack, body, client):
ack() # 3 秒以内に応答
async def async_handler():
# L3 実行 (10 秒程度)
# DB 更新
# Slack カード更新
loop = asyncio.new_event_loop()
loop.run_until_complete(async_handler())
9.2 L4 ポーリング¶
L4 は Managed Agent Session のステータスを 3 秒間隔でポーリング。
flowchart TD
Start["Backend"] --> Create["sessions.create()"]
Create --> Send["events.send(user.message)"]
Send --> Loop{"loop\n(max 120 回 = 360 秒)"}
Loop --> Retrieve["sessions.retrieve() → status"]
Retrieve --> CheckStatus{"status?"}
CheckStatus -->|"processing"| EventsList["sessions.events.list()"]
EventsList --> ToolConf{"tool_confirmation?"}
ToolConf -->|"Yes"| AllowOnce["events.send(allow_once)"] --> Loop
ToolConf -->|"No"| CustomTool{"custom_tool_use?"}
CustomTool -->|"Yes"| RunTool["handle_custom_tool()\n→ events.send(tool_result)"] --> Loop
CustomTool -->|"No"| Loop
CheckStatus -->|"idle"| Summary["break → summary 抽出"]
CheckStatus -->|"error"| ErrorHandle["break → エラー処理"]
注:
on_progressコールバックはrun_l4_session()のシグネチャに存在するが、MVP 実装ではポーリングループから呼び出されていない。Slack の Execution カードは L4 開始時と完了・失敗・中止の遷移タイミングでのみ更新される。ツール実行ごとのリアルタイム進捗更新は Post-MVP(docs/post-mvp-roadmap.md参照)。
9.3 中止処理¶
- ユーザーが [中止] → Backend が
sessions.cancel()を呼出 - 次回ポーリングで
cancelledステータスを検知 - ポーリングループ終了 → DB + Slack 更新
10. エラー境界¶
| 境界 | エラー種別 | 対処 |
|---|---|---|
| Slack → Backend | Socket Mode 切断 | 自動再接続 (Bolt 標準) |
| Backend → Anthropic (L1-L3) | API エラー / タイムアウト | リトライ (最大 3 回) → 失敗カード表示 |
| Backend → Anthropic (L4) | Session エラー | Execution ステータス failed → [再実行] ボタン |
| Backend → freee API | 401 | トークンリフレッシュ → リトライ |
| Backend → freee API | 429 | Retry-After に従いリトライ |
| Backend → freee API | 5xx | 最大 3 回 (exponential backoff) |
| Backend → SQLite | ロックエラー | WAL + BUSY_TIMEOUT で軽減 |
| Web → SQLite | 接続エラー | エラーページ表示 |
| MCP Server | ツール実行エラー | エラーテキストを Agent に返却 → Agent が判断 |
11. セキュリティ境界¶
flowchart TD
subgraph trust1 ["信頼境界 1: Backend プロセス"]
FreeeToken["freee OAuth Token\n(暗号化保存)"]
SlackToken["Slack Bot Token\nAnthropic API Key\n(環境変数)"]
Note1["認証情報は Backend 外に出ない\nLLM API に送信されるのは業務データのみ"]
end
subgraph trust2 ["信頼境界 2: Anthropic Cloud"]
ManagedAgent["Managed Agent Session"]
MCPExec["MCP Server 経由のツール実行"]
DataPolicy["データ保持なしポリシー (API)"]
end
subgraph trust3 ["信頼境界 3: MCP Server プロセス"]
Localhost["localhost 起動、ngrok 経由でのみ外部公開"]
ToolExec["ツール実行は MCP Server プロセス内で完結"]
end
trust1 --> trust2
trust2 --> trust3
LLM 入力のセキュリティ¶
- ユーザー入力は
<user_input>タグで隔離 (Prompt Injection 軽減) - HR 個人データ (氏名、勤怠) の LLM API 送信は許容
- マイナンバー等の重要個人情報は Post-MVP で対策
12. Post-MVP 拡張ポイント¶
| 領域 | MVP | Post-MVP |
|---|---|---|
| テナント | シングル (tenant_id はスキーマに存在) |
Multi-tenant Router + Tenant Config |
| 認証 | Basic 認証 | Firebase Auth + Google SSO |
| DB | SQLite | PostgreSQL (or Firestore) |
| Scheduler | APScheduler in Backend process | AWS EventBridge + Lambda / worker service |
| ツール | Custom Tool + MCP (3+2 ツール) | Shared Schema Layer + ベンダーアダプタ |
| デプロイ | Railway 単一コンテナ (nginx + Backend + MCP + Web) | Cloud Run / ECS (サービス分割) |
| 監視 | ログ出力のみ | Datadog / Cloud Logging |
| Skills | L4 システムプロンプト固定 | Managed Agents Skills (動的ローディング) |
| Policy Engine | 全ツール自動承認 | tool_permissions テーブルで制御 |
| Vault | 環境変数 + DB 暗号化 | Managed Agents Vault (テナント別) |
変更履歴¶
| 日付 | バージョン | 変更内容 |
|---|---|---|
| 2026-04-18 | v1.0 | 初版作成 |
| 2026-04-19 | v1.1 | freee ツール実行パスを Custom Tool Executor に一本化。MCP Server hr-freee を開発テスト用と明記 (issue #11) |
| 2026-04-20 | v1.2 | デプロイを「ローカル」から「Railway 単一コンテナ」に更新 (issue #31) |
| 2026-04-20 | v1.3 | 実装実態に同期: MCP を combined 構成に修正、Web ポートを 3100 に訂正、L2/L3 max_tokens を 8192 に訂正、on_progress が未配線である旨を明記、Custom Tool Executor のポーリング分岐を追加 |
| 2026-04-29 | v1.4 | In-process scheduler と hr_snapshots / hr_flags、Post-MVP の EventBridge 移行方針を追加 (issue #48) |
| 2026-04-29 | v1.5 | Rule-based A/B/C routing engine、tasks.routing_tier、routing_decisions の保存フローを追加 (issue #52) |
| 2026-05-05 | v1.6 | Lane B の E2E control、勤怠分析ジョブ、AIルーティング昇格を追加 |
| 2026-05-05 | v1.7 | Lane B analysis tools を L4 Custom Tool 経路へ追加 |
| 2026-05-06 | v1.8 | Lane A 通知ジョブ(#49/#50/#51)と idempotent marker 運用を追加 |
| 2026-05-08 | v1.9 | FAQ 短絡フローと月次 HR レポート draft / review / delivered フローを追加 |