tracks
대부분의 LLM 에이전트는 실행 때마다 처음부터 다시 시작합니다. 사용자 이름, 마지막 대화, 작업 중이던 파일을 잊어버립니다. 사용자별 정보는 매 차례 다시 확인해야 합니다.
이 튜토리얼은 에이전트에 지속형 메모리를 추가해 다음 실행이 마지막 실행이 끝난 지점부터 시작되도록 합니다. 메모리 레이어는 호스팅형 Supermemory로, 사용자별 사실을 저장하고 한 번의 호출로 반환하는 API입니다. Python으로 개인 운동 트레이너를 빌드합니다. 이 에이전트는 운동을 기록하고, 다음 세션을 제안하며, 스크립트를 별도로 실행해도 선호도와 최근 기록을 기억합니다.
구성은 단출합니다. Supermemory가 메모리를 담당하고, OpenAI Agents SDK가 에이전트 루프를 실행합니다. 트레이너는 두 개의 Python 파일과 pyproject.toml 파일로 이루어집니다.
Python 3.10 이상, OpenAI 계정, Supermemory 계정, 그리고 커맨드 라인 기본 사용 능력이 필요합니다.
Supermemory란?
Supermemory는 에이전트를 위한 AI 메모리 API로 설명하는 것이 가장 정확합니다. 사용자에 대한 문자열을 Supermemory에 전달하면, 나중에 그 사용자가 누구이며 최근 무엇을 했는지에 대한 간결한 보기를 반환합니다. 임베딩, 인덱싱, 검색은 모두 Supermemory 내부에서 실행되므로 에이전트 코드는 작게 유지됩니다.
LongMemEval 벤치마크는 긴 대화 이력 전반에서 메모리 시스템이 질문에 얼마나 잘 답하는지 테스트합니다. Supermemory는 올바른 사실의 81.6%를 회상합니다. 차선의 시스템인 Zep은 71.2%로, 약 10포인트 차이는 사용자 질문 10개당 정답 1개가 추가되는 효과입니다. 오픈 소스 저장소는 GitHub 스타 2.2만 개 이상으로 실제 사용의 또 다른 신호입니다.
메모리 vs RAG
에이전트 메모리 도구를 찾는 독자 대부분은 이전에 RAG를 사용해 보셨을 겁니다. It Supermemory를 그 옆에 놓고 비교하면 이해에 도움이 됩니다. RAG와 메모리는 서로 다른 문제를 해결하며, 실제로 같은 에이전트 안에서 함께 사용되는 경우가 많습니다.
RAG 시스템은 개발자가 한 번 준비한 문서 코퍼스를 가리킵니다. 제품 매뉴얼, 지원 문서, 내부 문서 등. 코퍼스는 배포 시 로드되고 런타임에 조회되며, 자주 바뀌지 않습니다. 에이전트는 제품 자체가 답을 알고 있는 질문에 이를 사용합니다.
메모리 시스템은 사용자를 가리킵니다. Supermemory는 에이전트가 사용자와 대화할 때 사용자별 사실을 기록하며, 대화가 누적될수록 저장소는 성장합니다. 에이전트는 선호도, 이력, 최근 활동처럼 사용자만이 답할 수 있는 질문에 이를 사용합니다.

실제 제품에서는 두 가지가 나란히 동작합니다. 회사 지식 베이스에 대한 RAG는 "환불 정책이 무엇인가요?"에 답합니다. Supermemory는 사용자에 대한 질문, 예를 들어 "지난주 내 벤치프레스 기록은?"에 답합니다. 같은 에이전트, 서로 다른 두 데이터 저장소, 두 가지 역할입니다.
사용자 프로필: 정적 사실과 동적 사실
Supermemory의 핵심 아이디어는 사용자 프로필입니다. 모든 로그는 두 버킷으로 분류됩니다: 잘 바뀌지 않는 정적 사실과, 현재 활동에 대한 동적 사실. 반복되는 패턴은 정적으로 승격되고, 최근 활동은 동적으로 남습니다.
에이전트가 프로필을 읽을 때, 한 번의 호출로 두 버킷과 일치하는 메모리 청크가 함께 반환됩니다.
이 구분은 같은 사용자에 대한 서로 다른 질문에 답하기 때문에 중요합니다:
|
정적 사실 |
동적 사실 |
|
아령과 풀업 바로 집에서 훈련함 |
현재 집중 분야: 상체 근력 |
|
왼쪽 무릎 부상, 딥 스쿼트 금지 |
최근 벤치: 185 lb로 5회 4세트 |
|
연말까지 벤치 20 lb 증량 목표 |
이번 주 그리스 더 그루브 풀업 진행 중 |
|
저녁에만 훈련, 아침 훈련 없음 |
어제 5km를 28분에 주파 |
첫 번째 행을 보세요. 정적 측면은 사용자가 어떻게 훈련하는지 말해 줍니다: 집에서, 보유한 장비로. 이는 주마다 바뀌지 않습니다. 동적 측면은 지금 무엇에 집중하는지 알려 줍니다: 이번 사이클의 상체.
운동 추천기는 두 가지 모두 필요합니다. 정적 측면은 헬스장 전용 운동을 제외하고, 동적 측면은 오늘의 세션을 선택합니다.
이 프로필 뒤에서 Supermemory는 원래 직접 만들어야 할 네 가지 일을 수행합니다. 원시 메모리를 저장하고, 각 청크를 임베딩하며, 읽기 시점에 유사도 검색을 수행하고, 로그된 콘텐츠에서 프로필 사실을 추출합니다. 이 네 가지는 코드에 드러나지 않습니다.

