메모리
메모리 (Memory)
Deep Agents로 만든 에이전트에 영구 메모리를 추가해서 대화를 거듭할수록 학습하고 개선되게 하세요.
메모리는 에이전트가 대화를 거듭하며 학습하고 개선되게 해줍니다. Deep Agents는 파일 시스템 기반 메모리로 메모리를 일급 기능으로 제공합니다. 에이전트는 메모리를 파일로 읽고 쓰며, 그 파일이 어디에 저장될지는 백엔드로 제어할 수 있습니다.
메모리 작동 방식 (How memory works)
- 에이전트를 메모리 파일에 연결합니다. 에이전트를 만들 때
memory=로 파일 경로를 전달하세요. 절차적 메모리(에이전트에게 작업을 수행하는 방법을 알려주는 재사용 가능한 지침)를 위해skills=로 스킬을 전달할 수도 있습니다. 백엔드는 파일이 어디에 저장되고 누가 접근할 수 있는지를 제어합니다. - 에이전트가 메모리를 읽습니다. 에이전트는 시작 시 메모리 파일을 시스템 프롬프트에 로드하거나, 대화 중에 필요할 때 온디맨드로 읽을 수 있습니다. 예를 들어 스킬은 온디맨드 로딩을 사용합니다. 에이전트는 시작 시 스킬 설명만 읽고, 작업과 일치할 때만 전체 스킬 파일을 읽습니다. 이렇게 하면 기능이 필요해질 때까지 컨텍스트를 가볍게 유지합니다.
- 에이전트가 메모리를 업데이트합니다(선택 사항). 에이전트가 새 정보를 배우면 내장
edit_file도구로 메모리 파일을 업데이트할 수 있습니다. 업데이트는 대화 중에 일어날 수도 있고(기본값), 백그라운드 통합을 통해 대화 사이에 백그라운드에서 일어날 수도 있습니다. 변경 사항은 지속되어 다음 대화에서도 사용할 수 있습니다. 모든 메모리가 쓰기 가능한 것은 아닙니다. 개발자가 정의한 스킬과 조직 정책은 보통 읽기 전용입니다. 자세한 내용은 읽기 전용 vs 쓰기 가능 메모리를 참조하세요.
가장 흔한 두 패턴은 에이전트 범위 메모리(모든 사용자에게 공유)와 사용자 범위 메모리(사용자별 격리)입니다.
코딩 에이전트가 AGENTS.md를 통해 발견하는 생성된 저장소 위키에 대해서는 OpenWiki를 참조하세요.
범위가 지정된 메모리 (Scoped memory)
에이전트 메모리는 범위를 지정해서 같은 메모리 파일을 에이전트를 사용하는 모든 사람이 접근하게 하거나, 메모리 파일을 사용자마다 개별로 지정할 수 있습니다.
에이전트 범위 메모리 (Agent-scoped memory)
에이전트가 시간이 지나며 진화하는 자신만의 영구 정체성을 갖게 하세요. 에이전트 범위 메모리는 모든 사용자에게 공유되므로, 에이전트는 모든 대화를 통해 자신만의 페르소나, 축적된 지식, 학습한 선호도를 쌓아갑니다. 사용자와 상호작용하며 전문성을 키우고, 접근 방식을 다듬고, 효과가 있는 것을 기억합니다. 쓰기 접근 권한이 있으면 스킬도 학습하고 업데이트할 수 있습니다.
핵심은 백엔드 네임스페이스입니다. 그것을 (assistant_id,)로 설정하면 이 에이전트의 모든 대화가 같은 메모리 파일을 읽고 씁니다.
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend } from "deepagents";
const agent = createDeepAgent({
memory: ["/memories/AGENTS.md"],
skills: ["/skills/"],
backend: new CompositeBackend(
new StateBackend(),
{
"/memories/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.assistantId], // [!code highlight]
}),
"/skills/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.assistantId], // [!code highlight]
}),
},
),
});
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend, createFileData } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
const store = new InMemoryStore(); // Use platform store when deploying to LangSmith
// Seed the memory file
await store.put(
["my-agent"],
"/memories/AGENTS.md",
createFileData(`## Response style
- Keep responses concise
- Use code examples where possible
`),
);
// Seed a skill
await store.put(
["my-agent"],
"/skills/langgraph-docs/SKILL.md",
createFileData(`---
name: langgraph-docs
description: Fetch relevant LangGraph documentation to provide accurate guidance.
---
# langgraph-docs
Use the fetch_url tool to read https://docs.langchain.com/llms.txt, then fetch relevant pages.
`),
);
const agent = createDeepAgent({
memory: ["/memories/AGENTS.md"],
skills: ["/skills/"],
backend: (rt) => new CompositeBackend(
new StateBackend(rt),
{
"/memories/": new StoreBackend(rt, {
namespace: (rt) => ["my-agent"],
}),
"/skills/": new StoreBackend(rt, {
namespace: (rt) => ["my-agent"],
}),
},
),
store,
});
// Thread 1: the agent learns a new preference and saves it to memory
const config1 = { configurable: { thread_id: crypto.randomUUID() } };
await agent.invoke({
messages: [{ role: "user", content: "I prefer detailed explanations. Remember that." }],
}, config1);
// Thread 2: the agent reads memory and applies the preference
const config2 = { configurable: { thread_id: crypto.randomUUID() } };
await agent.invoke({
messages: [{ role: "user", content: "Explain how transformers work." }],
}, config2);
사용자 범위 메모리 (User-scoped memory)
각 사용자에게 자신만의 메모리 파일을 주세요. 에이전트는 사용자별 선호도, 컨텍스트, 기록을 기억하면서 핵심 에이전트 지침은 고정된 상태로 유지합니다. 사용자가 사용자 범위 백엔드에 저장된 경우 사용자별 스킬을 가질 수도 있습니다.
네임스페이스가 (user_id,)를 사용하므로 각 사용자는 격리된 메모리 파일 사본을 얻습니다. 사용자 A의 선호도는 사용자 B의 대화에 절대 새지 않습니다.
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend } from "deepagents";
const agent = createDeepAgent({
memory: ["/memories/preferences.md"],
skills: ["/skills/"],
backend: new CompositeBackend(
new StateBackend(),
{
"/memories/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity],
}),
"/skills/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity],
}),
},
),
});
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend, createFileData } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
const store = new InMemoryStore(); // Use platform store when deploying to LangSmith
// Seed preferences for two users
await store.put(
["user-alice"],
"/memories/preferences.md",
createFileData(`## Preferences
- Likes concise bullet points
- Prefers Python examples
`),
);
await store.put(
["user-bob"],
"/memories/preferences.md",
createFileData(`## Preferences
- Likes detailed explanations
- Prefers TypeScript examples
`),
);
// Seed a skill for Alice
await store.put(
["user-alice"],
"/skills/langgraph-docs/SKILL.md",
createFileData(`---
name: langgraph-docs
description: Fetch relevant LangGraph documentation to provide accurate guidance.
---
# langgraph-docs
Use the fetch_url tool to read https://docs.langchain.com/llms.txt, then fetch relevant pages.
`),
);
const agent = createDeepAgent({
memory: ["/memories/preferences.md"],
skills: ["/skills/"],
backend: (rt) => new CompositeBackend(
new StateBackend(rt),
{
"/memories/": new StoreBackend(rt, {
namespace: (rt) => [rt.serverInfo.user.identity],
}),
"/skills/": new StoreBackend(rt, {
namespace: (rt) => [rt.serverInfo.user.identity],
}),
},
),
store,
});
// When deployed, each authenticated request resolves
// `rt.serverInfo.user.identity` to the calling user, so Alice and Bob
// automatically see only their own preferences.
await agent.invoke(
{ messages: [{ role: "user", content: "How do I read a CSV file?" }] },
{ configurable: { thread_id: crypto.randomUUID() } },
);
고급 사용법 (Advanced usage)
메모리 경로와 범위의 기본 구성 옵션 외에도, 메모리에 대한 더 고급 파라미터를 구성할 수 있습니다:
| 차원 | 답하는 질문 | 옵션 |
|---|---|---|
| 지속 시간 | 얼마나 오래 지속되나요? | 단기(단일 대화) 또는 장기(대화를 넘어서) |
| 정보 유형 | 어떤 종류의 정보인가요? | 일화적(과거 경험), 절차적(지침과 스킬), 또는 의미적(사실) |
| 범위 | 누가 보고 수정할 수 있나요? | 사용자, 에이전트, 또는 조직 |
| 업데이트 전략 | 메모리는 언제 쓰이나요? | 대화 중(기본값) 또는 대화 사이 |
| 검색 | 메모리는 어떻게 읽히나요? | 프롬프트에 로드(기본값) 또는 온디맨드(예: 스킬) |
| 에이전트 권한 | 에이전트가 메모리에 쓸 수 있나요? | 읽기-쓰기(기본값) 또는 읽기 전용(공유 정책용) |
일화적 메모리 (Episodic memory)
일화적 메모리는 과거 경험의 기록을 저장합니다. 무엇이 일어났는지, 어떤 순서로, 결과가 어땠는지요. 의미적 메모리(AGENTS.md 같은 파일에 저장된 사실과 선호도)와 달리, 일화적 메모리는 전체 대화 컨텍스트를 보존하므로 에이전트는 그것에서 배운 무엇뿐 아니라 문제가 어떻게 해결됐는지도 회상할 수 있습니다. 코딩 에이전트를 위한 저장소 수준 위키를 생성하고 유지하려면 OpenWiki를 참조하세요.
Deep Agents는 이미 체크포인터를 사용하며, 이것이 일화적 메모리를 지원하는 메커니즘입니다. 모든 대화는 체크포인트된 스레드로 유지됩니다.
과거 대화를 검색 가능하게 만들려면 스레드 검색을 도구로 감싸세요. user_id는 파라미터로 전달하는 대신 런타임 컨텍스트에서 가져옵니다:
import { Client } from "@langchain/langgraph-sdk";
import { tool } from "@langchain/core/tools";
const client = new Client({ apiUrl: "<DEPLOYMENT_URL>" });
const searchPastConversations = tool(
async ({ query }, runtime) => {
const userId = runtime.serverInfo.user.identity; // [!code highlight]
const threads = await client.threads.search({
metadata: { userId },
limit: 5,
});
const results = [];
for (const thread of threads) {
const history = await client.threads.getHistory(thread.threadId);
results.push(history);
}
return JSON.stringify(results);
},
{
name: "search_past_conversations",
description: "Search past conversations for relevant context.",
}
);
메타데이터 필터를 조정해서 스레드 검색을 사용자 또는 조직 단위로 범위를 지정할 수 있습니다:
// Search conversations for a specific user
const userThreads = await client.threads.search({
metadata: { userId },
limit: 5,
});
// Search conversations across an organization
const orgThreads = await client.threads.search({
metadata: { orgId },
limit: 5,
});
이것은 복잡한 다단계 작업을 수행하는 에이전트에 유용합니다. 예를 들어 코딩 에이전트는 과거 디버깅 세션을 되돌아보고 곧바로 가능성이 높은 근본 원인으로 건너뛸 수 있습니다.
조직 수준 메모리 (Organization-level memory)
조직 수준 메모리는 사용자 범위 메모리와 같은 패턴을 따르지만, 사용자별 네임스페이스 대신 조직 전체 네임스페이스를 사용합니다. 조직의 모든 사용자와 에이전트에 적용되어야 하는 정책이나 지식에 사용하세요.
조직 메모리는 공유 상태를 통한 프롬프트 인젝션을 막기 위해 보통 읽기 전용입니다. 자세한 내용은 읽기 전용 vs 쓰기 가능 메모리를 참조하세요.
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend } from "deepagents";
const agent = createDeepAgent({
memory: [
"/memories/preferences.md",
"/policies/compliance.md",
],
backend: new CompositeBackend(
new StateBackend(),
{
"/memories/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity],
}),
"/policies/": new StoreBackend({
namespace: (rt) => [rt.context.orgId],
}),
},
),
});
애플리케이션 코드에서 조직 메모리를 채우세요:
import { Client } from "@langchain/langgraph-sdk";
import { createFileData } from "deepagents";
const client = new Client({ apiUrl: "<DEPLOYMENT_URL>" });
await client.store.putItem(
[orgId],
"/compliance.md",
createFileData(`## Compliance policies
- Never disclose internal pricing
- Always include disclaimers on financial advice
`),
);
권한을 사용해 조직 수준 메모리가 읽기 전용임을 강제하거나, 커스텀 검증 로직을 위해 정책 훅을 사용하세요.
백그라운드 통합 (Background consolidation)
기본적으로 에이전트는 대화 중(hot path)에 메모리를 씁니다. 대안은 메모리를 대화 사이에 백그라운드 작업으로 처리하는 것으로, 때로 sleep time compute라고도 합니다. 별도의 deep agent가 최근 대화를 검토하고 핵심 사실을 추출한 뒤 기존 메모리와 병합합니다.
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| Hot path(대화 중) | 메모리를 즉시 사용 가능, 사용자에게 투명 | 지연 시간 추가, 에이전트가 멀티태스킹해야 함 |
| 백그라운드(대화 사이) | 사용자 대면 지연 없음, 여러 대화에 걸쳐 종합 가능 | 다음 대화까지 메모리 사용 불가, 두 번째 에이전트 필요 |
대부분의 애플리케이션에서는 hot path로 충분합니다. 지연을 줄이거나 여러 대화에 걸친 메모리 품질을 높여야 할 때 백그라운드 통합을 추가하세요.
권장 패턴은 주 에이전트와 함께 통합 에이전트를 배포하는 것입니다. 그것은 최근 대화 기록을 읽고 핵심 사실을 추출해 메모리 스토어에 병합하는 deep agent이며, cron 일정으로 트리거합니다. 사용자가 실제로 에이전트와 상호작용하는 빈도를 반영하는 주기를 고르세요. 매일 꾸준히 트래픽이 있는 채팅 제품은 몇 시간마다 통합할 수 있지만, 일주일에 몇 번만 쓰는 도구는 매일 밤이나 주간 단위로만 실행하면 충분합니다. 사용자가 대화하는 것보다 훨씬 자주 통합하면 무의미한 실행에 토큰만 낭비합니다.
통합 에이전트 (Consolidation agent)
통합 에이전트는 최근 대화 기록을 읽고 핵심 사실을 메모리 스토어에 병합합니다. langgraph.json에서 주 에이전트와 함께 등록하세요:
import { createDeepAgent } from "deepagents";
import { Client } from "@langchain/langgraph-sdk";
import { tool } from "@langchain/core/tools";
const sdkClient = new Client({ apiUrl: "<DEPLOYMENT_URL>" });
const searchRecentConversations = tool(
async ({ query }, runtime) => {
const userId = runtime.serverInfo.user.identity; // [!code highlight]
const since = new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString();
const threads = await sdkClient.threads.search({
metadata: { userId },
updatedAfter: since,
limit: 20,
});
const conversations = [];
for (const thread of threads) {
const history = await sdkClient.threads.getHistory(thread.threadId);
conversations.push(history.values.messages);
}
return JSON.stringify(conversations);
},
{
name: "search_recent_conversations",
description: "Search this user's conversations updated in the last 6 hours.",
}
);
const agent = createDeepAgent({
model: "google_genai:gemini-3.6-flash",
systemPrompt: `Review recent conversations and update the user's memory file.
Merge new facts, remove outdated information, and keep it concise.`,
tools: [searchRecentConversations],
});
export { agent };
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent.ts:agent",
"consolidation_agent": "./src/consolidation-agent.ts:agent"
},
"env": ".env"
}
Cron
cron 작업이 고정 일정으로 통합 에이전트를 실행합니다. 에이전트는 최근 대화를 검색해 메모리로 종합합니다. 일정을 사용 패턴에 맞춰 통합 실행이 실제 활동을 대략 추적하게 하세요.
graph LR
Store[(Memory store)] -.->|reads| Conv1[Conversation 1]
Store -.->|reads| Conv2[Conversation 2]
Cron[Cron schedule] -->|periodic| Agent[Consolidation agent]
Agent -->|writes| Store
classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33
classDef schedule fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
class Conv1,Conv2 trigger
class Agent process
class Store output
class Cron schedule
cron 작업으로 통합 에이전트를 예약하세요:
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: "<DEPLOYMENT_URL>" });
const cronJob = await client.crons.create(
"consolidation_agent",
{
schedule: "0 */6 * * *",
input: { messages: [{ role: "user", content: "Consolidate recent memories." }] },
},
);
백그라운드 프로세스로 에이전트를 배포하는 방법에 대한 자세한 내용은 프로덕션 배포를 참조하세요.
읽기 전용 vs 쓰기 가능 메모리 (Read-only vs writable memory)
기본적으로 에이전트는 메모리 파일을 읽고 쓸 수 있습니다. 조직 정책이나 규정 준수 규칙 같은 공유 상태의 경우 메모리를 읽기 전용으로 만들어 에이전트가 그것을 참조할 수는 있지만 수정할 수 없게 하고 싶을 수 있습니다. 이렇게 하면 공유 메모리를 통한 프롬프트 인젝션을 막고 파일에 무엇이 있는지를 오직 애플리케이션 코드만 제어하도록 보장합니다.
| 권한 | 사용 사례 | 작동 방식 |
|---|---|---|
| 읽기-쓰기(기본값) | 사용자 선호도, 에이전트 자체 개선, 학습한 스킬 | 에이전트가 edit_file 도구로 파일을 업데이트 |
| 읽기 전용 | 조직 정책, 규정 준수 규칙, 공유 지식 베이스, 개발자가 정의한 스킬 | 애플리케이션 코드 또는 Store API로 채움. 특정 경로에 대한 쓰기를 거부하려면 권한을, 커스텀 검증 로직에는 정책 훅을 사용. |
보안 고려 사항: 한 사용자가 다른 사용자가 읽는 메모리에 쓸 수 있으면, 악의적인 사용자가 공유 상태에 지침을 주입할 수 있습니다. 이를 완화하려면:
- 특별한 공유 이유가 없으면 기본적으로 사용자 범위
(user_id)를 사용하세요 - 공유 정책에는 읽기 전용 메모리를 사용하세요(에이전트가 아닌 애플리케이션 코드로 채움)
- 에이전트가 공유 메모리에 쓰기 전에 human-in-the-loop 검증을 추가하세요. 민감한 경로에 대한 쓰기에 인간 승인을 요구하려면 interrupt를 사용하세요.
읽기 전용 메모리를 강제하려면 권한을 사용해 특정 경로에 대한 쓰기를 선언적으로 거부하세요. 커스텀 검증 로직(속도 제한, 감사 로깅, 콘텐츠 검사)에는 백엔드 정책 훅을 사용하세요.
동시 쓰기 (Concurrent writes)
여러 스레드가 메모리에 병렬로 쓸 수 있지만, 같은 파일에 대한 동시 쓰기는 last-write-wins 충돌을 일으킬 수 있습니다. 사용자 범위 메모리에서는 사용자가 보통 한 번에 하나의 활성 대화를 가지므로 드뭅니다. 에이전트 범위나 조직 범위 메모리의 경우 백그라운드 통합으로 쓰기를 직렬화하거나, 주제별로 메모리를 별도 파일로 구성해 경합을 줄이는 것을 고려하세요.
실제로 충돌로 인해 쓰기가 실패하면 LLM이 보통 재시도하거나 우아하게 복구할 만큼 똑똑하므로, 단 한 번의 유실된 쓰기는 치명적이지 않습니다.
같은 배포의 여러 에이전트 (Multiple agents in the same deployment)
공유 배포에서 각 에이전트에게 자신만의 메모리를 주려면 네임스페이스에 assistant_id를 추가하세요:
new StoreBackend({
namespace: (rt) => [
rt.serverInfo.assistantId, // [!code highlight]
rt.serverInfo.user.identity,
],
})
사용자 범위 지정 없이 에이전트별 격리만 필요하다면 assistant_id만 사용하세요.
더 알아보기
- OpenWiki: 코딩 에이전트가
AGENTS.md로 찾는 저장소 위키 생성 및 유지 - Backends: 메모리 파일이 저장될 위치 선택
- Context engineering: 단기 메모리, 오프로딩, 요약
- Skills: 온디맨드 절차적 메모리
- 이 문서를 MCP로 연결하면 Claude, VSCode 등에서 실시간 답변을 받을 수 있어요.
- GitHub에서 이 페이지 편집하기 또는 이슈 제출하기.