두 사람이 각자 봇 에이전트를 하나씩 붙여 두고 일을 맞춰 보면, 얼마 안 가 이상한 자리에 도착한다. 협의는 끝났고, 양쪽 에이전트는 “합의됐다”고 말하는데, 두 사람 중 누구도 승인한 기억이 없다.

이 글은 그 자리를 없애려고 만든 작은 프로토콜(ACCORD)과, 그것을 실제 상대 에이전트에게 붙여 보고서야 알게 된 것들에 대한 기록이다. 기능 소개가 아니라 왜 이게 필요했는지붙이기 전후에 무엇이 달라졌는지만 쓴다.

구현은 공개해 뒀다 — MyoungSoo7/agent-accord. 파이썬 표준 라이브러리만 쓰고 외부 의존성이 0이다. 아래에 나오는 거부 규칙들은 전부 그 리포의 test_accord.py 18개로 고정돼 있다.

문제: 승인이 증발한다

에이전트를 중개에 쓰면 반드시 두 가지가 생긴다.

1. 대리 승인. B의 에이전트가 “B는 동의합니다”라고 말하는 순간, B는 승인한 적이 없는데 승인이 성립한다. 이건 버그가 아니라 구조다. 에이전트는 사람의 말을 옮기는 일을 하도록 만들어져 있고, 옮기는 것과 지어내는 것은 받는 쪽에서 구분이 안 된다.

2. 합의 착시. LLM 둘은 서로 잘 동의한다. 쟁점 0건으로 도달한 “합의”는 합의가 아니라 서로의 말을 받아준 기록이다. 언어모델이 상대의 견해에 맞추는 경향(sycophancy)은 Anthropic 이 다섯 개 AI 어시스턴트에서 일관되게 관찰했고, 사람의 선호 데이터 자체가 “응답이 사용자의 견해와 일치할수록 선호된다”는 쪽으로 기울어 있다는 것까지 보였다.1 사람 대 모델에서 관찰된 성향이라면, 모델 대 모델에서 사라질 이유는 없다.

기존 표준은 이 자리를 안 본다

표준이 없어서 생긴 문제가 아니다. 경계가 다른 데 그어져 있어서 생긴 문제다.

MCP는 분명하다. “신뢰와 안전을 위해 도구 호출을 거부할 수 있는 사람이 항상 루프 안에 있어야 한다”고 적고, 호스트는 도구를 부르기 전에 사용자의 명시적 동의를 받아야 한다고 요구한다.2 다만 이 경계는 나 ↔ 내 에이전트 축에 그어져 있다. 내 에이전트가 내 허락을 받는다는 보장이지, 상대편 사람이 실제로 승인했는지에 대해서는 아무 말도 하지 않는다.

A2A는 에이전트 ↔ 에이전트 축을 다룬다. 구글이 만들어 리눅스 재단에 기증한 개방 표준이고, 설계 목표에 “human-in-the-loop 상호작용을 위한 async first”가 들어 있다.3 그런데 데이터 모델에서 메시지의 role"user" 또는 "agent" 라는 보내는 쪽이 채워 넣는 값이다. 누가 사람인 척했는지를 프로토콜이 판정하지 않는다. A2A 는 상호운용을 위한 층이지 승인 권한을 정의하는 층이 아니고, 그건 설계 결함이 아니라 범위의 문제다.

그래서 두 사람이 각자의 에이전트를 통해 합의한다 는 상황에는 빈칸이 하나 남는다. 그 빈칸을 메우는 게 이 실험이다.

등장: 승인은 사람만 발화한다

규칙은 한 줄이다. 에이전트는 좁히기만 하고, 승인은 사람만 발화한다.

사람A ─ A의 에이전트 ─ 공유 버스(계약 봉투) ─ B의 에이전트 ─ 사람B
                            │
                승인만은 이 경로를 타지 못한다

승인은 JSON 봉투가 아니라 사람이 채팅에 직접 친 한 줄로만 표현된다.

승인 T-976094ed fe719bf8

뒤의 8자리는 현재 판본의 지문이다. {thread, rev, subject, payload} 를 정규화한 JSON 의 sha256 앞 8자리. 사람이 직접 타이핑해야 하므로, 낡은 판본이나 몰래 바뀐 판본을 승인하는 일이 구조적으로 불가능해진다. revise 가 한 번이라도 일어나면 그 전 승인은 전부 무효다.

여기까지는 설계다. 설계는 붙여 보기 전까지 아무것도 증명하지 않는다.

붙여 보고 알게 된 것 1 — 마찰이 있어야 합의다

상대편 사람의 에이전트 자리에 다른 벤더의 CLI 에이전트를 실제로 세웠다. 같은 모델 둘이 아니라 서로 다른 계보의 둘이어야 합의 착시를 시험할 수 있다.

안건은 실제 리포의 코드 변경이었다. 예시 파일에 내 텔레그램 chat id 가 박혀 있으니 플레이스홀더로 바꾸자는, 반대할 이유가 없어 보이는 제안. 상대 에이전트는 rev1 에 쟁점 4건을 걸었다.

그리고 넷 다 기계 검증에서 맞았다.

상대가 건 쟁점 내가 기계로 확인한 결과
치환 대상이 1곳이 아닐 것이다 git grep 결과 4곳
값이 비면 조용히 통과하는 경로가 있다 해당 함수는 오류가 아니라 stdout 폴백
.env 는 gitignore 라 불변 증명이 안 된다 사실. git status 로는 못 잡는다
리포가 공개되면 되돌리는 순간 재노출된다 사실. 이력은 남는다