모든 메모리는 개발자가 선택한 문자열 태그가 붙습니다. 모든 읽기에서는 같은 문자열을 다시 전달해 반환 범위를 제한합니다. 트레이너는 프로필 동작을 보여 주기에 한 명의 사용자면 충분하므로 태그를 하드코딩합니다. 실제 앱은 인증된 사용자(JWT 등)에서 태그를 계산합니다.
Supermemory 환경 설정
트레이너에는 두 개의 API 키(Supermemory와 OpenAI)와 세 개의 의존성을 가진 Python 프로젝트가 필요합니다. 간단한 왕복 스크립트로 에이전트 코드가 키를 사용하기 전에 두 키가 정상 동작함을 확인합니다.
API 키 받기
Supermemory API 키는 console.supermemory.ai에 있으며, 아닙니다 app.supermemory.ai. app 서브도메인은 컨슈머용 메모리 제품(노트 저장, 공간 탐색)입니다. API 키 페이지가 없습니다. 이를 건너뛰고 바로 콘솔로 이동하세요.
다음은 console.supermemory.ai에서의 절차입니다:
-
로그인합니다.
-
사이드바에서 API Keys를 클릭합니다.
-
Create API Key를 클릭합니다.
-
이름을 지정합니다(트레이너 데모는
datacamp-tutorial사용). -
발급된 키를 복사합니다.
sm_로 시작합니다.

