> EulerAgent > 튜토리얼 > 기본 > 05. 대화 모드와 장기기억 — 에이전트를 나에게 맞추...

05. 대화 모드와 장기기억 — 에이전트를 나에게 맞추기


학습 목표

이 튜토리얼을 마치면 다음을 할 수 있습니다:


사전 준비

euleragent new my-assistant --template personal-assistant
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 워크플로우로 복잡한 태스크를 자동 분해합니다.

← 이전 목록으로 다음 →