개발과 취향
← 목록으로
개발 · 2026.08.19

커피 시음 후기 기록용 AI 에이전트, 모카 개발기 (2) — 에이전트의 컨텍스트 관리하기

내 AI 에이전트는 얼마나 기억해야하고 어디까지 기억해야하는가

개발모카AI 에이전트컨텍스트 관리OpenAI API사이드프로젝트
커피 시음 후기 기록용 AI 에이전트, 모카 개발기 (2) — 에이전트의 컨텍스트 관리하기

그래서 컨텍스트 관리가 뭔데

모카를 OpenAI API 단발성 호출 서버에서 AI 에이전트로 전환하며 제일 먼저 신경쓴 부분이 컨텍스트, 그러니까 기억이다. 기억이란 뭘까. 가장 간단한 예로 “아까 그거 있잖아.” 같은 발화를 했을 때 상대방이 “아까 그것”이 무엇인지 이해할 수 있다는 걸 들 수 있겠다. 현재 발화(“아까 그거 있잖아.”)엔 지시 대명사가 전부인데 그 이전 대화에서 이 지시 대명사가 그래서 무엇을 가리키는지 찾아 이해하는 것이다.

최초에는 이 기억을 두가지 관점에서 바라봤다. 첫번째는 LLM은 사용자와의 대화를 어디서부터 어디까지 기억하고 있을까, 두번째는 그래서 우리 서버는 OpenAI API에게 어디서부터 어디까지의 대화를 넘겨주어야할까.

찾아보니 LLM 자체는 기억이 없다고 하더라. 즉, 내가 몇번을 부르든 이 친구에게는 새로운 요청이란 뜻이며 내가 열심히 지시 대명사를 써봤자 1초 전에 말한 “아까 그게” 뭔지 알 수 없다는 뜻이었다. 그런데 내가 접해본 AI 챗봇을 돌이켜 생각보면 그들은 분명 기억을 하는 것처럼 느껴졌다. 그럼 이 친구처럼 기억이 유지되게 만드는 가장 간단한 방법은 매번 요청할 때마다 지금까지의 대화를 통째로 다시 보내주는 것이 될 수 있겠다고 생각했다. 그리고 이 “매번 다시 보내주는 묶음”이 바로 컨텍스트(context) 라고 생각했다.

사용자가 보는 것           실제로 일어나는 일
나: 어제 에티오피아 마셨어   요청 1: [어제 에티오피아 마셨어]
봇: 오, 어땠어?
나: 산미가 좋더라          요청 2: [어제 에티오피아 마셨어]
봇: 기록해줄까?                  [오, 어땠어?]
                              [산미가 좋더라] ← 전부 다시 보낸다

그럼 우리 서버는 OpenAI API에게 요청을 보낼 때, 매번 대화의 처음부터 끝까지 보내줘야할까? OpenAI API를 찾아보면 Response API 라는 것이 있는데 이걸 이용하면 현재 발화와 이전 발화를 연결할 수 있다. 그러니까 Responses API의 previous_response_id 를 가지고 기억 유지가 가능하다는 의미이다. 그렇다고 이 기능에게 모든 걸 맡기고 갈 순 없었다. 무한히 대화를 연결할 것이 아니라면 이 기능을 사용하더라도 어디까지 사용할지, 어떻게 사용할지의 정책은 필요하다고 생각했다.

대화가 길어질수록 API에 실어 보내는 양이 계속 늘어난다. 보내는 양은 곧 비용(LLM API는 주고받는 토큰 수만큼 과금된다)이고 연결된 대화가 너무 길어지면 모델이 앞부분을 제대로 활용하지 못하기도 한다. 그렇다고 아무거나 지우면 에이전트가 갑자기 “그거”를 못 알아듣게 된다.

그래서 컨텍스트 관리의 핵심 질문은 무엇을, 어디에, 얼마나 오래 기억하고 언제, 어떻게 잊을 것인가. 이다.

글을 본격적으로 시작하기 전에 용어를 하나 정의해야겠다. 대화 주고받음 한 번(사용자가 메시지 하나를 보내고, 에이전트가 답 하나를 돌려주는 것)을 턴(turn) 이라 부른다. 위 그림의 대화는 2턴짜리인 셈이다. “대화가 10턴 쌓였다”는 말은 그런 주고받음이 열 번 있었다는 뜻이고, 이 글에서 턴은 끝까지 이 의미로만 쓴다.

