0Pricing
AI Engineering Academy · レッスン

共有メモリとエージェント間通信

エージェントが読み書きするキーバリューストアベースの共有メモリ層を実装し、エージェント同士を密結合にせず非同期で協調できるようにします。

「共有メモリとエージェント間通信」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

エージェント分離の問題

マルチエージェントシステムでは、各エージェントが独自のコンテキストで動作するため、他のエージェントが何をしているか、何をしたかを把握できません。researcher agentが重要な事実を発見した場合、writer agentはどのようにそれを知ればよいのでしょうか。monitoring agentは、coder agentでエラーが発生したことをどのように知ればよいのでしょうか。共有通信メカニズムがなければ、エージェントは効果的に協調できない孤立したサイロになってしまいます。

共有メモリ:ブラックボードモデル

マルチエージェント通信の古典的な解決策はブラックボードモデルです。これは、どのエージェントからでも読み書きできる共有データストア(ブラックボード)です。エージェントは発見した情報を書き込み、他のエージェントの貢献を読み取り、共有状態を介して暗黙的に連携します。このモデルではエージェント同士が疎結合になり、互いの存在を知る必要はなく、共有メモリの構造だけを知っていれば済みます。

# Simple in-memory blackboard using a dictionary
from threading import Lock

class Blackboard:
    def __init__(self):
        self._data = {}
        self._lock = Lock()  # thread-safe for parallel agents

    def write(self, key: str, value, agent_id: str):
        with self._lock:
            self._data[key] = {'value': value, 'written_by': agent_id}
            print(f'[{agent_id}] wrote: {key}')

    def read(self, key: str):
        with self._lock:
            return self._data.get(key, {}).get('value')

    def keys(self):
        with self._lock:
            return list(self._data.keys())

blackboard = Blackboard()

Redisによる永続共有メモリ

エージェントが別々のプロセスやサービスとして動作する本番環境のマルチエージェントシステムでは、メモリ上の辞書だけでは不十分です。Redisは共有エージェントメモリで最も一般的な選択肢です。高速で、文字列、ハッシュ、リスト、ソート済みセットなどの豊富なデータ型をサポートし、有効期限を設定するTTLを備え、アトミック操作によって同時読み書きを安全に処理できます。

import redis
import json

class RedisSharedMemory:
    def __init__(self, prefix='agent:'):
        self.redis = redis.Redis(host='localhost', port=6379, decode_responses=True)
        self.prefix = prefix

    def set(self, key: str, value, ttl_seconds=3600):
        full_key = self.prefix + key
        self.redis.setex(full_key, ttl_seconds, json.dumps(value))

    def get(self, key: str):
        full_key = self.prefix + key
        raw = self.redis.get(full_key)
        return json.loads(raw) if raw else None

    def append_to_list(self, key: str, item):
        full_key = self.prefix + key
        self.redis.rpush(full_key, json.dumps(item))

    def get_list(self, key: str):
        full_key = self.prefix + key
        return [json.loads(x) for x in self.redis.lrange(full_key, 0, -1)]

memory = RedisSharedMemory(prefix='research_project:')

共有メモリの名前空間設計

複雑なマルチエージェントシステムでは、エージェントがさまざまな種類のデータを書き込むため、フラットなキー名前空間はすぐに混乱します。共有メモリを明確に整理するには、階層型名前空間を使用してください。一般的なパターンはproject_id:agent_role:data_typeです。たとえば、proj_123:researcher:findingsやproj_123:coder:error_logのようにします。これにより、プロジェクトのすべてのデータや、特定のエージェントのすべての出力を簡単に検索できます。

class NamespacedMemory:
    def __init__(self, project_id: str, agent_id: str, redis_client):
        self.base = f'{project_id}:{agent_id}'
        self.redis = redis_client

    def write_finding(self, topic: str, content: str):
        key = f'{self.base}:findings:{topic}'
        self.redis.set(key, content)

    def read_all_findings(self, project_id: str):
        # Read findings from ALL agents in this project
        pattern = f'{project_id}:*:findings:*'
        keys = self.redis.keys(pattern)
        return {k: self.redis.get(k) for k in keys}

# Usage
researcher_memory = NamespacedMemory('proj_123', 'researcher', redis_client)
researcher_memory.write_finding('competitors', 'OpenAI, Anthropic, Google...')

