コンテンツにスキップ

統合済みハンズオン (Consolidated Hands-on)

Canonical: ai-agent-development-practical-implementation-deep-dive-august2025.md

本ファイルは互換用の短縮ハンズオン別名。内容は Deep Dive に完全統合済みです。


本記事は「AI エージェント開発革命:Claude Code & GitHub Copilot 最新アップデート完全ガイド」の実践編です。概要理解から一歩進み、実際にAIエージェントを設定・運用するための具体的な手順とコード例を詳細に解説します。

この記事のポイント

  • カスタムサブエージェント作成

    専門分野特化エージェントの設定から実運用までの完全実装

  • MCP統合実装

    GitHub、Slack、Jira等のMCPサーバー統合とカスタム開発

  • 自動化ワークフロー構築

    Agent Modeを活用した完全自動開発パイプライン構築

  • 実運用監視システム

    エージェント動作のログ収集、性能監視、トラブルシューティング

Phase 1: Claude Code サブエージェント実装

1.1 カスタムエージェント設定ファイル作成

まず、専門特化サブエージェントの設定ファイルを作成します。

# Claude Codeエージェントディレクトリ作成
mkdir -p ~/.claude/agents
mkdir -p ~/.claude/commands
mkdir -p ~/.claude/hooks

データベース専門エージェント設定

<!-- ~/.claude/agents/database-architect.md -->
# データベースアーキテクト エージェント

## 専門分野
- データベース設計・最適化
- ERD設計とリレーション最適化
- インデックス戦略とクエリチューニング
- マイグレーション戦略とデータ移行

## 使用モデル設定
- Primary: Claude 3.5 Sonnet (高精度設計)
- Secondary: Claude 3.7 Sonnet (複雑なクエリ最適化)

## 責任範囲
### 設計フェーズ
- エンティティリレーション図(ERD)作成
- 正規化・非正規化判断
- インデックス設計戦略

### 実装フェーズ
- DDL文生成とレビュー
- マイグレーションスクリプト作成
- パフォーマンステスト設計

### 最適化フェーズ
- クエリ実行計画分析
- インデックス効果測定
- ボトルネック特定・改善

## コンテキスト設定
```yaml
context:
  technologies: [PostgreSQL, MySQL, MongoDB, Redis]
  patterns: [CQRS, Event Sourcing, Sharding]
  tools: [pgAdmin, MySQL Workbench, Studio 3T]

出力形式テンプレート

設計ドキュメント

  • ERD図(Mermaid形式)
  • テーブル定義書
  • インデックス設計書

実装コード

  • DDL(CREATE TABLE文)
  • DML(INSERT/UPDATE/DELETE文)
  • 最適化クエリ例
    **フロントエンド専門エージェント設定**
    
    ```markdown
    <!-- ~/.claude/agents/frontend-specialist.md -->
    # フロントエンド開発専門エージェント
    
    ## 専門分野
    - React/Next.js アプリケーション開発
    - TypeScript型安全実装
    - 状態管理(Redux Toolkit, Zustand)
    - UI/UXコンポーネント設計
    
    ## 使用モデル設定
    - Primary: GPT-4o (React生態系に強い)
    - Secondary: Claude 3.5 Sonnet (TypeScript型設計)
    
    ## 実装パターン
    ### コンポーネント設計
    ```typescript
    // 標準コンポーネント構造
    interface ComponentProps {
      // 型定義
    }
    
    export const Component: React.FC<ComponentProps> = ({
      // props分割代入
    }) => {
      // hooks使用
      // JSX return
    }
    

状態管理パターン

  • Zustand使用(軽量状態管理)
  • React Query(サーバー状態管理)
  • React Hook Form(フォーム状態)

ディレクトリ構造テンプレート

src/
  components/
    ui/           # 再利用UIコンポーネント
    forms/        # フォーム専用コンポーネント
    layouts/      # レイアウトコンポーネント
  hooks/          # カスタムフック
  stores/         # 状態管理
  utils/          # ユーティリティ関数
  types/          # TypeScript型定義
### 1.2 カスタムスラッシュコマンド実装

Claude Codeで使用する専用コマンドを作成します。

```markdown
<!-- ~/.claude/commands/review-database.md -->
# データベースレビューコマンド

以下の観点でデータベース設計を包括的にレビューしてください:

## 設計レビューポイント
1. **正規化レベルチェック**
   - 第1〜第5正規形への準拠確認
   - 非正規化の必要性判断

2. **インデックス戦略検証**
   - 主キー・外部キーインデックス
   - 複合インデックス効果性
   - 不要インデックス特定

3. **パフォーマンス予測**
   - クエリ実行計画シミュレーション
   - ボトルネック予測
   - スケーラビリティ評価

## 出力形式
- 問題点リスト(優先度付き)
- 改善提案(コード例付き)
- パフォーマンス改善見込み

<!-- ~/.claude/commands/frontend-scaffold.md -->
# フロントエンドスキャフォールディング

以下の要件で React + TypeScript プロジェクトの基本構造を作成してください:

## 生成対象
1. **基本ディレクトリ構造**
2. **TypeScript設定ファイル**
3. **ESLint/Prettier設定**
4. **基本コンポーネントテンプレート**
5. **状態管理セットアップ**

## 技術スタック
- React 18+ with TypeScript
- Vite (ビルドツール)
- Zustand (状態管理)
- React Query (データフェッチ)
- Tailwind CSS (スタイリング)

