본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 05. 19. 01:05

Microsoft Agent Framework의 실험적 패키지 HarnessAgent 살펴보기

요약

Microsoft Agent Framework의 실험적 확장 패키지인 `Microsoft.Agents.AI.Harness`를 소개합니다. 이 패키지는 도구 호출과 장시간 반복 실행이 필요한 에이전트 개발 시, 복잡한 파이프라인을 한 줄의 코드로 간소화하여 자동화된 루프, 이력 영속화, 컨텍스트 압축 기능을 제공합니다.

핵심 포인트

  • HarnessAgent는 도구 호출(Tool Calling)의 자동 루프를 실행하는 FunctionInvokingChatClient를 포함합니다.
  • 서비스 호출마다 대화 이력을 저장하는 영속화 기능을 지원합니다.
  • Context Window 관리를 위해 컨텍스트를 압축하는 CompactionProvider 기능을 제공합니다.
  • 확장 메서드를 통해 기존 IChatClient를 매우 간단하게 HarnessAgent로 변환할 수 있습니다.
  • TodoProvider와 같은 실험적 AIContextProvider를 통해 태스크 관리를 지원합니다.

서론

Microsoft Agent Framework가 성공적으로 GA(General Availability)된 지 어느 정도 시간이 흘렀으며, 그 위에 올라가는 형태로 실험적인 확장 패키지들이 조금씩 추가되고 있습니다. 이번에는 그중에서도 개인적으로 관심이 갔던 Microsoft.Agents.AI.Harness를 가볍게 살펴보겠습니다.

간략히 말하자면, 이 패키지는 수많은 도구(Tool)를 호출하며 장시간 반복 실행되는 타입의 에이전트(Agent)를 작성할 때, 매번 수동으로 구성해야 했던 전형적인 파이프라인을 한 줄의 코드로 만들어 주는 패키지입니다. Agent Framework 자체는 GA 상태이지만, 이 Microsoft.Agents.AI.Harness 자체는 아직 프리뷰(Preview) 단계이며, 관련 AIContextProvider 군도 함께 실험적 기능(MAAI001)으로 제공되고 있습니다.

공식 리포지토리는 여기입니다.

HarnessAgent란 무엇인가?

HarnessAgentMicrosoft.Agents.AI.Harness 패키지에서 제공하는 에이전트로, AIAgent를 상속받아 내부에서 IChatClient를 래핑(Wrap)하는 구현체입니다. IChatClient에 대해 장시간 루프(Loop)를 위한 전형적인 파이프라인을 한꺼번에 구성해 주는 것이 본체의 역할입니다.

Harness 관련 작업은 다음 PR을 기점으로 진행되고 있습니다.

구체적으로 어떤 기능들을 쌓아 올려 주는지 살펴보면, 대략 다음의 3가지입니다.

  • FunctionInvokingChatClient를 통해 도구 호출(Tool Calling)의 자동 루프를 실행합니다.
  • PerServiceCallChatHistoryPersistingChatClient를 통해 도구 호출 루프 중 각 서비스 호출마다 이력을 영속화(Persist)합니다.
  • AIContextProviderChatClientCompactionProvider. 장시간 태스크에서 이력이 폭발적으로 늘어나지 않도록, 호출 시마다 컨텍스트 윈도우(Context Window)를 압축합니다.

직접 구성하려고 하면 만들 수 있는 파이프라인이지만, 매번 이를 작성하는 것은 솔직히 힘들며, 어떻게 구성하는 것이 정석인지 매번 고민하는 것도 번거롭기 때문에 이를 한꺼번에 관리해 주는 것은 큰 도움이 됩니다.

구성 방법으로는 IChatClient의 확장 메서드인 chatClient.AsHarnessAgent(maxContextWindowTokens, maxOutputTokens, new HarnessAgentOptions { ... })가 준비되어 있어, IChatClient만 있다면 한 줄로 에이전트를 만들 수 있습니다. HarnessAgentOptions를 통해 전달할 수 있는 주요 항목은 다음과 같습니다.

  • Name / Description / Id와 같은 식별 계열
  • ChatOptions (Instructions, Tools, MaxOutputTokens 등)
  • ChatHistoryProvider (기본값은 압축 전략이 포함된 reducer가 설정된 InMemoryChatHistoryProvider)
  • AIContextProviders (후술할 TodoProvider 등을 추가로 연결 가능)