writer_memory = NamespacedMemory('proj_123', 'writer', redis_client)
all_findings = writer_memory.read_all_findings('proj_123')

構造化メモリと非構造化メモリ

共有メモリの内容には、LLMが読み取る非構造化データ(生のテキストのまとまり)と、構造化データ(型付きフィールドを持つJSONやPythonオブジェクト)があります。構造化メモリのほうが、プログラムからのクエリ、検証、マージを行えるため望ましい方法です。各エージェントが共有メモリに書き込む内容のスキーマを必ず定義して文書化し、あるエージェントのバグによってメモリストアが破損しないよう、そのスキーマに対して書き込みを検証してください。

from pydantic import BaseModel
from typing import Optional, list
from datetime import datetime

class ResearchFinding(BaseModel):
    topic: str
    summary: str
    sources: list[str]
    confidence: float  # 0.0 to 1.0
    written_by: str
    timestamp: datetime

# Validated write - bad data is caught before it enters shared memory
def write_finding(memory, finding_dict: dict):
    finding = ResearchFinding(**finding_dict)  # validates on creation
    memory.set(f'findings:{finding.topic}', finding.model_dump())
    print(f'Validated finding written for topic: {finding.topic}')

Pub/Subによるイベント駆動通信

エージェントは共有メモリをポーリングして更新を確認する代わりに、タスクの完了時に他のエージェントへ通知するpublish/subscribe(pub/sub)通信を使用できます。Agent Aがイベント('research_complete')を発行すると、そのイベントを購読しているAgent Bが起動して処理を開始します。Redis pub/subやRabbitMQ、Kafkaなどのメッセージキューがこのパターンをサポートしています。

import redis

# Publisher (researcher agent)
def researcher_agent(topic, redis_client):
    findings = do_research(topic)
    redis_client.set(f'findings:{topic}', findings)
    
    # Notify all subscribers that research is done
    redis_client.publish('agent_events', f'research_complete:{topic}')
    print(f'Research complete, published event for topic: {topic}')

# Subscriber (writer agent) - runs in separate process
def writer_agent_listener(redis_client):
    pubsub = redis_client.pubsub()
    pubsub.subscribe('agent_events')
    
    for message in pubsub.listen():
        if message['type'] == 'message':
            event = message['data']
            if event.startswith('research_complete:'):
                topic = event.split(':')[1]
                findings = redis_client.get(f'findings:{topic}')
                write_draft(findings)  # start writing immediately

LangGraphでの共有メモリ

LangGraphでは、エージェント間で共有されるメモリそのものがグラフ状態オブジェクトです。各ノードは、同じ型付き状態辞書から読み取り、そこへ書き込みます。LangGraphが読み書きの調整を自動的に処理します。より複雑なシナリオでは、依存性注入を使って、外部メモリクライアント(Redisやデータベース)を各ノード関数に注入することもできます。

from langgraph.graph import StateGraph
from typing import TypedDict

class SharedState(TypedDict):
    # All shared data lives here - every node can read any field
    query: str
    research_findings: str   # written by researcher, read by writer
    written_draft: str       # written by writer, read by reviewer
    review_notes: str        # written by reviewer, read by writer (loop)
    final_output: str        # written by synthesizer

# Researcher writes to 'research_findings'
def researcher(state: SharedState) -> dict:
    findings = search_and_summarize(state['query'])
    return {'research_findings': findings}  # partial state update

# Writer reads 'research_findings', writes 'written_draft'
def writer(state: SharedState) -> dict:
    draft = write_from_findings(state['research_findings'])  # reads researcher output
    return {'written_draft': draft}

メモリの競合と一貫性

複数のエージェントが共有メモリに同時に書き込むと、書き込み競合が発生することがあります。2つのエージェントが互いの作業を上書きしたり、読み取りと書き込みの間に古いデータを読み取ったりする可能性があります。これには、楽観的ロック(書き込む前にバージョンを確認する)、Redisでのアトミックなcompare-and-swap操作、または重要なメモリフィールドへの唯一の書き込み担当者となるオーケストレーターエージェントを介して書き込みを直列化する方法を使います。