## 生成ファイル例
- `src/components/ui/Button.tsx`
- `src/hooks/useApi.ts`
- `src/stores/appStore.ts`
- `vite.config.ts`
- `tsconfig.json`

1.3 エージェント実行コマンド例

設定したエージェントの実際の使用方法:

# データベース専門エージェントでERD作成
claude --agent database-architect "ECサイトの商品・注文管理データベースを設計してください"

# フロントエンド専門エージェントでコンポーネント作成
claude --agent frontend-specialist "ユーザープロファイル編集フォームを作成してください"

# カスタムコマンドでプロジェクト初期化
claude /frontend-scaffold "管理画面プロジェクト"

Phase 2: GitHub Copilot Agent Mode 実装

2.1 Agent Mode プロジェクト設定

GitHub Copilot Agent Modeの詳細設定を実装します。

.vscode/settings.json 設定

{
  "github.copilot.enable": {
    "*": true,
    "plaintext": false,
    "markdown": true
  },
  "github.copilot.editor.enableAutoCompletions": true,
  "github.copilot.chat.localeOverride": "ja",
  "github.copilot.chat.welcomeMessage": "always",

  // Agent Mode 専用設定
  "github.copilot.agent.enabled": true,
  "github.copilot.agent.autoTrigger": "onPush",
  "github.copilot.agent.reviewEnabled": true,

  // MCP設定統合
  "github.copilot.mcp.enabled": true,
  "github.copilot.mcp.servers": {
    "github": {
      "command": "github-mcp-server",
      "env": {
        "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
      }
    },
    "filesystem": {
      "command": "filesystem-mcp-server",
      "args": ["--root", "./"]
    }
  }
}

GitHub Actions Workflow統合

# .github/workflows/copilot-agent-review.yml
name: Copilot Agent Code Review

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  copilot-review:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      pull-requests: write
      issues: write

    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'

    - name: Install GitHub CLI
      run: |
        type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
        curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
        sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
        sudo apt update && sudo apt install gh -y

    - name: Setup GitHub Copilot CLI
      run: |
        gh extension install github/gh-copilot

    - name: Run Copilot Agent Review
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # プルリクエストの差分取得
        gh pr diff ${{ github.event.pull_request.number }} > changes.diff

        # Agent Modeでコードレビュー実行
        gh copilot suggest \
          --agent-mode \
          "以下の変更をレビューし、問題点と改善提案をMarkdown形式で出力してください。セキュリティ、パフォーマンス、可読性の観点から評価してください。" \
          < changes.diff > review.md

        # レビュー結果をPRコメントに追加
        gh pr comment ${{ github.event.pull_request.number }} --body-file review.md

    - name: Quality Gate Check
      run: |
        # Agent Modeによる品質チェック
        gh copilot suggest \
          --agent-mode \
          "このプルリクエストをマージしても安全か判定し、理由と共にYES/NOで回答してください。" \
          < changes.diff > quality_gate.txt

        # 結果をArtifactとして保存
        echo "QUALITY_GATE_RESULT=$(cat quality_gate.txt)" >> $GITHUB_ENV

    - name: Update PR Status
      if: contains(env.QUALITY_GATE_RESULT, 'NO')
      run: |
        gh pr ready ${{ github.event.pull_request.number }} --undo
        gh pr comment ${{ github.event.pull_request.number }} \
          --body "🚫 **Quality Gate Failed**: Agent review detected issues. Please address before merging."

2.2 Issue自動処理システム構築

GitHub Copilot Coding Agentによる完全自動開発システム:

Issue Template設定

# .github/ISSUE_TEMPLATE/feature-request-agent.yml
name: 🤖 Feature Request (Agent Auto-Implementation)
description: 機能要求をAIエージェントが自動実装します
title: "[AGENT] "
labels: ["agent-auto", "feature"]
body:
  - type: textarea
    id: description
    attributes:
      label: 機能の詳細
      description: 実装したい機能を詳細に記述してください
      placeholder: |
        例:
        - ユーザー認証システムの実装
        - JWT トークンベース
        - ログイン・ログアウト・パスワードリセット機能
        - 管理者・一般ユーザーの権限管理
      value: ""
    validations:
      required: true

  - type: dropdown
    id: priority
    attributes:
      label: 実装優先度
      options:
        - "🔴 緊急 (24時間以内)"
        - "🟡  (72時間以内)"  
        - "🟢  (1週間以内)"
        - "⚪  (時間に余裕あり)"
    validations:
      required: true

  - type: checkboxes
    id: requirements
    attributes:
      label: 要件確認
      options:
        - label: "テストコードを含む実装"
          required: false
        - label: "ドキュメントの自動更新"
          required: false
        - label: "セキュリティレビューを含む"
          required: false

Agent自動実装Workflow

# .github/workflows/agent-auto-implementation.yml
name: Agent Auto Implementation

on:
  issues:
    types: [opened, labeled]

