05. 대화 모드와 장기기억 — 에이전트를 나에게 맞추기
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다:
euleragent chat으로 에이전트와 인터랙티브 대화를 시작한다- 에이전트에게 선호·금지·습관을 학습시키고 장기기억에 저장한다
--continue로 이전 세션을 이어받아 대화를 재개한다--history-limit으로 로드할 대화 수를 조절한다--debug-memory로 메모리 회수 과정을 관찰한다memory status/search/open명령어로 저장된 기억을 조회한다
사전 준비
- 워크스페이스 초기화 완료 (
euleragent init) - 에이전트 생성:
euleragent new my-assistant --template personal-assistant
workspace.yaml에서 메모리 설정 확인:
cat .euleragent/config/workspace.yaml
memory:
enabled: true
store: sqlite
sqlite_path: .euleragent/state/memory.db
embedding:
provider: fake # 오프라인: SHA256 해시 기반 임베딩
# provider: ollama # 온라인: 의미 기반 유사도 검색
model: nomic-embed-text
retrieval:
top_k: 8
min_score: 0.2
임베딩 프로바이더 선택:
fake는 네트워크 없이 작동하는 결정론적 임베딩을 사용합니다. 의미 기반 유사도 검색이 필요하면 Ollama를 실행하고provider: ollama로 변경하세요.
에이전트 설정 확인:
cat .euleragent/agents/my-assistant/agent.yaml
name: my-assistant
memory:
enabled: true
retrieval:
top_k: 5
min_score: 0.2
장기기억(LTM)이란?
euleragent의 장기기억은 SQLite 데이터베이스에 대화 내용을 저장하고, 새로운 대화를 시작할 때 관련 과거 발화를 자동으로 검색하여 컨텍스트에 주입합니다.
세션 종료 → 대화 저장 (SQLite)
↓
다음 세션 시작 (--continue)
↓
관련 과거 대화 검색 (벡터 유사도)
↓
시스템 메시지에 주입
↓
LLM이 과거 맥락을 기억하며 응답
자동으로 구조화되는 정보:
| 패턴 | 카테고리 | 예시 |
|---|---|---|
| "나는 Python을 선호해" | preference | Python 사용 선호 |
| "절대 긴 설명은 하지 마" | prohibition | 간결한 응답 |
| "나는 매일 아침 코드 리뷰를 해" | routine | 아침 코드 리뷰 |
| "우리 프로젝트는 FastBridge야" | context | 프로젝트명 |
MCP 카탈로그와 메모리: MCP 검색 프로바이더를 사용하는 환경에서는 MCP 카탈로그 변경(소스 추가/제거)이 메모리 컨텍스트에 영향을 줄 수 있습니다. 예를 들어 이전 세션에서 사용 가능했던 검색 소스가 카탈로그 업데이트 후 제거되면, 해당 소스를 참조하는 과거 기억의 유효성이 달라질 수 있습니다.
단계별 실습
Step 1: 첫 번째 채팅 세션 시작
euleragent chat my-assistant --project default
예상 출력:
Chat with my-assistant (project: default). Type 'exit' to quit.
───────────────────────────────────────────────────────────
기본 인사 및 컨텍스트 공유:
You: 안녕! 나는 소프트웨어 개발자야. 주로 백엔드 개발을 하고 있어.
Agent: 안녕하세요! 백엔드 개발자로서 어떤 도움이 필요하신가요?
기술 스택이나 현재 작업 중인 프로젝트가 있으면 알려주세요.
세션 내 히스토리: 세션 내에서는
--continue없이도 대화 히스토리가 자동으로 누적됩니다.--continue는 세션 종료 후 다음 세션에서 이전 내용을 불러오는 것입니다.
Step 2: 선호·금지·습관 학습시키기
에이전트에게 나의 선호와 작업 방식을 가르칩니다. 아래처럼 자연스럽게 대화하면 자동으로 장기기억에 저장됩니다:
You: 앞으로 Python만 써줘. Ruby나 JavaScript 예시는 필요 없어.
Agent: 알겠습니다. 모든 코드 예시는 Python으로만 작성하겠습니다.
You: 설명은 짧게 해줘. 코드 먼저 보여주고 설명은 최소한으로.
Agent: 네, 앞으로 코드 우선, 설명 최소 방식으로 답변드리겠습니다.
You: 나는 매일 아침 9시에 GitHub PR 리뷰를 시작해.
Agent: 오전 9시 PR 리뷰 루틴을 기억했습니다. 관련 도움이 필요하면 말씀해주세요.
You: 절대 외부 라이브러리 추천하지 마. 표준 라이브러리만 사용해.
Agent: 이해했습니다. 표준 라이브러리만 사용하는 솔루션을 제공하겠습니다.
You: 우리 서비스 이름은 FastBridge야. FastAPI 기반 API 게이트웨이야.
Agent: FastBridge 프로젝트 (FastAPI 기반 API 게이트웨이) 컨텍스트를 저장했습니다.
You: 문서는 한국어로 써줘. 코드 주석도 한국어로.
Agent: 네, 모든 문서와 코드 주석을 한국어로 작성하겠습니다.
exit
Step 3: 저장 확인 — 메모리 상태 조회
세션을 종료한 후 저장된 내용을 확인합니다:
euleragent memory status --project default
예상 출력:
Project: default
DB: /home/user/my-project/.euleragent/state/memory.db
Conversations: 12 ← user + assistant 발화 합계
Vectors: 12 ← 임베딩된 발화 수
Profile entries: 5 ← 추출된 선호/금지/습관 수
Last updated: 2026-02-23 09:30:00
프로필 항목 확인은 SQLite에서 직접 조회할 수 있습니다:
euleragent memory open --project default
예상 출력:
Memory DB path: /home/user/my-project/.euleragent/state/memory.db
To inspect directly:
sqlite3 /home/user/my-project/.euleragent/state/memory.db
Useful queries:
SELECT role, substr(content, 1, 80) FROM conversations ORDER BY created_at LIMIT 20;
SELECT category, value FROM profiles;
SQLite 직접 조회:
sqlite3 .euleragent/state/memory.db "SELECT category, value FROM profiles;"
예상 출력:
preference|Python만 사용, 표준 라이브러리만
prohibition|외부 라이브러리 추천 금지
prohibition|긴 설명 금지
routine|매일 아침 9시 PR 리뷰
context|FastBridge 프로젝트 — FastAPI 기반 API 게이트웨이
Step 4: 메모리 직접 검색
키워드로 과거 대화를 검색합니다:
euleragent memory search --project default --q "Python 코딩 스타일" --top-k 5
예상 출력:
Search results for "Python 코딩 스타일" (project: default):
[1] score=0.8234 role=user 2026-02-23 09:15:00
앞으로 Python만 써줘. Ruby나 JavaScript 예시는 필요 없어.
[2] score=0.7112 role=user 2026-02-23 09:17:00
절대 외부 라이브러리 추천하지 마. 표준 라이브러리만 사용해.
[3] score=0.6891 role=assistant 2026-02-23 09:15:00
알겠습니다. 모든 코드 예시는 Python으로만 작성하겠습니다.
다른 키워드로 검색:
euleragent memory search --project default --q "프로젝트" --top-k 3
예상 출력:
[1] score=0.9102 role=user 2026-02-23 09:25:00
우리 서비스 이름은 FastBridge야. FastAPI 기반 API 게이트웨이야.
[2] score=0.7834 role=assistant 2026-02-23 09:25:00
FastBridge 프로젝트 (FastAPI 기반 API 게이트웨이) 컨텍스트를 저장했습니다.
Step 5: 다음 세션 — --continue로 이어받기
프로세스를 종료하고 새 터미널에서 시작해도 이전 대화 내용을 기억합니다:
euleragent chat my-assistant --project default --continue
예상 출력:
Chat with my-assistant (project: default). Type 'exit' to quit.
[memory] Loaded 12 turns from long-term memory.
───────────────────────────────────────────────────────────
에이전트가 이전 세션을 기억하는지 확인합니다:
You: 오늘 할 일 목록 정리해줘.
Agent: ## FastBridge 오늘 할 일
**오전 (9시부터 PR 리뷰):**
- [ ] GitHub PR 리뷰
- [ ] 리뷰 피드백 반영
**이후 작업:**
- [ ] FastBridge API 게이트웨이 개발
- [ ] 코드 주석 한국어 정리
(Python 표준 라이브러리 기반 작업 우선)
이전 세션에서 학습한 내용(FastBridge 프로젝트, 오전 9시 PR 리뷰, Python 선호, 한국어 주석)을 모두 기억하고 있습니다.
Step 6: --history-limit으로 로드할 대화 수 조절
기본값은 최근 20개 발화를 로드합니다. 대화가 많이 쌓이면 토큰 절약을 위해 조절할 수 있습니다:
# 기본값: 최근 20개 발화
euleragent chat my-assistant --project default --continue
# 더 많이: 최근 50개 발화 (더 풍부한 컨텍스트)
euleragent chat my-assistant --project default --continue --history-limit 50
# 더 적게: 최근 10개 발화 (빠른 응답, 토큰 절약)
euleragent chat my-assistant --project default --continue --history-limit 10
언제 조절하나요?
| 상황 | 권장 값 |
|---|---|
| 짧은 세션, 단순 질문 | 10 |
| 일반적인 개발 대화 | 20 (기본) |
| 복잡한 프로젝트, 긴 맥락 필요 | 50 |
| 최대 컨텍스트 (긴 리서치) | 100 |
Step 7: --debug-memory로 메모리 회수 관찰
메모리 회수 과정을 실시간으로 확인합니다:
euleragent chat my-assistant \
--project default \
--continue \
--debug-memory
예상 출력:
Chat with my-assistant (project: default). Type 'exit' to quit.
[memory] Loaded 12 turns from long-term memory.
You: FastBridge API에서 요청 검증하는 코드 짜줘.
[memory] Retrieved 3 LTM hit(s):
[0.82] "앞으로 Python만 써줘"
[0.79] "절대 외부 라이브러리 추천하지 마"
[0.77] "FastBridge — FastAPI 기반 API 게이트웨이"
Agent: ```python
# FastBridge 요청 검증 미들웨어
from fastapi import Request, HTTPException
from typing import Callable
async def validate_request(request: Request, call_next: Callable):
# API 키 검증
api_key = request.headers.get("X-API-Key")
if not api_key:
raise HTTPException(status_code=401, detail="API 키가 필요합니다")
# ... (표준 라이브러리만 사용)
[memory] Session: 14 turns | Profile: 5 entries | LTM hits: 3
`--debug-memory`는 다음 정보를 보여줍니다:
- 각 발화 후 회수된 LTM 항목과 유사도 점수
- 현재 세션의 누적 발화 수
- 저장된 프로필 항목 수
---
### Step 8: 세션 간 메모리 작동 원리
`--continue` 없을 때와 있을 때의 차이:
--continue 없이: --continue 포함: ──────────────────── ──────────────────────────────── [system] [system] [user: 현재 입력] [user: 이전 세션 발화 1] ← LTM에서 로드 [asst: 이전 세션 발화 1] [user: 이전 세션 발화 2] [asst: 이전 세션 발화 2] ... [user: 현재 입력]
세션 내에서는 `--continue` 없이도 히스토리가 자동 누적됩니다. `--continue`는 **프로세스 종료 후 다음 세션**에서 이전 내용을 복원하는 기능입니다.
---
### Step 9: 런 아티팩트에서 메모리 컨텍스트 확인
각 채팅 발화는 별도의 run으로 처리되며, 아티팩트에 메모리 회수 결과가 저장됩니다:
```bash
# 가장 최근 런 ID 확인
ls -t .euleragent/runs/ | head -1
# e5f6a1b2c3d4
cat .euleragent/runs/e5f6a1b2c3d4/artifacts/memory_context.json
예상 출력:
{
"profile": {
"preference": ["Python만 사용, 표준 라이브러리만"],
"prohibition": ["외부 라이브러리 추천 금지", "긴 설명 금지"],
"routine": ["매일 아침 9시 PR 리뷰"],
"context": ["FastBridge 프로젝트 — FastAPI 기반 API 게이트웨이"]
},
"hits": [
{
"ref_id": "conv_abc123",
"text": "앞으로 Python만 써줘. Ruby나 JavaScript 예시는 필요 없어.",
"role": "user",
"score": 0.8234,
"created_at": 1708659000.0
},
{
"ref_id": "conv_def456",
"text": "절대 외부 라이브러리 추천하지 마. 표준 라이브러리만 사용해.",
"role": "user",
"score": 0.7891,
"created_at": 1708659300.0
}
],
"hit_count": 3
}
MCP 검색 프로바이더를 사용하는 경우, 검색 라우팅 결정도 아티팩트로 기록되어 메모리에서 조회할 수 있습니다:
cat .euleragent/runs/e5f6a1b2c3d4/artifacts/search_routing.jsonl
{"query": "FastBridge API 검증", "routed_to": "local_kb", "reason": "internal_doc_match", "timestamp": "2026-02-23T09:30:01Z"}
이 라우팅 기록은 이후 세션에서 --continue로 재개할 때 컨텍스트의 일부로 활용됩니다.
전체 메모리 흐름 다이어그램
euleragent chat my-assistant --continue
│
├─ LongTermMemory.get_recent_history(limit=20)
│ SQLite에서 최근 발화를 시간순으로 로드
│
├─ Runner.run(session_messages=[...])
│ ├─ _compose_context() 시스템 메시지 구성
│ ├─ [session_messages 주입] 이전 세션 발화 삽입
│ ├─ _do_ltm_retrieval() 벡터 검색으로 관련 발화 추가
│ ├─ _generate_plan() LLM이 전체 컨텍스트로 응답
│ └─ _save_turns_to_ltm() 신규 발화만 저장 (중복 방지)
│
└─ artifacts/memory_context.json 회수 결과 기록
예상 출력 요약
# 채팅 시작
$ euleragent chat my-assistant --project default
Chat with my-assistant (project: default). Type 'exit' to quit.
# 메모리 상태 확인
$ euleragent memory status --project default
Conversations: 12
Vectors: 12
Profile entries: 5
# 메모리 검색
$ euleragent memory search --project default --q "Python"
[1] score=0.8234 role=user "앞으로 Python만 써줘..."
# --continue로 재개
$ euleragent chat my-assistant --project default --continue
[memory] Loaded 12 turns from long-term memory.
# --debug-memory
You: 코드 짜줘
[memory] Retrieved 3 LTM hit(s): [0.82] ... [0.79] ... [0.77] ...
[memory] Session: 14 turns | Profile: 5 entries
자주 묻는 질문 / 흔한 오류
Q: --continue를 써도 이전 대화 내용이 없다고 나옵니다.
첫 번째 세션에서 exit를 입력하고 정상 종료했는지 확인하세요. 비정상 종료(Ctrl+C)의 경우 일부 발화가 저장되지 않을 수 있습니다. memory status로 저장된 발화 수를 확인합니다:
euleragent memory status --project default
# Conversations: 0 이면 저장이 안 된 것
Q: memory search에서 관련 내용이 검색되지 않습니다.
min_score 임계값이 너무 높을 수 있습니다. workspace.yaml에서 낮춰보세요:
memory:
retrieval:
min_score: 0.0 # 임시로 0으로 낮춰서 모든 결과 확인
또는 검색 쿼리를 실제 저장된 문장에 더 가깝게 바꿔보세요.
Q: fake 임베딩을 쓰면 의미 기반 검색이 안 되나요?
fake 임베딩은 SHA256 해시 기반이므로 의미 유사도가 아닌 텍스트 일치에 가깝게 동작합니다. 예를 들어 "Python 선호"로 검색할 때 "Python을 써줘"라는 발화가 잘 매칭되지 않을 수 있습니다. 의미 기반 검색을 원하면 Ollama를 실행하고 provider: ollama로 설정하세요:
memory:
embedding:
provider: ollama
model: nomic-embed-text
Q: --project를 다르게 설정하면 기억이 공유되나요?
아니요. 프로젝트별로 메모리가 분리됩니다. --project default와 --project work는 완전히 다른 메모리 공간을 사용합니다. 채팅과 메모리 조회에 같은 --project 값을 사용해야 합니다.
# 세션 1
euleragent chat my-assistant --project work
# 조회 시 같은 project 사용
euleragent memory status --project work
euleragent memory search --project work --q "Python"
Q: 에이전트가 선호를 계속 잊어버립니다.
agent.yaml에서 memory.enabled: true인지 확인하세요. 그리고 대화가 끝나면 반드시 exit로 정상 종료하세요. Ctrl+C로 강제 종료하면 마지막 발화가 저장되지 않을 수 있습니다.
Q: 저장된 메모리를 초기화하려면 어떻게 하나요?
프로젝트별 메모리를 초기화하려면 SQLite DB를 직접 삭제합니다:
# DB 위치 확인
euleragent memory open --project default
# DB 삭제 (주의: 복구 불가)
rm .euleragent/state/memory.db
전체 초기화가 아니라 특정 발화만 삭제하려면 SQLite에서 직접 처리합니다:
sqlite3 .euleragent/state/memory.db
sqlite> DELETE FROM conversations WHERE content LIKE '%삭제할 내용%';
sqlite> .quit
다음 단계: 06_dynamic_workflow.md — Dynamic 워크플로우로 복잡한 태스크를 자동 분해합니다.