DESIGN DOC · 2026-05-13

Issue 自動分解 → Task Tree → Slack 承認 → Calendar 連携

LINE で受け取った 主題 (Issue) を AI が依存関係付きの Task Tree に分解、 Slack で承認、 会議系 task は Google Calendar に自動予約、 LINE group に reply まで完結する設計。 manager の「LINE 一文 → タスク化までの所要時間」を 10 分から 30 秒に。

最終更新: 2026-05-13 / 関連: Slack 連携 (3 Phase) / LINE push (双方向化)

1. 解こうとしている問題

現在の運用フロー:

  1. LINE で 「増田が税理士を気に入っていない」 などの主題 (context) が入る
  2. manager がコメントを付ける
  3. コメントの中に subtask を手で分解 (「増田と話す」 「新しい税理士をタスク」 「XXX」)
  4. 誰が担当か、 関係者は誰か、 会議は必要か、 を都度判断

問題:

  • 主題と task が同じレイヤーで扱われる → 主題が解決したのか、 個別 task が完了したのか曖昧
  • 依存関係が消える → 「税理士候補を出してから増田に紹介」 という順序が manager の頭の中だけ
  • 関係者の合意形成が分断 → 「増田」 「高野院長」 「経理担当」 が同じ画面で進捗を見られない
  • 会議の調整に時間がかかる → 「話す」 task で全員のカレンダー突き合わせを手作業

2. 中核概念 — Issue / Task / Subtask の階層

3 レイヤーで階層化する。

レイヤー定義誰が完了判定するか
Issue (主題) 解決すべき業務上の問題 / 状況。 1 つの Issue は 1 つの目的を持つ 「増田が税理士を気に入っていない」 executive (高野院長) / primary_owner
Task (タスク) Issue を解決するための具体的な行動単位。 依存関係で順序が決まる 「増田と意思確認の面談」 / 「新税理士候補リスト作成」 / 「既存税理士との解約交渉」 各 task の primary_owner
Subtask (サブタスク) 1 つの Task を実行するための小さなアクション。 通常 30 分以内で完了 「面談日程を 3 候補メールで送る」 / 「候補税理士 5 件リスト化」 / 「契約書を確認」 subtask 担当者本人
💡 設計ポイント

Issue は「閉じる (resolved)」 こと自体に意味がある。 個別 task が全部完了しても、 Issue が解決していないことがある (例: 増田が新税理士も気に入らない → 別 task tree が派生)。 → Issue を resolved にできるのは executive のみ。

3. 具体例 — 「増田が税理士を気に入っていない」

📥 LINE 入力 (manager grp)

「最近、 増田さんが税理士の田中先生のことを気に入ってないみたいで。 確定申告のやり取りで何度かトラブル。 新しい税理士を探したほうがいいかも」

AI が抽出する Issue + Task Tree

Issue: 増田が税理士 (田中先生) を気に入っていない ├─ topic_type: relationship + business ├─ primary_owner: 高野院長 ├─ stakeholders: 増田、 高野院長、 経理担当 (田中) └─ Tasks ├─ T1. 増田と現状確認の 1on1 面談 [type=meeting] [担当: 田中マネ / 関係者: 高野院長] │ └─ Subtask: 面談候補日 3 件を Google Calendar から提示 ├─ T2. 新税理士候補のリスト作成 (5 件) [type=investigation] [担当: 経理] [並列] │ ├─ Subtask: 業界紹介 3 件 │ └─ Subtask: 既存ネットワーク 2 件 ├─ T3. 候補税理士との面談 [type=meeting] [担当: 高野院長 + 経理] [depends: T2] │ └─ Subtask: 面談候補日設定 + invite ├─ T4. 増田に最終候補を紹介 [type=communication] [担当: 田中マネ] [depends: T1, T3] │ └─ Subtask: 紹介 1on1 設定 + 比較資料準備 └─ T5. 既存税理士との解約 + 引継ぎ [type=action] [担当: 経理] [depends: T4 (OK 出たら)] ├─ Subtask: 解約意思を 田中先生 に書面通達 └─ Subtask: 引継ぎ書類リスト作成