jobs:
  auto-implement:
    if: contains(github.event.issue.labels.*.name, 'agent-auto')
    runs-on: ubuntu-latest

    permissions:
      contents: write
      issues: write
      pull-requests: write

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Parse Issue Requirements
      id: parse
      run: |
        # Issue本文を解析
        ISSUE_BODY='${{ github.event.issue.body }}'
        ISSUE_TITLE='${{ github.event.issue.title }}'

        # 機能名を抽出(タイトルから[AGENT]を除去)
        FEATURE_NAME=$(echo "$ISSUE_TITLE" | sed 's/\[AGENT\]//g' | xargs)

        echo "feature_name=$FEATURE_NAME" >> $GITHUB_OUTPUT
        echo "issue_number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT

    - name: Create Feature Branch
      run: |
        BRANCH_NAME="agent/issue-${{ steps.parse.outputs.issue_number }}-$(echo '${{ steps.parse.outputs.feature_name }}' | tr ' ' '-' | tr '[:upper:]' '[:lower:]')"
        git checkout -b "$BRANCH_NAME"
        echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV

    - name: Setup Development Environment
      run: |
        # Node.js環境セットアップ
        npm install

        # Agent Mode用の依存関係インストール
        gh extension install github/gh-copilot

    - name: Agent Implementation Phase
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # Issue要件をAgent Modeに渡して実装指示
        IMPLEMENTATION_PROMPT="以下の要件でフルスタックアプリケーションを実装してください:

        要件:
        ${{ github.event.issue.body }}

        実装指針:
        1. セキュリティベストプラクティスに従う
        2. テストコードを必ず含める
        3. TypeScript/ESLintルールに準拠
        4. APIドキュメントを自動生成
        5. エラーハンドリングを適切に実装

        ファイル構造:
        - src/components/ (React コンポーネント)
        - src/hooks/ (カスタムフック)
        - src/services/ (API クライアント)
        - tests/ (テストファイル)
        - docs/ (ドキュメント)"

        # Agent Modeで実装実行
        gh copilot suggest --agent-mode "$IMPLEMENTATION_PROMPT" > implementation_log.md

        # 実装されたファイルを確認・調整
        if [[ -n $(git status --porcelain) ]]; then
          git add .
          git commit -m "feat: implement ${{ steps.parse.outputs.feature_name }} via Agent Mode

          Auto-generated implementation based on Issue #${{ steps.parse.outputs.issue_number }}

          Implemented files:
          $(git diff --name-only HEAD~1)

          Co-authored-by: GitHub Copilot Agent <noreply@github.com>"
        fi

    - name: Quality Assurance Phase
      run: |
        # エージェントによるコード品質チェック
        QUALITY_CHECK="実装されたコードを以下の観点で包括的にレビューしてください:
        1. セキュリティ脆弱性の有無
        2. パフォーマンスの最適化余地
        3. テストカバレッジの十分性
        4. コード品質とメンテナンス性
        5. アクセシビリティ対応"

        gh copilot suggest --agent-mode "$QUALITY_CHECK" > quality_review.md

        # テスト実行
        npm run test 2>&1 | tee test_results.txt

        # TypeScript型チェック
        npm run type-check 2>&1 | tee type_check_results.txt

        # ESLint実行
        npm run lint 2>&1 | tee lint_results.txt

    - name: Create Pull Request
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        # PR作成
        gh pr create \
          --title "🤖 [Auto-Implementation] ${{ steps.parse.outputs.feature_name }}" \
          --body "## 自動実装完了レポート

        ### 対応Issue
        Closes #${{ steps.parse.outputs.issue_number }}

        ### 実装内容
        $(cat implementation_log.md)

        ### 品質レビュー結果
        $(cat quality_review.md)

        ### テスト結果
        \`\`\`
        $(cat test_results.txt)
        \`\`\`

        ### 型チェック結果
        \`\`\`
        $(cat type_check_results.txt)
        \`\`\`

        ### リント結果
        \`\`\`
        $(cat lint_results.txt)
        \`\`\`

        ---
        🤖 Generated with GitHub Copilot Agent Mode
        " \
          --label "agent-generated,ready-for-review"

        # PRリンクをIssueコメントに追加
        PR_URL=$(gh pr view --json url -q .url)
        gh issue comment ${{ steps.parse.outputs.issue_number }} \
          --body "🤖 **自動実装完了**

        Pull Request: $PR_URL

        実装内容が要件を満たしているかレビューをお願いします。
        修正が必要な場合は、PRにコメントしていただければ自動で対応します。"

2.3 MCP統合カスタム開発

独自のMCPサーバーを開発して機能を拡張します。

カスタムMCPサーバー実装

// mcp-servers/project-manager-server/src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ListToolsRequestSchema,
  CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

interface ProjectState {
  name: string;
  status: 'planning' | 'development' | 'testing' | 'deployment' | 'completed';
  tasks: Task[];
  resources: Resource[];
}

interface Task {
  id: string;
  title: string;
  description: string;
  assignee: string;
  status: 'todo' | 'in_progress' | 'review' | 'done';
  priority: 'low' | 'medium' | 'high' | 'urgent';
  dueDate?: Date;
  dependencies: string[];
}

interface Resource {
  id: string;
  name: string;
  type: 'file' | 'url' | 'database' | 'api';
  location: string;
  description: string;
}

class ProjectManagerServer {
  private server: Server;
  private projects: Map<string, ProjectState> = new Map();

  constructor() {
    this.server = new Server(
      {
        name: "project-manager-mcp-server",
        version: "1.0.0",
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );

    this.setupHandlers();
  }

  private setupHandlers() {
    // リソース一覧取得
    this.server.setRequestHandler(
      ListResourcesRequestSchema,
      async () => {
        const resources = [];

        for (const [projectId, project] of this.projects) {
          resources.push({
            uri: `project://${projectId}`,
            mimeType: "application/json",
            name: project.name,
            description: `Project: ${project.name} (${project.status})`,
          });
        }

        return { resources };
      }
    );

    // リソース読み取り
    this.server.setRequestHandler(
      ReadResourceRequestSchema,
      async (request) => {
        const uri = request.params.uri;
        const match = uri.match(/^project:\/\/(.+)$/);

        if (!match) {
          throw new Error(`Invalid resource URI: ${uri}`);
        }

        const projectId = match[1];
        const project = this.projects.get(projectId);

        if (!project) {
          throw new Error(`Project not found: ${projectId}`);
        }

        return {
          contents: [
            {
              uri,
              mimeType: "application/json",
              text: JSON.stringify(project, null, 2),
            },
          ],
        };
      }
    );

    // ツール一覧
    this.server.setRequestHandler(
      ListToolsRequestSchema,
      async () => {
        return {
          tools: [
            {
              name: "create_project",
              description: "新しいプロジェクトを作成",
              inputSchema: {
                type: "object",
                properties: {
                  name: { type: "string" },
                  description: { type: "string" },
                },
                required: ["name"],
              },
            },
            {
              name: "add_task",
              description: "プロジェクトにタスクを追加",
              inputSchema: {
                type: "object",
                properties: {
                  projectId: { type: "string" },
                  title: { type: "string" },
                  description: { type: "string" },
                  assignee: { type: "string" },
                  priority: {
                    type: "string",
                    enum: ["low", "medium", "high", "urgent"]
                  },
                  dueDate: { type: "string", format: "date" },
                },
                required: ["projectId", "title"],
              },
            },
            {
              name: "update_task_status",
              description: "タスクステータスを更新",
              inputSchema: {
                type: "object",
                properties: {
                  projectId: { type: "string" },
                  taskId: { type: "string" },
                  status: {
                    type: "string",
                    enum: ["todo", "in_progress", "review", "done"]
                  },
                },
                required: ["projectId", "taskId", "status"],
              },
            },
            {
              name: "generate_project_report",
              description: "プロジェクト進捗レポート生成",
              inputSchema: {
                type: "object",
                properties: {
                  projectId: { type: "string" },
                  format: {
                    type: "string",
                    enum: ["markdown", "json", "html"]
                  },
                },
                required: ["projectId"],
              },
            },
          ],
        };
      }
    );

    // ツール実行
    this.server.setRequestHandler(
      CallToolRequestSchema,
      async (request) => {
        const { name, arguments: args } = request.params;

        switch (name) {
          case "create_project":
            return this.createProject(args as { name: string; description?: string });

          case "add_task":
            return this.addTask(args as {
              projectId: string;
              title: string;
              description?: string;
              assignee?: string;
              priority?: string;
              dueDate?: string;
            });

          case "update_task_status":
            return this.updateTaskStatus(args as {
              projectId: string;
              taskId: string;
              status: string;
            });

          case "generate_project_report":
            return this.generateProjectReport(args as {
              projectId: string;
              format?: string;
            });

          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      }
    );
  }

  private async createProject(args: { name: string; description?: string }) {
    const projectId = `project_${Date.now()}`;
    const project: ProjectState = {
      name: args.name,
      status: 'planning',
      tasks: [],
      resources: [],
    };

    this.projects.set(projectId, project);

    return {
      content: [
        {
          type: "text",
          text: `プロジェクト "${args.name}" を作成しました。ID: ${projectId}`,
        },
      ],
    };
  }

  private async addTask(args: {
    projectId: string;
    title: string;
    description?: string;
    assignee?: string;
    priority?: string;
    dueDate?: string;
  }) {
    const project = this.projects.get(args.projectId);
    if (!project) {
      throw new Error(`Project not found: ${args.projectId}`);
    }

    const taskId = `task_${Date.now()}`;
    const task: Task = {
      id: taskId,
      title: args.title,
      description: args.description || '',
      assignee: args.assignee || '',
      status: 'todo',
      priority: (args.priority as any) || 'medium',
      dueDate: args.dueDate ? new Date(args.dueDate) : undefined,
      dependencies: [],
    };

    project.tasks.push(task);

    return {
      content: [
        {
          type: "text",
          text: `タスク "${args.title}" をプロジェクト ${args.projectId} に追加しました。`,
        },
      ],
    };
  }

  private async updateTaskStatus(args: {
    projectId: string;
    taskId: string;
    status: string;
  }) {
    const project = this.projects.get(args.projectId);
    if (!project) {
      throw new Error(`Project not found: ${args.projectId}`);
    }

    const task = project.tasks.find(t => t.id === args.taskId);
    if (!task) {
      throw new Error(`Task not found: ${args.taskId}`);
    }

    task.status = args.status as any;

    return {
      content: [
        {
          type: "text",
          text: `タスク "${task.title}" のステータスを "${args.status}" に更新しました。`,
        },
      ],
    };
  }

  private async generateProjectReport(args: {
    projectId: string;
    format?: string;
  }) {
    const project = this.projects.get(args.projectId);
    if (!project) {
      throw new Error(`Project not found: ${args.projectId}`);
    }

    const format = args.format || 'markdown';

    if (format === 'markdown') {
      const report = this.generateMarkdownReport(project);
      return {
        content: [
          {
            type: "text",
            text: report,
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(project, null, 2),
        },
      ],
    };
  }

  private generateMarkdownReport(project: ProjectState): string {
    const totalTasks = project.tasks.length;
    const completedTasks = project.tasks.filter(t => t.status === 'done').length;
    const inProgressTasks = project.tasks.filter(t => t.status === 'in_progress').length;
    const todoTasks = project.tasks.filter(t => t.status === 'todo').length;

    const progress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;

    return `# ${project.name} プロジェクトレポート

## 概要
- **ステータス**: ${project.status}
- **進捗率**: ${progress}% (${completedTasks}/${totalTasks})

## タスク状況
- ✅ 完了: ${completedTasks}
- 🔄 進行中: ${inProgressTasks}
- ⏳ 未着手: ${todoTasks}

## タスク詳細

${project.tasks.map(task => `
### ${task.title}
- **ステータス**: ${task.status}
- **優先度**: ${task.priority}
- **担当者**: ${task.assignee || '未割り当て'}
- **説明**: ${task.description}
${task.dueDate ? `- **期限**: ${task.dueDate.toLocaleDateString()}` : ''}
`).join('\n')}

---
_Generated by Project Manager MCP Server_
`;
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
  }
}