또한, 본체 패키지인 Microsoft.Agents.AI 측의 Harness 네임스페이스에 장시간 태스크를 위한 AIContextProvider가 몇 가지 동봉되어 있습니다.

  • TodoProvider: TodoList_Add / TodoList_Complete / TodoList_Remove / TodoList_GetRemaining / TodoList_GetAll과 같은 도구를 에이전트에게 전달하여 태스크를 관리하게 하기 위한 것
  • AgentModeProvider: plan / execute 모드를 전환하는 도구 제공
  • FileMemoryProvider / FileAccessProvider: 파일 기반의 메모리 및 데이터 액세스
  • SubAgentsProvider: 서브 에이전트(Sub-Agent)에게 업무를 위임하기 위한 것
  • 도구 호출 승인 플로우를 삽입하는 UseToolApproval 확장 메서드

이렇게 나열해 보면 Todo 관리, plan / execute 모드 전환, 파일 기반 메모, 서브 에이전트(Sub-Agent)로의 위임, 도구 호출(Tool Call) 승인 등 GitHub Copilot과 같은 코딩 에이전트를 사용할 때 자주 접하는 기능들이 부품으로서 한데 모여 있음을 알 수 있습니다. 이번에는 그중에서 HarnessAgentTodoProvider를 조합하는 간단한 예제를 시도해 보겠습니다.

프로젝트 준비

솔루션 AgentFrameworkHarnessLab.slnx를 준비하고, 그 아래에 콘솔 애플리케이션 프로젝트 2개를 만들어 나갑니다.

  • HelloHarness — 우선 순수한 HarnessAgent를 실행
  • HarnessWithTodoTodoProvider를 조합하여 장기 태스크(Long-running task) 스타일로 실행

각 프로젝트에서 사용할 NuGet 패키지는 다음과 같습니다.

  • Microsoft.Agents.AI (1.6.1)
  • Microsoft.Agents.AI.OpenAI (1.6.1)
  • Microsoft.Agents.AI.Harness (1.6.1-preview.260514.1)
  • Azure.Identity
  • Microsoft.Extensions.Configuration.UserSecrets

Microsoft.Agents.AI.Harness만 프리뷰(Preview) 버전이므로, NuGet에서 가져올 때는 prerelease를 활성화해야 합니다.

인증은 AzureCliCredential을 사용하므로, 사전에 az login을 해둡니다. Foundry의 엔드포인트(Endpoint)와 배포명(Deployment Name)은 기사에서 직접 작성하지 않고, User Secrets에서 읽어오는 방식으로 구성합니다. 각 프로젝트에서 다음과 같이 설정해 주세요.

dotnet user-secrets init
dotnet user-secrets set "Endpoint" "<Foundry의 엔드포인트 URL>"
dotnet user-secrets set "DeploymentName" "<모델의 배포명>"

간단한 HarnessAgent 실행하기

먼저 HelloHarness 프로젝트에서 순수한 HarnessAgent를 실행해 보겠습니다. 날씨를 반환하는 단순한 도구(Tool)를 준비하고, 에이전트에게 두 번 정도 질문하여 세션(Session)이 유지되는지도 확인해 보겠습니다.

#pragma warning disable MAAI001 // Microsoft.Agents.AI.Harness 는 실험적 기능
#pragma warning disable OPENAI001 // OpenAIClient 의 BearerTokenPolicy 오버로드 는 실험적
using System.ClientModel.Primitives;
...

포인트만 보충해 두겠습니다.

IChatClient를 구성할 때 AzureOpenAIClient가 아닌 OpenAIClient + BearerTokenPolicy를 사용하는 이유는, Foundry의 v1 엔드포인트의 경우 AzureOpenAIClient의 라우팅(Routing)과 맞지 않는 케이스가 있기 때문입니다. OpenAIClient의 생성자에 BearerTokenPolicy를 전달하여, AzureCliCredential에서 가져온 토큰을 그대로 실어 보냅니다 (이 생성자는 OPENAI001로 실험적 취급됩니다). 참고로 BearerTokenPolicy에 전달하는 https://cognitiveservices.azure.com/.default는 Foundry의 엔드포인트 URL이 아니라, Microsoft Entra ID의 스코프(Scope) 식별자입니다.

