AI 에이전트가 '기억이 충분한데도' 흐려지는 이유 — Anthropic이 제시한 컨텍스트 엔지니어링
이전 글에서 세션 경계 문제를 해결하는 방법을 이야기했다. 그런데 곧 다른 병목을 마주했다. 그건 컨텍스트 윈도우에 여유가 남아 있는데도 에이전트가 흐려지는 현상이었다. 이전에 명시했던 규칙을 30분 뒤에 무시하고, 도구를 늘렸더니 답이 더 두루뭉술해지고, 같은 질문에 두 번째 답이 첫 번째보다 흐려졌다.
Anthropic은 이 현상을 컨텍스트 부패(context rot)라 부른다. 10분짜리 회의와 3시간짜리 회의에서 우리가 같은 밀도로 발언을 기억하지 못하는 것과 같다. 왜 이런 일이 일어나는지는 다음 장에서 짚는다. 일단 이 글의 출발점은, 컨텍스트 윈도우에 여유가 있어도 그 안에서 무엇이 살아남고 무엇이 묻히는지가 따로 결정된다는 사실이다.
그렇다면 고민되는 부분은 “무엇을 더 넣을까”가 아니라 “무엇을 넣지 않을 것인가”가 된다. 이전 글이 세션 사이에 무엇을 파일로 남길지를 다뤘다면, 이 글은 세션 안에서 무엇을 컨텍스트 윈도우에 들이지 않을지를 다룬다. Anthropic은 이 골라내는 기술을 context engineering이라 부른다.
1. Summary — context engineering이 다루는 것
원문은 context를 LLM이 다음 토큰을 뽑을 때 보는 토큰 전체로 정의한다. System prompt, 사용자 메시지, 모델이 여태 한 답변, 호출된 도구의 정의, 그 도구들이 돌려준 응답, 그리고 모델이 외부에서 끌어온 모든 자료. 이 모든 것이 한 추론 시점에 모델이 보는 컨텍스트다. Context engineering은 이 컨텍스트를 큐레이션하는 작업이다. 원문은 이것을 prompt engineering의 자연스러운 진화로 본다. 한 번에 잘 짜인 프롬프트 하나를 쓰는 일이, 매 추론마다 어떤 토큰들이 모델 앞에 놓일지를 결정하는 일로 진화했다고 말이다.
큐레이션(Curation)은 방대한 정보나 콘텐츠 속에서 필요한 것만 선별하고 분류하여, 새로운 가치를 부여해 제공하는 일을 뜻한다.
큐레이션이라는 단어가 거창하게 들릴 수 있는데, 이 작업이 필요한 이유는 의외로 단순하다. 모델이 한 번에 쏟을 수 있는 집중력에 한계가 있기 때문이다. 원문은 이를 어텐션 예산(attention budget)이라 부른다. 사람이 회의가 길어질수록 발언 하나하나에 쓰는 집중력이 줄어들 듯, 모델도 컨텍스트에 새 토큰이 들어올 때마다 이 예산을 조금씩 깎아 쓴다. 도입부에서 말한 컨텍스트 부패가 이 예산이 바닥나는 현상이다.
예산에 왜 한계가 있는지에 대한 답은 모델 아키텍처에 있다. 원문은 세 가지 근거를 든다.
- Transformer는 모든 토큰이 다른 모든 토큰을 참조하는 구조라 토큰이 n개면 관계가 n²개로 늘어난다. 컨텍스트가 길어질수록 이 관계망이 얇게 펴진다.
- 모델의 학습 데이터는 짧은 시퀀스가 더 흔하므로, 모델은 짧은 컨텍스트에 훨씬 많은 경험을 가진 채 긴 컨텍스트를 처음 마주한다.
- Position encoding을 길이에 맞게 보간(interpolation)하는 트릭으로 긴 시퀀스를 다루지만, 이 보간은 위치 정보의 정확도를 약간씩 떨어뜨린다.
세 요인이 합쳐지면 성능이 절벽처럼 뚝 떨어지는 게 아니라 완만한 경사면을 따라 내려간다. 컨텍스트가 길어진다고 모델이 갑자기 멍청해지는 것은 아니지만, 정확도와 회상 능력은 조용히 깎인다.
여기서 좋은 컨텍스트의 원리가 자연스럽게 따라 나온다. 원하는 동작을 끌어내는 최소한의 고신호 토큰. 원문은 정적인 컨텍스트가 큐레이션되는 곳을 세 층으로 짚는다.
- 지시(system prompt) — 너무 빡빡한 if-else도, 너무 추상적인 원칙도 아닌 적정 고도(right altitude)에서 쓴다.
- 도구(tools) — 기능이 겹치거나 어떤 도구를 골라야 할지 모호해지는 bloated tool sets를 피하고, 잘 설계된 코드베이스의 함수처럼 한 가지 일만 분명하게 하도록 만든다.
- 예시(few-shot examples) — 가능한 모든 엣지 케이스를 나열하기보다, 기대 동작을 잘 그려내는 전형적(canonical) 예시 몇 개를 고른다.
세 층에 적용할 공통 원리는 같다. 더 많이 넣는 게 아니라, 신호가 약한 것을 덜어내는 쪽이다.
2. 원문이 말하지 않은 것들
2.1. 세 전략이 사실 하나인 이유
원문은 long-horizon 전략을 셋으로 나눠 차례로 소개한다.
- Compaction은 컨텍스트 윈도우가 한계에 가까워지면 지금까지의 대화를 모델이 요약하게 하고 새 윈도우를 그 요약으로 다시 연다.
- Structured note-taking은 에이전트가 작업 중에 핵심 진행 상태를 외부 파일에 적어두고, 필요할 때 다시 읽어 들인다.
- Sub-agent는 깊은 탐색이 필요한 일을 별도의 깨끗한 컨텍스트 윈도우에서 돌리고, 끝나면 짧은 요약만 메인 에이전트로 돌려보낸다.
원문은 셋을 나란히 놓고 “task 성격에 따라 골라 쓰라”고 한다 — 대화 흐름이 중요하면 compaction, 마일스톤이 또렷한 반복 작업이면 note-taking, 병렬 탐색이 가치 있는 연구라면 sub-agent.
처음 읽으면 셋이 다른 기법처럼 보인다. 그런데 한 발짝 떨어져서 보면 셋 다 같은 한 가지 동작을 한다. 어텐션 예산을 잡아먹고 있는 토큰 덩어리를 컨텍스트 밖으로 꺼내고, 필요할 때 압축된 형태로 다시 들여온다. 차이는 빼내는 대상 하나뿐이다.
- Compaction이 빼내는 것은 과거 메시지다. 모델 자신이 자기 대화를 요약하게 하고 원본 메시지 히스토리는 버리고 요약만 새 윈도우에 들여놓는다.
- Note-taking이 빼내는 것은 진행 상태다. “지금까지 한 일 / 다음에 할 일”을 파일에 적어 컨텍스트 밖에 두고, 그 파일이 필요한 시점에 모델이 직접 읽는다.
- Sub-agent가 빼내는 것은 탐색 과정이다. 수만 토큰이 들 수도 있는 깊은 탐색을 별도 컨텍스트에서 끝내버리고, 메인 에이전트는 그 결과의 1~2천 토큰짜리 요약만 받는다.
세 가지 모두 원본 토큰은 컨텍스트에 두지 않고 — 요약이든 파일이든 sub-agent의 결론이든 — 압축된 대체물만 컨텍스트에 들인다. 백엔드식으로 말하면 셋은 같은 인터페이스의 세 가지 구현이다. 인터페이스가 정의하는 동작은 하나 — 토큰 덩어리를 컨텍스트 밖 어딘가에 두고, 컨텍스트 안에는 그걸 대신할 짧은 무언가만 남긴다. 구현체끼리 다른 점은 어떤 종류의 토큰을 밖으로 빼내는가 하나뿐이다.
원문이 셋을 나란히 놓고 “task에 따라 골라 쓰라”고 하는 건 좋은 조언이다. 하지만 그 조언 밑에 깔린 공통 메커니즘은 원문이 짚어주지 않는다. 그래서 셋이 별개의 트릭처럼 읽히고, 어떤 task에 어떤 걸 쓸지를 경험에 기대서 맞춰보게 되기 쉽다. 공통 메커니즘을 한 번 짚어두면 고르는 기준이 달라진다 — “이 task에서 컨텍스트를 잡아먹는 토큰 덩어리는 무엇이고, 그걸 어디로 빼낼 수 있는가”를 먼저 묻게 된다. 빼낼 대상이 정해지면 어느 전략이 맞는지는 거의 자동으로 따라온다. 과거 메시지면 compaction, 진행 상태면 note-taking, 탐색 과정이면 sub-agent.
2.2. Tool 정의는 호출되지 않아도 비용을 낸다
도구(tool)는 에이전트가 매 턴 “호출할지 말지” 고를 수 있는 기능 단위 — 파일 읽기, API 호출, 검색 실행 같은 것들이다. 이 절에서 볼 것은 도구가 하는 일이 아니라, 모델이 매 턴 고르기 위해 “도구가 어떻게 소개되어 있어야 하는가?” 이다.
원문은 “bloated tool sets는 컨텍스트를 낭비한다”라고 한 줄로 짚고 지나간다. 아마 이 문장은 가볍게 읽히고 넘겨질 것이다. 백엔드 개발자의 직관에서 쓰지 않는 dependency는 크게 문제되지 않기 때문이다. build.gradle에 라이브러리를 하나 더 적어두면 빌드 산출물이 약간 무거워지고 빌드 시간이 약간 늘어날 뿐, 우리가 그 라이브러리를 호출하지 않는 한 런타임 성능은 깎이지 않는다. 비용은 쓸 때 생긴다 — 우리는 이 직관에 익숙하다.
그런데 에이전트의 도구 집합은 다르다. MCP 서버 열 개를 붙여 도구 쉰 개를 등록한 순간, 그중 하나도 호출하지 않은 턴에서조차 쉰 개의 정의 전부가 시스템 프롬프트에 실려 모델 앞에 놓인다. 어텐션 예산은 얼마나 자주 호출되느냐가 아니라 등록되어 있다는 사실만으로 깎인다. 백엔드의 dependency가 디스크에 잠자고 있는 자원이라면, tool 정의는 매 턴 깨어나 자기 자리를 차지하는 자원이다.
왜 매 턴일까? LLM 추론은 상태가 없기 때문이다. 직전 턴에 read_file을 골랐으니 다음 턴에는 그 도구의 시그니처를 빼도 된다 — 이런 최적화가 통하지 않는다. 모델은 매 턴 “어떤 도구를 호출할 수 있는가”라는 선택지의 전체 집합을 새로 받아야 다음 행동을 정한다. 정의 한 줄 한 줄이 “이걸 호출할 수도 있다”는 선택지로 거기 있어야 의미가 있다. 그러니 도구를 추가하는 일은 “필요할 때만 발동되는 기능을 하나 더 얹는” 일이 아니라, 이후 모든 추론에서 시야 한구석을 계속 차지하는 토큰 덩어리를 등록하는 일에 가깝다.
크기 감각도 직관과 어긋난다. 잘 쓴 도구 정의 하나는 이름·설명·파라미터 스키마·타입·예시까지 갖추면 어렵지 않게 수백 토큰이 된다. MCP 서버 두세 개만 붙여도 시스템 프롬프트 상단에 수천에서 수만 토큰이 고정 비용으로 따라붙는다. 이 비용은 사용자 메시지가 길든 짧든, 호출이 일어나든 안 일어나든 매 턴 같은 액수로 빠져나간다. 1장에서 본 어텐션 예산의 경사면 위에서, 이만큼은 대화가 시작되기도 전에 이미 깎여 있는 양이다.
여기까지 보면 원문이 “bloated tool sets를 피하라”고 한 조언의 무게가 조금 다르게 읽힌다. 그건 모호성을 피하라는 인터페이스 설계 권고로도 읽히지만, 한 단계 더 들어가면 이 자원의 비용 모델이 dependency가 아니라 임대료에 가깝다는 사실을 숨겨놓은 말로 읽힌다. 백엔드 세계에서 “일단 추가해두고 안 쓰면 그만”으로 끝날 수 있는 결정이, tool 세계에서는 안 써도 매달 빠져나가는 결정이 된다.
3. 내 작업에 어떻게 적용할 것인가
3.1. 설계부터 시작 — 구현에 대한 생각을 하지 않게
설계를 시작할 때 가장 첫 번째로 중요하게 생각한 건 코드의 구조나 엔드포인트 모양이 아니라 에이전트가 설계만 생각하게 하자 였다.
만약 구현 에이전트에게 설계에 관련된 프롬프트를 주면 매 턴 이 설계 관련 문구를 시야 한구석에 둔 채 작업을 하게 된다. 그러면 특정 턴에서 “잠깐, 이 feature를 다시 쪼개야 하는 거 아니야?” 라는 생각을 할 여지가 생긴다. 이 여지를 원칙 몇 줄(“재설계는 하지 마라”)로 막으려 할 수도 있지만, 설계 어휘 자체가 구현 세션의 컨텍스트에 애초에 들어오지 않게 하는 쪽이 더 낫다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# designer-prompt.md (설계 세션용, 프로젝트 시작 시 한 번만 돈다)
너는 Spring Boot 회원가입 API의 설계를 담당한다.
## 책임
- 요구사항을 feature 단위로 쪼갠다.
- 각 feature의 경계가 애매하면 **다시 쪼개는 것을 망설이지 않는다.**
- 테스트 전략을 feature별로 명시한다 (단위 / 통합 / 경계 조건).
- 각 feature에 verification 체크리스트를 붙인다 (어떤 조건이 만족되어야 완료인가).
## 산출물
- feature_list.json (스키마 v1, passes 필드는 전부 false로 시작)
- 설계 노트 (아키텍처 결정, 엔드포인트 목록, 의존성 판정)
## 금기
- 구현 코드를 쓰지 않는다.
- 테스트 코드를 쓰지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# coder-prompt.md (구현 세션용, 한 feature마다 한 번씩 돈다)
너는 Spring Boot 회원가입 API의 구현을 담당한다.
## 책임
- feature_list.json의 미통과 항목 중 가장 위의 것 하나를 집는다.
- 그 feature의 verification이 전부 통과하도록 코드와 테스트를 쓴다.
- 커밋 단위는 feature 단위. 커밋 메시지에 feature id를 붙인다.
- 작업 종료 시 feature_list.json의 passes를 갱신한다.
## 제약
- **feature 경계는 고정되어 있다. 재설계가 필요해 보이면 중단하고 보고한다.**
- verification 항목에 없는 기능은 추가하지 않는다.
- 다른 feature의 코드는 이번 커밋에서 건드리지 않는다.
## 금기
- 요구사항을 다시 쪼개지 않는다.
- 설계 노트를 수정하지 않는다.
designer 쪽 책임 항목에는 “다시 쪼개는 것을 망설이지 않는다”가 있고, coder 쪽 제약 항목에는 “feature 경계는 고정되어 있다”가 있다. 그리고 coder 쪽에는 “쪼갠다”, “경계를 다시 긋는다” 같은 설계 동사가 아예 등장하지 않는다. 금기 항목에서 “요구사항을 다시 쪼개지 않는다”로 한 번 불려 나오고 끝이다. 이 작은 차이가 두 가지 효과를 가져온다.
첫 번째는 권한의 경계다. 에이전트는 매 턴 “내가 할 수 있는 일은 무엇인가”를 시스템 프롬프트에서 다시 확인하고 움직인다. 그 목록 안에 “feature를 다시 쪼갠다”가 없으면, 에이전트가 설계 작업을 할 경로 자체가 사라진다. 원칙 한 줄로 “재설계하지 마라”를 걸어두는 것과는 다르다. 원칙은 프롬프트 어딘가에 적혀 있고 에이전트는 매번 그 원칙을 참고해서 판단을 내리지만, 원칙은 지켜지지 않을 때도 있다. 반면 언급조차 하지 않으면 그런 일을 시작할 계기가 없어진다.
두 번째는 토큰의 경계다. 구현 에이전트에게 설계 어휘를 언급해서 시스템 프롬프트에 올라가면, 그 후 매 턴 그만큼의 쓰이지 않는 어텐션 예산이 소모된다. 호출되든 안 되든 — 2장의 임대료 비유에서 짚었듯 — 말이다. 그러니 구현 프롬프트에서 설계 어휘를 빼는 것은 단순히 “나중에 혼동을 줄이려고” 하는 주석 수준의 정돈이 아니라 어텐션 예산의 고정 지출을 줄이는 작업이다. 하나의 작업이 권한의 경계와 토큰의 경계라는 서로 다른 층의 문제를 동시에 해결한다.
3.2. 구현 — 꼭 필요한 것만 효율적으로 기억하게
구현할 땐 “바깥에 둘 진행 상태를 얼마나 또렷한 모양으로 둘 것인가?”에 집중했다. 스키마를 딱딱하게 잡으면 읽기·쓰기 비용이 싸다. 스키마 없는 자연어 노트는 의사결정 흐름을 담기에 적합하지만 읽기·쓰기 비용은 비싸다. 나는 이 두 구조의 장점을 동시에 가져가는 쪽을 택했다.
1
2
3
4
5
6
7
8
9
10
11
// feature_list.json — 한 feature의 entry
{
"id": "feat-03-email-verification",
"title": "이메일 인증 토큰 발급 및 검증",
"passes": false,
"verification": [
"POST /auth/verify-email 에 유효 토큰 전송 시 200",
"만료된 토큰 전송 시 410 Gone",
"이미 사용된 토큰 전송 시 409 Conflict"
]
}
1
2
3
4
5
6
7
8
9
10
11
# claude-progress.txt — 같은 작업 중의 한 단락
## 2025-10-14 세션 끝
feat-03(이메일 인증) 작업 중. verification 3개 중 2개 통과. 세 번째
("이미 사용된 토큰 → 409")에서 막힘. 원래는 토큰 테이블에 used_at
컬럼 하나 추가하고 not null 체크로 끝낼 생각이었는데, 동시에 두 번
검증 요청이 들어오면 첫 번째가 used_at을 세우기 전에 두 번째가 통과
해버리는 경로가 있음. 낙관적 락(@Version)으로 가는 게 맞아 보이는데,
이건 feat-03의 verification에 없는 결정이라 다음 세션 시작할 때
판단 필요 — 경계 안쪽에서 처리하면 되는가, 설계로 돌려야 하는가.
두 파일이 같은 feat-03을 다루지만 담는 것이 다르다. JSON 쪽은 verification 세 항목 중 어느 것이 통과했는지를 boolean 값으로 간단히 읽을 수 있다. 한 항목의 passes를 뒤집는 편집은 글자 다섯 개를 바꾸는 일이고, 나머지 항목과 파일 구조는 건드릴 필요가 없다. 반면 자연어 노트는 읽기·쓰기 모두 이렇게 간단하지가 않다. 막힌 이유, 올라온 대안, 그 대안이 어디까지 설계를 건드리는지를 한 단락으로 풀어내려면 문장들이 서로 맞물려야 하기 때문이다.
| 관점 | feature.json | claude-progress.txt |
|---|---|---|
| 담는 것 | 이산적 판정 (통과/미통과) | 판정에 이르는 길 (막힌 이유, 대안, 분기) |
| 읽기 비용 | boolean 하나 읽기 | 단락의 문맥을 따라 읽기 |
| 쓰기 비용 | 한 필드만 뒤집는 편집 | 앞 뒤 문장의 호흡을 맞추는 편집 |
| 깨질 때의 증상 | 편집하기 위해 전체 문맥 파악 필요 | 판정 문장이 중복 기록되어 ‘진실’이 둘이 됨 |
그런데 역할 분리가 무너지면 위와 같은 효과가 사라지니, 두 파일이 각자 맡은 역할이 있다는 점을 분명히 해두어야 한다.
3.3. 구현 — 탐색 자체를 어디서 돌릴 것인가
로그인 후에 간헐적으로 401이 뜨는 증상이 나타났다. 의심 원인은 세 가지였다. 세션 만료 경계값, 토큰 검증 로직의 경계 조건, Redis 세션 캐시의 교체 정책. 각 원인 후보마다 재현 시나리오를 짜고 로그를 뒤지고 설정값을 확인하는 데 토큰이 수천에서 수만씩 들어갔다. 그리고 이 세 갈래를 한 세션의 컨텍스트 안에서 모두 굴리면 어느 갈래가 어디까지 갔는지, 어떤 가설이 기각되었는지가 한 윈도우 안에서 뒤섞이기 시작했다. 결과가 엉망이 되는 걸 보고 나서 sub-agent 전략을 써봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 개념 의사코드 — 실제로 돌려본 구성이 아니라 "이런 모양으로 돌린다"는 스케치
# 워커는 새 컨텍스트를 열어 자기한테 주어진 갈래 하나만 파고든다.
# 탐색 중에 쌓이는 토큰은 워커 안에 머물고, 메인 세션에는 결론 요약만 넘어간다.
worker = SubAgentWorker(
task = "로그인 직후 401 간헐 발생 — 세션 만료 경계값 의심",
context = "clean", # 메인 세션 히스토리 상속 없음
scope_files = ["SessionConfig", "AuthFilter", "logs/2025-10-12"],
)
result = worker.invoke()
# result = {
# conclusion: "세션 만료는 이 문제의 원인이 아님",
# evidence: ["재현 시나리오 2건 모두 만료 전에 401 발생", ...],
# }
# 메인 세션이 받는 건 result 하나다.
# 재현 시나리오 전문, 뒤져본 로그, 기각한 가설은 메인 컨텍스트에 들어오지 않는다.
핵심은 중간 과정을 메인에 들이지 않는다는 데 있다. 만약 메인 세션이 세 갈래의 탐색 과정을 전부 본다면, 어느 결론이 어느 갈래에서 나왔는지를 되짚는 데만 어텐션 예산이 낭비된다.
3.4. 운영 국면 — 매 턴 시야에 둘 도구를 어떻게 고를 것인가
회원가입 API 작업을 여러 세션에 걸쳐 돌리는 동안, 내 프로젝트 루트 .mcp.json에 등록되어 있던 MCP 서버 목록은 한때 이런 모양이었다.
각 서버의
command·args·env는 이 글의 관심사가 아니라 주석으로 축약했다 — 실제 파일은 주석 없는 순수 JSON이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// .mcp.json (전) — 붙여두면 편해 보여서 붙여둔 상태
{
"mcpServers": {
"filesystem": { /* 로컬 파일 읽기/쓰기 */ },
"git": { /* 커밋/diff/로그 */ },
"github": { /* 이슈, PR */ },
"mysql": { /* DB 스키마, 쿼리 */ },
"redis": { /* 키 조회 */ },
"fetch": { /* 임의 URL 본문 가져오기 */ },
"web-search": { /* 일반 웹 검색 */ },
"puppeteer": { /* 브라우저 자동화 */ },
"postman": { /* 저장된 요청 실행 */ }
}
}
이 목록이 다음처럼 줄었다.
1
2
3
4
5
6
7
8
9
// .mcp.json (후) — 기준을 통과한 줄만 남긴 상태
{
"mcpServers": {
"filesystem": { /* 로컬 파일 읽기/쓰기 */ },
"git": { /* 커밋/diff/로그 */ },
"mysql": { /* DB 스키마, 쿼리 */ },
"redis": { /* 키 조회 */ }
}
}
여기서 볼 것은 어떤 줄이 남고 어떤 줄이 빠졌는가 다. 빠진 다섯 줄은 쓸모없는 도구여서 뺀 게 아니다. 에이전트가 매 턴 도구 목록을 읽는 비용이 호출 여부가 아니라 등록된 줄 수에 비례한다는 걸 알고 나니, 고르는 기준이 바뀌었다. “이 도구가 안 쓰이는가”가 아니라 “이 도구가 매 턴 이 자리를 차지할 만큼 자주 필요한가“다.
github이 먼저 빠졌다. Git 이력을 확인하는 일의 99%는 git 하나로 해결되고, 이슈와 PR은 브라우저로 여는 편이 더 빠르다. 같은 공간을 다루는 도구 두 개가 붙어 있으면 에이전트 입장에서는 매 턴 “어느 쪽을 써야 하는가”를 한 번 더 따져야 한다. fetch와 web-search도 비슷한 이유로 제거했다. 회원가입 API 구현에서 외부 URL을 가져오거나 일반 검색을 돌릴 필요는 거의 없었고, 있다 해도 브라우저로 한 번 여는 쪽이 더 의미가 있다. puppeteer는 E2E 재현 시나리오가 필요한 세션에서만 사용했다. postman은 로컬 curl 한 줄로 대체되는 경우가 대부분이어서 제거했다.
이 기준은 프로젝트마다 달라질 수 있다. “매 턴 이 자리를 차지할 만큼 자주 필요한가”로 질문을 잡으면 상황에 맞는 조합이 나온다. 그리고 이 질문은 도구에만 해당하지 않는다. 시스템 프롬프트의 어느 줄, 예시의 어느 덩어리, 설명 문서의 어떤 내용에도 같은 질문을 대볼 수 있다.
닫는 말
이전 글을 마치고 나서 들었던 생각은 “AI 기억을 잘 기록해두자” 였다. 이번 글을 쓰고 나서는 이렇게 생각이 정리되었다. 기억을 기록해두는 것만큼, 기억 공간 안에 무엇을 들이지 않을지가 중요하다.
두 글이 합쳐지면 이렇게 정리된다.
- 세션 사이에는 진행 상태를 파일로 남겨라. (이전 글)
- 세션 안에 들일 것만 골라서 들여라. (이번 글)
이번 시간까지 다룬 건 에이전트의 성능이 나빠지는 걸 막는 방법이었다. 다음에 알아보려고 하는 건 어떻게 하면 에이전트의 성능을 더 뾰족하게 다듬을 수 있는지에 대한 방법이다. 기대해주시길…!