const server = new ProjectManagerServer();
server.run().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

MCPサーバー設定ファイル

{
  "mcpServers": {
    "project-manager": {
      "command": "node",
      "args": ["./mcp-servers/project-manager-server/dist/index.js"],
      "env": {}
    },
    "github": {
      "command": "github-mcp-server",
      "args": ["--token", "${{ secrets.GITHUB_TOKEN }}"]
    },
    "database": {
      "command": "database-mcp-server",
      "args": ["--connection-string", "${{ secrets.DATABASE_URL }}"]
    },
    "slack": {
      "command": "slack-mcp-server", 
      "env": {
        "SLACK_BOT_TOKEN": "${{ secrets.SLACK_BOT_TOKEN }}"
      }
    }
  }
}

Phase 3: 監視・運用システム構築

3.1 エージェント動作ログシステム

AIエージェントの動作を監視・分析するシステムを構築します。

// monitoring/agent-logger.ts
import fs from 'fs';
import path from 'path';

interface AgentLogEntry {
  timestamp: Date;
  agentType: 'claude-code' | 'copilot-agent' | 'custom-mcp';
  agentId: string;
  action: string;
  input: string;
  output: string;
  executionTime: number;
  success: boolean;
  error?: string;
  metadata?: Record<string, any>;
}