에이전트를 구성하는 AsHarnessAgent의 인자에서 maxContextWindowTokensmaxOutputTokens를 전달하는 이유는, HarnessAgent

내부에서 컨텍스트 윈도우 (Context Window)를 압축할 때의 임계값 (Threshold) 역할을 하기 때문입니다. 이번에는 꽤 큰 모델을 사용할 생각으로 대략적인 값을 입력했지만, 실제 운영 시에는 사용하는 모델의 컨텍스트 윈도우와 출력 상한에 맞춰 조정하는 것이 좋습니다.

실행하면 다음과 같은 결과가 나왔습니다.

=== User: 히로시마 날씨를 알려줘. ===
[tool] get_weather: location=히로시마
Agent: 히로시마의 현재 날씨는 맑음, 기온은 22도입니다.
...

두 번째 질문인 "도쿄는?"이라는 다소 막연한 질문도 제대로 날씨 이야기로 해석해 주고 있네요. 세션을 넘나들며 대화의 문맥 (Context)이 유지되고 있다는 것을 알 수 있습니다. HarnessAgentAgentSession의 조합이 대화 이력 유지를 백그라운드에서 모두 처리해 주고 있으며, 게다가 입력 예산 (maxContextWindowTokens - maxOutputTokens)에 가까워지면 자동으로 컨텍스트 압축도 실행되므로, 이 Program.cs에는 채팅 이력을 다루는 코드가 거의 존재하지 않는다는 점도 좋습니다.

TodoProvider와 조합하기

다음으로는 조금 긴 태스크를 TodoProvider와 조합하여 시도해 보겠습니다. 여러 도시의 기온을 가져와서 비교해 달라는 업무를 에이전트에게 부탁하고, 그 과정에서 Todo를 쌓아가며 처리하게 하는 방식입니다.

HarnessWithTodo 프로젝트를 만들고, 다음과 같은 Program.cs를 작성합니다.

#pragma warning disable MAAI001 // Microsoft.Agents.AI.Harness와 TodoProvider는 실험적 기능
#pragma warning disable OPENAI001 // OpenAIClient의 BearerTokenPolicy 오버로드는 실험적
using System.ClientModel.Primitives;
...

포인트는 HarnessAgentOptions.AIContextProvidersnew TodoProvider()를 전달하는 부분과, Instructions를 통해 "TodoList_Add를 한 다음에 TodoList_Complete를 해줘"라고 절차를 전달하는 부분입니다. TodoProvider를 끼워 넣으면 TodoList_Add / TodoList_Complete 등의 도구 (Tool)를 에이전트가 자동으로 갖게 되므로, 이쪽에서 "도구로 등록"하는 등의 절차는 필요하지 않습니다.

실행 결과는 다음과 같았습니다.

=== User: 히로시마와 도쿄와 시나가의 기온을 조사해서, 기온이 높은 순서대로 나열해서 알려주세요. ===
[tool] get_temperature: city=히로시마
[tool] get_temperature: city=도쿄
...

백그라운드에서 TodoList_Add가 제대로 4건 쌓여 있고, 에이전트가 처리를 진행하면서 차례대로 TodoList_Complete를 호출하여 소화해 나가는 모습을 볼 수 있습니다. 최종적으로 모든 Todo가 체크 완료된 상태가 된 것도 보기 좋습니다.

이번에는 짧은 태스크라 체감이 적지만, 더 길고 복잡한 태스크가 되면 "현재 어디까지 진행되었고 무엇이 남아 있는지"가 세션 내에 구조화된 형태로 남기 때문에, 중간에 멈췄다가 재개하는 것과 같은 유스케이스 (Use case)와도 궁합이 좋아 보입니다.

참고로 마지막 agent.GetService<TodoProvider>() 부분에서 알 수 있듯이, Provider는 에이전트로부터 일반적인 방식으로 꺼낼 수 있으므로 UI 측에서 진행 상황을 시각화하는 등의 작업도 가능합니다.