에이전트의 LLM 호출을 위해 OpenAI 키도 필요합니다. 아직 없다면 platform.openai.com/api-keys에서 발급받으세요.
프로젝트 루트에 .env 파일을 만들어 두 키를 넣으세요. 커밋하지 마세요.
SUPERMEMORY_API_KEY=sm_your_key_here
OPENAI_API_KEY=sk-your_key_here
Supermemory의 무료 요금제는 결제 정보 입력 없이 본 튜토리얼에 충분합니다. 정확한 제한은 요금제 페이지에 있습니다.
의존성 설치
튜토리얼은 프로젝트 설정과 실행에 uv를 사용합니다. 만약 uv가 없다면 astral.sh/uv의 원라이너로 한 번 설치하세요.
프로젝트를 초기화합니다:
uv init supermemory-trainer
cd supermemory-trainer
uv init가 생성한 README.md를 삭제하세요. 자동 생성된 hello.py는 다음 단계에서 대체할 것이므로 지금은 두세요.
세 가지 의존성을 추가합니다:
-
supermemory==3.37.0은 메모리 클라이언트로, 본 튜토리얼에서 검증한 버전으로 고정합니다. -
openai-agents는 OpenAI Agents SDK입니다. 패키지 이름은 하이픈 표기이며, 임포트 경로는
agents입니다. -
python-dotenv는 방금 만든.env파일을 읽습니다.
uv add supermemory==3.37.0 openai-agents python-dotenv
결과 pyproject.toml:
[project]
name = "supermemory-trainer"
version = "0.1.0"
description = "Personal exercise trainer agent built with Supermemory and the OpenAI Agents SDK."
requires-python = ">=3.10"
dependencies = [
"openai-agents>=0.10.2",
"python-dotenv>=1.2.1",
"supermemory==3.37.0",
]
설정 검증
에이전트 코드를 작성하기 전에, 한 문장으로 Supermemory가 한 번 일하는 모습을 보겠습니다. 아래 스크립트는 하나의 사실을 Supermemory에 보내고, 파이프라인을 기다린 뒤, 프로필을 다시 읽어옵니다. 이 스크립트가 문제없이 실행되면 키가 정상이고 SDK에 접근 가능한 것입니다. 출력은 또한 Supermemory가 원시 텍스트로 무엇을 하는지 첫인상을 제공합니다.
프로젝트 루트의 hello.py를 열고 자동 생성된 본문을 다음 임포트와 쓰기 호출로 교체하세요:
import time
from dotenv import load_dotenv
from supermemory import Supermemory
load_dotenv()
client = Supermemory()
USER_ID = "demo_warmup"
response = client.add(
content="The user is learning Supermemory by building a personal trainer agent.",
container_tag=USER_ID,
)
print(f"client.add() -> id={response.id} status={response.status}")
load_dotenv()는 Supermemory()가 생성되기 전에 .env에서 API 키를 환경 변수로 읽어옵니다. 클라이언트는 SUPERMEMORY_API_KEY를 자동으로 인식합니다. container_tag="demo_warmup" 값은 이 단일 사실을 임시 사용자 범위로 지정합니다.
이제 같은 파일 하단에 대기와 읽기 코드를 추가합니다:
print("Waiting 20 seconds for processing...")
time.sleep(20)
prof = client.profile(container_tag=USER_ID, q="learning")
print(f"profile.static ({len(prof.profile.static)}): {prof.profile.static}")
print(f"profile.dynamic ({len(prof.profile.dynamic)}): {prof.profile.dynamic}")
print(f"search_results.results ({len(prof.search_results.results)}):")
for r in prof.search_results.results[:3]:
print(f" - {r['memory']} (similarity={r['similarity']:.3f})")
20초 대기는 Supermemory의 임베딩·추출 파이프라인이 새 메모리를 처리할 시간을 줍니다. 이를 생략하면 읽기가 아무것도 반환하지 않아 스크립트가 고장난 것처럼 보일 수 있습니다.
파일을 실행합니다:
uv run python hello.py
예상 출력:
client.add() -> id=zNLsJBrY1PZupAeZ3Qn6EL status=queued
Waiting 20 seconds for processing...
profile.static (0): []
profile.dynamic (1): ['Building a personal trainer agent to learn Supermemory.']
search_results.results (1):
- Building a personal trainer agent to learn Supermemory. (similarity=0.650)
이 출력의 세 가지가 중요합니다. client.add()는 Supermemory가 문서를 비동기로 처리하므로 status="queued" 상태로 즉시 반환됩니다. 20초 대기는 임베딩·추출 파이프라인을 위한 시간입니다. 읽기 시점에는 원시 문장이 검색 가능한 메모리 청크가 됩니다.
흥미로운 줄은 profile.dynamic입니다. 입력은 "The user is learning Supermemory by building a personal trainer agent."라는 문장이었습니다. 출력은 동적 사실 'Building a personal trainer agent to learn Supermemory.'입니다. Supermemory는 3인칭 문장을 사용자에 대한 1인칭 사실로 다시 썼습니다. 프로필 추출기가 제 역할을 한 것입니다.
profile.static은 빈 리스트입니다. 정적 사실은 관련 로그가 여러 개 누적된 뒤에야 천천히 통합되므로, 단 한 번의 워밍업 쓰기로는 생성되지 않습니다. 트레이너의 추천 도구는 이를 고려해 static을 보너스로 취급하고 보장값으로 보지 않습니다.
Supermemory 에이전트 빌드: Python 개인 운동 트레이너
트레이너는 client.add()와 client.profile()를 두 개의 에이전트 도구로 감싸, 사용자가 대화하는 동안 읽기와 쓰기가 자동으로 이루어지게 합니다. 운동 이력은 메모리와 잘 맞습니다. 장비, 부상, 최근 기록은 LLM 학습 데이터에 존재하지 않으며, 세션마다 누적됩니다.