元の LINE 一文から 1 Issue + 5 Tasks + 8 Subtasks + 依存関係 + 担当者 + 関係者 を AI が自動抽出。 全所要時間 30 秒。 manager は Slack で 「approve all」 押すだけ。

4. エンドツーエンド フロー (7 stage)

1

LINE message 受信 AUTO

LINE-MEMORY の既存 webhook が受信、 SQLite + Slack 既存通知に流れる。 さらに新規 path として extract_issues() が並列で走る。

2

Issue 抽出 (Claude opus-4-7) AUTO

会話 window (5-20 分) を Claude opus-4-7 で解析、 主題候補 を抽出。 出力: {title, topic_type, priority, urgency, stakeholders, talent_id, source_msg_indices}。 confidence ≥ 0.6 のみ採用。

3

Task Tree 提案 (Claude opus-4-7) AUTO

Issue を input に propose_task_tree() で 3-7 個の task と subtask を提案。 各 task に推奨担当者を handlers テーブルから推論 (role / specialty マッチング)、 依存関係を DAG (Directed Acyclic Graph) で記述。

4

Slack に通知 (Block Kit) AUTO

Tree 構造の Block を含む Slack message を post。 [ 全て承認 ] [ 編集 (modal) ] [ 却下 ] のボタン。 executive と primary_owner にメンション。

5

operator が承認 / 編集 SEMI

"全て承認" 押下 → 全 task が internal_tasks に転記。 "編集" → modal で個別 task の担当者・期限・依存変更が可能。 大体 30 秒で完結。

6

Calendar event 作成 (meeting type task のみ) AUTO

task_type=meeting の task は Google Calendar API で候補日時を 3 件提案 → manager が選択 → event 作成 + 関係者を invite。 candidate slot は freeBusy API で全 attendees の空き時間から自動算出。

7

LINE group に reply 投稿 SEMI

承認確定後、 LINE group に「✅ ご相談を 5 個のタスクに整理しました。 詳細は talent page で確認できます」 と push 通知 (前述の LINE Push 設計)。 meeting 確定後は「📅 面談を 5/15 14:00 で設定しました」 と続報。 audience=mixed の group は executive が承認後に投稿。

5. スキーマ追加

5.1 Issues テーブル (新規)

