
Agent Framework Harness와 Agent Governance Toolkit을 이용한 파일 조작 에이전트 제어
요약
본 기사는 Microsoft Agent Framework의 Harness와 Agent Governance Toolkit (AGT)을 결합하여 파일 조작 에이전트를 제어하는 방법을 다룹니다. 이를 통해 단순히 프롬프트로만 제한할 수 없었던, 실행 계층에서 도구 호출(예: 파일 삭제) 자체를 정책에 따라 허용하거나 거부할 수 있습니다.
핵심 포인트
- Harness는 에이전트가 사용할 기능과 도구를 모아 제공하는 메커니즘입니다.
- AGT는 도구 호출을 정책 기반으로 검사하고 실행 계층에서 제어합니다.
- 두 컴포넌트를 결합하여 강력한 파일 액세스 및 장기 태스크 관리가 가능해집니다.
- Harness는 복잡한 에이전트의 파이프라인 구성 요소를 정형화합니다.
서론
지난 기사에서는 Agent Governance Toolkit (AGT)의 정책 엔진을 단독으로 평가하고, Microsoft Agent Framework의 에이전트에 .WithGovernance()를 통해 통합하는 방법을 소개했습니다.
이번에는 한 단계 더 나아가, Microsoft Agent Framework의 Harness와 AGT를 결합합니다. 주제는 에이전트가 로컬 작업 디렉토리를 조작하는 파일 액세스 에이전트입니다.
에이전트에게 파일을 다루게 하면 다음과 같은 요구사항이 즉시 발생합니다.
- 파일 목록 표시, 읽기, 검색은 허용하고 싶다
- 파일 생성, 삭제, 교체는 허용하고 싶지 않다
- 그 판단을 에이전트의 프롬프트에만 맡기고 싶지 않다
- 거부된 도구 호출을 감사 로그(Audit Log)로 추적하고 싶다
이러한 경우, Harness가 "무엇을 실행할 수 있는지"를 제공하고, AGT가 "그 실행을 허용할지"를 판단하도록 역할을 분담하는 것이 유효합니다.
본 기사에서는 다음 샘플을 참조합니다.
Harness와 AGT의 역할 분담
먼저, 두 컴포넌트의 역할을 정리합니다.
| 컴포넌트 | 담당하는 역할 |
|---|---|
| Agent Framework Harness | 파일 액세스 등 에이전트가 이용할 수 있는 기능과 도구를 모아서 제공함 |
| Agent Governance Toolkit | 도구 호출을 정책에 비추어 허용 또는 거부하고, 판단 결과를 이벤트로 출력함 |
Harness는 에이전트의 능력을 구성하는 메커니즘입니다. 샘플에서는 AsHarnessAgent()에 FileSystemAgentFileStore를 전달하여, file_access_ls, file_access_read, file_access_grep 등의 도구를 에이전트에 공개하고 있습니다.
반면, Harness가 도구를 공개했다고 해서 모든 호출을 실행해도 된다는 뜻은 아닙니다. AGT의 .WithGovernance()를 후단에 적용하면, 에이전트가 도구를 호출하기 직전의 파이프라인에 거버넌스(Governance) 판정을 삽입할 수 있습니다.
즉, 프롬프트에 "삭제하지 마세요"라고 쓰는 것뿐만 아니라, 삭제 도구를 호출하더라도 실행 계층에서 거부할 수 있게 됩니다.
Microsoft Agent Framework Harness 자세히 이해하기
여기서 이번에 이용하고 있는 Harness 자체를 조금 더 자세히 살펴보겠습니다.
Microsoft.Agents.AI.Harness는 단순히 파일 조작 도구를 추가하는 패키지가 아닙니다. 도구를 반복적으로 호출하며 장시간 태스크를 진행하는 에이전트에서 필요하기 쉬운 처리들을 정형화된 파이프라인으로 구성하기 위한 확장 기능입니다.
Agent Framework의 일반적인 AIAgent에서도 도구, 대화 이력, 실행 루프, 컨텍스트 관리를 개별적으로 구성할 수 있습니다. 하지만 다음과 같은 처리를 애플리케이션마다 반복해서 구현하면 코드가 복잡해집니다.
- 모델이 반환한 function call을 실행하고 결과를 모델에 다시 전달하는 루프
- 도구 실행을 포함한 대화 이력 저장
- 긴 태스크로 인해 늘어나는 컨텍스트 압축
- Todo, 파일 액세스, 서브 에이전트 등의 보조 기능
- 도구 실행 전의 승인 플로우
Harness는 이러한 장시간 태스크용 부품들을 IChatClient에서 AIAgent로 한꺼번에 연결합니다.
HarnessAgent 내부에서 구성되는 것
참조 기사에서는 Harness의 핵심을 다음 3가지 계층으로 설명하고 있습니다.
IChatClient
│
├─ FunctionInvokingChatClient
...
이것들을 수동으로 조합하는 대신, 다음 확장 메서드를 호출합니다.
AIAgent agent = chatClient.AsHarnessAgent(
maxContextWindowTokens,
maxOutputTokens,
...
AsHarnessAgent()는 IChatClient가 있다면 장시간 태스크를 상정한 HarnessAgent
AsHarnessAgent()는 IChatClient가 있다면 장시간 태스크를 상정한 HarnessAgent를 구성하는 진입점입니다. 이번 샘플에서는 이 호출에 FileSystemAgentFileStore와 여러 옵션을 추가했습니다.
Microsoft.Agents.AI.Harness 및 이와 관련된 AIContextProvider는 참조된 기사 시점에서는 실험적 기능(experimental feature)입니다. API, 도구(tool) 이름, 패키지 버전은 변경될 가능성이 있으므로, 도입 시에는 사용하는 버전의 공식 문서와 릴리스 노트를 확인하십시오.
컨텍스트 윈도우 (Context Window) 압축
장시간 작동하는 에이전트에서는 사용자의 지시, 모델의 응답, 함수 호출 (function call), 함수 결과 (function result)가 이력(history)에 축적됩니다. 이력이 모델의 컨텍스트 상한에 도달하면 새로운 도구 호출이나 결과를 넣을 수 없게 됩니다.
Harness는 maxContextWindowTokens와 maxOutputTokens를 기준으로 입력에 사용할 수 있는 예산(budget)을 계산합니다.
const int maxContextWindowTokens = 1_050_000;
const int maxOutputTokens = 128_000;
AIAgent agent = chatClient.AsHarnessAgent(
...
개념적으로는 모델의 컨텍스트 상한에서 다음 응답을 위한 토큰을 제외한 범위가 대화 이력과 도구 결과에 사용할 수 있는 입력 예산이 됩니다. 이력이 늘어나면 Harness의 컴팩션 (compaction) 메커니즘이 오래된 이력을 압축하여, 이후의 추론에 필요한 정보를 남기면서 컨텍스트를 작게 만듭니다.
이 덕분에 매번 messages를 직접 잘라내거나(truncate), 과거의 도구 결과(tool result)를 수동으로 요약하는 코드를 작성할 필요가 줄어듭니다. 다만, 컴팩션은 "모든 것을 완전히 보존하는" 기능은 아닙니다. 중요한 상태를 잃고 싶지 않다면 Todo나 파일 메모리 등 구조화된 상태(structured state)를 사용하여 명시적으로 저장하는 설계가 중요합니다.
샘플 구성
샘플의 주요 부분은 다음과 같습니다.
AGTPolicywithMAFApp03/
├── AGTPolicywithMAFApp03.csproj
├── Program.cs
...
working은 에이전트가 액세스하는 파일 스토어(file store)입니다. 프로젝트 파일에서는 정책(policy)과 이 디렉토리를 빌드 출력으로 복사하고 있습니다.
<ItemGroup>
<None Include="policies\default.yaml"
CopyToOutputDirectory="PreserveNewest" />
...
실행 시에는 AppContext.BaseDirectory를 기준으로 경로를 구성하므로, 실행 디렉토리에 의존성이 낮은 구성입니다.
환경 및 패키지
샘플은 .NET 10을 대상으로 합니다. 사용 중인 주요 패키지는 다음과 같습니다 (버전은 샘플 작성 시점 기준입니다).
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="Microsoft.AgentGovernance" Version="5.0.0" />
...
Azure OpenAI의 엔드포인트(endpoint)를 설정하고, Azure CLI로 인증합니다.
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5-mini"
az login
Windows PowerShell의 경우 다음과 같이 설정할 수 있습니다.
$env:AZURE_OPENAI_ENDPOINT = "https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-5-mini"
az login
AzureCliCredential을 사용하므로 애플리케이션에 키를 내장할 필요가 없습니다.
정책으로 파일 조작을 제어하기
policies/default.yaml에서는 읽기 계열의 도구(tool)는 허용하고, 변경 계열의 도구는 거부하고 있습니다.
apiVersion: governance.toolkit/v1
version: "1.0"
name: governed-file-access-policy
...
여기서 중요한 것은 default_action과 priority의 조합입니다.
이 샘플은 default_action: allow이므로, 정책에 없는 도구는 기본적으로 허용됩니다. 그 상태에서 변경 계열 도구에는 우선순위가 높은 deny를 설정했습니다. ConflictStrategy.DenyOverrides도 지정되어 있으므로, 허용과 거부가 충돌할 경우에는 거부가 우선됩니다.
더 엄격한 화이트리스트 (Whitelist) 방식으로 만들고 싶다면, default_action: deny로 변경하고 필요한 도구를 명시적으로 allow 하도록 설계할 수 있습니다. 운영 환경에서는 Harness의 버전 업데이트로 새로운 도구가 추가되더라도 의도치 않게 실행되지 않도록 화이트리스트 방식을 검토하는 것이 좋습니다.
참고로 tool_name은 Harness가 공개하는 함수 (function) 명입니다. file_access_read와 file_access_Read는 서로 다른 이름이므로, 정책의 문자열은 실제 도구 이름과 완전히 일치시켜야 합니다.
Harness 에이전트에 AGT 통합하기
Program.cs의 핵심 부분을 추출하면 다음과 같습니다.
using AgentGovernance;
using AgentGovernance.Extensions.Microsoft.Agents;
using AgentGovernance.Policy;
...
AsHarnessAgent()가 제공하는 기능
AsHarnessAgent()에 FileSystemAgentFileStore를 설정하면, 에이전트는 파일 조작용 도구를 사용할 수 있게 됩니다.
이 샘플에서는 파일 조작 이외의 기능은 비활성화했습니다.
DisableTodoProvider = true,
DisableAgentModeProvider = true,
DisableAgentSkillsProvider = true,
...
이는 데모 대상을 파일 액세스에 집중하기 위함입니다. 실제 애플리케이션에서는 필요한 기능만 활성화하고, 각각의 도구에 대응하는 정책을 준비합니다. 기능을 늘릴수록 정책의 대상과 감사 (Audit) 항목도 늘어난다는 점에 주의하십시오.
FileAccessProviderOptions와 AGT의 차이
DisableReadOnlyToolApproval이나 DisableWriteToolApproval은 Harness 측의 승인 플로우 (Approval flow)에 관한 설정입니다. 이것들을 비활성화한다고 해서 AGT의 정책 평가가 무효화되는 것은 아닙니다.
Harness는 "도구를 어떻게 제공할 것인가"를 담당하고, AGT는 "제공된 도구 호출을 실행해도 되는가"를 담당합니다. 승인 UI나 수동 확인, 그리고 정책에 의한 강제적인 거부는 대체 관계가 아니라 함께 사용할 수 있는 별개의 방어 계층입니다.
.WithGovernance()가 경계선이 된다
다음 부분이 통합의 핵심입니다.
.WithGovernance(
kernel,
new AgentFrameworkGovernanceOptions
...
EnableFunctionMiddleware = true를 통해, 에이전트의 함수 (function) 호출을 AGT가 인터셉트 (Intercept) 합니다. 에이전트의 추론 결과가 "쓰기를 실행한다"라고 하더라도, 실제 도구 실행 전에 정책을 조회하며, deny라면 실행되지 않습니다.
BlockedToolResultFactory를 지정하면, 거부 시 에이전트에게 반환할 결과를 애플리케이션 측에서 제어할 수 있습니다. 사용자용 설명을 반환하거나 감사 ID (Audit ID)를 포함하는 경우 등에 활용할 수 있습니다.
2개의 Agent ID에 주의하기
이 샘플에는 의도적으로 서로 다른 2개의 식별자가 있습니다.
Name = "GovernedFileAccessAgent"
이것은 Harness / Agent Framework 측의 에이전트 이름입니다.
DefaultAgentId = "governed-file-access-agent"
이것은 AGT가 정책 평가(Policy Evaluation)와 감사 이벤트(Audit Event)에 사용하는 에이전트 식별자(Identifier)입니다.
두 값을 동일하게 맞출 필요는 없습니다. 오히려 프레임워크상의 표시 이름과 거버넌스·감사용의 안정적인 ID를 분리해 두면, 표시 이름을 변경하더라도 감사 로그의 추적 키를 유지할 수 있습니다. 여러 에이전트를 운영하는 경우에는 테넌트(Tenant)나 환경을 포함한 고유한 ID 체계를 결정해 두는 것이 좋습니다.
실행하여 동작 확인하기
샘플은 읽기, 쓰기, 삭제의 3가지를 순서대로 시도합니다.
Console.WriteLine("=== Allowed read ===");
await RunAndPrintAsync(
agent,
...
실행 명령어는 다음과 같습니다.
dotnet run --project AGTPolicywithMAFApp03/AGTPolicywithMAFApp03.csproj
기대하는 결과는 다음과 같습니다.
| 작업 | AGT의 판단 | 파일에 미치는 영향 |
|---|---|---|
file_access_ls / file_access_read | allow | sample.txt를 목록 확인 및 읽기 가능 |
file_access_write | deny | blocked-write.txt가 생성되지 않음 |
file_access_delete | deny | sample.txt가 삭제되지 않음 |
거부된 경우, BlockedToolResultFactory에 의해 다음과 같은 결과가 에이전트에게 반환됩니다.
Tool call blocked by governance policy: ...
중요한 점은 에이전트가 거부되었음을 설명하는 것뿐만이 아닙니다. 거부된 도구(Tool) 자체가 실행되지 않았다는 것입니다. 프롬프트 인젝션(Prompt Injection) 등으로 에이전트의 지시가 바뀌더라도, 정책이 유지되는 한 쓰기나 삭제의 경계선은 실행 계층(Execution Layer)에 남아 있습니다.
거버넌스 이벤트를 감사에 활용하기
샘플에서는 모든 이벤트를 콘솔로 출력하고 있습니다.
kernel.OnAllEvents(evt =>
{
Console.WriteLine(
...
개발 중에는 동작 확인용으로 사용할 수 있지만, 운영 환경에서는 Application Insights나 OpenTelemetry 등으로 연결하는 것이 실용적입니다. 적어도 다음 정보를 기록할 수 있도록 구성하면 조사가 용이해집니다.
- 에이전트 ID
- 도구 이름
- 적용된 정책 이름
- allow / deny 결과
- 거부 이유
- 요청을 식별하는 상관 ID (Correlation ID)
로그에는 파일 내용이나 민감한 인자(Argument)를 그대로 기록하지 않는 설계도 필요합니다. 감사를 위해 필요한 정보와 저장해서는 안 되는 데이터를 구분하여 생각해야 합니다.
default_action: allow와 deny의 구분 사용
샘플은 설명을 쉽게 하기 위해 default_action: allow를 채택했습니다. Harness가 제공하는 읽기·쓰기·삭제·교체 중 위험한 것만 deny하는 구성입니다.
하지만 실제 운영에서는 다음과 같이 구분하여 사용합니다.
deny 리스트 방식
default_action: allow
기존 도구를 폭넓게 사용하면서 위험한 작업만 명시적으로 금지하는 방식입니다. 도입은 간단하지만, 새로운 도구가 추가되었을 때 허용될 가능성이 있습니다.
allow 리스트 방식
default_action: deny
필요한 도구를 모두 명시적으로 허용하는 방식입니다. 초기 설정은 늘어나지만, 미지의 도구에 대해 안전한 방향(Fail-safe)으로 대응할 수 있습니다.
파일 조작을 운영 데이터에 대해 실행하는 경우에는 default_action: deny를 기본으로 하고, 읽기 대상 도구와 경로 조건을 단계적으로 허용하는 설계가 적합합니다. 나아가 에이전트별 ID, 환경별 정책, 속도 제한(Rate Limiting), 서킷 브레이커(Circuit Breaker) 등을 조합하면 더욱 강력한 경계를 구축할 수 있습니다.
이 조합이 유용한 이유
Harness와 AGT의 조합에는 주로 4가지 이점이 있습니다.
- 능력과 권한을 분리할 수 있다
Harness의 구성을 변경하지 않고도 YAML 정책만으로 실행 가능 여부를 변경할 수 있습니다.
- 프롬프트가 아닌 실행 계층에서 거부할 수 있다
에이전트의 지시나 대화 기록이 변화해도, deny 규칙은 툴 호출 경계에서 적용됩니다.
- 감사 가능한 판단이 된다
어떤 에이전트의 어떤 툴 호출이, 어떤 규칙과 일치하여 거부되었는지를 이벤트로 처리할 수 있습니다.
- 승인 흐름과 강제 정책을 병용할 수 있다
Harness의 승인 기능을 사용자 경험(UX)을 위해 사용하고, AGT를 넘어서는 안전 경계로 사용할 수 있습니다.
이는 파일 접근에만 국한된 이야기가 아닙니다. 데이터베이스 업데이트, 외부 API 호출, 티켓 종료, 이메일 전송 등 되돌리기 어려운 작업을 에이전트에게 위임하는 경우에도 같은 사고방식을 적용할 수 있습니다.
요약
이번 샘플에서는 다음 흐름으로 읽기 전용 파일 접근 에이전트를 구성했습니다.
- Harness의
FileAccessProvider로 파일 조작 툴을 공개하고, AGT의 YAML에서 읽기는 허용하되 쓰기/삭제/교체는 거부합니다. .WithGovernance()와 Function Middleware를 이용해 툴 호출에 정책을 삽입하며, 가버넌스 이벤트와 차단 시 결과를 감사 및 사용자 알림에 활용합니다.
Harness는 에이전트에게 능력을 부여하고, Agent Governance Toolkit은 그 능력에 경계선을 긋습니다. AI 에이전트를 프로덕션 환경에 가깝게 만들기 위해서는 편리한 도구를 추가하는 것뿐만 아니라, 그 도구를 언제, 누가, 어떤 조건으로 실행할 수 있는지를 코드 외부에서 제어할 수 있는 것이 중요합니다.
이번 샘플 코드는 여기입니다.
참고 링크
토론(Discussion)

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