class AgentLogger {
  private logDir: string;
  private currentLogFile: string;

  constructor(baseDir: string = './logs') {
    this.logDir = baseDir;
    this.ensureLogDirectory();
    this.currentLogFile = this.getLogFilePath();
  }

  private ensureLogDirectory() {
    if (!fs.existsSync(this.logDir)) {
      fs.mkdirSync(this.logDir, { recursive: true });
    }
  }

  private getLogFilePath(): string {
    const today = new Date().toISOString().split('T')[0];
    return path.join(this.logDir, `agent-logs-${today}.jsonl`);
  }

  log(entry: Omit<AgentLogEntry, 'timestamp'>) {
    const logEntry: AgentLogEntry = {
      timestamp: new Date(),
      ...entry,
    };

    const logLine = JSON.stringify(logEntry) + '\n';
    fs.appendFileSync(this.currentLogFile, logLine);

    // 重要なエラーはSlackに通知
    if (!entry.success && entry.agentType === 'copilot-agent') {
      this.sendSlackAlert(logEntry);
    }
  }

  private async sendSlackAlert(entry: AgentLogEntry) {
    const webhook = process.env.SLACK_WEBHOOK_URL;
    if (!webhook) return;

    const message = {
      text: `🚨 Agent Error Alert`,
      blocks: [
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: `*Agent Error Detected*\n\n*Agent:* ${entry.agentType} (${entry.agentId})\n*Action:* ${entry.action}\n*Error:* ${entry.error}\n*Time:* ${entry.timestamp.toISOString()}`
          }
        }
      ]
    };

    try {
      await fetch(webhook, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(message),
      });
    } catch (error) {
      console.error('Failed to send Slack alert:', error);
    }
  }

  // 統計情報生成
  generateDailyStats(date?: string): AgentStats {
    const targetDate = date || new Date().toISOString().split('T')[0];
    const logFile = path.join(this.logDir, `agent-logs-${targetDate}.jsonl`);

    if (!fs.existsSync(logFile)) {
      return this.emptyStats();
    }

    const logs = fs.readFileSync(logFile, 'utf-8')
      .split('\n')
      .filter(line => line.trim())
      .map(line => JSON.parse(line) as AgentLogEntry);

    return this.calculateStats(logs);
  }

  private calculateStats(logs: AgentLogEntry[]): AgentStats {
    const stats: AgentStats = {
      totalExecutions: logs.length,
      successRate: 0,
      averageExecutionTime: 0,
      agentBreakdown: {},
      actionBreakdown: {},
      errorBreakdown: {},
      performanceMetrics: {
        fastExecutions: 0, // < 1s
        normalExecutions: 0, // 1-5s
        slowExecutions: 0, // > 5s
      }
    };

    if (logs.length === 0) return stats;

    let successCount = 0;
    let totalExecutionTime = 0;

    logs.forEach(log => {
      // 成功率計算
      if (log.success) successCount++;

      // 実行時間計算
      totalExecutionTime += log.executionTime;

      // パフォーマンス分類
      if (log.executionTime < 1000) {
        stats.performanceMetrics.fastExecutions++;
      } else if (log.executionTime < 5000) {
        stats.performanceMetrics.normalExecutions++;
      } else {
        stats.performanceMetrics.slowExecutions++;
      }

      // エージェント別統計
      if (!stats.agentBreakdown[log.agentType]) {
        stats.agentBreakdown[log.agentType] = {
          count: 0,
          successCount: 0,
          avgExecutionTime: 0,
        };
      }
      stats.agentBreakdown[log.agentType].count++;
      if (log.success) {
        stats.agentBreakdown[log.agentType].successCount++;
      }

      // アクション別統計
      stats.actionBreakdown[log.action] = 
        (stats.actionBreakdown[log.action] || 0) + 1;

      // エラー別統計
      if (!log.success && log.error) {
        stats.errorBreakdown[log.error] = 
          (stats.errorBreakdown[log.error] || 0) + 1;
      }
    });

    stats.successRate = (successCount / logs.length) * 100;
    stats.averageExecutionTime = totalExecutionTime / logs.length;

    // エージェント別平均実行時間計算
    Object.keys(stats.agentBreakdown).forEach(agentType => {
      const agentLogs = logs.filter(log => log.agentType === agentType);
      const totalTime = agentLogs.reduce((sum, log) => sum + log.executionTime, 0);
      stats.agentBreakdown[agentType].avgExecutionTime = totalTime / agentLogs.length;
    });

    return stats;
  }

  private emptyStats(): AgentStats {
    return {
      totalExecutions: 0,
      successRate: 0,
      averageExecutionTime: 0,
      agentBreakdown: {},
      actionBreakdown: {},
      errorBreakdown: {},
      performanceMetrics: {
        fastExecutions: 0,
        normalExecutions: 0,
        slowExecutions: 0,
      }
    };
  }
}