프로젝트 구조와 에이전트
트레이너는 작아서 프로젝트 전체가 두 개의 Python 파일과 이미 가지고 있는 pyproject.toml에 담깁니다:
supermemory-trainer/
├── .env # your real keys (gitignored)
├── .env.example # placeholders, committed
├── .gitignore
├── .python-version
├── main.py # agent definition, system prompt, REPL loop
├── pyproject.toml
└── tools.py # log_workout and suggest_next_session
tools.py에는 곧 작성할 두 개의 메모리 기반 도구가 들어 있습니다. log_workout은 client.add()를 통해 운동을 Supermemory에 기록합니다. suggest_next_session은 client.profile()로 사용자 프로필을 읽습니다. main.py는 두 함수를 임포트하고 에이전트를 연결합니다.
main.py의 대부분은 OpenAI Agents SDK 보일러플레이트입니다. 시스템 프롬프트의 한 문장이 Supermemory 작업을 수행합니다: 사용자에 대한 모든 사실은 도구 호출을 통해서만 돌아와야 합니다. 에이전트는 자체 메모리가 없다고 지시받습니다. 이 한 가지 규칙이 트레이너를 메모리 기반으로 만듭니다.
이제 main.py에서 임포트와 시스템 프롬프트부터 작성하세요:
import asyncio
from agents import Agent, Runner, SQLiteSession
from tools import log_workout, suggest_next_session
SYSTEM_PROMPT = """You are a personal exercise trainer who logs the user's
workouts and recommends what to do next.
You have no memory of the user's history on your own. Every fact about the
user lives in Supermemory and reaches you only through tool calls.
Two rules, no exceptions:
1. Whenever the user reports completing a workout, call log_workout immediately, before responding. Extract the exercise, sets, reps, weight, and any notes from what they said. If a value is missing, ask one short follow-up question instead of guessing. After logging, confirm in one short sentence and stop. Do NOT recommend the next session unless the user asks for one.
2. When the user explicitly asks what to do next (or asks for a recommendation, suggestion, or plan), call suggest_next_session first. Never recommend from your own training data. The tool returns the user's
recent activity, stable preferences, and matching past sessions. Reference those facts directly in your reply.
Keep replies concise (2-4 sentences). Be specific: name the exercise, sets, reps, and weight. Honor any injuries or equipment constraints the tool surfaces.
"""
시스템 프롬프트의 두 규칙 모두 모델을 Supermemory로 우회시킵니다.
규칙 1은 사용자가 운동 완료를 보고할 때마다 log_workout 호출을 강제하여 모든 운동이 메모리 저장소에 도달하도록 합니다. 규칙 2는 어떤 추천이든 하기 전에 suggest_next_session 호출을 강제하여 모든 추천이 Supermemory가 아는 내용에 기반하도록 합니다.
이 규칙이 없으면 에이전트는 자신의 학습 데이터에서 답하며, 이는 메모리 레이어의 의미를 퇴색시킵니다.
이제 같은 파일에 에이전트와 채팅 루프를 정의합니다:
def build_agent() -> Agent:
return Agent(
name="Trainer",
instructions=SYSTEM_PROMPT,
tools=[log_workout, suggest_next_session],
model="gpt-5",
)
async def chat() -> None:
agent = build_agent()
session = SQLiteSession(session_id="trainer-cli")
print("Trainer ready. Type a message, or 'exit' to quit.\n")
while True:
try:
message = input("You: ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not message:
continue
if message.lower() in {"exit", "quit"}:
break
result = await Runner.run(agent, message, session=session)
print(f"\nTrainer: {result.final_output}\n")
if __name__ == "__main__":
asyncio.run(chat())
이 블록에서 주목할 두 줄이 있습니다. tools=[log_workout, suggest_next_session]이 두 개의 메모리 기반 도구를 등록합니다. tools.py의 각 함수에 있는 @function_tool 데코레이터는 SDK에 해당 함수가 호출 가능함을 알려 줍니다. 데코레이터가 없으면 생성 호출은 성공하더라도 런타임에 에이전트는 도구를 갖지 못합니다.
SQLiteSession(session_id="trainer-cli")은 실행 중인 Python 프로세스 내부에 단기 턴 히스토리를 유지합니다. Supermemory는 프로세스 간 장기 사용자 사실을 유지합니다. Python 프로세스를 종료하면 SQLite 세션은 사라지지만, Supermemory 데이터는 남습니다.
중요: Jupyter의 이벤트 루프가 asyncio.run()와 충돌하므로 main.py는 스크립트로 실행하세요, Jupyter 셀에서 실행하지 마세요. 동기식 Supermemory() 클라이언트는 Agents SDK가 도구를 스레드 풀에서 실행하므로 비동기 도구 함수 안에서도 동작합니다. SDK 자체에 관한 자세한 내용은 OpenAI Agents SDK 튜토리얼을 참고하세요.
운동 기록 도구 작성
log_workout은 에이전트 메모리의 쓰기 측입니다. 이 함수는 에이전트로부터 구조화된 인자(운동명, 세트 수, 반복 수, 무게, 선택적 노트)를 받아, 이를 짧은 영어 문장으로 바꾼 뒤 client.add()를 통해 Supermemory에 전달합니다. 임베딩·추출 파이프라인은 그 다음 Supermemory 내부에서 실행되며 트레이너가 추가로 할 일은 없습니다.
tools.py를 열고 임포트와 공유 클라이언트 하나로 시작하세요:
from agents import function_tool
from dotenv import load_dotenv
from supermemory import Supermemory
load_dotenv()
USER_ID = "demo_user"
client = Supermemory()
load_dotenv()는 임포트 시점에 실행되어 Supermemory()가 생성되기 전에 SUPERMEMORY_API_KEY가 환경에 반영되도록 합니다. 환경을 로드하기 전에 클라이언트를 생성하면 인증되지 않은 클라이언트가 생기며, 첫 호출은 혼란스러운 401을 반환합니다. 이 파일의 두 도구 함수는 하나의 클라이언트와 하나의 USER_ID 상수를 공유합니다.
클라이언트 아래에 기록 도구를 추가하세요:
@function_tool
def log_workout(
exercise: str,
sets: int,
reps: int,
weight: float,
notes: str = "",
) -> str:
"""Log a completed workout to the user's memory.
Args:
exercise: Name of the exercise.
sets: Number of sets performed.
reps: Number of reps per set.
weight: Weight in pounds. Pass 0 for bodyweight or cardio.
notes: Optional notes about the session.
"""
print(f"[log_workout] {exercise=} {sets=} {reps=} {weight=} {notes=}")
content = f"Performed {exercise}: {sets} sets of {reps} reps at {weight} lbs."
if notes:
content += f" Notes: {notes}"
response = client.add(content=content, container_tag=USER_ID)
print(f"[log_workout] -> id={response.id} status={response.status}")
return f"Logged {exercise} ({sets}x{reps} @ {weight} lb)."
@function_tool의 독스트링은 LLM이 도구 호출 여부를 결정할 때 보는 정보입니다. Args: 블록은 파라미터별 설명에 매핑됩니다. 둘 다 함수와 에이전트 간 계약의 일부입니다.
이 도구는 JSON이 아닌 평문 문장을 client.add()로 보냅니다. Supermemory의 프로필 추출기는 자연어를 읽고 그로부터 사실을 추론합니다. JSON도 기술적으로는 동작하지만, 내러티브가 없어 요약할 맥락이 부족해 추출 품질이 저하됩니다. "Performed bench press: 4 sets of 5 reps at 185.0 lbs"처럼 추출기가 다루기 쉬운 깔끔한 문장이 좋습니다.
두 개의 print() 호출은 각 도구 호출을 터미널에 기록합니다: 먼저 파싱된 인자, 그다음 응답.
[log_workout] exercise='bench press' sets=4 reps=5 weight=185.0 notes=''
[log_workout] -> id=xY7AK3qLzBPx5Vd2HnRf1M status=queued
status="queued" 값은 앞서 워밍업 스크립트와 동일합니다. 원시 로그는 저장되었지만, 파이프라인이 끝날 때까지 client.profile()이 검색 결과로 반환하지 않습니다. 나중에 이를 대기하는 검증 단계를 추가합니다.
운동 제안 도구 작성
suggest_next_session은 읽기 측이며, 정적·동적 분할의 이점이 드러나는 곳입니다. client.profile(container_tag=USER_ID, q=focus) 한 번의 호출로 세 가지 사용자 보기를 한 번에 반환합니다.
안정적인 선호도는 profile.static로, 현재 활동은 profile.dynamic로, 가장 근접한 과거 메모리는 search_results.results로 돌아옵니다. 이 도구의 역할은 세 보기를 에이전트가 인용할 수 있는 하나의 컨텍스트 블록으로 평탄화하는 것입니다.
몇 번의 운동 후, 도구는 다음과 같은 출력을 생성합니다:
Recent activity:
- Trains at home instead of a gym
- Performed deadlift: 3 sets of 5 reps at 225.0 lbs
- Performed 5k run in 26 minutes
- Reports no knee pain during bench press
- Performed bench press: 4 sets of 5 reps at 185.0 lbs
Closest matching past entries:
- Trains at home instead of a gym
- Performed deadlift: 3 sets of 5 reps at 225.0 lbs
- Performed bench press: 4 sets of 5 reps at 185.0 lbs
- Performed 5k run in 26 minutes
- Reports no knee pain during bench press
에이전트는 이 블록을 읽고 사용자의 실제 이력에 기반한 추천을 작성합니다. Supermemory의 프로필이 없다면 동일한 컨텍스트를 직접 구성했어야 합니다. 이는 별도의 의미 검색, 자체 프로필 저장소, 결과 병합을 의미합니다. 단일 client.profile() 호출이 이 세 가지를 대체합니다.
아래 코드를 tools.py의 log_workout 아래에 추가하세요:
@function_tool
def suggest_next_session(focus: str) -> str:
"""Fetch the user's training history and preferences for a given focus.
Returns a context string the agent can use to recommend the next session.
The agent is responsible for the actual recommendation. This tool only
surfaces what Supermemory knows about the user.
Args:
focus: What the user wants to train next (e.g. "upper body", "legs",
"cardio", "today"). Drives semantic search against past logs.
"""
print(f"[suggest_next_session] focus={focus!r}")
profile = client.profile(container_tag=USER_ID, q=focus)
static_facts = profile.profile.static
dynamic_facts = profile.profile.dynamic
matches = profile.search_results.results
print(
f"[suggest_next_session] static={len(static_facts)} "
f"dynamic={len(dynamic_facts)} matches={len(matches)}"
)
sections = []
if static_facts:
sections.append("Stable preferences and constraints:")
sections.extend(f"- {fact}" for fact in static_facts)
if dynamic_facts:
sections.append("Recent activity:")
sections.extend(f"- {fact}" for fact in dynamic_facts)
if matches:
sections.append("Closest matching past entries:")
for r in matches[:5]:
sections.append(f"- {r['memory']}")
if not sections:
return (
"No prior training history found for this user. "
"Ask the user about their goals, equipment, and recent training."
)
return "\n".join(sections)
client.profile(container_tag=USER_ID, q=focus)는 ProfileResponse 객체를 반환합니다. 짧은 로그 5개 후, 도구가 읽는 세 필드는 다음과 같습니다:
profile.profile.static # [] (list[str])
profile.profile.dynamic # ["Performed bench press: 4 sets of 5 reps at 185.0 lbs", ...]
profile.search_results.results # [{"memory": "...", "similarity": 0.631, ...}, ...] (list[dict])
각 검색 결과는 Pydantic 객체가 아닌 Python dict입니다. 텍스트는 r["memory"], 점수는 r["similarity"]로 접근하세요. 전체 dict는 다음 키를 가집니다:
-
id -
memory -
rootMemoryId -
metadata -
updatedAt -
version -
similarity -
filepath -
documents
Supermemory의 OpenAI Agents SDK 통합 페이지에 있는 r.memory or r.chunk 스니펫은 supermemory==3.37.0에서 AttributeError를 발생시킵니다. 대괄호 표기법을 사용하세요.
static은 여기서는 비어 있으므로 if static_facts:로 분기합니다. 초기 수십 개의 로그 동안은 dynamic과 search_results 분기가 실질적인 역할을 합니다.
Supermemory는 기본 유사도 임계값도 적용합니다. 한 번 언급한 사실은 모든 쿼리에 항상 돌아오지 않을 수 있습니다. 위의 5개 로그는 모두 q="today"에서 반환되었지만, 더 구체적인 쿼리 문자열은 더 적게 반환할 수 있습니다. if matches: 가드는 실패 없이 이를 처리합니다.
세션 1 실행: 운동 기록
세션 1을 시작하여 Supermemory가 나중에 읽어올 수 있도록 몇 가지 운동을 기록하세요. 스크립트를 실행합니다:
uv run python main.py
벤치프레스, 5km 러닝, 데드리프트를 기록하고, 선호도 문장 하나도 남기세요: "I only train at home, no gym." 에이전트는 운동 하나당 log_workout를 한 번 호출하며, 도구의 print() 줄이 터미널에 모든 호출을 보여 줍니다.

예시 출력입니다. 모델이 비결정론적이므로 에이전트의 정확한 문구는 달라질 수 있습니다.
세 줄의 status=queued은 Supermemory가 작업을 이어받는 순간입니다. 각 줄은 Supermemory 측 임베딩·추출 파이프라인을 거치는 문서 하나에 해당합니다. 이런 짧은 텍스트 로그는 약 12초 내에 client.profile()로 검색 가능해집니다.
트레이너 코드에는 이를 기다리는 부분이 없습니다. 에이전트는 진행하고, Supermemory는 백그라운드에서 작업을 마칩니다.
각 로그는 정확히 한 번의 log_workout 호출을 발생시키고 에이전트는 멈춥니다. 선제적 추천 없음, 추가 도구 호출 없음, 후속 제안 없음. 첫 번째 시스템 프롬프트 규칙이 이 역할을 합니다. 이 규칙이 없다면 에이전트는 각 로그 후 다음 세션을 제안해 도구 호출이 두 배로 늘어납니다.
exit를 입력해 세션 1을 종료하세요. Python 프로세스가 끝나며 SQLiteSession도 함께 사라집니다. 이제 운동 로그와 선호도 문장은 container_tag="demo_user" 아래 Supermemory에 저장되어, 이를 기록한 스크립트와 분리됩니다.
회상 검증 및 세션 2 실행
세션 2 전에, 세션 1의 사실들이 조회 가능함을 확인하세요. 새 Python REPL을 열거나 아래를 짧은 스크립트로 저장하세요:
from dotenv import load_dotenv
from supermemory import Supermemory
load_dotenv()
client = Supermemory()
prof = client.profile(container_tag="demo_user", q="training")
print(f"static ({len(prof.profile.static)}): {prof.profile.static}")
print(f"dynamic ({len(prof.profile.dynamic)}):")
for fact in prof.profile.dynamic:
print(f" - {fact}")
print(f"matches ({len(prof.search_results.results)}):")
for r in prof.search_results.results[:5]:
print(f" - {r['memory']} (similarity={r['similarity']:.3f})")
두 세션 사이에 캡처한 실제 출력:
static (0): []
dynamic (5):
- Trains at home instead of a gym
- Performed deadlift: 3 sets of 5 reps at 225.0 lbs
- Performed 5k run in 26 minutes
- Reports no knee pain during bench press
- Performed bench press: 4 sets of 5 reps at 185.0 lbs
matches (5):
- Trains at home instead of a gym (similarity=0.682)
- Performed deadlift: 3 sets of 5 reps at 225.0 lbs (similarity=0.643)
- Performed bench press: 4 sets of 5 reps at 185.0 lbs (similarity=0.631)
- Performed 5k run in 26 minutes (similarity=0.585)
- Reports no knee pain during bench press (similarity=0.585)
Supermemory의 추출기가 만든 결과를 보세요. 사용자는 "I only train at home, no gym"이라고 한 번 말했습니다. 추출기는 이를 동적 사실 "Trains at home instead of a gym"으로 바꾸었습니다.
벤치프레스 로그에는 무릎 통증이 없다는 노트가 포함되었습니다. 추출기는 그 단일 로그를 운동에 대한 사실과 통증 부재에 대한 사실, 두 가지 동적 사실로 분리했습니다.
네 개의 로그가 다섯 개의 정규화된 동적 사실과, 유사도 0.585~0.682 사이의 일치 메모리 청크 다섯 개로 바뀌었습니다. 이러한 분할, 정규화, 매칭은 트레이너 코드에서 수행하지 않았습니다. 만약 dynamic이 비어 있다면 10초 정도 더 기다렸다가 스니펫을 다시 실행하세요. 처리 큐가 가끔 몰릴 수 있습니다.
이제 완전히 새로운 프로세스에서 세션 2를 시작합니다:
uv run python main.py
새로운 Python 인터프리터입니다. 세션 1과 공유 메모리도, 웜 캐시도 없습니다. 에이전트가 회상하는 모든 것은 Supermemory에서 옵니다.
메시지 하나를 보냅니다: "What should I do for my workout today?"

예시 출력입니다. 동일한 메모리 저장소, 새로운 Python 프로세스입니다.
에이전트는 suggest_next_session("today")를 호출합니다. 도구는 static=0 dynamic=5 matches=5를 출력합니다. 캡처된 실행에서는 집에서 할 수 있는 하체 세션(스쿼트, 런지, 스텝업)으로 응답했습니다.
추천이 이전 로그와 일치한 이유는 Supermemory의 프로필이 그 내용을 알려 주었기 때문입니다. 벤치, 데드리프트, 5km 러닝은 상체 또는 유산소였고, 사용자는 집에서만 훈련했습니다. 두 사실 모두 같은 client.profile() 호출에서 반환되었습니다. 모델이 비결정론적이므로 문구는 달라질 수 있지만, 회상 경로는 동일합니다.
Supermemory 에이전트의 다음 단계
데모는 한 명의 사용자, 두 개의 도구, 하나의 CLI입니다. 실제 트레이너 버전은 에이전트 루프를 손대기 전에 Supermemory의 형태로 세 방향으로 확장됩니다.
실제 사용자별로 메모리 범위를 지정하세요. USER_ID = "demo_user" 상수는 한 사람에게만 통합니다. 프로덕션 앱은 인증된 사용자 ID에서 태그를 계산합니다. 예: container_tag="user_sarah" 또는 container_tag=customer_id. 읽기마다 태그를 전달하므로 사용자 간 메모리는 분리됩니다. tools.py 한 곳만 바꾸면 됩니다. 다른 코드는 그대로입니다.
더 많은 메모리 기반 도구를 추가하세요. 디로드 주간, PR 트래킹, 주간 모빌리티 프롬프트 등. 각 기능은 동일한 @function_tool 함수로, 쓰기는 client.add(), 읽기는 같은 container_tag에 대한 client.profile()를 호출합니다. 도구의 형태는 같습니다. 에이전트가 기록하고 요청하는 내용만 달라집니다.
Supermemory 장애를 처리하세요. client.add()와 client.profile()을 try/except supermemory.APIError로 감싸 Supermemory의 일시적 장애가 에이전트를 중단시키지 않도록 하세요. 제한된 환경에서 에이전트를 실행한다면 요청별 타임아웃을 설정하세요.
에이전트 루프 측 작업은 Supermemory와 독립적이며 나중에 변경할 수 있습니다. CLI 앞단에 Telegram, Discord, Slack을 두어 사용자가 운동을 텍스트로 보내면 봇이 Runner.run()을 호출하도록 하세요. 또는 프레임워크를 교체하세요. 스택이 이미 LangChain 에이전트라면 Supermemory에는 LangChain 통합이 있으며, 메모리 코드는 바뀌지 않습니다.
정적·동적 분할은 다른 도메인에도 잘 맞습니다.
- 고객 지원 에이전트: 정적 = 알려진 이슈와 계정 선호도, 동적 = 미해결 티켓과 최근 문의.
- 코딩 에이전트: 정적 = 선호 언어와 프레임워크, 동적 = 현재 작업과 최근에 다룬 파일.
사용자가 진실의 근원일 때 이 분할은 유효합니다.
마무리
이제 두 개의 도구와 프로세스 간 지속형 메모리를 가진 Python 트레이너를 만들었습니다. client.add()는 운동을 기록합니다. client.profile()은 한 번의 호출로 사용자에 대한 정적 사실, 동적 사실, 의미 기반 일치 결과를 container_tag에 따라 범위 지정해 읽어옵니다. Supermemory가 청크 분할, 임베딩, 검색, 프로필 추출을 담당하므로 데모는 해당 코드를 작성할 필요가 없습니다.
이를 RAG와 결합하면 같은 에이전트가 사용자와 제품에 관한 질문 모두에 답합니다. LLM 에이전트 설명은 더 넓은 에이전트 패턴을 다루며, Associate AI Engineer for Developers 트랙은 메모리 기반 에이전트를 더욱 심화합니다.
Supermemory FAQ
Supermemory란 무엇인가요?
Supermemory는 호스팅형 메모리 API로, AI 에이전트가 세션 간 사용자 기억을 유지할 수 있도록 장기 컨텍스트를 저장, 인덱싱, 검색합니다.
Supermemory는 일반 벡터 데이터베이스와 무엇이 다른가요?
Supermemory는 저장소 위에 임베딩, 인덱싱, 의미 검색, 프로필 스타일의 사실 추출을 추가하여, 단 한 번의 API 호출로 활용 가능한 메모리를 제공합니다.
Supermemory는 RAG와 사용자 메모리를 모두 처리할 수 있나요?
예. 파일과 URL에 대한 문서형 RAG와 사용자 중심 메모리를 모두 지원하여, 같은 API로 제품 지식과 개인 이력을 함께 구동할 수 있습니다.
Supermemory에서 임베딩 모델을 직접 관리해야 하나요?
아니요. Supermemory가 임베딩과 검색 파이프라인을 대신 실행합니다. 원시 텍스트나 콘텐츠를 보내고 나중에 쿼리하면 되며, 모델이나 인덱스를 직접 관리할 필요가 없습니다.
Supermemory를 체험할 수 있는 무료 플랜이 있나요?
예. 테스트와 소규모 프로젝트를 위한 무료 플랜이 있으며, 월별 토큰 및 쿼리 제한 범위에서 통합과 실험을 진행할 수 있습니다.