생각해보니 기억이 여러 곳에 있는 거 같다

“에이전트의 기억”이라고 하면 하나의 상태 같지만 좀 더 뜯어보니 수명이 서로 다른 여러 개의 상태가 각자 다른 곳에 살고 있다는 걸 알 수 있었다. 모카의 경우는 아래와 같다 :

┌─ 브라우저 ────────────┐   ┌─ 서버(메모리) ──────────────────┐
│                              │   │                                          │
│  화면에 보이는 대화              │   │  대화 기록(트랜스크립트)                       │
│  ⏳ 페이지가 떠 있는 동안        │   │  ⏳ 작업 하나가 끝날 때까지                     │
│                              │   │     (무활동 1시간 소멸 · 크기는 20턴까지)        │
│                              │   │                                          │
│  작성 중인 폼(draft)           │   └─────────────────────────┘
│  ⏳ 폼이 떠 있는 동안           │
│                              │
└──────────────────┘

┌─ 디스크 ─────────────┐   ┌─ OpenAI 서버 ─────────────────┐
│                              │   │                                          │
│  DB (노트 기록) · 사진 보관 폴더  │   │  턴 안의 중간 상태                           │
│  ⏳ 영구 — 여기만 진짜다         │   │  ⏳ 응답 한 번 만드는 동안 쓴다                │
│                              │   │     (데이터 보존은 별개 — 뒤에서 다룬다)        │
│                              │   │                                          │
└──────────────────┘   └─────────────────────────┘

설계의 대원칙은 하나였다. 영구히 남는 기억은 디스크(DB와 사진)뿐이고, 나머지는 전부 잃어버려도 되는 임시 기억이다. 임시 기억이 날아가서 생기는 최악의 상황은 “사용자가 한 번 더 말해야 하는 것”이지, 데이터가 사라지는 게 아니다. 편리성을 위해 임시 상태를 영구 상태에 가깝게 관리할수록 설계와 코드는 복잡해지고 복잡해진만큼 버그가 늘어난다. 모카의 최초 설계에선 임시 상태를 디스크에 저장해서 관리했었는데 이것이 나중에 사용자 요청이 충돌하여 사용자 요청을 받아들일 수 없는 상태가 발생했었다. 그래서 버그를 수정할 때 최소한의 설계로 다시 시작하는 것이 확장에 유리하다고 생각해 임시 상태를 관리하지 않는 방향으로 수정했다.

서버의 기억은 언제 지워지는가

모카 서버는 사용자와 나눈 대화를 메모리에 들고 있다. 이 기억은 에이전트가 “그 시음기 저장해줘”, “아까 그 커피 말이야”를 알아들을 수 있게 만든다. 문제는 이 기억을 무한정 쌓을 수 없다는 것. 심지어 이건 서버 메모리다. 언젠가는 지워야 하는 이 기억을 언제 지울 것인지가 생각해볼 지점이었다.

가장 직관적인 방법: 최근 N턴만 남기기

가장 쉽게 떠올릴 수 있는 방법은 오래된 것부터 잘라내는 것이다. 대화를 최근 10턴이면 10턴, 20턴이면 20턴만 남기고, 넘치면 제일 오래된 턴부터 버린다. 일종의 슬라이딩 윈도우라고 생각할 수 있겠다. 구현이 쉽고 예측 가능해서 가장 먼저 떠오른 방법이었다. 그런데 이 방식의 문제는 대화가 아니라 “작업”의 관점에서 보았을 때, 잘리는 지점이 하필 작업 한가운데일 수 있다. 는 것이다.

구체적인 예로, 사용자가 커피 하나를 기록하는 중이라고 했을 때:

[1턴]  "어제 산 에티오피아 예가체페 코케허니 마셔봤어"  ← 원두 정보가 여기 있다
[2턴]  "산미가 밝고 홍차 같은 느낌"
[3턴]  "아 물은 93도로 내렸어"
  ⋮
  (레시피 얘기, 잡담이 이어지며 대화가 길어진다)
  ⋮
[11턴] "좋아, 그거 이제 저장해줘"                  ← "그거" = 1턴의 그 원두

윈도우가 10턴이라면, 11턴째 사용자 발화가 들어오는 순간 앞의 1턴이 잘려나간다. 그 1턴이 하필 원두 이름이 담긴 턴일 때 에이전트는 “그거”가 뭔지 알 길이 없어지고, 사용자 입장에서는 잘 알아듣던 에이전트가 갑자기 말귀를 못알아 듣는 상황이 되어버린다.