interface AgentStats {
  totalExecutions: number;
  successRate: number;
  averageExecutionTime: number;
  agentBreakdown: Record<string, {
    count: number;
    successCount: number;
    avgExecutionTime: number;
  }>;
  actionBreakdown: Record<string, number>;
  errorBreakdown: Record<string, number>;
  performanceMetrics: {
    fastExecutions: number;
    normalExecutions: number;
    slowExecutions: number;
  };
}

export { AgentLogger, AgentLogEntry, AgentStats };

3.2 パフォーマンス監視ダッシュボード

// monitoring/dashboard-generator.ts
import { AgentLogger, AgentStats } from './agent-logger';
import fs from 'fs';
import path from 'path';

class DashboardGenerator {
  private logger: AgentLogger;

  constructor(logger: AgentLogger) {
    this.logger = logger;
  }

  generateDashboard(days: number = 7): string {
    const dashboardData = this.collectDashboardData(days);
    return this.generateHTML(dashboardData);
  }

  private collectDashboardData(days: number) {
    const data = [];
    const today = new Date();

    for (let i = 0; i < days; i++) {
      const date = new Date(today);
      date.setDate(today.getDate() - i);
      const dateStr = date.toISOString().split('T')[0];

      const stats = this.logger.generateDailyStats(dateStr);
      data.push({
        date: dateStr,
        stats
      });
    }

    return data.reverse(); // 古い順に並び替え
  }

  private generateHTML(data: Array<{date: string, stats: AgentStats}>): string {
    const chartData = this.prepareChartData(data);

    return `
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI Agent Performance Dashboard</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body { 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0; padding: 20px; background-color: #f5f5f5;
        }
        .container { max-width: 1200px; margin: 0 auto; }
        .header { 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px;
            text-align: center;
        }
        .metrics-grid { 
            display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px; margin-bottom: 30px;
        }
        .metric-card {
            background: white; padding: 25px; border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); text-align: center;
        }
        .metric-value { 
            font-size: 2.5em; font-weight: bold; color: #667eea;
            margin: 10px 0;
        }
        .chart-container {
            background: white; padding: 30px; border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-bottom: 30px;
        }
        .chart-canvas { max-height: 400px; }
        .error-list {
            background: white; padding: 25px; border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .error-item {
            padding: 15px; margin: 10px 0; background: #fff5f5;
            border-left: 4px solid #fc8181; border-radius: 5px;
        }
        .agent-breakdown {
            display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px; margin-bottom: 30px;
        }
        .last-updated {
            text-align: center; color: #666; font-size: 0.9em; margin-top: 30px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🤖 AI Agent Performance Dashboard</h1>
            <p>AI エージェントの性能監視とパフォーマンス分析</p>
        </div>

        ${this.generateMetricsSection(data)}
        ${this.generateChartsSection(chartData)}
        ${this.generateAgentBreakdownSection(data)}
        ${this.generateErrorAnalysisSection(data)}

        <div class="last-updated">
            Last updated: ${new Date().toLocaleString('ja-JP')}
        </div>
    </div>

    <script>
        ${this.generateChartScripts(chartData)}
    </script>
</body>
</html>`;
  }