Harness에는 이 외에도 AgentModeProvider / FileMemoryProvider / FileAccessProvider / SubAgentsProviderUseToolApproval 확장 메서드가 준비되어 있으므로, 나머지 기능들도 각각 가볍게 살펴보겠습니다.

AgentModeProvider로 모드 전환

AgentModeProvider를 포함하면 에이전트에 AgentMode_Set / AgentMode_Get

도구가 전달되며, 현재의 모드 이름이 시스템 프롬프트(System Prompt)에 자동으로 삽입됩니다. 기본적으로는 plan (대화형으로 계획 생성)과 execute (자율 실행) 두 가지가 준비되어 있으며, AgentModeProvider.SetMode를 통해 프로그램 측에서 강제로 전환할 수도 있습니다.

여기서는 기왕 하는 김에 TodoProvider와 조합하여, plan 모드에서는 Todo에 계획을 쌓기만 하고, execute 모드로 전환한 뒤에는 Todo를 소화하면서 실제로 도구(Tool)를 호출하여 결과를 정리하는, 흔히 볼 수 있는 에이전트의 동작을 재현해 보겠습니다. 도구는 HelloHarness에서 사용했던 것과 동일한 get_weather를 재사용합니다.

// 날씨를 반환하는 도구 (HelloHarness와 동일. execute 모드일 때 호출됨)
AITool getWeatherTool = AIFunctionFactory.Create(
([Description("지명 (예: 히로시마, 도쿄, 시나가와)")] string location) =>
...

실행 결과는 다음과 같습니다.

초기 모드: plan
=== User (plan): 히로시마, 도쿄, 시나가와의 현재 날씨를 알려주세요. ===
Agent: 날씨를 확인하기 위한 Todo를 생성했습니다.
...

plan 모드에서는 get_weather를 호출하지 않고 Todo를 3건 쌓는 것만으로 멈추며, execute 모드로 전환된 후에야 비로소 도구가 호출되고 그와 동시에 Todo가 소화되는 것을 확인할 수 있습니다. 모드와 Todo의 조합을 통해, 이러한 "먼저 계획 → 확인 → 실행"과 같은 워크플로우(Workflow)를 자연스럽게 표현할 수 있습니다. AgentMode_Set 도구를 에이전트 스스로 호출하게 하면, 사용자 승인을 거쳐 스스로 모드를 전환하는 구조로도 구현할 수 있습니다.

FileMemoryProvider를 이용한 세션 내 파일 메모리

FileMemoryProvider는 에이전트에 FileMemory_SaveFile / FileMemory_ReadFile / FileMemory_ListFiles 등의 도구를 전달하여, 세션 스코프(Session Scope)의 "파일 형식의 장기 기억"을 실현합니다. 스토리지는 AgentFileStore 추상화를 거치므로, FileSystemAgentFileStore를 전달하면 로컬 파일, InMemoryAgentFileStore를 전달하면 인메모리(In-memory) 방식으로 전환할 수 있습니다.

var memoryRoot = Path.Combine(AppContext.BaseDirectory, "memories");
Directory.CreateDirectory(memoryRoot);
var fileStore = new FileSystemAgentFileStore(memoryRoot);
...

"히로시마 출신이고 오코노미야키를 좋아한다"라고 가르쳐준 뒤, "좋아하는 음식을 기억하고 있니?"라고 물어본 실행 결과가 다음과 같습니다.

=== User: 저는 히로시마 출신이고, 좋아하는 음식은 오코노미야키입니다. 기억해 주세요. ===
Agent: 네, 기억하겠습니다.
- 출신: 히로시마
...

user_profile.md 본체와 더불어, 메모리의 인덱스 파일인 memories.md도 자동으로 업데이트되는 것을 알 수 있습니다. 에이전트가 다음 턴(Turn)에서 자신의 메모리를 쉽게 떠올릴 수 있도록 하는 장치입니다.

FileAccessProvider를 이용한 공유 폴더를 통한 데이터 처리

FileAccessProviderFileMemoryProvider와 유사하지만, 이는 세션을 넘어 공유되는 지속적인 폴더를 다루기 위한 Provider입니다. 입력 데이터의 읽기와 출력 파일의 쓰기가 주요 유스케이스(Use Case)가 됩니다.

사전에 sales.csv를 놓아둔 폴더를 공유 폴더로 전달하여, 요약 보고서를 작성하도록 해보겠습니다.

// 공유 폴더를 준비하고, 입력 CSV 파일을 하나 놓아둡니다.
var dataFolder = Path.Combine(AppContext.BaseDirectory, "data");
Directory.CreateDirectory(dataFolder);
...

실행 결과는 다음과 같았습니다.

=== User: sales.csv의 내용을 읽어서, 매출이 높은 도시 순으로 정렬한 요약을 summary.md로 저장해 주세요. ===
Agent: sales.csv를 분석하여 매출 순 요약을 summary.md에 저장했습니다.
--- 공유 폴더 내용 ---
...

입력 파일에는 건드리지 않고, 별도의 파일로 summary.md를 작성해 줍니다. FileMemoryProvider와 달리 인덱스 파일(index file)을 만들지 않고 폴더 내용을 그대로 사용하는 방식이므로, 데이터 I/O의 가교(bridge)로서 심플하게 사용할 수 있을 것 같습니다.

SubAgentsProvider를 통한 타 에이전트로의 작업 위임

SubAgentsProvider를 포함하면, 부모 에이전트가 다른 에이전트에게 작업을 위임하기 위한 도구 세트(toolset)를 갖게 됩니다. SubAgentsProvider의 생성자에 전달한 에이전트의 이름이 선택지가 됩니다.

주요 도구는 다음과 같습니다.

  • SubAgents_StartTask: 지정한 서브 에이전트(Sub-Agent)에게 작업을 시작하도록 함 (작업 ID 반환)
  • SubAgents_WaitForFirstCompletion: 지정된 작업 중 가장 먼저 완료된 것을 기다림
  • SubAgents_GetTaskResults: 완료된 서브 작업의 결과를 가져옴
  • SubAgents_GetAllTasks: 현재 서브 작업 목록과 상태를 가져옴
  • SubAgents_ContinueTask: 완료된 서브 작업의 세션에 추가 입력을 보내 작업을 계속함
  • SubAgents_ClearCompletedTask: 완료된 서브 작업을 정리하여 메모리를 해제함

작업 시작은 블로킹(blocking)되지 않고 병렬로 실행되는 구조이므로, 전형적인 사용 패턴은 "SubAgents_StartTask로 여러 작업을 병렬로 던지기 → SubAgents_WaitForFirstCompletion으로 하나씩 완료를 기다리기 → SubAgents_GetTaskResults로 결과 회수하기 → SubAgents_ClearCompletedTask로 정리하기"라는 흐름이 됩니다.

get_temperature 도구는 HarnessWithTodo 섹션의 것을 그대로 유용하고 있습니다.

// 서브 에이전트 측 (기온 취득 전담)
AIAgent temperatureAgent = chatClient.AsHarnessAgent(
maxContextWindowTokens,
...

실행 결과는 다음과 같았습니다.

=== User: 히로시마, 도쿄, 시나가와의 현재 기온을 표로 알려주세요. ===
[tool] get_temperature: city=広島
[tool] get_temperature: city=東京
...

부모 에이전트는 스스로 get_temperature 도구를 가지고 있지 않음에도 불구하고, 서브 에이전트를 통해 제대로 결과를 수집하여 표로 정리해 줍니다. 역할이 다른 에이전트들을 조합할 때 매우 유용해 보이네요.

UseToolApproval로 도구 승인 시 "다음에는 묻지 않기" 기능 추가

마지막으로 Provider가 아닌, AIAgentBuilder용 확장 메서드인 UseToolApproval을 살펴보겠습니다.

Agent Framework 본체에는 원래 "도구 호출 승인 플로우(tool calling approval flow)"가 마련되어 있습니다. 간단히 말하면, ApprovalRequiredAIFunction으로 위험한 도구를 래핑(wrap)해 두면, 에이전트가 해당 도구를 호출하려는 시점에 실행되지 않고 응답 메시지 안에 ToolApprovalRequestContent

섞여 들어오게 됩니다. 호출 측은 이를 보고 승인 여부를 판단하여, request.CreateResponse(approved: true/false)로 만든 응답을 세션에 반환해 주는 흐름입니다. 승인 요청이 없어질 때까지 이 상호작용을 while 루프로 돌려야 하므로, 핸들링 루프는 직접 작성해야 합니다. ApprovalRequiredAIFunction 주변의 기본적인 동작은 이전 기사에서 자세히 다루었으므로 그쪽을 참고해 주세요.

단, 이 표준 플로우는 "호출할 때마다 승인 요청이 오므로 매번 while 루프로 핸들링하여 반환한다"는 사양이기 때문에, 동일한 도구를 여러 번 호출하는 시나리오에서는 사용자가 피로를 느낄 수 있습니다. 그래서 AIAgentBuilderUseToolApproval을 끼워 넣으면, 승인 응답에 "이 도구는 앞으로 묻지 마", "동일한 인자라면 묻지 마"와 같은 추가 플래그를 실을 수 있게 됩니다. 해당 규칙은 AgentSession에 기록되어, 다음번 RunAsync 호출 시에는 아예 ToolApprovalRequestContent가 오지 않게 됩니다. GitHub Copilot의 auto approve와 유사한 동작입니다. 다만, 첫 번째 승인 루프 자체는 여전히 직접 돌려야 한다는 점에 주의하세요.

구체적으로는 ToolApprovalRequestContent를 위한 CreateAlwaysApproveToolResponse / CreateAlwaysApproveToolWithArgumentsResponse라는 확장 메서드가 준비되어 있으며, 이것들로 만든 응답을 반환하면 UseToolApproval 미들웨어가 규칙을 세션에 기록하는 방식입니다.

실제로 deploy라는 위험한 도구를 ApprovalRequiredAIFunction으로 래핑하여, 첫 번째에만 "다음부터 이 deploy는 묻지 마"라는 응답을 보내고, 두 번째에는 그대로 실행되는 모습을 살펴보겠습니다.

// 승인이 필요한 도구
AITool deployTool = new ApprovalRequiredAIFunction(
AIFunctionFactory.Create(
...

prod 환경에 승인하여 배포한 후, 이어서 staging에도 배포를 요청해 보니 다음과 같이 되었습니다.

=== User: prod 환경에 배포해 주세요. ===
[승인] deploy를 승인합니다 (향후 동일한 도구는 자동 승인)
[tool] deploy: environment=prod
...

두 번째 staging 배포에서는 승인 요청이 스킵되고, 즉시 deploy 도구가 호출되는 것을 확인할 수 있습니다. UseToolApproval을 한 줄 추가하는 것만으로 이 동작을 얻을 수 있다는 점이 편리하네요.

요약

이상으로 이번에는 Microsoft.Agents.AI.Harness를 가볍게 살펴보았습니다. 포인트를 정리하면 다음과 같습니다.

  • HarnessAgent는 장시간 루프(long-running loop)를 위한 표준 파이프라인(function invocation, per-service-call의 영속화, 컨텍스트 압축)을 한 번에 구성해 주는 편리한 에이전트입니다.
  • TodoProvider를 비롯한 AIContextProvider 군이 장시간 태스크에서 필요하게 되는 상태 관리 기능(Todo, 모드 전환, 파일 메모리, 서브 에이전트, 도구 승인)을 제공합니다.
  • 모두 실험적 기능(MAAI001)이므로 API가 변경될 가능성이 있습니다.

"직접 FunctionInvokingChatClient 위에 이것저것 쌓아 올리던 코드를 깔끔하게 정리하고 싶은 사람"이나 "planning을 수행하며 장시간 루프를 돌리는 에이전트를 작성하고 싶은 사람"에게 딱 좋은 도구 상자라는 생각이 들었습니다. 프리뷰 단계이긴 하지만, Agent Framework의 향후 방향성을 엿본다는 의미에서도 흥미로운 패키지이므로 관심 있는 분들은 꼭 한번 사용해 보세요.

그럼, 즐거운 Harness 라이프 되시길!

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
1

댓글

0