슬라이딩 윈도우: 기준이 '몇 턴 반복 했는가'
[11턴]이 들어오는 순간, 윈도우(최근 10턴) 밖으로 밀려난 [1턴]이 잘린다

    [1턴]  원두 정보  ✂️ 잘려나감!
 ┌─────────────────────────────────┐
 │  [2턴]  산미 얘기                                        │
 │  [3턴]  물 온도 얘기                                     │
 │    ⋮                                                  │
 │  [11턴] "그거 저장해줘" ← 근데 "그거"는 방금 잘린 [1턴]에 있다  │
 └─ 윈도우가 남기는 최근 10턴 ───────────────────┘

작업(커피 하나 기록하기)은 아직 안 끝났는데, 기억의 앞부분이 사라졌다

또 다른 방법: 요약해서 압축하기

오래된 대화 순으로 대화를 통째로 버리는 대신, LLM에게 “지금까지 대화를 요약해줘”라고 시켜서 짧은 요약본으로 긴 전체 대화를 바꿔치기하는 방법도 고려해봤다. 하지만 모카를 구현할 때는 이 방법을 채택하지 않았는데 이유는 두가지였다. (1) 모카는 대체로 “커피 시음기 및 레시피 기록하기” 정도의 짧은 작업 단위를 가지는데 이 짧은 작업을 굳이 압축하기 위해 굳이 LLM을 한번 더 호출하는 것이 비용대비 효율이 애매하다. API 호출 비용은 비싸니까… (2) 압축하는 과정에서 노트에 기록해야하는 핵심 정보가 유실될 수 있다. 커피 기록이 단순 요약본으로 대체되면서 노트에 담겨야할 정보들이 유실되면 노트를 편하게 기록할 수 있다는 최대 장점이자 핵심 기능이 상당히 훼손된다. 따라서 컨텍스트 압축은 채택되지 못했다.

최종 선택: 작업이 끝나는 순간 전체 비우기

앞서 말한대로 모카는 짧은 단위 작업 위주의 에이전트이다. 사용자가 시음 노트 또는 레시피로 기록하고 싶은 내용을 발화하고 모카가 적는다. 그리고 저장한다. 이 이상으로 대화가 복잡하고 길어질 일이 적다. 모카는 커피 시음 노트를 쉽게 적기 위해 개발된 에이전트니까. 그래서 “몇 턴이나 쌓였나”를 세는 대신, “이 작업이 끝났다”는 분명한 순간에 기억을 통째로 비우는 방식을 채택하기로 했다.

작업이 끝나는 케이스는 두가지로 구분했다.

  • 사용자가 폼에서 [저장]을 눌러 기록이 DB에 들어갔을 때 — 이 커피 얘기는 완료된 걸로 한다.
  • 사용자가 폼에서 [취소]를 눌러 기록 저장을 취소했을 때 — 이 커피 얘기는 없던 걸로 한다.

둘 다 사용자가 직접 누른 버튼으로 인해 폼이 닫히는 모든 순간 을 기준으로 삼았으며 “커피 하나에 대한 대화가 여기서 끝난다”는 신호로 삼기에는 꽤 강력한 것들이라고 생각했다.

기준: '작업이 끝났는가'

┌─ 커피 A 기록 작업 ────────┐  ┌─ 커피 B 기록 작업 ───┐
│  [1턴][2턴][3턴] ⋯ [저장!]    │   │  [1턴][2턴] ⋯        │
└─ 저장 완료 → 🧹 전체 비움 ───┘   └─ 새 기억으로 시작 ────┘

기억이 초기화 되는 지점이 항상 작업과 작업 '사이'다

이 방법의 큰 장점은 언제 컨텍스트를 비울지를 LLM에게 판단시키지 않는다 는 것이다. 모카는 이 방법을 채택함으로써 컨텍스트 비우기는 결정론적 이벤트(버튼 클릭, 시간 만료)로 설계되어 같은 상황이면 항상 같은 결과가 나오게 됐다.

이 방법과 다른 방향으론 “이제 대화 주제가 바뀐 것 같으니 비울까?”를 모델에게 물어보는 설계를 고려해볼 수 있다. 그러나 이 방법은 컨텍스트가 비워지는 시점이 확률적이게 되고, 같은 상황인데도 어떤 날은 컨텍스트가 비워지고 어떤 날은 비워지지 않는 일이 발생할 가능성이 존재하게 된다.