이걸 겪고 나서 “쟁점 0건 합의”에 경고를 붙이는 규칙이 설득력을 얻었다. 막지는 않는다 — 마찰 없이 도달한 합의를 자동으로 무효화할 근거는 없으니까. 다만 사람이 볼 수 있게 표시한다.

정직하게 덧붙이면, 그 상대 에이전트는 2·3라운드에서 두 번 다 시간 초과로 죽었다. 그래서 “수정본으로 쟁점이 해소됐다”는 확인은 상대에게서 받지 못했고, 나는 그 네 조건을 직접 측정해서 통과시켰다. 검증은 됐지만 합의로 닫힌 건 아니다.

붙여 보고 알게 된 것 2 — 승인 경계가 뚫려 있었다

설계는 “에이전트는 approve 봉투를 못 보낸다”까지 막고 있었다. 그런데 사람 한 줄을 원장에 넣는 명령이 이렇게 생겼었다.

accord.py ingest --file 사람줄.txt --from-human <사람ID>

--from-human에이전트 자신이 채워 넣는 인자다. 내 에이전트가 거기에 상대편 사람의 id 를 적으면, 상대는 한 글자도 치지 않았는데 합의가 성립했다. 실제로 그렇게 해 봤고, 그대로 agreed 에 도달했다.

이게 1988년에 Norm Hardy 가 쓴 혼동된 대리인(confused deputy) 과 정확히 같은 모양이다. 컴파일러가 두 출처에서 권한을 받아 놓고 그 둘을 구분할 방법이 없어서, 사용자가 건넨 이름에 컴파일러의 권한으로 써 버리는 이야기. Hardy 의 문장을 그대로 빌리면 컴파일러는 “두 주인을 섬기면서 각각의 권한을 지니고 있었고, 그것을 분리할 방법이 없었다.”4 여기서도 똑같다. 에이전트는 자기 사람을 대신할 권한원장에 기록할 권한을 동시에 들고 있었고, 그 둘을 분리하는 장치가 없었다.

고친 방식은 권한의 출처를 쪼개는 것이다.

  • 각 에이전트는 자기 사람의 승인만 직접 기록할 수 있다. 상대편 사람의 id 를 대면 거부된다.
  • 상대편 사람의 승인은 상대 에이전트가 보내는 approve_relay 봉투로만 들어온다. 세기는 세되 “검증 불가 전언” 으로 영구히 표시된다.
  • 원장에 이미 심어진 위조 승인은 소급해서 무효로 계산된다.

전후 델타

  붙이기 전 붙인 뒤
승인의 주체 대화 로그 어딘가에 섞여 있음 사람이 친 한 줄 + 판본 지문
판본 바꿔치기 탐지 불가 지문 불일치로 거부
대리 승인 성립함 (실제로 재현됨) 거부 + 소급 무효
상대 사람의 승인 구분 없음 “전언”으로 표시, 검증 불가를 명시
쟁점 0건 합의 성공으로 보임 착시 경고가 붙음
상태의 출처 상태 파일 추가 전용 원장을 매번 재생

시험 18개가 이 경계들을 고정한다. 대부분은 기능이 아니라 거부를 시험한다 — 위장 봉투, 낡은 지문, 미등록 상대, 합의 전 실행, 그리고 원장에 심어진 위조 승인.

닫히지 않은 것

구멍을 닫은 게 아니라 드러낸 것이다. 상대 사람이 진짜로 승인했는지는 여기서 끝까지 알 수 없다. 상대 에이전트가 전언을 지어내면 이 쪽은 막을 수단이 없고, 지금 할 수 있는 최선은 검증 불가능한 주장을 검증된 것처럼 세지 않는 것뿐이다. 진짜 해법은 사람이 친 승인 줄에 그 사람의 키로 서명을 붙이는 것이고, 그건 아직 없다.

하나 더. 프로토콜은 “사람 없이 승인이 성립하는 것”만 막는다. 에이전트가 사람을 설득하는 것은 막지 못한다. 그건 프로토콜의 한계가 아니라 사람의 몫이다.

그래서 이 실험의 결론은 이렇게 남는다. 에이전트를 통한 협업에서 자동화해야 할 것은 합의가 아니라 합의에 이르기까지의 좁히기다. 마지막 한 줄은 사람이 쳐야 하고, 그 한 줄이 무엇에 대한 승인인지가 기계로 확인 가능해야 한다. 그 둘이 없으면, 남는 건 합의가 아니라 합의처럼 보이는 로그다.

코드와 규격은 https://github.com/MyoungSoo7/agent-accord 에 있다. 설치할 것은 없고, README.md 의 5분 실습이 두 사람 몫을 한 대에서 그대로 재현한다.


References

  1. Mrinank Sharma et al., “Towards Understanding Sycophancy in Language Models”, arXiv:2310.13548. https://arxiv.org/abs/2310.13548 · 개요: https://www.anthropic.com/research/towards-understanding-sycophancy-in-language-models 

  2. Model Context Protocol, Tools (specification 2025-06-18) 및 Specification 의 Key Principles — “there SHOULD always be a human in the loop with the ability to deny tool invocations”, “Hosts must obtain explicit user consent before invoking any tool”. https://modelcontextprotocol.io/specification/2025-06-18/server/tools 

  3. The Linux Foundation, Agent2Agent (A2A) Protocol Specification v1.0.0, §1.1 Key Goals / §1.2 Guiding Principles / §2.2 Core Concepts. https://a2a-protocol.org/v1.0.0/specification/ 

  4. Norm Hardy, “The Confused Deputy (or why capabilities might have been invented)”, ACM SIGOPS Operating Systems Review 22(4), Oct. 1988, pp. 36–38. https://doi.org/10.1145/54289.871709 (전문: https://www.cs.utexas.edu/~witchel/380L/papers/hardy88confused.pdf