Azure SRE 에이전트는 내부 Microsoft 서비스와 자체 시스템을 운영하는 외부 팀을 위해 매주 수만 건의 인시던트를 처리합니다. 지난달, 이 중 한 건은 에이전트 자체에 관한 것이었습니다.
우리의 KV 캐시 적중률 알림이 발생하기 시작했습니다. 플릿 전체에서 캐시된 토큰 비율이 떨어지고 있었습니다. 우리는 대시보드를 열지 않았습니다. 그저 에이전트에게 물어봤을 뿐입니다.
에이전트는 병렬 서브 에이전트를 생성하고, 로그를 검색하며, 자체 소스 코드를 읽고 분석을 내놓았습니다. 첫 번째 발견: Claude Haiku의 캐시 적중률은 0%였습니다. 에이전트는 입력 분포를 확인한 결과, 평균 호출이 약 180토큰으로, Haiku 프롬프트 캐싱을 위한 Anthropic의 최소 기준인 4,096토큰보다 훨씬 적다는 것을 발견했습니다. 구조적으로 이러한 요청은 캐시될 수 없었습니다. 오탐지였습니다.
실제 문제는 Claude Opus에서 발생했습니다. 캐시 적중률이 일주일 동안 약 70%에서 약 48%로 떨어졌습니다. 에이전트는 이 하락을 배포 이력과 상관관계 분석하여 캐싱이 의존하는 공통 접두사를 깨뜨린 단일 PR(풀 리퀘스트)에 의한 프롬프트 순서 재구조화가 원인임을 추적했습니다. 에이전트는 두 가지 수정 사항을 제출했습니다. 하나는 캐시할 수 없는 모든 요청을 알림에서 제외하는 것이었고, 다른 하나는 프롬프트 파이프라인에서 접두사 안정성을 복원하는 것이었습니다.
바로 이 조사가 우리가 지금 개발하는 방식입니다. 우리는 대시보드나 수동 로그 쿼리부터 시작하는 경우가 드뭅니다. 에이전트에게 질문하는 것부터 시작합니다.
3개월 전에는 이 중 어떤 것도 할 수 없었습니다. 돌파구는 더 나은 플레이북을 구축하는 것이 아니었습니다. 바로 **하네스 엔지니어링(harness engineering)**이었습니다. 이는 조사가 진행됨에 따라 에이전트가 스스로 컨텍스트를 발견하도록 하는 것이었습니다.
이 게시물은 이를 가능하게 한 아키텍처 결정에 관한 것입니다.
우리가 시작한 곳
지난 게시물, 신뢰할 수 있는 AI 에이전트를 위한 컨텍스트 엔지니어링: Azure SRE 에이전트 구축 교훈에서 우리는 단일 제너럴리스트 에이전트로 전환하는 것이 어떻게 더 복잡한 조사를 가능하게 했는지 설명했습니다. 해결률은 상승하고 있었고, 많은 내부 팀에서 에이전트는 이제 인시던트의 약 50%를 자율적으로 조사하고 완화할 수 있었습니다. 우리는 올바른 방향으로 나아가고 있었습니다.
하지만 점수가 균일하지 않았고, 그 원인을 파고들자 불편한 패턴이 드러났습니다. 높은 성과를 보이는 시나리오들은 한 가지 공통점을 가지고 있었습니다: 많은 인간의 개입(스캐폴딩)으로 구축되었다는 점입니다. 특정 인시던트 유형에 대한 맞춤형 대응 계획, 알려진 실패 모드에 대한 수제 서브 에이전트, 불투명한 도구로 노출된 미리 작성된 로그 쿼리에 의존했습니다. 우리는 에이전트의 추론 능력을 측정하는 것이 아니라 – 해당 시나리오에 얼마나 많은 엔지니어링 노력이 사전에 투입되었는지를 측정하고 있었습니다. 새로운 것에 대해서는 에이전트가 시작할 곳이 없었습니다.
우리는 이러한 격차를 수동 검토를 통해 발견했습니다. 매주 엔지니어들은 낮은 점수를 받은 조사 스레드를 읽고 수정 사항을 적용했습니다: 프롬프트를 조이고, 도구 스키마를 수정하며, 가드레일을 추가했습니다. 각 수정은 실제적인 개선이었습니다. 하지만 우리는 일주일에 50개의 스레드만 검토할 수 있었습니다. 에이전트는 만 건을 처리하고 있었습니다. 우리는 인간의 속도로 디버깅하고 있었습니다. 이 두 숫자 사이의 간격이 바로 우리의 사각지대였습니다.
우리는 이러한 고된 작업을 대신할 만큼 강력한 에이전트가 필요했습니다. 스스로를 조사할 수 있는 에이전트 말입니다. 도그푸딩(Dogfooding)은 단순한 철학이 아니라, 확장할 수 있는 유일한 방법이었습니다.
역전: 세 가지 예측
우리가 직면한 문제는 구조적이었습니다. KV 캐시 조사가 이를 명확히 보여줍니다. 캐시 적중률 하락은 원격 측정(Telemetry)에서 볼 수 있었지만, 원인은 아니었습니다. 에이전트는 원격 측정을 배포 이력과 상관관계 분석하고, 관련 코드를 검사하며, 접두사 안정성을 깨뜨린 diff에 대해 추론해야 했습니다. 우리는 계속해서 다양한 형태로 동일한 격차에 부딪혔습니다: 여러 방향을 가리키는 로그, 계측되지 않은 경로의 실패 모드, 커밋 레벨에서만 의미가 있는 회귀. 원격 측정은 증상을 보여주었지만, 실제로 무엇이 변경되었는지는 보여주지 않았습니다.
우리는 원격 측정을 통해 추론하도록 에이전트를 구축해왔습니다. 우리는 에이전트가 시스템 자체를 추론하도록 할 필요가 있었습니다.
에이전트가 실패할 때의 본능은 에이전트를 제한하는 것입니다: 쿼리를 미리 작성하고, 컨텍스트를 미리 가져오고, 도구를 미리 선별하는 것입니다. 이는 제어하는 것처럼 느껴집니다. 실제로는 한계를 만듭니다. 에이전트는 엔지니어가 미리 예상한 것만 처리할 수 있습니다.
답은 조사가 진행됨에 따라 필요한 것을 발견할 수 있는 에이전트입니다. KV 캐시 인시던트에서, 메트릭 이상에서 배포 이력, 그리고 특정 diff에 이르는 각 단계는 이전 단계가 드러낸 정보에서 이어졌습니다. 이는 미리 스크립트된 경로가 아니었습니다. 점진적 발견을 통해 올바른 컨텍스트로 나아가는 것이 새로운 시나리오를 처리할 수 있는 심층 에이전트를 만드는 핵심입니다.
세 가지 아키텍처 결정이 이를 가능하게 했습니다 – 그리고 각 결정은 다음 결정에 영향을 미쳤습니다.
예측 1: 에이전트의 세계로서의 파일 시스템
우리의 첫 번째 예측은 맞춤형 API 계층 대신 파일 시스템을 에이전트의 작업 공간으로 제공하는 것이었습니다.
소스 코드, 런북, 쿼리 스키마, 과거 조사 노트 등 에이전트가 추론하는 모든 것은 파일로 노출됩니다. 에이전트는 read_file, grep, find, shell을 사용하여 그 세계와 상호 작용합니다. SearchCodebase API도, RetrieveMemory 엔드포인트도 없습니다.
이는 오래된 유닉스 아이디어입니다: 이질적인 리소스를 단일 인터페이스로 줄이는 것. 코딩 에이전트는 이미 이런 방식으로 작동합니다. SRE 에이전트에도 동일한 패턴이 효과적이라는 것이 밝혀졌습니다.
최신 모델(Frontier models)은 저장소를 탐색하고, 로그를 그랩하고, 파일을 패치하고, 명령을 실행하는 개발자 워크플로우에 기반하여 훈련됩니다. 파일 시스템은 그 사전 지식 위에 계층화된 추상화가 아닙니다. 그것과 일치합니다.
우리가 에이전트의 세계를 리포지토리와 같은 작업 공간으로 구현했을 때, 인간의 "의도 부합(Intent Met)" 점수 – 온콜 엔지니어가 판단한 실제 근본 원인에 대한 에이전트의 조사 해결률 – 는 새로운 인시던트에서 45%에서 75%로 상승했습니다.
하지만 인터페이스 설계는 절반의 이야기일 뿐입니다. 나머지 절반은 그 안에 무엇을 넣느냐입니다.
코드 저장소: 가장 높은 레버리지의 컨텍스트
팀들은 에이전트가 정확한 쿼리를 생성할 것이라고 신뢰하지 않았기 때문에 미리 로그 쿼리를 작성했습니다. 그 불신은 정당했습니다. 모델은 테이블 이름을 환각하고, 컬럼 스키마를 추측하며, 잘못된 클러스터를 대상으로 쿼리를 작성합니다. 하지만 답은 더 엄격한 제한이 아니었습니다. 더 나은 기반 다지기였습니다.
리포지토리가 스키마입니다. 다른 모든 것은 여기에서 파생됩니다.
에이전트가 로그를 생성하는 코드를 읽을 때, 쿼리 구성은 더 이상 추측이 아닙니다. 에이전트는 정확히 발생한 예외와 각 경로가 실행되는 조건을 알고 있습니다. 스택 추적이 의미를 갖게 되고, 로그를 읽을 수 있게 됩니다. 하지만 쿼리 기반 다지기를 넘어, 코드 접근은 원격 측정만으로는 제공할 수 없었던 세 가지 새로운 기능을 잠금 해제했습니다:
- 문서보다 진실성(Ground truth). 문서는 시간이 지남에 따라 달라지고 대시보드는 증상을 보여줍니다. 코드는 서비스가 실제로 수행하는 내용입니다. 실제로 대부분의 조사는 로그가 구현과 함께 읽힐 때만 의미가 있었습니다.
- 시점별 조사(Point-in-time investigation). 에이전트는 인시던트 발생 시점의 정확한 커밋을 체크아웃하여, 현재 HEAD가 아닌 실제 diffs와 실패를 연관시킬 수 있습니다. 이것이 바로 KV 캐시 조사를 해결한 방법입니다: PR이 접두사 안정성을 깨뜨렸고, 이 diff가 유일하게 이를 볼 수 있는 곳이었습니다. 커밋 이력 없이는 코드 회귀를 외부 요인과 구별할 수 없습니다.
- 원격 측정(Telemetry)이 없는 곳에서도 추론. 일부 코드 경로는 제대로 계측되지 않았습니다. 에이전트는 여전히 소스 코드를 통해 로직을 추적하고 로그가 존재하지 않을 때도 동작을 설명합니다. 이는 특히 새로운 실패 모드 – 아무도 계측할 생각을 하지 않았기 때문에 놓치기 쉬운 것들 – 에서 특히 유용합니다.
벡터 저장소가 아닌 파일 시스템으로서의 메모리
우리의 첫 번째 메모리 시스템은 과거 세션 학습에 RAG를 사용했습니다. 이는 순환 의존성을 가지고 있었습니다: 제한된 에이전트가 제한된 세션에서 학습하고 제한된 지식을 생성했습니다. 쓰레기를 넣으면 쓰레기가 나옵니다(Garbage in, garbage out).
하지만 더 깊은 문제는 검색이었습니다. SRE 컨텍스트에서 임베딩 유사성은 관련성에 대한 약한 대리 지표입니다. "KV 캐시 회귀"와 "프롬프트 접두사 불안정성"은 임베딩 공간에서는 멀리 떨어져 있지만 여전히 동일한 인과 사슬을 설명합니다. 우리는 재순위화, 쿼리 확장, 하이브리드 검색을 시도했습니다. 어떤 것도 의미론적 유사성과 진단 관련성 사이의 근본적인 불일치를 해결하지 못했습니다.
우리는 RAG를 에이전트가 표준 도구 인터페이스를 통해 읽고 쓰는 구조화된 마크다운 파일로 대체했습니다. 모델은 각 파일에 의미론적으로 이름을 부여합니다: 서비스 요약에는 overview.md, 소유권 및 에스컬레이션 경로에는 team.md, 클러스터 접근 및 쿼리 패턴에는 logs.md, 실패 모드 및 이전 학습에는 debugging.md. 각 파일은 에이전트의 방향을 정하는 데 충분한 컨텍스트를 담고 있으며, 필요할 때 더 깊은 파일로 연결되는 링크를 포함합니다.
핵심 설계 선택은 모델이 쿼리 매칭을 통해 메모리를 검색하는 것이 아니라, 메모리를 탐색하도록 하는 것이었습니다.
파일 탐색은 컨텍스트가 축적됨에 따라 관련성이 자연스럽게 드러나도록 합니다. 이는 청킹, 오버랩 튜닝, 재순위화를 완전히 제거했습니다. 또한 최신 모델은 임베딩이 관련성을 추측하는 것보다 컨텍스트를 따르는 데 더 능숙하기 때문에 더 정확하다는 것이 입증되었습니다. 추가적인 이점으로, 메모리 상태는 주기적으로 스냅샷될 수 있습니다.
한 가지 문제는 아직 해결되지 않았습니다: 데이터의 신선도(staleness). 두 세션이 debugging.md에 충돌하는 패턴을 작성할 때, 모델은 이를 조정해야 합니다. 서비스 동작이 변경되면, 오래된 항목은 오해의 소지가 있을 수 있습니다. 우리는 타임스탬프와 명시적인 사용 중단 노트를 사용하지만, 아직 체계적인 해결책은 없습니다. 이는 활발히 연구되고 있는 영역이며, 대규모로 메모리를 구축하는 사람은 누구나 이 문제에 직면할 것입니다.
인식론적 경계로서의 샌드박스
파일 시스템은 또한 에이전트가 볼 수 있는 것을 정의합니다. 샌드박스에 없는 것은 에이전트가 추론할 수 없습니다. 우리는 이를 제한이 아닌 기능으로 간주합니다. 보안 경계와 인식론적 경계는 동일한 메커니즘에 의해 강제됩니다.
그 경계 내에서 에이전트는 완전한 실행 권한을 가집니다: 임의의 bash, python, jq, 그리고 pip 또는 apt를 통한 패키지 설치. 이러한 범위는 우리가 맞춤형 도구로 구축하지 않았을 기능들을 잠금 해제합니다. KV 캐시 인시던트의 프롬프트 순서 수정처럼 gh cli로 PR을 엽니다. 이제 모델별로 추적하는 캐시 적중률 대시보드처럼 Grafana 대시보드를 푸시합니다. 필요할 때 조사 중간에 도메인별 CLI 도구를 설치합니다. 별도의 맞춤 통합이 필요 없고, 쉘(shell)만 있으면 됩니다.
반복되는 교훈은 간단했습니다: 올바른 실행 환경에 있는 범용적인 에이전트가 맞춤형 도구를 가진 전문 에이전트보다 뛰어난 성능을 발휘합니다. 맞춤형 도구는 유지보수 비용을 축적합니다. 쉘 명령은 무료로 조합됩니다.
예측 2: 컨텍스트 계층화
코드 접근은 에이전트에게 서비스가 무엇을 하는지 알려줍니다. 그러나 에이전트가 무엇에 접근할 수 있는지, 도구가 어떤 리소스에 범위가 지정되는지, 또는 조사가 어디서 시작되어야 하는지는 알려주지 않습니다.
이러한 격차는 즉시 드러났습니다. 사용자들은 "어떤 팀의 인시던트를 처리하나요?"라고 물었지만 에이전트는 답을 할 수 없었습니다. 도구만으로는 충분하지 않습니다. 통합에는 모델이 무엇이 존재하고, 어떻게 구성되어 있으며, 언제 사용해야 하는지 알 수 있도록 하는 앰비언트 컨텍스트도 필요합니다.
우리는 **컨텍스트 훅(context hooks)**으로 이를 해결했습니다: 에이전트가 행동을 취하기 전에 방향을 제시하기 위해 프롬프트 구성 시점에 주입되는 구조화된 컨텍스트입니다.
- 연결 도구(Connectors) - 무엇에 접근할 수 있는가? Log Analytics, Outlook, Grafana와 같은 연결된 시스템들의 목록과 그 구성입니다.
- 저장소(Repositories) - 이 시스템은 무엇을 하는가? 직렬화된 저장소 트리와 AGENTS.md, Copilot.md, CLAUDE.md와 같이 팀별 지침이 포함된 파일들입니다.
- 지식 지도(Knowledge map) - 이전에 무엇을 배웠는가? 상위 레벨 파일이 더 깊은 시나리오별 파일로 연결되는 2계층 메모리 인덱스로, 모델이 필요할 때만 상세 내용을 탐색할 수 있도록 합니다.
- Azure 리소스 토폴로지(Azure resource topology) - 리소스는 어디에 있는가? 구독, 리소스 그룹, 지역 전반의 관계를 직렬화한 지도로, 조사가 올바른 범위에서 시작될 수 있도록 합니다.
이러한 컨텍스트 훅들은 콜드 스타트(cold start)를 정보가 풍부한 시작으로 바꿉니다. 이는 초기의 잘못된 선택이 단순히 토큰을 낭비하는 것이 아니기 때문에 중요합니다. 조사를 잘못된 방향으로 이끌게 됩니다.
유능한 에이전트라도 무엇이 존재하고, 무엇이 중요하며, 어디서 시작해야 하는지 알아야 합니다.
예측 3: 절약형 컨텍스트 관리
계층화된 컨텍스트는 새로운 문제를 야기합니다: 예산입니다. 직렬화된 저장소 트리, 리소스 토폴로지, 연결 도구 매니페스트, 메모리 인덱스는 빠르게 컨텍스트를 채웁니다. 에이전트가 소스 파일과 로그를 읽기 시작하면, 복잡한 인시던트는 컨텍스트 한계에 부딪힙니다. 우리는 컨텍스트 사용을 의도적으로 절약해야 했습니다.
파일 시스템을 통한 도구 결과 압축
대규모 도구 출력은 에이전트가 그로부터 어떤 가치도 추출하기 전에 컨텍스트를 소비하기 때문에 비용이 많이 듭니다. 많은 경우, 해당 출력의 작은 일부 또는 파생된 요약만이 실제로 유용합니다. 우리의 프레임워크는 이러한 결과를 파일로 에이전트에 노출합니다. 에이전트는 grep, jq, python과 같은 도구를 사용하여 모델 인터페이스 외부에서 이를 처리할 수 있으므로, 최종 결과만 컨텍스트에 들어갑니다.
파일 시스템은 단순히 기능 추상화가 아닙니다 – 예산 관리의 기본 요소이기도 합니다.
컨텍스트 가지치기 및 자동 압축
오랜 조사는 불필요한 무게(dead weight)를 축적합니다. 가설이 좁아지면서, 이전 컨텍스트는 노이즈가 됩니다. 우리는 두 가지 압축 전략으로 이를 처리합니다.
컨텍스트 가지치기(Context Pruning)는 세션 중간에 실행됩니다. 컨텍스트 사용량이 임계값을 넘으면, 우리는 오래된 도구 호출과 출력을 다듬거나 제거하여 – 여전히 중요한 것에 창을 집중시킵니다.
자동 압축(Auto-Compact)은 세션이 컨텍스트 한계에 도달할 때 작동합니다. 프레임워크는 발견 사항과 작업 가설을 요약한 다음, 그 요약에서부터 재개합니다. 사용자 관점에서는 눈에 보이는 한계가 없습니다. 긴 조사도 잘 작동합니다.
병렬 서브 에이전트
KV 캐시 조사는 두 가지 독립적인 가설에 따라 추론해야 했습니다: 알림 정의가 타당한지, 그리고 캐시 동작이 실제로 회귀했는지. 에이전트는 각 작업에 대해 병렬 서브 에이전트를 생성했으며, 각 에이전트는 자체 컨텍스트 창에서 작동했습니다. 둘 다 완료되면, 그 결론을 병합했습니다.
이 패턴은 독립적인 구성 요소를 가진 모든 작업에 일반화될 수 있습니다. 검색 속도를 높이고, 중간 작업이 메인 컨텍스트 창을 소비하는 것을 방지하며, 한 가설이 다른 가설에 편향되는 것을 막습니다.
피드백 루프
이러한 아키텍처 예측은 우리가 원래의 확장성 격차를 해소할 수 있도록 해주었습니다. 인간의 속도로 에이전트를 디버깅하는 대신, 마침내 에이전트가 스스로를 고치기 위해 사용하는 것을 시작할 수 있었습니다.
예를 들어, 우리는 다양한 LLM 오류에 직면했습니다: 타임아웃, 429s(너무 많은 요청), 응답 스트리밍 도중의 실패, 잘못된 페이로드를 생성하는 코드 버그로 인한 400s. 이러한 작은 문제들(paper cuts)은 조사를 중간에 중단시키고 일부 대화는 완전히 끊어지게 만들었습니다.
그래서 우리는 이러한 실패에 대한 일일 모니터링 작업을 설정했습니다. 에이전트는 지난 24시간 동안의 오류를 검색하고, 가장 많이 발생하는 오류를 클러스터링하며, 각각을 코드베이스 내의 근본 원인으로 추적하고, PR을 제출합니다. 우리는 병합하기 전에 수동으로 검토합니다. 2주 동안 오류는 80% 이상 감소했습니다.
지난 한 달 동안, 우리는 에이전트를 다음과 같은 다양한 시나리오에 성공적으로 사용했습니다:
- 사용자 이탈률을 분석하고 현재 매주 검토하는 대시보드를 구축했습니다.
- 어떤 빌드가 가장 많은 핫픽스를 필요로 하는지 상관관계를 분석하여, 코드베이스의 불안정한 영역을 드러냈습니다.
- 보안 분석을 실행하여 읽기 경로에서 취약점을 발견했습니다.
- 엄격한 인간 검토를 거쳐 자체 책임 있는 AI 검토의 일부를 작성하는 데 도움을 주었습니다.
- 고객 보고 문제 및 LiveSite 알림을 엔드 투 엔드로 처리합니다.
막힐 때마다 우리는 에이전트와 대화하고 가르치며, 메모리를 업데이트하도록 요청하고, 그러면 그 종류의 문제는 다시는 실패하지 않습니다.
이 게시물의 제목은 문자 그대로입니다. 에이전트가 스스로를 조사하는 것은 은유가 아닙니다. 이는 예약된 작업, 인시던트 트리거, 사용자들과의 직접적인 대화를 통해 구동되는 실제 워크플로우입니다.
우리가 배운 것
우리는 에이전트가 할 수 없는 것을 보완하기 위해 몇 달 동안 스캐폴딩을 구축했습니다. 돌파구는 그것을 제거하는 것이었습니다. 모든 미리 작성된 쿼리는 모델에게 생각하지 말라고 말한 곳이었습니다. 모든 선별된 도구는 모델을 대신하여 내려진 결정이었습니다. 모든 미리 가져온 컨텍스트는 문제를 이해하기 전에 무엇이 중요할지에 대한 추측이었습니다.
역전은 간단했지만 받아들이기 어려웠습니다: 답변 공간을 미리 계산하는 것을 멈추는 것입니다. 구조화된 시작점, 탐색 방법을 아는 파일 시스템, 무엇에 접근할 수 있는지 알려주는 컨텍스트 훅, 그리고 긴 조사 동안 에이전트의 효율성을 유지하는 예산 관리. 이것들을 모델에게 제공하는 것입니다.
스스로를 조사하는 에이전트는 이 접근 방식의 증거이자 결과입니다. 에이전트는 자신의 버그를 찾아내고, 코드 내의 근본 원인을 추적하며, 자신의 수정 사항을 제출합니다. 우리가 그렇게 설계했기 때문이 아닙니다. 시스템에 대해 추론하도록 설계했는데, 그 시스템 중 하나가 에이전트 자신이었을 뿐입니다.
우리는 여전히 배우고 있습니다. 데이터의 신선도 문제는 미해결이고, 예산 튜닝은 여전히 대부분 경험적이며, 우리는 에이전트를 조용히 제약하는 컨텍스트에 내재된 가정을 정기적으로 발견합니다.
그러나 우리는 새로운 문턱을 넘었습니다: 당신의 플레이북을 따르는 에이전트에서 다음 플레이북을 작성하는 에이전트로 말입니다.
이 게시물을 공동 작성해준 visagarwal에게 감사드립니다.