에이전트를 개발하면서 가장 골치아팠던게 비결정적 상황에서 확률적으로 발생하는 버그를 디버깅하는 것이었는데 데이터 저장과 관련된 크고 중요한 기능을 되도록이면 덜 골치아픈 곳에 두어서 서비스를 좀 더 안정적으로 확장시킬 수 있도록 했다.

저장을 수반하는 작업이 아니라면?

“저장하면 컨텍스트를 비운다”는 정책만으로는 부족한 부분이 존재한다. 만약 저장까지 가지 않고 대화가 흐지부지 된다면? 그렇게 된다면 컨텍스트가 영영 비워지지 않게 된다. 그래서 저장 여부와 관계 없이 컨텍스트를 비우는 케이스를 추가했다.

  • 1시간 동안 활동이 없으면 비운다. : 기준은 마지막 활동 시각이다. 대화 시작 시각 기준이 된다면 한창 진행 중인 대화가 “생성된 지 1시간 됐다”는 이유로 끊기는 황당한 일이 생길 수 있다.
  • 20턴이 넘으면 오래된 턴부터 잘라낸다. : 제일 처음 생각했던 슬라이딩 윈도우를 보조 정책으로 도입했다. 주력 정리 수단이 아니라, 한 작업 안에서 대화가 비정상적으로 길어졌을 때 메모리와 비용이 무한히 불어나는 걸 막는 방어선이다. 정상 흐름에서는 그 전에 저장/취소로 비워질것이라 생각했다.

정리하면 아래와 같다:

대화 기록이 지워지는 4가지 경로

┌─ 사용자가 정하는 경우 (주력) ───┐   ┌─ 서버가 정하는 경우 (안전판) ───┐
│  [저장] 완료 → 전체 비움         │   │  1시간 무활동 → 전체 비움         │
│  폼 닫음([취소] 등) → 전체 비움   │   │  20턴 초과 → 오래된 턴부터 잘림    │
└──────────────────┘   └──────────────────┘

(+ 서버 재시작 — 메모리에만 두므로 자연히 소멸)

서버만 기억하는 것이 아니다

모카는 클라이언트에서만 유지되는 기억이 있다. 대표적으로 에이전트가 제안한 폼(작성 중인 기록) 은 서버가 기억하지 않는다. 작성 중인 기록은 전부 브라우저가 들고 있고, 사용자가 메시지를 보낼 때마다 현재 폼 상태를 요청에 통째로 실어 보낸다.

브라우저                                       서버
┌─ 작성 중인 폼 ───────────┐
│  원두 = 예가체페                  │   ──▶  매 요청마다: 발화 + 현재 폼 전체
│  온도 = 93도 ← 사용자가 직접 수정!  │          "산미 항목도 추가해줘" + { 원두, 온도: 93도 }
└───────────────────┘   ◀──  응답: 새 폼 제안
                                             (서버에는 아무것도 남지 않는다)

최초 설계에선 DB에 “작성 중” 상태를 저장했다. 그렇게 되니 최종적으로 어느 데이터가 최종의 진실된 데이터인지 구분하기 어렵거나 사실상 임시 상태를 저장하는 이득이 없는 결과를 낳게 되었다.

예를 들어 클라이언트에서 정보를 보내면 서버가 저장하고 저장 결과를 OpenAI API로 보낸다고 하자. 클라이언트에서 보낸 정보중에 무엇이 변경되었는지 검증하지 않으면 어차피 다 OpenAI API로 보내야한다. 변경 감지를 코드로 구현하는게 나을까, 통으로 보내는 게 나을까. 이것은 경우에 따라 다를 수 있다. 데이터 사이즈가 크지 않은 모카의 경우 변경 감지를 구현하는 것보다 그냥 데이터를 전부 보내는 게 깔끔하다고 생각했다. 그럼 사실상 DB에 저장된 데이터는 무정차 정류장 정도 밖에 되지 않는다.

