0Pricing
AI Engineering Academy · 강의

공유 메모리와 에이전트 간 통신

에이전트가 읽고 쓸 수 있는 키-값 저장소를 사용해 공유 메모리 계층을 구현하고, 에이전트 간 강한 결합 없이 비동기 협업을 가능하게 합니다.

공유 메모리와 에이전트 간 통신은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

에이전트 격리의 문제

다중 에이전트 시스템에서는 각 에이전트가 자체 컨텍스트에서 실행되며 다른 에이전트가 무엇을 하고 있는지 또는 무엇을 했는지 알 수 없습니다. 조사 에이전트가 중요한 사실을 발견했다면 작성 에이전트는 이를 어떻게 알 수 있을까요? 모니터링 에이전트는 코딩 에이전트에서 오류가 발생한 시점을 어떻게 알 수 있을까요? 공유 통신 메커니즘이 없다면 에이전트는 서로 협력할 수 없는 고립된 사일로가 됩니다.

공유 메모리: Blackboard 모델

다중 에이전트 통신의 전통적인 해결책은 블랙보드 모델입니다. 이는 모든 에이전트가 읽거나 쓸 수 있는 공유 데이터 저장소(블랙보드)입니다. 에이전트는 자신이 발견한 내용을 게시하고, 다른 에이전트의 기여를 읽으며, 공유 상태를 통해 암묵적으로 협력합니다. 이 모델은 에이전트를 서로 분리하므로 에이전트가 서로의 존재를 알 필요 없이 공유 메모리의 구조만 알면 됩니다.

# 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를 사용한 이벤트 기반 통신

업데이트를 확인하기 위해 공유 메모리를 반복해서 조회하는 대신, 에이전트는 게시/구독(pub/sub) 통신을 사용해 작업을 완료했을 때 다른 에이전트에 알릴 수 있습니다. 에이전트 A가 이벤트('research_complete')를 게시하면 해당 이벤트를 구독한 에이전트 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}

메모리 충돌 및 일관성

여러 에이전트가 공유 메모리에 동시에 쓰면 쓰기 충돌이 발생할 수 있습니다. 두 에이전트가 서로의 작업을 덮어쓰거나, 읽기와 쓰기 사이에 오래된 데이터를 읽을 수 있습니다. 다음 방법으로 처리하십시오. 쓰기 전에 버전을 확인하는 낙관적 잠금, Redis의 원자적 비교 및 교환 연산, 또는 중요한 메모리 필드에 대한 유일한 작성자인 조정 에이전트를 통해 쓰기를 직렬화하는 방법이 있습니다.

# 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를 사용하십시오. 재시작 후에도 데이터가 유지되어야 하는 장기 실행 프로젝트에는 적절한 색인이 있는 관계형 데이터베이스를 사용하십시오. 서비스 간 이벤트 기반 조정이 필요하다면 선택한 저장소 위에 발행/구독 기능을 추가하십시오.

빠른 확인

이 단원에서 배운 공유 메모리와 에이전트 간 통신에 대한 이해도를 확인해 보십시오.

단원 요약

이 단원에서는 다음을 배웠습니다. 칠판 모델은 모든 에이전트가 읽고 쓸 수 있는 공유 데이터 저장소를 사용하므로 에이전트 간 직접 결합 없이 암시적 조정이 가능합니다. Redis는 분산 다중 에이전트 시스템에서 영구 공유 메모리를 구축할 때 가장 일반적으로 선택되는 도구이며, 발행/구독은 이벤트 기반 통신을 가능하게 하여 의존 작업이 완료되는 즉시 에이전트가 반응하도록 합니다. 다음으로 에이전트 작업을 위한 코드 실행 루프를 살펴보겠습니다.

자주 묻는 질문

“공유 메모리와 에이전트 간 통신” 강의는 무료인가요?

네 — “공유 메모리와 에이전트 간 통신” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“공유 메모리와 에이전트 간 통신”에서 뭘 배우나요?

에이전트가 읽고 쓸 수 있는 키-값 저장소를 사용해 공유 메모리 계층을 구현하고, 에이전트 간 강한 결합 없이 비동기 협업을 가능하게 합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 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(으)로 돌아가기