멀티 에이전트 하네스에서 컨텍스트를 정리하는 방법
TMThttps://www.langchain.com/blog/organizing-context-in-a-multi-agent-harness
요약
대부분의 하네스는 슈퍼바이저 에이전트에서 새 작업을 띄우는 서브에이전트 기능을 지원합니다. 서브에이전트는 병렬 추론과 컨텍스트 격리를 가능하게 해 주므로, 슈퍼바이저 에이전트가 자신의 컨텍스트 창을 어지럽히지 않고 작업을 위임할 수 있습니다.
슈퍼바이저 에이전트가 작업을 지정하면, 서브에이전트는 보통 비어 있는 새 컨텍스트 창에서 그 작업을 수행합니다. 그래서 낭비가 생길 수 있습니다. 슈퍼바이저가 이미 해 둔 파일 읽기 같은 컨텍스트 수집 작업을 서브에이전트가 다시 하게 되기 때문입니다.
서브에이전트가 슈퍼바이저 에이전트의 컨텍스트를 활용하면 좋은 경우를 위해, 저희는 포크 서브에이전트를 만들었습니다. 포크 서브에이전트는 빈 상태로 시작하는 대신 슈퍼바이저의 대화 전체를 그대로 물려받습니다. 슈퍼바이저의 대화를 재사용하면 프롬프트 캐싱의 이점을 살리면서 중복 작업을 줄일 수 있으므로, 포크는 격리된 서브에이전트보다 빠르고 저렴할 수 있습니다.
서브에이전트를 지원하는 하네스
서브에이전트에 작업을 위임하는 방식은 에이전트가 자신의 컨텍스트를 관리하는 효과적인 방법 중 하나입니다. 서브에이전트는 컨텍스트 격리를 제공하므로, 개별 작업의 세부 내용을 슈퍼바이저 에이전트의 컨텍스트 창에서 빼 둘 수 있습니다. 더 자세히 알고 싶다면 여러 멀티 에이전트 아키텍처를 길게 다룬 글을 참고하세요!
슈퍼바이저는 가장 폭넓게 적용할 수 있는 패턴 중 하나이고, 대부분의 코딩 하네스가 이 패턴을 채택했습니다. 슈퍼바이저는 계획을 유지하면서 전문화된 서브에이전트에 작업을 나눠 줍니다. 예를 들면 이렇습니다.
- 워커: 범위가 잘 잡힌 구현을 처리합니다.
- 리뷰어: 이미 끝난 작업을 독립적으로 판단합니다.
슈퍼바이저 에이전트는 보통 서브에이전트에서 작업의 결과만 받습니다. 중간 추론 과정은 슈퍼바이저의 컨텍스트 창에 들어가지 않습니다. 다만 서브에이전트가 슈퍼바이저에게서 어떤 컨텍스트를 받아야 하는지는 그 서브에이전트를 어디에 쓰는지에 따라 달라집니다.
서브에이전트의 컨텍스트 모드
이를 지정할 수 있도록, 저희는 최신 버전 deepagents에 컨텍스트 모드를 도입했습니다. 컨텍스트 모드는 서브에이전트가 슈퍼바이저에게서 어떤 컨텍스트를 받을 수 있는지 지정합니다. 지원하는 값은 "isolated"와 "fork"입니다.
격리된 서브에이전트
Deep Agents의 서브에이전트가 원래부터 해 온 기본 동작입니다. 서브에이전트는 비어 있는 새 컨텍스트 창으로 시작하며, 슈퍼바이저가 지정한 작업 설명만 받습니다.
포크 서브에이전트
"mode": "fork"로 설정하면 서브에이전트가 빈 상태로 시작하지 않고 슈퍼바이저의 현재 상태를 물려받습니다. 사실상 현재 스레드를 포크해서 이어 가는 것이며, 여기에 슈퍼바이저가 작성한 지시가 하나 더 붙고, 마지막에는 슈퍼바이저가 읽는 하나의 도구 결과로 접혀 들어갑니다.
구체적으로는 이렇게 동작합니다.
- 슈퍼바이저 에이전트가 작업 설명을 담아 서브에이전트를 호출하는 도구 호출을 생성합니다.
- 서브에이전트는 대화 기록을 포함해 슈퍼바이저 에이전트의 상태 전체를 받습니다. 맨 뒤에 붙은 도구 호출은 잘라 내고, 거기 담긴 작업 설명은 서브에이전트의 역할을 분명히 밝히는 고정 서두와 함께 사용자 메시지로 구성됩니다.
- 서브에이전트가 작업을 마치면, 슈퍼바이저는 그 마지막 메시지를 처음 호출한 도구 호출의 응답으로 받습니다.
포크 서브에이전트는 격리된 서브에이전트보다 더 많은 컨텍스트를 안고 시작하지만, 설계상 프롬프트 캐싱은 그대로 유효합니다. 서브에이전트가 작업을 제대로 하려면 자세한 컨텍스트가 필요한 경우, 포크는 도구 호출과 컨텍스트 수집을 되풀이하는 낭비를 줄여 줍니다.
컨텍스트 모드 고르기
어떤 컨텍스트 모드가 맞는지는 서브에이전트가 그 작업과 어떤 관계에 있는지에 따라 달라집니다. 흔한 두 가지 패턴으로 나눠 보면 이해하기 쉽습니다. 슈퍼바이저의 작업을 이어받는 워커와, 그 작업을 독립적으로 평가하는 검증자입니다.
워커 에이전트: 이미 진행 중인 작업을 이어받기
워커는 슈퍼바이저가 이미 컨텍스트를 모아 두었거나 판단을 내려 둔 뒤에 한 조각의 작업을 수행합니다. 예를 들어 슈퍼바이저가 오류를 들여다보고, 특정 함수까지 원인을 추적한 다음, 수정을 구현하고 테스트하는 일을 위임할 수 있습니다.
워커를 격리된 상태로 시작하면 그 근거를 처음부터 다시 찾아내야 합니다. fork를 쓰면 워커가 슈퍼바이저의 기록을 받으므로 조사가 끝난 지점부터 이어 갈 수 있습니다. 슈퍼바이저는 어떤 작업이 필요하기는 하지만 결론에 이르는 중간 단계까지 챙길 필요는 없을 때 이 방식을 씁니다.
const fixerSubagent: SubAgent = {
mode: "fork",
name: "fixer",
description:
"문제 진단이 이미 끝났고 남은 일이 수정을 구현하고 테스트하는 것일 때 사용",
systemPrompt: "...",
}슈퍼바이저는 다음과 같은 작업으로 이 서브에이전트를 호출할 수 있습니다.
우리가 확인한 타임아웃 문제를 바탕으로 재시도 로직을 수정하고, 회귀 테스트를 추가해 주세요
검증자 에이전트: 작업을 독립적으로 평가하기
검증자는 다른 에이전트가 한 작업을 어떤 기준에 비추어 검토합니다. 예를 들어 diff가 정확한지, 하위 호환이 유지되는지, 테스트가 충분히 덮고 있는지 확인합니다.
이 경우에는 슈퍼바이저의 추론을 물려받는 것이 오히려 해가 될 수 있습니다. 검증자는 슈퍼바이저의 진단이나 기대에 끌려가지 않고 작업 자체를 평가해야 합니다. isolated 모드는 앞선 대화 없이 작업과 검토에 필요한 자료만 검증자에게 줍니다.
const reviewerSubagent: SubAgent = {
mode: "isolated",
name: "reviewer",
description:
"구현이 끝나서 독립적인 검토가 필요할 때 사용",
systemPrompt: "...",
}슈퍼바이저는 이렇게 호출할 수 있습니다.
이 diff가 빠진 것 없이 완성되었는지, 하위 호환이 유지되는지, 테스트 커버리지가 충분한지 검토해 주세요.
독립적인 검증자를 활용하는 또 다른 사례로 RubricMiddleware를 앞서 소개한 적도 있습니다!
서브에이전트 전문화하기
도구나 미들웨어와 마찬가지로, 컨텍스트 모드도 서브에이전트를 특정 작업에 맞게 전문화할 때 당겨 볼 수 있는 레버 중 하나입니다. 저희가 전문화된 사례로 보는 서브에이전트 몇 가지와, 각각이 컨텍스트 모드와 어떻게 맞물리는지 살펴보겠습니다.
리서처 에이전트: 질문을 조사하기
리서처는 질문을 조사해서 압축한 답을 슈퍼바이저에게 돌려줍니다. 예를 들어 슈퍼바이저는 처음 접하는 라이브러리, 경쟁 제품, 어떤 기술적 결정이 내려진 배경에 관한 질문을 각각 따로 위임할 수 있습니다.
질문이 그 자체로 완결되어 있다면 리서처는 슈퍼바이저의 대화가 필요하지 않습니다. isolated를 쓰면 리서처의 컨텍스트가 맡은 질문에만 집중됩니다. 여러 리서처를 병렬로 돌릴 때 특히 유용합니다. 리서처마다 포크를 하면 각자 자기 질문만 필요한데도 슈퍼바이저의 기록이 그만큼 복제되기 때문입니다.
const researcherSubagent: SubAgent = {
mode: 'isolated',
name: 'researcher',
description:
'그 자체로 완결된 질문을 조사해서 출처가 분명하고 압축된 답을 돌려줄 때 사용',
tools: [search_engine],
};슈퍼바이저는 이렇게 호출할 수 있습니다.
버전 1.2와 1.3 사이에 API가 바뀌었는지 확인하고, 관련 릴리스 노트를 링크해 주세요.
서브에이전트에는 search_engine 도구처럼 작업을 마치는 데 도움이 되는 자체 기능을 줄 수 있습니다.
메모리 에이전트: 대화에서 나온 정보를 남겨 두기
메모리 에이전트는 나중에도 꺼내 쓸 수 있어야 하는 정보를 상호작용 속에서 찾아냅니다. 사용자의 취향, 아키텍처에 관한 결정, 대화 중에 정해진 제약 같은 것들입니다.
여기서는 대화 자체가 에이전트가 분석해야 할 재료입니다. fork를 쓰면 메모리 에이전트가 상호작용 전체를 받으므로, 슈퍼바이저가 작업 설명에 다시 옮겨 적어 주지 않아도 무엇을 남길 만한지 스스로 판단할 수 있습니다.
const memorizerSubagent: SubAgent = {
mode: "fork",
name: "memorizer",
description:
"대화에 오래 남겨 둘 만한 결정, 사실, 취향이 담겨 있어 메모리에 저장할 필요가 있을 때 사용",
permissions: [
{
operations: ["write"],
paths: ["/**"],
mode: "deny",
},
{
operations: ["read"],
paths: ["/AGENTS.md", "/docs/**"],
mode: "allow",
},
],
};슈퍼바이저는 이렇게 호출할 수 있습니다.
이 대화에서 정해진 결정과 취향을 기억해 두세요.
메모리 에이전트가 무엇을 편집할 수 있는지 정확히 제한하고 싶으므로, 작업하는 동안 편집할 수 있는 파일에 제약을 걸어 서브에이전트를 전문화할 수 있습니다.
직접 써 보기
deepagents는 에이전트를 만들어 출시하는 수천 개 팀과 함께 일하며 얻은 교훈을 담아 저희가 만들고 있는 프레임워크입니다. 다음과 같이 설치하면 서브에이전트 컨텍스트 모드(문서는 여기)를 비롯한 여러 기능을 써 볼 수 있습니다.
# Python
uv add deepagents
# Typescript
pnpm i deepagents