# Optimistic locking with Redis
def safe_write(redis_client, key, new_value, expected_version):
    with redis_client.pipeline() as pipe:
        try:
            pipe.watch(key + ':version')  # watch for concurrent modification
            current_version = int(pipe.get(key + ':version') or 0)
            
            if current_version != expected_version:
                raise ValueError(f'Version conflict: expected {expected_version}, got {current_version}')
            
            pipe.multi()  # start transaction
            pipe.set(key, new_value)
            pipe.set(key + ':version', current_version + 1)
            pipe.execute()  # atomic commit
            print('Write successful')
        except redis.WatchError:
            print('Conflict detected, retry write')

メモリのTTLとクリーンアップ

共有エージェントメモリは時間とともに蓄積され、管理しなければ際限なく増大する可能性があります。メモリエントリには必ず有効期限(TTL)を設定し、自動的に期限切れになるようにしてください。プロジェクト単位のメモリでは、プロジェクトが完了した時点ですべてのエントリを削除します。ワークフローの想定所要時間に合ったTTL値を使用してください。一時的なデータには短いTTL(数分)、再利用する可能性のある結果には長いTTL(数時間から数日)を設定します。

def cleanup_project_memory(redis_client, project_id: str):
    pattern = f'{project_id}:*'
    keys = redis_client.keys(pattern)
    if keys:
        redis_client.delete(*keys)
        print(f'Cleaned up {len(keys)} memory entries for project {project_id}')

# Set TTL when writing
def write_with_ttl(redis_client, key, value, ttl_hours=2):
    redis_client.setex(
        key,
        ttl_hours * 3600,  # convert to seconds
        json.dumps(value)
    )

# Register cleanup callback when workflow completes
def on_workflow_complete(project_id):
    cleanup_project_memory(redis_client, project_id)
    print(f'Workflow {project_id} complete, memory cleaned up')

エージェント履歴としてのメモリ

共有メモリには、出力だけでなくエージェントのアクション履歴も保存できます。どのエージェントが、いつ、何を、なぜ実行したかを記録すると、失敗のデバッグ、最終出力がどのように生成されたかの把握、中断されたワークフローの再開に非常に役立つ監査証跡を作成できます。このアクションログは、メモリを基盤としたLangSmithトレーシングに相当します。

import time
from dataclasses import dataclass

@dataclass
class AgentAction:
    agent_id: str
    action_type: str     # 'research', 'write', 'review', 'tool_call'
    input_summary: str
    output_summary: str
    timestamp: float
    success: bool

def log_action(memory, action: AgentAction):
    key = f'action_log:{action.agent_id}:{action.timestamp}'
    memory.set(key, vars(action))

# Usage in an agent
def researcher_with_logging(state, memory):
    start = time.time()
    findings = do_research(state['query'])
    log_action(memory, AgentAction(
        agent_id='researcher',
        action_type='research',
        input_summary=state['query'][:100],
        output_summary=findings[:100],
        timestamp=start,
        success=True
    ))
    return findings

メモリアーキテクチャの選択

適切な共有メモリアーキテクチャは、デプロイモデルによって異なります。単一プロセスのLangGraphワークフローでは、グラフ状態で十分です。マルチプロセスまたは分散エージェントではRedisを使用します。再起動後も永続性が必要な長期稼働プロジェクトでは、適切なインデックスを設定したリレーショナルデータベースを使用します。サービス間のイベント駆動型の連携には、選択したストレージの上にpub/subを追加します。

理解度チェック

このレッスンで学んだ共有メモリとエージェント間通信について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、ブラックボードモデルでは任意のエージェントが読み書きできる共有データストアを使用し、エージェント同士を直接結合せずに暗黙的な連携を実現すること、分散マルチエージェントシステムで永続的な共有メモリを実現するにはRedisが第一の選択肢であること、そしてpub/subによってイベント駆動型通信を実現し、依存先の処理が完了するとエージェントが即座に反応できることを学びました。次は、エージェントタスクのコード実行ループについて学びます。

よくある質問

「共有メモリとエージェント間通信」レッスンは無料ですか?

はい。「共有メモリとエージェント間通信」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「共有メモリとエージェント間通信」で何を学びますか?

エージェントが読み書きするキーバリューストアベースの共有メモリ層を実装し、エージェント同士を密結合にせず非同期で協調できるようにします。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「共有メモリとエージェント間通信」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 単一エージェントが行き詰まる理由
  2. オーケストレーター・サブエージェントパターン
  3. LangGraphでマルチエージェントパイプラインを構築する
  4. 共有メモリとエージェント間通信
← AI Engineering Academyに戻る