  private generateMetricsSection(data: Array<{date: string, stats: AgentStats}>): string {
    const totalStats = this.aggregateStats(data.map(d => d.stats));

    return `
        <div class="metrics-grid">
            <div class="metric-card">
                <h3>総実行回数</h3>
                <div class="metric-value">${totalStats.totalExecutions.toLocaleString()}</div>
                <p>過去${data.length}日間</p>
            </div>
            <div class="metric-card">
                <h3>成功率</h3>
                <div class="metric-value">${totalStats.successRate.toFixed(1)}%</div>
                <p>エラー率: ${(100 - totalStats.successRate).toFixed(1)}%</p>
            </div>
            <div class="metric-card">
                <h3>平均実行時間</h3>
                <div class="metric-value">${(totalStats.averageExecutionTime / 1000).toFixed(2)}s</div>
                <p>ミリ秒: ${totalStats.averageExecutionTime.toFixed(0)}ms</p>
            </div>
            <div class="metric-card">
                <h3>パフォーマンス</h3>
                <div class="metric-value">${this.getPerformanceGrade(totalStats)}</div>
                <p>高速実行: ${totalStats.performanceMetrics.fastExecutions}回</p>
            </div>
        </div>`;
  }

  private generateChartsSection(chartData: any): string {
    return `
        <div class="chart-container">
            <h2>📊 日次実行統計</h2>
            <canvas id="dailyStatsChart" class="chart-canvas"></canvas>
        </div>

        <div class="chart-container">
            <h2>⚡ パフォーマンス分布</h2>
            <canvas id="performanceChart" class="chart-canvas"></canvas>
        </div>

        <div class="chart-container">
            <h2>🎯 エージェント別成功率</h2>
            <canvas id="agentSuccessChart" class="chart-canvas"></canvas>
        </div>`;
  }

  private generateAgentBreakdownSection(data: Array<{date: string, stats: AgentStats}>): string {
    const totalStats = this.aggregateStats(data.map(d => d.stats));

    return `
        <div class="agent-breakdown">
            ${Object.entries(totalStats.agentBreakdown).map(([agentType, breakdown]) => `
                <div class="metric-card">
                    <h3>${agentType}</h3>
                    <div class="metric-value">${breakdown.count}</div>
                    <p>実行回数</p>
                    <hr>
                    <p><strong>成功率:</strong> ${((breakdown.successCount / breakdown.count) * 100).toFixed(1)}%</p>
                    <p><strong>平均時間:</strong> ${(breakdown.avgExecutionTime / 1000).toFixed(2)}s</p>
                </div>
            `).join('')}
        </div>`;
  }

  private generateErrorAnalysisSection(data: Array<{date: string, stats: AgentStats}>): string {
    const totalStats = this.aggregateStats(data.map(d => d.stats));
    const topErrors = Object.entries(totalStats.errorBreakdown)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 5);

    if (topErrors.length === 0) {
      return `
          <div class="error-list">
              <h2>🎉 エラー分析</h2>
              <p style="text-align: center; color: #48bb78; font-size: 1.2em;">
                  素晴らしい!過去${data.length}日間でエラーは発生していません。
              </p>
          </div>`;
    }

    return `
        <div class="error-list">
            <h2>⚠️ よく発生するエラー (Top 5)</h2>
            ${topErrors.map(([error, count]) => `
                <div class="error-item">
                    <strong>${error}</strong>
                    <span style="float: right; color: #e53e3e;">${count}回</span>
                    <br>
                    <small>対策を検討してください</small>
                </div>
            `).join('')}
        </div>`;
  }

  private prepareChartData(data: Array<{date: string, stats: AgentStats}>) {
    return {
      labels: data.map(d => d.date),
      dailyExecutions: data.map(d => d.stats.totalExecutions),
      dailySuccessRate: data.map(d => d.stats.successRate),
      dailyAvgTime: data.map(d => d.stats.averageExecutionTime / 1000),
    };
  }

  private generateChartScripts(chartData: any): string {
    return `
        // 日次実行統計チャート
        new Chart(document.getElementById('dailyStatsChart'), {
            type: 'line',
            data: {
                labels: ${JSON.stringify(chartData.labels)},
                datasets: [{
                    label: '実行回数',
                    data: ${JSON.stringify(chartData.dailyExecutions)},
                    borderColor: '#667eea',
                    backgroundColor: 'rgba(102, 126, 234, 0.1)',
                    yAxisID: 'y'
                }, {
                    label: '成功率 (%)',
                    data: ${JSON.stringify(chartData.dailySuccessRate)},
                    borderColor: '#48bb78',
                    backgroundColor: 'rgba(72, 187, 120, 0.1)',
                    yAxisID: 'y1'
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: {
                        type: 'linear',
                        display: true,
                        position: 'left',
                    },
                    y1: {
                        type: 'linear',
                        display: true,
                        position: 'right',
                        grid: {
                            drawOnChartArea: false,
                        },
                    }
                }
            }
        });

        // パフォーマンス分布チャート
        const totalStats = ${JSON.stringify(this.aggregateStats(data.map(d => d.stats)))};
        new Chart(document.getElementById('performanceChart'), {
            type: 'doughnut',
            data: {
                labels: ['高速 (<1s)', '通常 (1-5s)', '低速 (>5s)'],
                datasets: [{
                    data: [
                        totalStats.performanceMetrics.fastExecutions,
                        totalStats.performanceMetrics.normalExecutions,
                        totalStats.performanceMetrics.slowExecutions
                    ],
                    backgroundColor: ['#48bb78', '#ed8936', '#e53e3e']
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    legend: {
                        position: 'bottom'
                    }
                }
            }
        });

        // エージェント別成功率チャート
        const agentLabels = Object.keys(totalStats.agentBreakdown);
        const agentSuccessRates = agentLabels.map(agent => 
            (totalStats.agentBreakdown[agent].successCount / totalStats.agentBreakdown[agent].count) * 100
        );

        new Chart(document.getElementById('agentSuccessChart'), {
            type: 'bar',
            data: {
                labels: agentLabels,
                datasets: [{
                    label: '成功率 (%)',
                    data: agentSuccessRates,
                    backgroundColor: '#667eea',
                    borderColor: '#4c51bf',
                    borderWidth: 1
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: {
                        beginAtZero: true,
                        max: 100
                    }
                }
            }
        });`;
  }

  private aggregateStats(statsArray: AgentStats[]): AgentStats {
    // 複数日の統計を集約する実装
    const aggregated: AgentStats = {
      totalExecutions: 0,
      successRate: 0,
      averageExecutionTime: 0,
      agentBreakdown: {},
      actionBreakdown: {},
      errorBreakdown: {},
      performanceMetrics: {
        fastExecutions: 0,
        normalExecutions: 0,
        slowExecutions: 0,
      }
    };

    // 集約ロジック実装
    // ... (実装省略)

    return aggregated;
  }

  private getPerformanceGrade(stats: AgentStats): string {
    const fastRatio = stats.performanceMetrics.fastExecutions / stats.totalExecutions;
    if (fastRatio > 0.8) return 'A+';
    if (fastRatio > 0.6) return 'A';
    if (fastRatio > 0.4) return 'B';
    if (fastRatio > 0.2) return 'C';
    return 'D';
  }

  saveDashboard(outputPath: string = './monitoring/dashboard.html') {
    const html = this.generateDashboard();
    fs.writeFileSync(outputPath, html);
    console.log(`Dashboard saved to: ${outputPath}`);
  }
}

