메모리
메모리 (Memory)
에이전트에게 실행을 가로질러 갱신·검색·재사용할 수 있는 지속 노트북을 주는데, 저장된 모든 파일을 매 프롬프트에 로드하지 않아요.
출처: 문서
Pydantic AI Harness가 0.x 릴리스인 동안 API는 마이너 릴리스 사이에 바뀔 수 있어요. 그럴 때는 deprecation 경고와 릴리스 노트의 마이그레이션 안내가 정확히 어떻게 업그레이드할지 알려줘요. 버전 정책을 참고하세요.
본문
노트북 모델 (Notebook model)
Memory는 각 에이전트에 마크다운 파일로 만든 노트북을 줘요.
MEMORY.md가 주요 노트북이에요. 기본적으로 경계진 발췌와 다른 파일 이름이 구분된 사용자 역할 컨텍스트로 현재 요청에 추가돼요.- 다른 파일은 더 길거나 집중된 메모를 담아요. 모델이 요청 시 읽거나 경계진 텍스트 검색으로 찾아요.
모델은 네 도구를 얻어요.
| 도구 | 용도 |
|---|---|
write_memory |
파일에 추가하거나 유일한 텍스트 조각 하나를 교체. 쓰기는 낙관적 동시성과 실행·도구 호출에서 파생된 멱등 식별자를 써요. |
read_memory |
메모리 파일 하나의 경계진 접두사 읽기. |
delete_memory |
파일 삭제. 주요 노트북은 보호돼요. |
search_memory |
구성된 결과·문자·파일 스캔 한도가 적용된 노트북 파일 검색. |
from pydantic_ai import Agent
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
agent = Agent(
'anthropic:claude-sonnet-4-6',
capabilities=[Memory(FileStore('.agent-memory'))],
defer_model_check=True,
)
네임스페이스는 도구에 공급되지 않고 애플리케이션 코드가 해석해요. 그래서 모델이 도구 호출에서 다른 사용자의 네임스페이스를 선택할 수 없어요.
주입 모드와 한도 (Injection modes and limits)
자동 주입은 기본적으로 활성화돼요. 신뢰된 사용 안내는 모델 지시문에 남고, 모델이 쓴 메모리는 현재 요청의 사용자 역할 부분에서 <memory> 구분자로 둘러싸여요. 안내, 주요 노트북, 파일 목록이 함께 유한 max_tokens 예산을 공유해요. 토큰당 문자 4개로 추정되고, 기본은 2,000 근사 토큰이에요. max_lines는 주요 노트북에 대한 추가 한도예요. 백엔드 읽기는 max_memory_size로 한정되고 요청된 경로 수는 프롬프트 예산에서 파생돼, capability가 결코 무한 파일이나 목록을 요청하지 않아요. 안 맞는 콘텐츠는 read_memory나 search_memory를 쓰라는 프롬프트와 함께 빠져요.
heading이 설정되면 같은 ## {heading}이 신뢰된 안내와 사용자 역할 메모리 블록을 모두 표시해요.
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
memory = Memory(
FileStore('.agent-memory'),
max_tokens=2_000,
max_lines=200,
)
현재 요청만 주입된 사용자 역할 부분을 유지해, 사본이 메시지 히스토리에 쌓이지 않아요. 각 모델 요청은 write_memory나 외부 갱신이 MEMORY.md를 바꾼 뒤를 포함해 최신 경계진 스냅샷을 받아요.
캐시 안정 프롬프트를 위해 inject_memory=False를 설정해요. 도구는 남아 있고, 모델은 필요할 때만 메모리를 가져올 수 있어요.
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
memory = Memory(FileStore('.agent-memory'), inject_memory=False)
injection_errors='ignore'(기본)이면 저장소 실패가 자동 주입을 건너뛰고 콘텐츠 안전 텔레메트리를 내보내요. 스팬은 백엔드 타입과 해석된 범위의 해시를 기록하고, 성공적인 주입은 수를, 실패는 예외 타입을 기록해요. 메모리 콘텐츠를 기록하지 않아요. 메모리 주입 없이 진행하는 것보다 실행이 실패해야 하면 injection_errors='raise'를 설정해요. 네임스페이스·저장소 해석기 실패는 항상 전파돼요. 도구 실패는 여전히 도구 오류로 반환되고, 이 설정은 자동 주입만 제어해요.
지속성과 동시성 (Persistence and concurrency)
저장소 계약은 낙관적 compare-and-swap 변경과 멱등성을 포함해요. 낡은 리비전에 기반한 쓰기는 동시 편집을 덮어쓰는 대신 충돌로 실패해요. 같은 실행·도구 호출을 재생해도 그 변경을 두 번 적용하지 않아요. 다른 인수로 그 작업 식별자를 재사용하면 MemoryOperationConflictError를 일으켜요. 이 보장들은 변경 작업에 속하므로 커스텀 저장소는 별도 읽기·쓰기 호출을 조합하지 않고 원자적으로 구현해야 해요.
모든 MemoryStore.read 호출은 유한 max_chars를, 모든 list_paths 호출은 유한 limit를 포함해요. 저장소는 콘텐츠가 더 있으면 경계진 접두사와 MemoryFile.truncated=True를 반환하고, 버전은 여전히 완전한 파일을 나타내요. read_memory는 그 경계진 결과를 잘림으로 표시해요. write_memory는 과대하게 외부 공급된 파일을 추가하거나 편집하기를 거부해요. 그렇게 하면 부분 읽기에서 새 콘텐츠를 파생하니까요. 백킹 저장소를 통해 먼저 교정하거나 교체하세요. 커스텀 SearchableMemoryStore.search도 결과 한도만큼 max_file_chars를 지켜야 해요.
| 저장소 | 지속성·동시성 경계 |
|---|---|
InMemoryStore() |
프로세스 수명; 그 저장소 인스턴스를 쓰는 태스크에 걸쳐 원자적. |
FileStore(directory) |
로컬 파일시스템; 원자적 마크다운 교체 + 숨은 SQLite 저널이 회복, 크로스 프로세스 compare-and-swap, 지속 멱등 영수증 제공. |
SqliteMemoryStore(database=...) |
지속 단일 호스트 저장; compare-and-swap과 멱등성은 데이터베이스 트랜잭션에서 강제. |
PostgresMemoryStore(pool) |
지속 공유 저장; compare-and-swap과 멱등성은 데이터베이스 트랜잭션에서 강제. 호출자가 풀 수명주기를 소유. |
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore, SqliteMemoryStore
local_memory = Memory(FileStore('.agent-memory'))
sqlite_memory = Memory(SqliteMemoryStore(database='.agent-memory.db'))
SqliteMemoryStore는 대신 호출자 소유 sqlite3.Connection을 쓸 수 있어요. 작업이 이벤트 루프 밖에서 실행되므로 그 연결을 check_same_thread=False로 만들고 애플리케이션에서 수명주기를 관리해요. 연결은 저장소 전용이어야 하고 모든 작업 시작에서 유휴해야 해요. 호출은 활성 호출자 트랜잭션을 커밋·롤백하는 대신 실패해요.
FileStore는 저널을 root 안의 .memory-store.sqlite3에 둬요. 저장소를 복사·백업할 때 마크다운 파일과 함께 보관하세요. capability 밖에서 마크다운 파일을 편집하면 콘텐츠 버전이 바뀌고 준비된 작업과 충돌할 수 있어요. 저널이 트랜잭션 준비와 파일시스템 교체 사이에 중단된 작업을 회복해요.
PostgresMemoryStore는 드라이버 무관 PostgresPool 프로토콜을 받아, 하네스가 특정 PostgreSQL 드라이버를 요구하지 않아요. 드라이버를 애플리케이션에서 설치·관리하세요.
pip install asyncpg
uv add asyncpg
import asyncpg
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import PostgresMemoryStore
async def build_memory() -> tuple[Memory[None], asyncpg.Pool]:
pool = await asyncpg.create_pool('postgres://localhost/app')
memory = Memory(PostgresMemoryStore(pool))
return memory, pool
애플리케이션 시작 시 build_memory를 부르고 종료 시 반환된 풀을 닫아요. 저장소가 관리하지 않아요.
네임스페이스 (Namespaces)
하나의 Agent가 여러 사용자를 서빙할 때 네임스페이스 해석기를 쓰세요. 실행당 한 번 타이핑된 의존성에서 실행되고, 그 결과는 모델 대면 도구 스키마에서 숨겨져요.
from dataclasses import dataclass
from pydantic_ai import Agent
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
@dataclass
class AppDeps:
user_id: str
agent = Agent(
'anthropic:claude-sonnet-4-6',
deps_type=AppDeps,
capabilities=[
Memory(
FileStore('/var/lib/myapp/memory'),
namespace=lambda ctx: ctx.deps.user_id,
)
],
defer_model_check=True,
)
네임스페이스 격리는 capability가 다루는 레코드를 제어해요. 커스텀·공유 백킹 저장소에 대한 권한 부여 시스템이 아니에요. 애플리케이션 의존성에서 정체성을 검증하고, 백엔드 자격 증명을 제한하고, 커스텀 저장소가 해석된 네임스페이스를 벗어날 수 없게 하세요.
에이전트 하나의 여러 메모리 (Multiple memories on one agent)
에이전트는 한 번에 여러 Memory capability를 실을 수 있어요. 개인 노트북 + 공유 조직 노트북 같은 것. 세 제약이 적용돼요.
- 각 인스턴스에 뚜렷한
agent_name또는namespace를 주세요. 주입된 블록은 해석된 범위로 추적되므로, 저장소만 다른 인스턴스는 같은 범위로 해석되어 서로의 주입을 교체해요. - 모든 인스턴스가 같은 도구 이름을 정의하므로, 도구 스키마를 구분되게 유지하려면 하나를 뺀 모든 인스턴스를
prefix_tools로 감싸요. - 각 인스턴스에 뚜렷한
heading을 설정해 모델이 블록을 별도 구역으로 보게 해요.agent_name은 저장 키이고 프롬프트에 결코 나타나지 않아 그것을 표시할 수 없어요.
from pydantic_ai import Agent
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
agent = Agent(
'anthropic:claude-sonnet-4-6',
capabilities=[
Memory(FileStore('/var/lib/myapp/memory'), heading='Your notes'),
Memory(FileStore('/var/lib/myapp/memory'), agent_name='org', heading='Org notes').prefix_tools('org'),
],
defer_model_check=True,
)
검색 (Search)
search_memory는 문자 그대로 텍스트 검색을 수행하고 항상 세 경계를 적용해요.
max_search_results는 반환되는 일치를 한정, 기본 10.max_search_result_chars는 결합된 범위 상대 파일 이름과 스니펫 텍스트를 한정, 기본 4,000자.max_search_files는 폴백 스캔이 검사할 수 있는 파일 수를 한정, 기본 1,000.
동봉된 저장소는 SearchableMemoryStore를 구현해요. MemoryStore만 구현하는 커스텀 저장소의 경우 search_memory는 최대 max_search_files + 1 경로를 요청하고 최대 max_search_files를 스캔하며 경계진 읽기를 해요. 어휘 점수는 각 범위 상대 파일 이름과 경계진 콘텐츠만 써요. 테넌트 네임스페이스와 에이전트 이름은 결코 관련성에 영향 주지 않아요. 인덱스·시맨틱 백엔드에 선택적 검색 프로토콜을 구현하면서 같은 테넌트 경계와 결과 한도를 유지해요. 시맨틱 랭킹은 내장되지 않아요.
백엔드 디스패치 전에 쿼리는 1,000자와 32개 고유 공백 분리 용어로 한정돼요. 반복된 대소문자 무시 용어는 접혀 점수나 스캔 작업을 부풀릴 수 없게 해요.
구성 (Configuration)
from pydantic_ai_harness import Memory
from pydantic_ai_harness.memory import FileStore
Memory(
FileStore('.agent-memory'),
store_resolver=None, # optional per-run store resolver
agent_name='main', # storage segment inside the namespace; never shown to the model
heading='', # optional heading on guidance and injected content
namespace='', # string or per-run resolver
inject_memory=True, # False keeps prompts cache-stable
max_tokens=2_000, # finite approximate total injection budget
max_lines=200, # additional main-notebook line limit
max_memory_size=65_536, # per-file read, search, and write boundary
max_search_results=10,
max_search_result_chars=4_000,
max_search_files=1_000,
injection_errors='ignore', # or 'raise'
guidance=None, # None uses the default notebook guidance
)
에이전트 스펙 (Agent specs)
파이썬 스펙에서 에이전트를 만들 때 Memory를 커스텀 capability 타입으로 등록하세요.
from pydantic_ai import Agent
from pydantic_ai_harness import Memory
agent = Agent.from_spec(
{
'model': 'anthropic:claude-sonnet-4-6',
'capabilities': [
{'Memory': {'backend': 'file', 'directory': '.agent-memory'}},
],
},
custom_capability_types=[Memory],
defer_model_check=True,
)
직렬화 가능한 백엔드는 memory, file, sqlite예요. 네임스페이스 callable과 라이브 PostgreSQL 풀은 파이썬에서 구성해야 해요.
Durable execution 호환성 (Durable execution compatibility)
| 실행 모드 | 지원 |
|---|---|
정상 Agent.run 호출 |
자동 주입 또는 온디맨드 도구로 지원. |
| Temporal과 Prefect | 자동 스냅샷 로딩은 저널링된 capability 작업. 재생 시 기록된 스냅샷을 재사용해요. store_resolver와 callable namespace는 그 작업 전에 실행되어 결정적이고 백엔드 I/O가 없어야 해요. 백엔드를 쿼리하는 테넌트별 해석기는 워크플로우에 안전하지 않아요. |
| DBOS | 자동 스냅샷 로딩은 DBOS 스텝. 평범한 FunctionToolset 호출은 DBOS-durable이 아니에요. 필요하면 메모리 도구 작업을 애플리케이션 제공 DBOS 스텝으로 감싸요. |
Memory는 안정 기본 id='memory'를 지녀 구성 없이 durable 회복이 작동해요. 메모리 백엔드와 워크플로우 상태 백엔드는 독립적으로 유지돼요. durable execution이 인메모리 노트북을 지속하게 하지 않아요.
자동 스냅샷 로딩을 쓰는 각 모델 요청이 워크플로우 히스토리에 경계진 스냅샷 결과 하나를 기록하므로, 엔진의 히스토리 한도를 염두에 두고 max_memory_size와 max_tokens를 고르세요.
보안과 출처 (Security and provenance)
Memory는 모델이 쓴, 신뢰할 수 없는 콘텐츠로 미래 프롬프트에 다시 들어갈 수 있어요. 그것을 구분된 사용자 역할 부분에 두는 것이 모델 지시문에 비해 권위를 낮추지만, 하드 프롬프트 인젝션 경계는 아니에요. 덜 신뢰된 행위자가 저장소에 쓸 수 있으면 inject_memory=False를 쓰고, 더 강한 격리가 필요하면 애플리케이션 제어 검색을 통해서만 메모리를 노출하세요. 백엔드, 보존 정책, 접근 제어가 적절하지 않으면 비밀을 저장하지 마세요. 다른 신뢰 영역으로 렌더링하기 전에 콘텐츠를 샌니타이즈하세요.
Memory 레코드는 출처 인용이나 검증된 출처를 지니지 않아요. 애플리케이션이 감사 가능한 사실을 필요로 하면 메모 자체에 출처를 저장하거나 커스텀 저장소와 스키마를 구현해요. 낙관적 동시성은 유실 갱신을 막아요. 기억된 주장이 사실임을 확립하지는 않아요.
API 참조 (API reference)
공개 모듈은 Memory, MemoryToolset, 동봉된 저장소, 저장소 프로토콜, 변경·검색 결과 모델, 충돌 예외를 내보내요. pydantic_ai_harness.memory에서 import하세요.
Memory
Bases: AbstractCapability[AgentDepsT]
세션을 가로지르는 지속 에이전트 메모리.
MEMORY.md는 사용자 역할 컨텍스트로 주입되고 더 긴 주제 파일은 read_memory와 search_memory로 쓸 수 있어요. 자동 스냅샷 로딩은 capability 작업을 지원하는 durability 엔진이 저널링해요.
속성
- store — 저장 백엔드. 기본은 프로세스 수명만 지속. Default:
InMemoryStore - store_resolver — 선택적 실행별 저장소 해석기. 해석기 실패는 항상 전파. Default:
None - agent_name — 네임스페이스 안에서 메모리를 격리하는 저장 세그먼트. 범위 키의 일부일 뿐, 모델 대면 메모리 블록에 결코 렌더링되지 않아요. Default:
'main' - heading — 렌더링된 안내와 주입된 메모리 블록의 마크다운 제목. 설정되면
## {heading}으로 렌더링. 기본은 빈 것. 블록이 이미<memory>표시 안에 있으니 추가되지 않아요. 여러Memorycapability가 에이전트 하나를 공유하면 인스턴스별 뚜렷한 값을 설정해(예:heading='Team notes') 모델이 블록을 구별하게 해요. Default:'' - namespace — 정적 또는 실행별 테넌트 네임스페이스, 도구 인수로 결코 노출 안 됨. Default:
'' - inject_memory — 참이면 저장된 메모리 주입, 아니면 정적 도구 안내만 주입. Default:
True - max_tokens — 완전한 주입된 메모리 섹션의 근사 총 토큰 상한. Default:
2000 - max_lines — 주입에 대해 고려되는
MEMORY.md콘텐츠 줄 최대 수. Default:200 - max_memory_size — 백엔드 읽기·검색·쓰기의 파일별 문자 경계. Default:
65536 - max_search_results — 검색 하나가 반환하는 최대 일치. Default:
10 - max_search_result_chars — 검색 하나가 반환하는 최대 결합 스니펫 문자. Default:
4000 - max_search_files — 검색 하나가 스캔하는 최대 파일. Default:
1000 - guidance — 주입된 사용 안내 덮어쓰기.
''는 안내 비활성화. Default:None - injection_errors — 자동 주입 중 저장소 실패를 무시할지 일으킬지. Default:
'ignore'
메서드
- for_run —
@async def for_run(ctx) -> Memory[AgentDepsT]. 범위 해석을 이 실행에 격리한 복제본 반환. - resolve_scope —
def resolve_scope(ctx) -> tuple[MemoryStore, str]. 캐시된 실행 범위 반환, 또는 직접 toolset 사용용으로 해석. - get_toolset —
def get_toolset() -> AgentToolset[AgentDepsT] | None. 안정memorytoolset 제공. - get_instructions —
def get_instructions() -> AgentInstructions[AgentDepsT] | None. 메모리 사용에 대한 신뢰된 정적 안내 제공. 저장된 메모리는 모델이 쓴 콘텐츠가 지시문 채널에 놓이지 않게before_model_request가 사용자 역할 컨텍스트로 별도 추가해요. - before_model_request —
@async def before_model_request(ctx, request_context) -> ModelRequestContext. 경계진 메모리 스냅샷을 현재 사용자 요청에만 추가. - from_spec —
@classmethod def from_spec(...) -> Memory[AgentDepsT]. 직렬화 가능한 옵션에서 메모리 capability 구성. - get_serialization_name —
@classmethod def get_serialization_name(cls) -> str | None. 커스텀 capability 스펙이 쓰는 이름 반환.
MemoryToolset
Bases: FunctionToolset[AgentDepsT]
CAS와 지속 멱등성을 가진 범위 한정 읽기/쓰기/삭제/검색 도구.
안정 memory ID가 Temporal과 Prefect가 이 정적 toolset을 감싸게 해요. DBOS는 현재 평범한 FunctionToolset을 durable 스텝으로 바꾸지 않으므로, DBOS durability를 요구하는 애플리케이션은 그 래퍼를 제공해야 해요.
메서드
- write_memory —
@async def write_memory(ctx, content, file=MAIN_FILENAME, old_text=None) -> MemoryWriteResult. 추가 또는 유일 교체로 지속 메모리 쓰기.old_text를 생략하면 추가하고 필요 시 파일 생성.old_text를 넘기면 정확히 하나의 일치 구절 교체. 빈content로 그 구절 제거. 짧은 지속 사실은MEMORY.md에, 더 길거나 진화하는 주제는 별도 파일에. 낡은 항목은 모순된 중복을 추가하지 말고 갱신하세요.ctx: 프레임워크 제공 실행 컨텍스트.content: 추가할 텍스트, 또는old_text의 교체 텍스트.file: 메모리 파일 이름, 기본MEMORY.md.old_text: 교체할 정확한 구절, 정확히 한 번 나와야 함. - read_memory —
@async def read_memory(ctx, file) -> str. 메모리 파일 하나의 경계진 접두사 읽기. 메모리는 낡은 백그라운드 컨텍스트일 수 있으니 의존하기 전에 휘발성 사실을 검증하세요.file: 주입이나 검색이 반환한 메모리 파일 이름. - delete_memory —
@async def delete_memory(ctx, file) -> MemoryDeleteResult. 더는 유용하지 않은 비주요 메모리 파일 삭제.MEMORY.md는 삭제할 수 없어요. 대신write_memory로 텍스트를 제거하거나 고치세요.file: 삭제할 메모리 파일 이름. - search_memory —
@async def search_memory(ctx, query) -> MemorySearchResponse. 현재 테넌트·에이전트 범위에서 메모리 파일 검색. 결과는 경계진 스니펫. 더 큰 경계진 발췌가 관련 있으면read_memory를 불러요.query: 메모리 파일 이름과 콘텐츠에서 찾을 용어.