그럼 저장된 데이터를 활용하고 싶으니 클라이언트가 새로고침 됐을 때 서버 정보를 가져다가 사용한다고 하자. 그렇게 되면 서버에 있는 데이터와 클라이언트에 있는 데이터가 서로를 양방향으로 참조하게 되는데 그렇게 되는 순간 어느 쪽에 있는 데이터가 최종본인지 확신하기 어려워진다. 결국 어느 데이터가 최신인지 알기 위해선 데이터의 최종 수정 상태등을 관리해야하는데 이것을 감내할 만큼 이 폼(데이터)의 수명이 길고 복잡한가? 모카의 경우 데이터가 복잡하지 않고 주고받는 대화가 길게 이어지는 상황이 많지 않다. 커피 시음기와 레시피를 한 번 말하고 그걸 등록하면 끝이기 때문이다. 따라서 임시 상태 저장 기능이 지금 상당히 유효한 기능이라고 평가하기 어려운 면이 있다.

한 개의 대화 안에도 기억이 있다

사용자가 메시지 하나를 보내고 답을 받는 한 턴의 사이에도 별도의 기억이 있다.

모카는 에이전트로 답 하나를 만들기 위해 내부적으로 LLM을 여러 번 오간다. 모델이 “기존 노트 목록을 보여줘”라고 도구(tool)를 요청하면 서버가 실행해 결과를 돌려주고, 모델이 그걸 보고 다음 행동을 정하는 반복이다. 이 반복 사이의 문맥은 OpenAI 서버가 이어준다. 이것이 앞서 잠깐 언급 됐던 Responses API의 previous_response_id 체이닝이다. 직전 응답의 ID를 다음 요청에 넘기는 방식이다.

┌─ 한 턴 ──────────────────┐
│                                     │
│  모델 호출 ①  →  tool 실행 (노트 검색)  │
│      ↓ 이전 응답 ID로 연결             │
│  모델 호출 ②  →  tool 실행 (제안 작성)  │
│      ↓                              │
│  모델 호출 ③  →  최종 답변             │
│                                     │
└─ 턴이 끝나면 이 연결 고리는 버린다 ────┘

턴과 턴 사이는 우리 서버의 대화 기록이 잇는다

턴이 끝나면 우리 서버는 이 연결 고리(직전 응답 ID)를 초기화한다. OpenAI 쪽 기억은 답 하나를 만드는 동안만 쓰고, 턴을 넘어가는 기억은 우리 서버의 대화 기록이 담당한다. 기억의 관할을 명확하게 분리하여 “어디까지가 누구 기억인지” 혼란스러울 일을 줄였다.

여기서 한가지 고려할 점은 “답 하나를 만드는 동안만 쓴다”는 것은 모카가 이 기억을 사용하는 기간을 말한다. 그리고 OpenAI에서 데이터가 사라지는 시점과 이 사용 기간은 일치하지 않는다. 이 체이닝은 응답을 OpenAI 서버에 보존해두는 것(store)을 전제로 동작하고, 보존된 응답은 턴이 끝난 뒤에도 일정 기간(기본 30일) OpenAI 쪽에 남는다. 즉 감상 원문이 외부로 전송되고 저쪽에 한동안 보존된다는 것인데, 이건 이 구조를 선택하면서 인지하고 수용한 트레이드오프다. 감상 원문이 민감 데이터는 아니기 때문에 수용한 면이 있다.

그리고 이 한 턴 사이의 반복 안에도 무한한 호출 또는 대기를 방지하기 위해 어느 때 중단할지에 대한 정책이 수립되어있다. 에이전트 루프는 방치하면 도구를 무한정 부르며 비용을 소비 할 수 있는 구조라서, 최대 반복 상한 정책을 두었다고 보면 된다:

브레이크기본값막는 것
도구 호출 횟수8회도구를 무한정 부르는 폭주
턴 누적 토큰10만비용 폭탄
턴 경과 시간60초하염없는 기다림
응답 1건의 출력 길이4천 토큰끝없이 길어지는 답변

어느 제한이든 충족시키면 턴은 “다시 한 번 보내 달라”는 안내로 정리되고, 무엇에 걸렸는지는 로그에 구분해 남기도록 했다.

잊었으면 잊었다고 말했어야 했는데…

앞선 이야기들은 “언제, 어떻게 잊는가”였다. 개발할 땐 이게 제일 중요하다고 생각해서 여러번 반복해서 설계했다. 그리고 신나서 사용해보니… 컨텍스트 관리 결과를 사용자한테 알려주는 걸 빼먹었더라..