export { DashboardGenerator };

3.3 運用自動化スクリプト

#!/bin/bash
# monitoring/deploy-monitoring.sh

set -e

echo "🚀 AI Agent Monitoring System デプロイ開始"

# 1. 必要なディレクトリ作成
echo "📁 ディレクトリ構造作成中..."
mkdir -p logs monitoring/static monitoring/reports

# 2. Node.js依存関係インストール
echo "📦 依存関係インストール中..."
npm install --production

# 3. TypeScriptコンパイル
echo "🔧 TypeScriptコンパイル中..."
npx tsc --project monitoring/tsconfig.json

# 4. 監視サービス設定
echo "⚙️ 監視サービス設定中..."

# systemdサービスファイル作成
sudo tee /etc/systemd/system/agent-monitor.service > /dev/null <<EOF
[Unit]
Description=AI Agent Monitoring Service
After=network.target

[Service]
Type=simple
User=$(whoami)
WorkingDirectory=$(pwd)
ExecStart=/usr/bin/node monitoring/dist/monitor-service.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=LOG_LEVEL=info

[Install]
WantedBy=multi-user.target
EOF

# 5. cron設定(日次レポート生成)
echo "⏰ cron ジョブ設定中..."
(crontab -l 2>/dev/null; echo "0 1 * * * cd $(pwd) && node monitoring/dist/generate-daily-report.js") | crontab -

# 6. Nginx設定(ダッシュボード公開)
if command -v nginx >/dev/null 2>&1; then
    echo "🌐 Nginx設定中..."
    sudo tee /etc/nginx/sites-available/agent-dashboard > /dev/null <<EOF
server {
    listen 8080;
    server_name localhost;

    location / {
        root $(pwd)/monitoring/static;
        index dashboard.html;
        try_files \$uri \$uri/ =404;
    }

    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host \$host;
        proxy_cache_bypass \$http_upgrade;
    }
}
EOF

    sudo ln -sf /etc/nginx/sites-available/agent-dashboard /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
fi

# 7. サービス起動
echo "▶️ サービス起動中..."
sudo systemctl daemon-reload
sudo systemctl enable agent-monitor
sudo systemctl start agent-monitor

# 8. 初回ダッシュボード生成
echo "📊 初回ダッシュボード生成中..."
node monitoring/dist/generate-dashboard.js

# 9. ヘルスチェック
echo "🏥 ヘルスチェック実行中..."
sleep 5
if systemctl is-active --quiet agent-monitor; then
    echo "✅ 監視サービスが正常に動作しています"
else
    echo "❌ 監視サービスの起動に失敗しました"
    exit 1
fi

# 10. 設定完了メッセージ
echo ""
echo "🎉 AI Agent Monitoring System デプロイ完了!"
echo ""
echo "📊 ダッシュボード: http://localhost:8080"
echo "📝 ログファイル: $(pwd)/logs/"
echo "⚙️ サービス状態: systemctl status agent-monitor"
echo ""
echo "コマンド例:"
echo "  - ダッシュボード更新: node monitoring/dist/generate-dashboard.js"
echo "  - 日次レポート生成: node monitoring/dist/generate-daily-report.js"
echo "  - ログ監視: tail -f logs/agent-logs-$(date +%Y-%m-%d).jsonl"

まとめ

本記事では、朝の概要記事で紹介した AI エージェント技術を実際に運用するための具体的な実装方法を詳解しました:

  • Claude Code サブエージェント: 専門特化エージェントの設定・運用方法
  • GitHub Copilot Agent Mode: 完全自動開発パイプラインの構築
  • MCP統合: カスタムプロトコル開発とシステム連携
  • 監視・運用: パフォーマンス監視とトラブルシューティング

これらの実装により、AI エージェントを単なるツールから戦略的開発パートナーへと進化させることができます。

関連記事