자꾸 잊어버리는 LLM 에이전트 위에 제2의 뇌 구축하기
요약
LLM 에이전트가 컨텍스트를 누락하는 문제를 해결하기 위해 지침(Instructions)에 의존하는 대신, 모델 외부에서 실행되는 훅(Hooks)과 구조적 설계를 활용하는 방법을 제안합니다. 지시사항은 강제성이 없으므로, 라이프사이클 이벤트를 이용해 데이터 캡처와 커밋을 자동화해야 합니다.
핵심 포인트
- 지침(Instructions)은 모델의 선의에 의존할 뿐 계약이 아니므로 실패 가능성이 높음
- 작업과 충돌하는 지침은 모델이 중요한 작업 수행 중 데이터 기록을 생략하게 만듦
- 비용이 많이 드는 단계(인덱싱, 커밋)를 세션 종료 시점에 배치하면 누락 위험 증가
- 모델 외부의 라이프사이클 훅(Hooks)을 통해 강제적인 데이터 저장 구조를 구축해야 함
저는 프로젝트, 작업, 재정, 아이디어, 개인적인 일 등 제 삶의 모든 것을 마크다운 (Markdown) 파일 폴더에 보관합니다. LLM 에이전트는 이를 위한 프론트엔드 역할을 합니다. 제가 말을 하면, 에이전트는 노트를 바탕으로 대답하고 다시 노트에 기록합니다. 그 부분은 이제 잘 작동합니다. 오랫동안 작동하지 않았는데, 그 이유가 흥미로운 부분입니다.
요약 (TL;DR): 에이전트에게 컨텍스트 (Context)를 저장하라고 지시하는 더 나은 지침 (Instructions)을 작성하는 데 몇 달을 보냈지만, 에이전트는 계속해서 무언가를 놓쳤습니다. 비결은 지침에 있는 것이 아니라, 훅 (Hooks), 구조 (Structure), 그리고 경제성 (Economics)에 있었습니다. 강제성은 모델 외부에서 이루어져야 하며, 데이터는 캡처 (Capture) 비용이 저렴하고 라우팅 (Routing)이 분리되도록 형상화되어야 하며, 전체 시스템은 당신이 자신도 모르게 실행을 중단하지 않을 만큼 충분히 저렴해야 합니다.
실제로 무엇이 문제였나
설정은 명백했습니다. 매 세션마다 로드되는 긴 지침 파일에는 다음과 같이 적혀 있었습니다: 내가 새로운 것을 말하면 기록해라; 세션이 끝나면 인덱스 (Indexes)를 업데이트하고 커밋 (Commit)해라.
대부분의 경우 잘 작동했습니다. 그것이 문제입니다.
지침을 90%의 확률로 따르는 에이전트는 10%의 확률로 실패하는 것이 아닙니다. 10%의 확률로 조용히 (Silently) 실패하는 것입니다. 그리고 당신은 3주 후에 어떤 상태가 어떠냐고 물었을 때, 4월 이후로 업데이트가 중단된 노트를 바탕으로 자신 있게 대답하는 것을 보고서야 그 사실을 알게 됩니다. 예외도 없고, 레드 빌드 (Red build)도 없고, 실패한 작업 (Failed job)도 없습니다. 그저 당신이 한 번 말했던 사실이 어디에도 존재하지 않게 되었을 뿐이며, 그것이 사라졌다는 신호도 없습니다.
두 가지 원인이 있었으며, 두 가지 모두 도덕적인 문제라기보다는 구조적인 문제입니다:
- 지시사항(Instructions)이 작업과 충돌합니다. 세션의 주제는 결제 웹훅(payment webhook)의 버그에 관한 것입니다. 그런데 지시사항 파일에는 "새로운 사실이 들어오면 캡처하라"고 되어 있습니다. 모델은 버그를 해결하는 작업을 수행 중입니다. 이 과정에서 무언가는 희생되어야 하며, 그것은 사용자가 눈에 보이게 기다리고 있는 작업이 결코 아닙니다.
- 비용이 많이 드는 단계가 마지막에 위치합니다. "인덱스를 업데이트하고 커밋(commit)하라"는 것은 실제 상당한 작업량(chunk of work)이며, 이는 세션이 마무리되는 시점, 즉 컨텍스트(context)가 가장 길어지고 저를 포함한 모두가 일을 끝내고 싶어 하는 바로 그 순간에 배치됩니다. 가장 건너뛰기 쉬운 단계가 가장 건너뛰기 쉬운 순간에 예약되어 있는 것입니다.
지시사항을 더 강조해서 쓴다고 해서 이 문제들을 해결할 수는 없습니다. 저도 시도해 보았습니다. 지시사항(Instructions)은 계약이 아닙니다. 그것은 선의를 담은 제안일 뿐입니다.
훅(Hooks): 모델 외부에서의 강제 실행
첫 번째 해결책은 요청하는 것을 그만두는 것이었습니다.
대부분의 에이전트 하네스(agent harnesses)는 세션 시작, 세션 종료, 도구 호출(tool call) 전후, 또는 에이전트가 중단될 때와 같은 라이프사이클 이벤트(lifecycle events)에서 코드를 실행할 수 있습니다. 그곳이 바로 모델의 판단에 맡기지 않고 반드시 일어나야 하는 일들을 배치할 장소입니다.
따라서 "금고(vault)에 커밋되지 않은 작업이 남겨져 있어서는 안 된다"라는 규칙은 더 이상 지시사항 파일의 한 줄이 아니라, 스톱 훅(stop hook)이 되었습니다. 에이전트가 한 턴을 마치면, 셸 스크립트(shell script)가 금고가 더러운 상태(dirty)인지 확인하고 이를 커밋합니다. "커밋하는 것을 기억해 주세요"가 아닙니다. 모델이 그것을 생각하고 있었든 아니든, 이벤트에 따라 실행되는 스크립트입니다.
중요한 차이점은 다음과 같습니다:
- 판단(Judgment)은 모델의 몫입니다. 이 새로운 사실은 무엇을 의미하는가? 어떤 노트에 속해야 하는가? 이것은 작업인가 아니면 아이디어인가? 이것은 진정으로 모호한 작업이며 모델은 이를 잘 수행합니다.
- 보장(Guarantees)은 하네스(harness)의 몫입니다. 커밋이 발생했는가? 오늘의 배치(batch)가 실행되었는가? 라우팅되지 않은 입력이 있는가? 이것은 확인 가능한 조건이며, 모델이 이를 확인하는 주체가 되어서는 안 됩니다.
신뢰성 문제를 더 강력한 지시사항으로 해결하려는 유혹을 느낄 때마다, 정답은 언제나 훅(hook)이었습니다. 매번 말입니다.
그에 따른 정직한 결론은 다음과 같습니다: 만약 모델 외부에서 강제할 수 없다면, 그것이 일어날 것처럼 설계하지 마십시오. 모델 측의 단계는 최선(best-effort)을 다하는 수준으로 취급하고, 실패를 잡아내는 장치는 반드시 다른 곳에 두어야 합니다.
한눈에 보는 구조
INTERACTIVE (하루 종일, 저렴함) BATCH (03:00, 1회, 비쌈)
┌──────────────────────────┐ ┌────────────────────────────┐
│ session (모든 프로젝트) │ │ headless agent, cron 실행 │
...
대화형 세션(interactive session)이 할 수 있는 유일한 일은 한 줄을 추가하고 훅(hook)이 커밋(commit)하게 두는 것뿐입니다. 전체적인 맥락을 파악하여 판단이 필요한 모든 작업은 아무도 기다리지 않는 밤 시간에 단 한 번 실행됩니다.
구조: 캡처(capture)와 라우팅(routing)은 서로 다른 두 가지 작업입니다
두 번째 해결책은 "이것을 저장해"라는 명령이 실제로는 비용 프로필이 완전히 다른 두 가지 작업임에도 불구하고, 제가 이 둘을 하나로 묶어버렸다는 사실을 깨달은 것이었습니다.
**캡처 (Capture)**는 다음과 같습니다: 방금 중요한 말을 했습니다. 생각이 사라지기 전에 지금 당장 기록하십시오. 이는 원자적(atomic)이어야 하고, 저렴해야 하며, 그 어떤 것에도 의해 차단되어서는 안 됩니다. 이는 모델이 다른 작업으로 바쁜 세션 중간에 발생하므로 비용이 거의 들지 않아야 합니다. 즉, 읽기, 생각하기, 결정하기 없이 단 한 줄을 추가(append)하는 수준이어야 합니다.
**라우팅 (Routing)**은 다음과 같습니다: 그 한 줄이 무엇을 의미하는지, 50개의 노트 중 어디에 속하는지, 프로젝트의 상태를 변경하는지, 혹은 특정 태스크를 쓸모없게 만드는지 파악한 다음 인덱스(index)를 다시 작성하는 것입니다. 이는 비용이 많이 들고 전체적인 맥락이 필요하며, 인간이 기다리고 있는 동안 절대로 수행해서는 안 되는 작업입니다.
따라서 다음과 같이 구성합니다: 인박스(inbox) 파일. 하나의 추가 전용(append-only) 리스트입니다. 시스템이 알아야 한다고 제가 언급하는 모든 것은, 해당 세션의 주제가 무엇이든 상관없이, 어떤 세션에서든 동일한 턴(turn) 내에 타임스탬프가 찍힌 한 줄이 추가됩니다. 형식은 고정되어 있고, 해당 줄은 의도적으로 단순하며(dumb), 타임스탬프에는 초 단위까지 포함되어 있어 순서가 모호해지는 일이 없습니다.
캡처(capture) 시점에는 그 외에 아무 일도 일어나지 않습니다. 인박스(inbox)는 지저분해도 괜찮습니다. 그것은 기록(record)이 아니라 큐(queue)이기 때문입니다. 불변의 법칙(invariant)은 오직 이것뿐입니다: 비어 있는 인박스는 모든 것이 라우팅(routed)되었음을 의미한다. 비어 있지 않은 인박스는 조용히 증발해 버린 사실이 아니라, 눈에 보이고 확인 가능한 백로그(backlog)가 됩니다.
이 하나의 분리 작업은 제가 6개월 동안 했던 그 어떤 프롬프트 엔지니어링 (prompt engineering)보다 더 많은 문제를 해결했습니다. 캡처가 매우 가벼워지면서, 눈앞의 작업과 경쟁하는 일이 없어졌습니다. 사실 캡처가 무시되었던 이유가 바로 그것이었기 때문입니다.
동일한 직관이 지침(instructions) 자체에도 적용됩니다. 저는 여러 파일에 걸쳐 산문(prose) 형태로 절차를 분산해 두었고, 각 장소마다 설명이 조금씩 달랐습니다. 이는 에이전트가 매번 약간씩 다른 저장 절차를 즉흥적으로 수행하게 만드는 원인이 됩니다. 이제는 각 작업당 하나의 정전(canonical)적인 서면 절차가 한 곳에 존재하며, 다른 모든 것들은 그곳을 가리킵니다. 산문 파일들은 _언제(when)_를 말하고, 절차는 단 한 번 _어떻게(how)_를 말합니다.
구조, 두 번째 파트: 인덱스(index)는 문서가 아니라 투영(projection)이다
또 다른 구조적 요소: 에이전트는 모든 것을 읽지 않고도 어떻게 상황을 파악할 수 있을까요?
순진하게 생각하면, 에이전트가 볼트(vault)를 검색하게 두는 것입니다. 하지만 그것은 느리고, 비용이 많이 들며, 나쁜 의미에서 비결정론적(non-deterministic)입니다. 즉, 두 세션이 동일한 질문을 던져도 서로 다른 파일의 부분 집합을 읽게 되어 서로 다른 답변을 내놓게 됩니다.
대신 몇 가지 인덱스(index) 파일—활성 프로젝트, 모든 오픈 태스크, 개인 상태 등—이 존재하며, 이것들이
- 그것들은 문서가 아니라 투영(projections)입니다. 노트가 진실의 원천(source of truth)입니다. 인덱스는 파생된 뷰(view)이며, 야간 작업(nightly job)에 의해 *완전히 새로 작성(fully rewritten)*될 뿐, 결코 내용이 추가(append)되지 않습니다. 계속 추가되는 방식은 경계 없이 커지며 원천 데이터로부터 벗어나게 되지만, 새로 작성되는 방식은 정확하거나 혹은 명백히 고장 난 상태가 됩니다.
- 엄격한 제한(hard caps)이 있습니다. 최근 10개의 세션, 프로젝트 행당 60단어와 같은 제한입니다. 이러한 제한은 파일 자체에 기록되어 있어, 이를 생성하는 주체가 스스로의 제약 사항을 읽을 수 있습니다. 상세한 내용은 그것이 가리키는 노트에 존재합니다.
- 그것들을 읽는 것은 선택이 아닌 규칙입니다. "광범위한 질문에 대해서는 이 파일들만 읽으세요 - 팬아웃(fan-out) 금지." 이를 통해 보관함(vault)의 크기가 아무리 커지더라도 세션당 읽기 비용을 제한적이고 예측 가능하게 유지합니다.
이러한 제한 사항은 사람들이 반발하는 부분이기도 하지만, 동시에 시스템을 작동하게 만드는 핵심 요소입니다. 제한이 없는 인덱스는 한 달 안에 보관함의 두 번째 복사본이 되어버리며, 그렇게 되면 진실의 원천이 두 개가 됩니다. 이는 유용할 수 있는 최대 개수보다 하나 더 많은 수치입니다.
경제성: 시스템이 돌아가는 이유
에이전트 시스템에 대해 아무도 말해주지 않는 사실이 있습니다: 토큰 예산(token budget)은 결제 세부 사항이 아니라 아키텍처적 제약 사항(architectural constraint)이라는 점입니다.
수신함(inbox)을 적절히 라우팅(routing)하는 작업—읽기, 각 줄이 어디로 갈지 결정하기, 노트 업데이트, 4개의 인덱스 재작성, 커밋(committing)—은 비용이 많이 듭니다. 이를 모든 대화형 세션(interactive session)이 끝날 때마다 수행한다는 것은 모든 세션이 그 비용을 지불해야 함을 의미하며, 저는 작은 세션이 매우 많습니다. 이 비용은 거래의 잘못된 측면에 배치됩니다. 즉, 마감 기한이 없는 작업을 위해, 제가 가장 조급한 순간인 프리미엄 대화형 토큰을 지불하고 있는 것입니다.
그래서 위치를 옮겼습니다. 대화형 세션은 오직 캡처(capture)만 수행합니다. 비용이 많이 드는 라우팅은 크론(cron)에 따라 헤드리스 에이전트 프로세스(headless agent process)에서 매일 밤 한 번, 하루 동안 쌓인 수신함 전체를 대상으로 실행됩니다.
이러한 변화는 여러 가지를 동시에 해결했으며, 이는 보통 당신이 올바른 접점(seam)을 찾아냈을 때 나타나는 징후입니다:
- 배치(Batch)는 _하루 전체_를 보기 때문에, 한 줄씩 보는 14개의 개별 세션보다 더 잘 라우팅(route)합니다. 오전 11시에 생성된 작업이 오후 4시의 결정으로 인해 쓸모없게 되었다는 것을 파악할 수 있습니다.
- 비용이 많이 드는 작업은 아무도 기다리지 않을 때 수행되므로, 철저하게 처리할 여유가 있습니다. 즉, 품질 검사(quality pass), 링크 확인(link check), 인덱스 간의 모순 확인(contradiction check) 등을 수행할 수 있습니다.
- 저의 인터랙티브 세션(interactive sessions)은 더 저렴해졌고 더 빨라졌습니다. 왜냐하면 "완료" 직전의 마지막 단계가 이제 전체 동기화(full sync) 대신 파일 하나를 추가(append)하는 작업이 되었기 때문입니다.
- 보고서를 생성합니다. 저는 아침에 그것을 읽습니다: 무엇이 라우팅되었는지, 무엇이 변경되었는지, 그리고 제가 확인하기를 원하는 사항이 있는지 등을 말이죠.
그리고 제가 꼭 옹호하고 싶은, 황당하게 들릴 수도 있지만 전혀 그렇지 않은 부분이 있습니다: 노트북이 이를 실행하기 위해 새벽 02:28에 스스로 깨어난다는 점입니다.
배치는 03:00에 실행됩니다. 제가 잠든 사이이므로 노트북은 03:00에 절전(suspended) 상태가 됩니다. Persistent=true 설정이 된 systemd 타이머(timers)는 이 상황에서 유용합니다. 놓친 작업을 다음 부팅 시에 대신 실행해주기 때문입니다. 이는 제가 오전 9시에 노트북 덮개를 열자마자 밤새 진행된 전체 배치가 실행되었음을 의미했습니다. 제 기기는 바빠지고, 제 에이전트(agent)도 바빠지며, 커피를 마시기도 전에 하루의 첫 토큰 허용량(token allowance)이 어젯밤의 잡무를 처리하는 데 소진되었습니다.
해결책은 RTC 알람(RTC alarm)입니다. 시스템 타이머에 WakeSystem=true를 설정하면 하드웨어 클록(hardware clock)이 작동하여 절전 상태에서 기기를 깨웁니다. 기기는 02:28에 깨어나고, 작업이 실행되며, 가드(guard)가 여전히 한밤중이고 아무도 기기를 건드리지 않았는지 확인한 후 다시 절전 상태로 들어갑니다. 저는 완료된 보고서와 넉넉한 예산을 확인하며 잠에서 깨어납니다.
이것은 시스템에서 가장 화려하지 않은 부분이지만, 저의 아침을 바꾼 부분입니다. 경제성(economics)은 아키텍처(architecture)의 각주가 아닙니다. 그것은 이 시스템이 실제 일주일이라는 시간과 부딪히며 살아남을 수 있게 하는 이유입니다. 오전 9시에 치러야 할 비용 때문에 계속해서 피하게 되는 시스템은 3월이면 실행을 멈추게 될 시스템입니다.
동일한 베팅, 세 번
이 글을 쓰면서 깨달은 점은, 이 볼트(vault)가 제가 처음으로 이 결정을 내린 곳은 아니라는 사실입니다. 단지 가장 최근에 결정을 내린 곳일 뿐입니다. 제가 실제로 개발하고 있는 두 제품은 동일한 문장 위에 있습니다.
Kelaro와 Koa - 돈에 관해서는 절대 조용히 틀리지 않습니다. Kelaro는 회계 제품이며, Koa는 은행 명세서 PDF를 원장(ledger)으로 변환하는 그 밑단의 엔진입니다. 현재 시장 전체가 비전-LLM (vision-LLM) 추출 기술을 향해 질주하고 있습니다. 모델에 페이지를 건네주면 템플릿을 유지 관리할 필요 없이 구조화된 데이터(structured data)를 돌려받는 방식입니다. 이는 진정으로 유혹적입니다. 하지만 이는 비결정론적 시스템 (non-deterministic system)에 산술 연산을 맡기는 일이기도 하며, 저는 다른 사람의 장부에서 그런 결정을 내릴 의사가 없습니다. 여기서의 실패는 모델이 "모르겠습니다"라고 말하는 것이 아니라, 그럴듯해 보이는 합계를 반환하는 것입니다. FinGround (ACL 2026 Industry Track)는 금융 LLM 시스템에서 해결하고자 하는 실패 모드(failure modes), 즉 조작된 지표, 허구의 인용, 산술 오류에 대해 직설적으로 다룹니다.
Koa는 고정된 템플릿을 사용하고, 잔액이 일치하도록 강제하며, 불가능할 때는 — 아주 명확하게 — 거부합니다. 숫자를 추측하느니 차라리 명세서를 거부하는 쪽을 택합니다. 그 주변에서 Kelaro의 파이프라인은 재시도(retries), 타임아웃(timeouts), 운영자에게 보이는 상태(operator-visible state)와 같은 내구성이 있는 워크플로 (durable workflows) 위에서 실행됩니다. 산술 연산 과정 어디에도 모델은 개입하지 않습니다. 이것은 순수주의가 아닙니다. "돈에 관해서는 절대 조용히 틀리지 않는다"는 가치가 템플릿 작업을 건너뛰는 편리함보다 훨씬 더 중요하기 때문입니다.
Augur - 믿으라고 요구받는 증거가 아니라, 직접 확인할 수 있는 증거. Augur는 EU AI Act(EU 인공지능법) 대응 증거 제품입니다. 결과물은 검토자에게 전달할 수 있는 번들(bundle) 형태입니다. 흥미로운 설계 제약 조건은 앞서 언급한 것과 동일합니다. 판단 — 이 시스템의 문서가 실제로 의무 사항을 충족하는가? — 은 인간의 영역이며, 저는 그렇지 않은 척하지 않습니다. 하지만 최종 결과물은 반드시 _확인 가능(checkable)_해야 합니다. 즉, 고정된 기록 구조, 서명된 번들, 그리고 제 말을 믿지 않고도 누구나 실행할 수 있는 검증 수단이 있어야 합니다. 규제 기관은 자신만만해 보이는 요약본을 원하는 것이 아닙니다. 그들은 결과물(artifact)과 그것을 직접 확인할 수 있는 능력을 원합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기