구체적인 시나리오는 이렇다. 커피 얘기를 열심히 하다가 급한 일이 생겨 자리를 비웠다. 한시간 뒤 다시 와서 “그래서 그거 저장해줘”라고 요청했다. 그런데 서버의 컨텍스트는 1시간이 지나 수명이 만료되어 이미 비워졌다. 하지만 브라우저 화면에는 1시간 전에 나와 모카가 나눈 대화 기록이 남아있다. 내 눈에는 대화가 이어지는 중으로 보이는데 사실 서버는 진작 기억을 잃었고, 결과는 “앗차. 서버 컨텍스트가 사라졌구나.”가 된다. 화면의 대화(기억)와 서버의 기억은 수명이 달라서 생기는 문제다.

제일 처음 생각한 해결법은 기억이 사라진 채 시작하는 첫 턴에 안내를 붙이는 것이었다. 클라이언트가 서버에 요청을 보냈는데 서버의 컨텍스트가 비어있으면 현재 컨텍스트가 비어있다고 응답하는 순이다. 그럼 서버의 컨텍스트가 비어있는 케이스를 생각해보면 아래와 같이 나눠볼 수 있다.

서버가 보는 것: "대화 기록이 비어 있음". 원인은?

경우 A: 1시간 만료로 지웠다       → 안내가 필요하다
경우 B: 서버가 재시작돼서 날아갔다  → 안내가 필요하다 (근데 흔적조차 없다)
경우 C: 오늘 처음 온 첫 인사다    → 안내하면 이상하다 ("무슨 앞 얘기?")

서버 눈에는 세 경우가 완벽히 똑같이 생겼다

이 케이스를 어떻게 구분하면 좋을까. 현재 설계로는 “이전 대화가 있었는가” 에 대한 정보를 가진 쪽은 브라우저뿐이다. 그래서 안내 주체를 바꾸기로 했다. 서버는 “이번 턴을 빈 기억으로 시작했다”는 사실만 응답에 표시하고 그 사실을 안내할지, 안내를 한다면 어떻게 할지는 브라우저가 결정하기로 했다. 즉, 이전에 성공한 대화가 아직 없으면 안내도 없다 는 논리이다. 참고로 “화면에 메시지가 있는가”를 기준으로 삼지는 않았다. 앱을 열면 모카의 인사말이 항상 먼저 깔려 있어서 화면은 첫 발화 시점에도 비어 있지 않기 때문이다. 처음에는 서버에 상태를 하나 추가할까, 하는 생각을 했다. 그러니까 컨텍스트가 왜 사라졌는지에 대한 관리값을 추가하는 방식이다. 그런데 잊음을 알리기 위해 새로운 상태값을 추가하여 기억한다고 생각하니 또 넌센스로 느껴졌다. 뿐만 아니라 그렇다면 이 상태 데이터는 언제 어떻게 수정되고 비워질 건지에 대한 정책 역시 설계되어야 한다. 그래서 우선 간단하게 브라우저에서 안내를 담당하고, 추후 다른 설계가 필요하다면 확장 수정하기로 계획했다.

만들어본 적 없는 것을 설계한다는 건…

만들어본 적 없는 것을 설계한다는 건 항상 앗차의 연속인 거 같다. 그렇게 수많은 앗차를 통해 복잡해진 코드를 리팩토링하고… 리팩토링 굴레에 갇혀서 기능 수정과 버그 픽스로 넘어가질 못하고… 그래도 모카를 만들면서 에이전트의 동작 방식에 대해 좀 더 이해가 생긴 것 같아 다행이라고 생각한다.

5줄 요약

  1. 컨텍스트 관리는 “무엇을 어디에 얼마나 기억하고 언제 잊는가”로 정의했다. LLM은 스스로 기억하지 못하므로, 이건 전부 애플리케이션의 설계 책임으로 생각했다.
  2. 컨텍스트를 지우는 기준은 작업 단위로 설계했다. 하나의 작업은 하나의 트랜잭션처럼 이어지도록 설계했다.
  3. 비우는 시점을 LLM에게 판단시키지 않았다. 컨텍스트를 비우는 이벤트를 결정론적 이벤트로 만들어 같은 상황엔 늘 같은 결과가 나오고 디버깅이 용이하게 했다.
  4. 기억의 원천은 한 곳에만 두었다. 기억의 원천이 두 곳이 되는 순간 복잡성이 증가하게 되어 그를 능가할만한 이점이 있지 않는 한 설계를 유지하게 했다.
  5. 컨텍스트가 비었으면 비었다고 알려야한다.

소스 코드 : mocha