CREATE TABLE talent_mgmt.issues (
  id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id       text NOT NULL,
  talent_id       text REFERENCES talent_mgmt.talents(id),
  thread_id       text REFERENCES talent_mgmt.line_threads(id),
  source_message_ids bigint[] NOT NULL DEFAULT '{}',
  title           text NOT NULL,                        -- 「増田が税理士を気に入っていない」
  description     text,
  topic_type      text NOT NULL,                        -- 'relationship' | 'business' | 'health' | 'career' | 'finance' | 'other'
  priority        text NOT NULL DEFAULT 'P2'            -- P0/P1/P2/P3
    CHECK (priority IN ('P0','P1','P2','P3')),
  urgency         text NOT NULL DEFAULT 'no_deadline'
    CHECK (urgency IN ('immediate','this_week','this_month','no_deadline')),
  status          text NOT NULL DEFAULT 'open'          -- open | in_progress | resolved | dropped
    CHECK (status IN ('open','in_progress','resolved','dropped')),
  primary_owner   uuid REFERENCES talent_mgmt.handlers(id),
  stakeholders    uuid[] NOT NULL DEFAULT '{}',         -- handler ids 配列
  external_stakeholders jsonb DEFAULT '[]'::jsonb,      -- [{ name, role, contact }] talent本人 / 第三者
  confidence      numeric(3,2),
  rationale       text,
  ai_proposed     boolean NOT NULL DEFAULT false,
  approved_by     uuid REFERENCES talent_mgmt.handlers(id),
  approved_at     timestamptz,
  resolved_at     timestamptz,
  created_at      timestamptz NOT NULL DEFAULT now(),
  updated_at      timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX issues_status_idx ON talent_mgmt.issues(status);
CREATE INDEX issues_primary_owner_idx ON talent_mgmt.issues(primary_owner);
CREATE INDEX issues_talent_idx ON talent_mgmt.issues(talent_id);

5.2 internal_tasks 拡張

ALTER TABLE talent_mgmt.internal_tasks
  ADD COLUMN IF NOT EXISTS issue_id      uuid REFERENCES talent_mgmt.issues(id) ON DELETE SET NULL,
  ADD COLUMN IF NOT EXISTS parent_task_id text REFERENCES talent_mgmt.internal_tasks(id) ON DELETE CASCADE,
  ADD COLUMN IF NOT EXISTS task_type      text NOT NULL DEFAULT 'action'
    CHECK (task_type IN ('action','meeting','investigation','communication','decision')),
  ADD COLUMN IF NOT EXISTS stakeholders   uuid[] NOT NULL DEFAULT '{}',
  ADD COLUMN IF NOT EXISTS depends_on     text[] NOT NULL DEFAULT '{}',  -- 他 task_id の依存
  ADD COLUMN IF NOT EXISTS calendar_event_id text,                       -- Google Calendar event id
  ADD COLUMN IF NOT EXISTS calendar_event_url text;

CREATE INDEX IF NOT EXISTS internal_tasks_issue_idx  ON talent_mgmt.internal_tasks(issue_id);
CREATE INDEX IF NOT EXISTS internal_tasks_parent_idx ON talent_mgmt.internal_tasks(parent_task_id);

5.3 calendar integration 用 (tenant 単位)

CREATE TABLE talent_mgmt.tenant_calendar_integrations (
  tenant_id        text PRIMARY KEY,
  provider         text NOT NULL DEFAULT 'google_calendar',
  service_account_email text,                       -- service account 経由の場合
  oauth_refresh_token   text,                       -- OAuth2 経由の場合 (暗号化)
  default_calendar_id   text,
  timezone              text NOT NULL DEFAULT 'Asia/Tokyo',
  installed_by     uuid REFERENCES talent_mgmt.handlers(id),
  installed_at     timestamptz NOT NULL DEFAULT now(),
  active           boolean NOT NULL DEFAULT true
);

-- handler の Google account を保存 (event 作成時の attendee 解決用)
ALTER TABLE talent_mgmt.handlers
  ADD COLUMN IF NOT EXISTS google_account_email text;

6. AI prompt 設計

6.1 extract_issues() prompt (Claude opus-4-7)

あなたはマネージャー業務支援の AI です。
直近の LINE 会話 window から「主題 (Issue)」 を抽出してください。

Issue = 解決すべき業務上の問題 / 状況。 単発タスクではなく、 複数の行動を要する主題。

抽出基準 (1 つ以上):
1. 関係者の感情 / 意思の変化 (例: 「気に入っていない」「辞めたい」「やりたい」)
2. 構造的問題 (例: 「契約条件がおかしい」「料金が高すぎる」「業務分担が回らない」)
3. 中期的判断 (例: 「来年から方針変える」「事務所変更検討」)

返答は JSON のみ:
{
  "issues": [
    {
      "title": "30 字以内、 名詞句で",
      "description": "100 字以内、 背景情報",
      "topic_type": "relationship | business | health | career | finance | other",
      "priority": "P0 | P1 | P2 | P3",
      "urgency": "immediate | this_week | this_month | no_deadline",
      "primary_owner_hint": "高野院長 / 経理担当 / マネージャー田中 など (handlers.name に近い文字列)",
      "stakeholders_hint": ["増田", "高野院長", "経理担当"],
      "external_stakeholders": [{ "name": "田中先生", "role": "税理士 (外部)" }],
      "confidence": 0.0,
      "rationale": "なぜ Issue と判定したかの根拠 (80 字)",
      "source_msg_indices": [0, 2]
    }
  ]
}

6.2 propose_task_tree() prompt (Claude opus-4-7)

あなたは業務オペレーションの専門家です。
以下の Issue を解決するための Task Tree を提案してください。

入力:
- Issue: {title} / {description} / {topic_type} / {priority}
- primary_owner: {owner_handler}
- stakeholders: {list_of_handlers}
- external: {list_of_externals}
- 利用可能な handler 一覧: {handlers_with_role_and_specialty}

要件:
1. 3-7 個の Task に分解 (多すぎず少なすぎず)
2. 各 Task に primary_owner を 1 名 (handler.id) 割り当て
3. 各 Task に stakeholders を 0-3 名割り当て
4. 各 Task に task_type を割り当て:
   - action: 具体的行動 (書類作成、 発送、 手続き)
   - meeting: 会議 / 面談 / 1on1
   - investigation: 調査 / リサーチ / 候補出し
   - communication: 通知 / 連絡 / 報告
   - decision: 意思決定ポイント
5. 依存関係を depends_on で表現 (他 task の id を参照)
6. 各 Task に subtasks を 0-3 個 (= 1 task = 30 分以内の細分化)
7. 全体が DAG (循環なし)、 起点 task は 1-2 個

返答は JSON のみ:
{
  "tasks": [
    {
      "id": "t1",
      "title": "増田と現状確認の 1on1 面談",
      "task_type": "meeting",
      "primary_owner_id": "h-tanaka-01",
      "stakeholders": ["h-takano-00"],
      "due_at_suggested": "2026-05-16",
      "priority": "P1",
      "depends_on": [],
      "rationale": "本人の意思確認なくして次のステップに進めない",
      "subtasks": [
        { "title": "面談候補日 3 件を提示", "task_type": "communication" }
      ]
    }
  ]
}

7. Slack 承認 UI (Block Kit)

7.1 通知メッセージ (Block Kit 例)

{
  "blocks": [
    { "type": "header", "text": "🎯 Issue 検出: 増田が税理士を気に入っていない" },
    { "type": "section", "fields": [
      { "type": "mrkdwn", "text": "*topic:* relationship + finance" },
      { "type": "mrkdwn", "text": "*priority:* P1 / urgency: this_week" },
      { "type": "mrkdwn", "text": "*primary owner:* 高野院長" },
      { "type": "mrkdwn", "text": "*stakeholders:* 増田、 経理担当、 (外部) 田中先生" }
    ]},
    { "type": "divider" },
    { "type": "section", "text": { "type": "mrkdwn", "text":
      "*推奨タスク分解* (5 task / 8 subtask)\n" +
      "├─ T1. 増田と意思確認の面談 [meeting]  担当: 田中マネ\n" +
      "├─ T2. 新税理士候補リスト 5 件作成 [investigation] 担当: 経理\n" +
      "├─ T3. 候補税理士との面談 [meeting]  担当: 高野院長 + 経理 (depends: T2)\n" +
      "├─ T4. 増田に最終候補を紹介 [communication] 担当: 田中マネ (depends: T1, T3)\n" +
      "└─ T5. 既存税理士との解約・引継ぎ [action]  担当: 経理 (depends: T4)"
    }},
    { "type": "actions", "elements": [
      { "type": "button", "style": "primary", "text": "全て承認 + Calendar 設定", "action_id": "issue_approve_all" },
      { "type": "button", "text": "編集...", "action_id": "issue_edit" },
      { "type": "button", "style": "danger", "text": "却下", "action_id": "issue_reject" }
    ]}
  ]
}

7.2 操作フロー

操作挙動
全て承認 Issue を open で INSERT、 5 task + 8 subtask を internal_tasks に INSERT (depends_on の関係を保持)。 meeting type は Calendar 候補日提示 modal を pop up
編集 Modal を開いて task ごとに 担当者 / 期限 / type 変更 + 追加・削除可能。 確定で承認
却下 Issue を dropped、 task は登録しない。 元の LINE thread に記録のみ残る

7.3 Calendar 候補日 modal

meeting type task が approve されると、 各 meeting ごとに以下 modal が pop up:

📅 T1. 増田と意思確認の面談 — 日時候補
attendees: 田中マネ (h-tanaka-01) + 増田 (外部) + 高野院長 (option)
候補スロット (Google Calendar freeBusy API で算出):
  ⭕ 5/14 (火) 14:00-15:00
  ⭕ 5/15 (水) 16:00-17:00
  ⭕ 5/16 (木) 10:00-11:00
  ⭕ 他候補を見る...
[ 5/15 16:00 で確定 ] [ 編集 ] [ skip Calendar 連携 ]

確定 → Calendar event 作成 + attendees invite + Slack に確認 post + LINE group に push reply。

8. Google Calendar 連携

8.1 認証方式 (2 案)

  • (a) Service Account + Domain-wide Delegation (推奨、 Google Workspace 環境)
    • 1 service account で全 handler の Calendar 操作可能
    • operator は GCP コンソールで 1 度設定するだけ
    • handler 個別 OAuth 不要
  • (b) Per-handler OAuth2 (Workspace 外の事務所向け)
    • 各 handler が Talent Hub の設定 page で 「Google Calendar 接続」 ボタン
    • refresh_token を handlers.google_oauth_refresh_token に暗号化保存

8.2 freeBusy API による候補日算出

POST https://www.googleapis.com/calendar/v3/freeBusy
{
  "timeMin": "2026-05-13T09:00:00+09:00",
  "timeMax": "2026-05-20T18:00:00+09:00",
  "items": [
    { "id": "tanaka@example.com" },
    { "id": "takano@example.com" }
  ]
}

→ レスポンスから全員空きの 1h スロットを 3 件抽出 → modal に表示

8.3 Event 作成

POST https://www.googleapis.com/calendar/v3/calendars/primary/events
{
  "summary": "増田 — 税理士の件 1on1",
  "description": "Issue: 増田が税理士を気に入っていない\n親 Task: T1\nLINE thread: line_t-masuda-91",
  "start": { "dateTime": "2026-05-15T16:00:00+09:00" },
  "end":   { "dateTime": "2026-05-15T17:00:00+09:00" },
  "attendees": [
    { "email": "tanaka@example.com" },
    { "email": "takano@example.com" }
  ],
  "conferenceData": {
    "createRequest": { "requestId": "uuid", "conferenceSolutionKey": { "type": "hangoutsMeet" } }
  },
  "reminders": { "useDefault": true }
}

→ event id を internal_tasks.calendar_event_id に保存
→ Google Meet URL は internal_tasks.calendar_event_url に保存
💡 talent / external 参加者

talent 本人 / 外部 stakeholder の Google account を持っていない場合、 attendees に email アドレスのみ追加 (任意)、 または LINE Push で「日時確定しました」 のみ通知して Calendar event は internal のみ。

9. 自動 / 半自動 の境界

operator から「自動化もしくは半自動化したい」 という要件。 各 stage の自動度を整理:

stage自動度人手介入
LINE 受信完全自動なし
Issue 抽出完全自動なし (confidence ≥ 0.6 のみ)
Task Tree 提案完全自動なし
Slack 通知完全自動なし
承認 / 編集半自動operator 1 クリック (or modal 編集)
internal_tasks INSERT完全自動承認後の処理
Calendar event 作成半自動候補日選択 (1 クリック)
LINE group reply半自動 (audience による)audience=mixed/external は executive 1 クリック確認
✅ operator の朝ルーチン

朝 Slack を開く → Issue 通知 5 件 → 各 Issue に対し 「全て承認」 (3 秒) → meeting 候補日選択 (10 秒) → 完了。 1 Issue あたり 30 秒、 5 Issue で 2.5 分。 既存運用 (1 Issue 10 分 × 5 = 50 分) と比べて 20 倍効率化

10. 実装 Phase

Phase時期達成内容
Phase 0 2026-06 schema 追加 (issues + internal_tasks 拡張)、 mock UI に Issue list page 追加
Phase 1 2026-06 〜 07 analyzer.py に extract_issues() + propose_task_tree() 追加 (Claude opus-4-7)、 Slack Block Kit で承認 UI
Phase 2 2026-07 〜 08 Google Calendar 連携 (service account + freeBusy + event 作成)、 候補日 modal、 attendees 解決ロジック
Phase 3 2026-08 〜 09 LINE group push reply 連動 (LINE Push 設計 と統合)、 audience 別 投稿制御
Phase 4 2026-09 以降 Issue dashboard (open / in_progress / resolved の trend)、 SLA monitoring、 stakeholder graph 可視化