메모리 도구
메모리 도구 (Memory tool)
메모리 도구는 Claude가 대화를 넘나들며 정보를 저장하고 다시 꺼내 쓸 수 있게 해 줘요. 세션 사이에도 유지되는 메모리 파일 디렉토리에 저장하니까, 모든 걸 컨텍스트 창에 넣어두지 않아도 시간이 지나며 지식이 쌓여요. 메모리는 적시에 필요한 컨텍스트만 꺼내 쓰는 방식이라, 오래 돌아가는 에이전트 세션의 컨텍스트 창이 넘치는 걸 막아 주는 데 특히 도움이 돼요. 이 도구는 클라이언트 쪽에서 동작해서, 저장 위치와 방식을 우리 인프라로 직접 제어할 수 있어요.
출처: 문서
본문
메모리 도구는 Claude가 대화를 넘나들며 정보를 저장하고 검색할 수 있게 해 줘요. Claude는 세션 사이에도 유지되는 메모리 파일 디렉토리에서 파일을 만들고, 읽고, 갱신하고, 삭제할 수 있어서, 모든 걸 컨텍스트 창에 집어넣지 않고도 시간이 지나며 지식을 쌓을 수 있어요.
메모리는 적시에 필요한 컨텍스트만 꺼내는 방식을 지원해요. 에이전트는 관련 정보를 처음부터 다 불러오는 대신, 배운 내용을 메모리 파일에 기록해 두고 필요할 때 다시 읽어와요. 이렇게 하면 활성 컨텍스트가 현재 작업에 집중된 채로 유지돼요. 이는 컨텍스트 창을 압도해 버릴 수도 있는 오래 돌아가는 세션에서 특히 중요하죠. 더 넓은 패턴은 효과적인 컨텍스트 엔지니어링에서 확인할 수 있어요.
메모리 도구는 클라이언트 쪽에서 동작해요. Claude가 파일 작업을 요청하면 우리 애플리케이션이 그 작업을 실행해 주는 구조예요. 데이터를 어디에 어떻게 저장할지는 우리 인프라로 직접 제어할 수 있어요.
사용 사례 (Use cases)
- 여러 에이전트 세션에 걸쳐 프로젝트 컨텍스트 유지하기
- 과거 상호작용, 결정, 피드백에서 얻은 교훈을 새 작업에 적용하기
- 시간이 지나며 지식 기반을 쌓아 가기
동작 방식 (How it works)
메모리 도구가 활성화되면 Claude는 작업을 시작하기 전에 자동으로 메모리 디렉토리를 확인해요. 작업 중에는 배운 내용을 /memories 아래의 파일에 저장하고, 이후 대화에서 그 파일을 다시 읽어 이전 작업을 이어가요.
메모리 도구는 클라이언트 쪽에서 동작하므로 Claude는 메모리 작업을 요청만 해요. 우리 애플리케이션이 각 요청을 우리가 제어하는 저장소에 대해 실행하고, 그 결과를 tool_result 블록으로 돌려줘요(도구 호출 처리 참고). /memories 경로는 핸들러가 실제 저장소(사용자별 디렉토리나 데이터베이스의 키 등)에 매핑하는 접두사일 뿐이에요. 메모리는 전적으로 우리 애플리케이션 안에 있어요. 이후 대화에서 같은 tools 항목을 보내고 핸들러가 같은 저장소를 제공하면, 같은 메모리에서 이어서 작업하게 돼요. 보안을 위해 모든 메모리 작업을 /memories 디렉토리 안으로 한정해야 해요(경로 탐색 보호 참고).
예시: 메모리 도구 호출이 동작하는 방식
전형적인 상호작용은 이렇게 진행돼요:
1. 사용자 요청:
"Help me respond to this customer service ticket."
2. Claude가 메모리 디렉토리를 확인:
"I'll help you respond to the customer service ticket. Let me check my memory for any previous context."
Claude가 메모리 도구를 호출해요:
{
"type": "tool_use",
"id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"name": "memory",
"input": {
"command": "view",
"path": "/memories"
}
}
3. 애플리케이션이 디렉토리 내용을 반환:
{
"type": "tool_result",
"tool_use_id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"content": "Here're the files and directories up to 2 levels deep in /memories, excluding hidden items and node_modules:\n4.0K\t/memories\n1.5K\t/memories/customer_service_guidelines.xml\n2.0K\t/memories/refund_policies.xml"
}
4. Claude가 관련 파일을 읽어요:
{
"type": "tool_use",
"id": "toolu_01D5E6F7G8H9I0J1K2L3M4N5",
"name": "memory",
"input": {
"command": "view",
"path": "/memories/customer_service_guidelines.xml"
}
}
5. 애플리케이션이 파일 내용을 반환:
{
"type": "tool_result",
"tool_use_id": "toolu_01D5E6F7G8H9I0J1K2L3M4N5",
"content": "Here's the content of /memories/customer_service_guidelines.xml with line numbers:\n 1\t<guidelines>\n 2\t<addressing_customers>\n 3\t- Always address customers by their first name\n 4\t- Use empathetic language\n..."
}
6. Claude가 메모리를 활용해 도와줘요:
"Based on your customer service guidelines, I can help you craft a response. Please share the ticket details..."
메모리 도구는 모든 Claude 4 이상 모델에서 사용할 수 있어요. Anthropic 제공 도구 전체 목록은 도구 레퍼런스에서 확인하세요.
시작하기 (Getting started)
메모리 도구를 쓰는 데는 두 단계가 필요해요:
- 요청에 메모리 도구를 추가하세요.
tools항목{"type": "memory_20250818", "name": "memory"}이 전체 설정이에요.name은 반드시memory여야 하고, Anthropic 제공 도구에는 입력 스키마를 정의하지 않아요. - 각 메모리 명령에 대한 클라이언트 쪽 핸들러를 구현하세요. 핸들러는
/memories밖의 경로를 반드시 거부해야 하니, 작성 전에 경로 탐색 보호를 읽어 주세요.
기본 사용법 (Basic usage)
ant messages create <<'YAML'
model: claude-opus-5-5
max_tokens: 2048
tools:
- type: memory_20250818
name: memory
messages:
- role: user
content: Help me respond to this customer service ticket.
YAML
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=2048,
messages=[
{
"role": "user",
"content": "Help me respond to this customer service ticket.",
}
],
tools=[{"type": "memory_20250818", "name": "memory"}],
)
print(message)
const anthropic = new Anthropic();
const message = await anthropic.messages.create({
model: "claude-opus-5-5",
max_tokens: 2048,
messages: [
{
role: "user",
content: "Help me respond to this customer service ticket."
}
],
tools: [{ type: "memory_20250818", name: "memory" }]
});
console.log(message);
var client = new AnthropicClient();
var message = await client.Messages.Create(
new()
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 2048,
Messages =
[
new()
{
Role = Role.User,
Content = "Help me respond to this customer service ticket.",
},
],
Tools = [new MemoryTool20250818()],
}
);
Console.WriteLine(message);
client := anthropic.NewClient()
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 2048,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Help me respond to this customer service ticket.")),
},
Tools: []anthropic.ToolUnionParam{
{OfMemoryTool20250818: &anthropic.MemoryTool20250818Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
import com.anthropic.models.messages.MemoryTool20250818;
// ...
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(2048L)
.addTool(MemoryTool20250818.builder().build())
.addUserMessage("Help me respond to this customer service ticket.")
.build();
Message message = client.messages().create(params);
IO.println(message);
$client = new Client();
$message = $client->messages->create(
model: Model::CLAUDE_OPUS_5_5,
maxTokens: 2048,
messages: [
[
'role' => 'user',
'content' => 'Help me respond to this customer service ticket.',
],
],
tools: [new MemoryTool20250818],
);
echo $message;
client = Anthropic::Client.new
message = client.messages.create(
model: Anthropic::Model::CLAUDE_OPUS_5_5,
max_tokens: 2048,
messages: [
{
role: "user",
content: "Help me respond to this customer service ticket."
}
],
tools: [
{
type: "memory_20250818",
name: "memory"
}
]
)
puts message
메모리 핸들러 구현하기 (Implement the memory handler)
위와 같은 요청에 대한 Claude의 답변은 tool_use 블록으로 끝나요. 이 블록이 view /memories 같은 메모리 작업을 요청하는 거예요. 우리 애플리케이션이 그 작업을 실행하고 결과를 tool_result 블록으로 돌려준 뒤, 대화를 다시 보내 Claude가 이어갈 수 있게 해요. 이것이 표준적인 도구 사용 루프예요.
네 개 SDK에는 메모리 도구 헬퍼가 있어서 도구 인터페이스와 루프를 대신 처리해 줘요. BetaAbstractMemoryTool(Python과 C#)을 서브클래싱하거나, betaMemoryTool(TypeScript)을 쓰거나, BetaMemoryToolHandler(Java)를 구현해서 메모리를 우리 저장소(디스크의 파일, 데이터베이스, 클라우드 스토리지, 암호화된 파일 등)와 연결할 수 있어요. Python과 TypeScript에는 즉시 쓸 수 있는 로컬 파일시스템 구현인 BetaLocalFilesystemMemoryTool도 함께 들어 있어요. 헬퍼와 도구 러너 표면은 각 SDK의 beta 네임스페이스에 있는데, 메모리 도구 자체가 beta 헤더를 요구하지는 않아요. Go와 Ruby SDK에는 메모리 헬퍼가 없어서 이 예제들은 도구 사용 루프를 직접 돌려요. PHP는 핸들러 클로저를 범용 BetaRunnableTool로 감싸 주고요. 이 세 가지 모두 인메모리 저장소를 쓰는데, 우리 저장소로 교체하면 돼요.
client = anthropic.Anthropic() memory = BetaLocalFilesystemMemoryTool(base_path="./memory")
runner = client.beta.messages.tool_runner( model="claude-opus-5-5", max_tokens=1024, messages=[ { "role": "user", "content": "Remember that customer Acme Corp prefers email follow-ups.", } ], tools=[memory], )
final_message = runner.until_done() print(final_message.content)
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
import { betaMemoryTool } from "@anthropic-ai/sdk/helpers/beta/memory";
import { BetaLocalFilesystemMemoryTool } from "@anthropic-ai/sdk/tools/memory/node";
const client = new Anthropic();
const backend = await BetaLocalFilesystemMemoryTool.init("./memory");
const memory = betaMemoryTool(backend); // or pass your own handlers object
const runner = client.beta.messages.toolRunner({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Remember that customer Acme Corp prefers email follow-ups."
}
],
tools: [memory],
max_iterations: 10
});
const finalMessage = await runner;
console.log(finalMessage.content);
using Anthropic;
using Anthropic.Helpers.Beta;
using Anthropic.Models.Beta.Messages;
var client = new AnthropicClient();
// Your subclass of BetaAbstractMemoryTool
var memory = new FilesystemMemoryTool("./memories");
var runner = client.Beta.Messages.ToolRunner(
new MessageCreateParams
{
Model = Anthropic.Models.Messages.Model.ClaudeOpus5_5,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content = "Remember that customer Acme Corp prefers email follow-ups.",
},
],
},
[memory],
maxIterations: 10
);
var finalMessage = await runner.RunUntilDoneAsync();
Console.WriteLine(finalMessage);
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"slices"
"sort"
"strings"
"github.com/anthropics/anthropic-sdk-go"
)
// An in-memory store that maps memory file paths to their contents.
// Use your own storage in production.
var store = map[string]string{}
type memoryCommand struct {
Command string `json:"command"`
Path string `json:"path"`
FileText string `json:"file_text"`
OldStr string `json:"old_str"`
NewStr string `json:"new_str"`
InsertLine int `json:"insert_line"`
InsertText string `json:"insert_text"`
OldPath string `json:"old_path"`
NewPath string `json:"new_path"`
}
func executeMemory(raw json.RawMessage) string {
var cmd memoryCommand
if err := json.Unmarshal(raw, &cmd); err != nil {
return "Error: invalid memory command"
}
switch cmd.Command {
case "view":
if content, ok := store[cmd.Path]; ok {
lines := strings.Split(strings.TrimSuffix(content, "\n"), "\n")
for i, line := range lines {
lines[i] = fmt.Sprintf("%6d\t%s", i+1, line)
}
return fmt.Sprintf("Here's the content of %s with line numbers:\n%s", cmd.Path, strings.Join(lines, "\n"))
}
if cmd.Path == "/memories" {
listing := []string{"1.0K\t/memories"}
for path := range store {
listing = append(listing, "1.0K\t"+path)
}
sort.Strings(listing[1:])
return fmt.Sprintf("Here're the files and directories up to 2 levels deep in %s, excluding hidden items and node_modules:\n%s", cmd.Path, strings.Join(listing, "\n"))
}
return fmt.Sprintf("The path %s does not exist. Please provide a valid path.", cmd.Path)
case "create":
store[cmd.Path] = cmd.FileText
return "File created successfully at: " + cmd.Path
case "str_replace":
content, ok := store[cmd.Path]
if !ok || !strings.Contains(content, cmd.OldStr) {
return fmt.Sprintf("No replacement was performed, old_str `%s` did not appear verbatim in %s.", cmd.OldStr, cmd.Path)
}
store[cmd.Path] = strings.Replace(content, cmd.OldStr, cmd.NewStr, 1)
return "The memory file has been edited."
case "insert":
content, ok := store[cmd.Path]
if !ok {
return fmt.Sprintf("Error: The path %s does not exist", cmd.Path)
}
lines := strings.Split(content, "\n")
if cmd.InsertLine < 0 || cmd.InsertLine > len(lines) {
return fmt.Sprintf("Error: Invalid `insert_line` parameter: %d. It should be within the range of lines of the file: [0, %d]", cmd.InsertLine, len(lines))
}
lines = slices.Insert(lines, cmd.InsertLine, strings.TrimSuffix(cmd.InsertText, "\n"))
store[cmd.Path] = strings.Join(lines, "\n")
return fmt.Sprintf("The file %s has been edited.", cmd.Path)
case "delete":
if _, ok := store[cmd.Path]; !ok {
return fmt.Sprintf("Error: The path %s does not exist", cmd.Path)
}
delete(store, cmd.Path)
return "Successfully deleted " + cmd.Path
case "rename":
if _, ok := store[cmd.OldPath]; !ok {
return fmt.Sprintf("Error: The path %s does not exist", cmd.OldPath)
}
if _, ok := store[cmd.NewPath]; ok {
return fmt.Sprintf("Error: The destination %s already exists", cmd.NewPath)
}
store[cmd.NewPath] = store[cmd.OldPath]
delete(store, cmd.OldPath)
return fmt.Sprintf("Successfully renamed %s to %s", cmd.OldPath, cmd.NewPath)
default:
return "Error: unknown command " + cmd.Command
}
}
func main() {
client := anthropic.NewClient()
tools := []anthropic.ToolUnionParam{{OfMemoryTool20250818: &anthropic.MemoryTool20250818Param{}}}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Remember that customer Acme Corp prefers email follow-ups.")),
}
for {
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
if message.StopReason != anthropic.StopReasonToolUse {
for _, block := range message.Content {
if block.Type == "text" {
fmt.Println(block.Text)
}
}
break
}
results := []anthropic.ContentBlockParamUnion{}
for _, block := range message.Content {
if block.Type == "tool_use" {
results = append(results, anthropic.NewToolResultBlock(block.ID, executeMemory(block.Input), false))
}
}
messages = append(messages, message.ToParam(), anthropic.NewUserMessage(results...))
}
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.helpers.BetaMemoryToolHandler;
import com.anthropic.helpers.BetaToolRunner;
import com.anthropic.models.beta.messages.BetaMemoryTool20250818;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.MessageCreateParams; // beta package, not models.messages
import com.anthropic.models.beta.messages.ToolRunnerCreateParams;
import com.anthropic.models.messages.Model;
import java.nio.file.Path;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Your BetaMemoryToolHandler implementation of the six memory commands
BetaMemoryToolHandler handler = new FileSystemMemoryToolHandler(Path.of("memories"));
MessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024L)
.addTool(BetaMemoryTool20250818.builder().build())
.addUserMessage("Remember that customer Acme Corp prefers email follow-ups.")
.build();
ToolRunnerCreateParams runnerParams = ToolRunnerCreateParams.builder()
.betaMemoryToolHandler(handler)
.initialMessageParams(createParams)
.maxIterations(10)
.build();
BetaToolRunner runner = client.beta().messages().toolRunner(runnerParams);
for (BetaMessage message : runner) {
IO.println(message);
}
}
<?php
use Anthropic\Beta\Messages\BetaMemoryTool20250818;
use Anthropic\Client;
use Anthropic\Lib\Tools\BetaRunnableTool;
use Anthropic\Messages\Model;
$client = new Client();
// An in-memory store that maps memory file paths to their contents.
// Use your own storage in production.
$store = [];
$memory = new BetaRunnableTool(
definition: new BetaMemoryTool20250818,
run: function (array $input) use (&$store): string {
$path = $input['path'] ?? '';
switch ($input['command']) {
case 'view':
if (isset($store[$path])) {
$numbered = [];
foreach (explode("\n", preg_replace('/\n\z/', '', $store[$path])) as $i => $line) {
$numbered[] = sprintf("%6d\t%s", $i + 1, $line);
}
return "Here's the content of {$path} with line numbers:\n" . implode("\n", $numbered);
}
if ($path === '/memories') {
$listing = ["1.0K\t/memories"];
foreach (array_keys($store) as $stored) {
$listing[] = "1.0K\t{$stored}";
}
return "Here're the files and directories up to 2 levels deep in {$path}, excluding hidden items and node_modules:\n" . implode("\n", $listing);
}
return "The path {$path} does not exist. Please provide a valid path.";
case 'create':
$store[$path] = $input['file_text'];
return "File created successfully at: {$path}";
case 'str_replace':
$position = strpos($store[$path] ?? '', $input['old_str']);
if ($position === false) {
return "No replacement was performed, old_str `{$input['old_str']}` did not appear verbatim in {$path}.";
}
$store[$path] = substr_replace($store[$path], $input['new_str'] ?? '', $position, strlen($input['old_str']));
return 'The memory file has been edited.';
case 'insert':
if (!isset($store[$path])) {
return "Error: The path {$path} does not exist";
}
$lines = explode("\n", $store[$path]);
if ($input['insert_line'] < 0 || $input['insert_line'] > count($lines)) {
return "Error: Invalid `insert_line` parameter: {$input['insert_line']}. It should be within the range of lines of the file: [0, " . count($lines) . "]";
}
array_splice($lines, $input['insert_line'], 0, [preg_replace('/\n\z/', '', $input['insert_text'])]);
$store[$path] = implode("\n", $lines);
return "The file {$path} has been edited.";
case 'delete':
if (!isset($store[$path])) {
return "Error: The path {$path} does not exist";
}
unset($store[$path]);
return "Successfully deleted {$path}";
case 'rename':
if (!isset($store[$input['old_path']])) {
return "Error: The path {$input['old_path']} does not exist";
}
if (isset($store[$input['new_path']])) {
return "Error: The destination {$input['new_path']} already exists";
}
$store[$input['new_path']] = $store[$input['old_path']];
unset($store[$input['old_path']]);
return "Successfully renamed {$input['old_path']} to {$input['new_path']}";
default:
return "Error: unknown command {$input['command']}";
}
},
);
$runner = $client->beta->messages->toolRunner(
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Remember that customer Acme Corp prefers email follow-ups.']],
model: Model::CLAUDE_OPUS_5_5,
tools: [$memory],
maxIterations: 10,
);
$finalMessage = $runner->runUntilDone();
print_r($finalMessage->content);
require "anthropic"
client = Anthropic::Client.new
TOOLS = [{type: "memory_20250818", name: "memory"}].freeze
# An in-memory store that maps memory file paths to their contents.
# Use your own storage in production.
STORE = {}
def execute_memory(input)
path = input[:path]
case input[:command]
when "view"
if STORE.key?(path)
lines = STORE[path].chomp.split("\n", -1)
lines = [""] if lines.empty?
numbered = lines.each_with_index.map { |line, i| format("%6d\t%s", i + 1, line) }
"Here's the content of #{path} with line numbers:\n#{numbered.join("\n")}"
elsif path == "/memories"
listing = ["1.0K\t/memories"] + STORE.keys.map { |stored| "1.0K\t#{stored}" }
"Here're the files and directories up to 2 levels deep in #{path}, excluding hidden items and node_modules:\n#{listing.join("\n")}"
else
"The path #{path} does not exist. Please provide a valid path."
end
when "create"
STORE[path] = input[:file_text]
"File created successfully at: #{path}"
when "str_replace"
unless STORE.key?(path) && STORE[path].include?(input[:old_str])
return "No replacement was performed, old_str `#{input[:old_str]}` did not appear verbatim in #{path}."
end
STORE[path] = STORE[path].sub(input[:old_str]) { input[:new_str].to_s }
"The memory file has been edited."
when "insert"
return "Error: The path #{path} does not exist" unless STORE.key?(path)
lines = STORE[path].split("\n", -1)
lines = [""] if lines.empty?
if input[:insert_line] < 0 || input[:insert_line] > lines.length
return "Error: Invalid `insert_line` parameter: #{input[:insert_line]}. It should be within the range of lines of the file: [0, #{lines.length}]"
end
lines.insert(input[:insert_line], input[:insert_text].chomp)
STORE[path] = lines.join("\n")
"The file #{path} has been edited."
when "delete"
return "Error: The path #{path} does not exist" unless STORE.key?(path)
STORE.delete(path)
"Successfully deleted #{path}"
when "rename"
return "Error: The path #{input[:old_path]} does not exist" unless STORE.key?(input[:old_path])
return "Error: The destination #{input[:new_path]} already exists" if STORE.key?(input[:new_path])
STORE[input[:new_path]] = STORE.delete(input[:old_path])
"Successfully renamed #{input[:old_path]} to #{input[:new_path]}"
else
"Error: unknown command #{input[:command]}"
end
end
messages = [{role: "user", content: "Remember that customer Acme Corp prefers email follow-ups."}]
loop do
message = client.messages.create(
model: Anthropic::Model::CLAUDE_OPUS_5_5,
max_tokens: 1024,
messages: messages,
tools: TOOLS
)
unless message.stop_reason == :tool_use
puts message.content
break
end
tool_results = message.content.filter_map do |block|
next unless block.type == :tool_use
{type: "tool_result", tool_use_id: block.id, content: execute_memory(block.input)}
end
messages << {role: "assistant", content: message.content} << {role: "user", content: tool_results}
end
Go, PHP, Ruby 예제의 인메모리 저장소는 자체로 완결되도록 만든 거예요. 각각 tool_use 블록의 input에 있는 command 필드로 분기해서 도구 명령에서 설명하는 문자열을 돌려줘요. 프로덕션 핸들러는 이 데모 저장소들이 생략한 경로 검증도 필요해요. SDK 자체의 완전한 예제는 다음에서 볼 수 있어요:
- Python: examples/memory/basic.py
- TypeScript: examples/tools-helpers-memory.ts
- C#: MemoryToolExample
- Java: BetaMemoryToolExample.java
도구 명령 (Tool commands)
클라이언트 쪽 구현은 다음 명령들을 처리해야 해요. 아래 사양은 권장 동작과 반환 문자열을 설명한 거예요. Claude는 도구 결과에 담긴 텍스트를 그대로 읽으므로, 애플리케이션 필요에 따라 다른 문자열을 반환해도 돼요.
view
디렉토리 내용이나 파일 내용을 선택적인 줄 범위로 보여줘요:
{
"command": "view",
"path": "/memories/notes.txt",
"view_range": [1, 10]
}
view_range는 선택 사항이고 텍스트 파일 보기에 적용돼요. [start_line, end_line]은 해당 줄들을 돌려주고, [start_line, -1]은 start_line부터 파일 끝까지 돌려줘요.
반환 값 (Return values)
디렉토리의 경우: 파일과 디렉토리를 크기와 함께 보여주는 목록을 반환하세요:
Here're the files and directories up to 2 levels deep in {path}, excluding hidden items and node_modules:
{size}\t{path}
{size}\t{path}/{filename1}
{size}\t{path}/{filename2}
- 최대 2단계 깊이의 파일을 나열해요
- 사람이 읽기 좋은 크기를 보여줘요 (예:
5.5K,1.2M) - 숨김 항목(
.로 시작하는 파일)과node_modules를 제외해요 - 크기와 경로 사이에 탭 문자를 사용해요
빈 저장소에서 /memories를 첫 번째로 view하는 것은 오류가 아니에요. SDK의 로컬 파일시스템 메모리 도구(BetaLocalFilesystemMemoryTool)는 Claude의 첫 호출 전에 메모리 루트를 만들고, 목록 헤더 뒤에 빈 디렉토리 자신의 크기·경로 한 줄을 붙여서 반환해요.
파일의 경우: 헤더와 줄 번호와 함께 파일 내용을 반환하세요:
Here's the content of {path} with line numbers:
{line_numbers}{tab}{content}
줄 번호 형식:
- 너비: 6자, 오른쪽 정렬에 공백 패딩
- 구분자: 줄 번호와 내용 사이에 탭 문자
- 번호 매기기: 1부터 시작 (첫 줄이 1번)
- 줄 제한: 999,999줄보다 많은 파일은 오류를 반환해야 해요:
"File {path} exceeds maximum line limit of 999,999 lines."
예제 출력:
Here's the content of /memories/notes.txt with line numbers:
1 Hello World
2 This is line two
10 Line ten
100 Line one hundred
Claude의 도구 설명에는 view가 이미지 파일(.jpg, .jpeg, .png)도 표시하고, 16,000자보다 긴 파일의 텍스트 보기는 잘라서 보여준다고 나와 있어요. 이미지 경로에 대한 view 호출과 긴 파일의 후속 범위 보기를 예상하세요.
오류 처리 (Error handling)
- 파일이나 디렉토리가 없음:
"The path {path} does not exist. Please provide a valid path."
create
새 파일을 만들어요:
{
"command": "create",
"path": "/memories/notes.txt",
"file_text": "Meeting notes:\n- Discussed project timeline\n- Next steps defined\n"
}
반환 값 (Return values)
- 성공:
"File created successfully at: {path}"
오류 처리 (Error handling)
- 파일이 이미 있음:
"Error: File {path} already exists"
Claude의 도구 설명에는 create가 파일을 "만들거나 덮어쓴다"고 나와 있어요. 따라서 이미 존재하는 경로에 대한 create 호출을 예상하세요. 오류를 반환하는 것이 참고 동작이고, 덮어쓰는 것도 유효한 구현 선택이에요.
str_replace
파일의 텍스트를 교체해요:
{
"command": "str_replace",
"path": "/memories/preferences.txt",
"old_str": "Favorite color: blue",
"new_str": "Favorite color: green"
}
str_replace에서 new_str은 선택 사항이에요. 생략하면 old_str이 교체 없이 삭제돼요.
반환 값 (Return values)
- 성공:
"The memory file has been edited."뒤에 줄 번호가 있는 편집된 파일 일부
오류 처리 (Error handling)
- 파일이 없음:
"Error: The path {path} does not exist. Please provide a valid path." - 텍스트를 찾지 못함:
"No replacement was performed, old_str `\{old_str}` did not appear verbatim in {path}." - 텍스트 중복:
old_str이 여러 번 나타나면 반환하세요:"No replacement was performed. Multiple occurrences of old_str `\{old_str}` in lines: {line_numbers}. Please ensure it is unique"
디렉토리 처리 (Directory handling)
경로가 디렉토리라면 "파일이 존재하지 않음" 오류를 반환하세요.
insert
특정 줄에 텍스트를 삽입해요:
{
"command": "insert",
"path": "/memories/todo.txt",
"insert_line": 2,
"insert_text": "- Review memory tool documentation\n"
}
insert_text는 insert_line 줄 뒤에 삽입되고, 0이면 파일 맨 앞에 삽입돼요.
반환 값 (Return values)
- 성공:
"The file {path} has been edited."
오류 처리 (Error handling)
- 파일이 없음:
"Error: The path {path} does not exist" - 잘못된 줄 번호:
"Error: Invalid `insert_line` parameter: {insert_line}. It should be within the range of lines of the file: [0, {n_lines}]"
디렉토리 처리 (Directory handling)
경로가 디렉토리라면 "파일이 존재하지 않음" 오류를 반환하세요.
delete
파일이나 디렉토리를 삭제해요:
{
"command": "delete",
"path": "/memories/old_file.txt"
}
반환 값 (Return values)
- 성공:
"Successfully deleted {path}"
오류 처리 (Error handling)
- 파일이나 디렉토리가 없음:
"Error: The path {path} does not exist"
디렉토리 처리 (Directory handling)
디렉토리와 그 안의 모든 내용을 재귀적으로 삭제해요. 도구 설명은 Claude에게 /memories 디렉토리 자체는 삭제할 수 없다고 알려 주므로, 경로가 메모리 루트인 delete는 거부하세요.
rename
파일이나 디렉토리의 이름을 바꾸거나 이동해요:
{
"command": "rename",
"old_path": "/memories/draft.txt",
"new_path": "/memories/final.txt"
}
반환 값 (Return values)
- 성공:
"Successfully renamed {old_path} to {new_path}"
오류 처리 (Error handling)
- 원본이 없음:
"Error: The path {old_path} does not exist" - 대상이 이미 있음: 오류를 반환하세요(덮어쓰지 마세요):
"Error: The destination {new_path} already exists"
디렉토리 처리 (Directory handling)
디렉토리 이름을 바꿔요. 도구 설명은 Claude에게 /memories 디렉토리 자체는 이름을 바꿀 수 없다고 알려 주므로, old_path가 메모리 루트인 rename은 거부하세요.
프롬프트 가이드 (Prompting guidance)
요청의 tools에 메모리 도구가 있으면, API가 시스템 프롬프트에 이 지침을 자동으로 추가해요. 직접 보낼 필요는 없어요:
IMPORTANT: ALWAYS VIEW YOUR MEMORY DIRECTORY BEFORE DOING ANYTHING ELSE.
MEMORY PROTOCOL:
1. Use the `view` command of your `memory` tool to check for earlier progress.
2. ... (work on the task) ...
- As you make progress, record status / progress / thoughts etc in your memory.
ASSUME INTERRUPTION: Your context window might be reset at any moment, so you risk losing any progress that is not recorded in your memory directory.
Claude의 도구 설명에는 이미 메모리 디렉토리를 정리 정돈하라고 나와 있어서, 그 지침을 다시 반복할 필요는 없어요. 그래도 Claude가 어수선한 메모리 파일을 만들면 프롬프트에 다시 강조할 수 있어요:
Note: when editing your memory folder, always try to keep its content up-to-date, coherent and organized. You can rename or delete files that are no longer relevant. Do not create new files unless necessary.
Claude가 메모리에 무엇을 쓰는지도 안내할 수 있어요. 예를 들어 "메모리 시스템에는 <topic>과 관련된 정보만 적어 주세요."처럼요.
보안 고려 사항 (Security considerations)
우리 애플리케이션이 Claude가 요청한 모든 파일 작업을 실행하므로, 이 보호 장치들은 우리 책임이에요:
민감한 정보 (Sensitive information)
Claude는 보통 민감한 정보를 메모리 파일에 쓰는 걸 거부해요. 더 강한 보장이 필요하다면, 핸들러가 파일을 쓰기 전에 민감 데이터를 제거하는 검증을 추가하세요.
파일 저장 크기 (File storage size)
메모리 파일 크기를 추적하고 파일이 커질 수 있는 상한을 정하세요. view 명령이 반환하는 문자 수에 상한을 두고, 나머지는 view_range로 넘겨가며 보게 하는 걸 고려하세요.
메모리 만료 (Memory expiration)
오랫동안 접근하지 않은 메모리 파일은 주기적으로 삭제하세요.
경로 탐색 보호 (Path traversal protection)
이런 보호 장치들을 고려하세요:
- 모든 경로가
/memories로 시작하는지 검증하세요 - 경로를 정규 형식으로 변환해 메모리 디렉토리 안에 머무는지 확인하세요
../,..\\같은 시퀀스나 다른 탐색 패턴을 포함한 경로는 거부하세요- URL 인코딩된 탐색 시퀀스(
%2e%2e%2f)를 주의하세요 - 언어 내장 경로 보안 유틸리티를 사용하세요(예: Python의
pathlib.Path.resolve()와relative_to())
오류 처리 (Error handling)
메모리 도구는 텍스트 편집기 도구와 비슷한 오류 처리 패턴을 써요. 각 명령의 오류 메시지는 도구 명령에 나열되어 있어요. Claude에게 오류를 알리려면 도구 결과의 is_error를 true로 설정하고 메시지를 content에 넣으세요:
{
"type": "tool_result",
"tool_use_id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"content": "Error: The path /memories/notes.txt does not exist",
"is_error": true
}
컨텍스트 편집 연동 (Context editing integration)
메모리 도구는 컨텍스트 편집과 함께 사용해 오래 돌아가는 대화를 관리할 수 있어요. 자세한 내용은 컨텍스트 편집을 참고하세요.
컴팩션과 함께 쓰기 (Using with compaction)
메모리 도구는 컴팩션과도 함께 쓸 수 있어요. 컴팩션은 서버 쪽에서 오래된 대화 컨텍스트를 요약해 줘요. 컨텍스트 편집은 클라이언트에서 특정 도구 결과를 지우고요. 컴팩션은 대화가 컨텍스트 창 한계에 가까워지면 서버에서 자동으로 대화 전체를 요약해요.
오래 돌아가는 에이전트에는 둘 다 쓰는 걸 고려하세요. 컴팩션은 클라이언트 쪽 기록 관리 없이 활성 컨텍스트를 작게 유지하고, 메모리는 요약에서 살아남아야 하는 정보를 보존해 줘요.
다중 세션 소프트웨어 개발 패턴 (Multisession software development pattern)
여러 에이전트 세션에 걸친 소프트웨어 프로젝트라면, 작업이 진행되며 즉흥적으로 쓰는 대신 메모리 파일을 의도적으로 설정하세요. 다음 패턴은 메모리를 복구 메커니즘으로 바꿔 줘요. 각 새 세션은 마지막 세션이 기록한 상태에서 이어가게 돼요.
패턴 동작 방식 (How the pattern works)
-
초기화 세션 (Initializer session): 첫 세션이 실제 작업이 시작되기 전에 메모리 파일을 설정해요. 여기에는 진행 로그(완료한 것과 앞으로 할 것을 추적), 기능 체크리스트(작업 범위 정의), 프로젝트에 필요한 시작·초기화 스크립트 참조가 포함돼요.
-
이후 세션 (Subsequent sessions): 각 새 세션은 그 메모리 파일들을 읽는 것으로 시작해요. 코드 베이스를 다시 탐색하거나 이전 결정을 다시 추적할 필요 없이 프로젝트 상태를 복원해 줘요.
-
세션 종료 시 갱신 (End-of-session update): 세션이 끝나기 전에 완료한 것과 남은 것을 진행 로그에 갱신해요. 이렇게 하면 다음 세션이 정확한 시작점을 갖게 돼요.
핵심 원칙 (Key principle)
한 번에 하나의 기능만 작업하세요. 코드가 작성됐다고 해서가 아니라, 종단 간 검증으로 동작이 확인된 후에만 기능을 완료로 표시하세요. 이렇게 해야 진행 로그가 세션마다 정확하게 유지돼요.