관리자·실무자·검증자 세 에이전트가 계층을 지켜 일을 주고받습니다. 관리자는 검증자를 부를 수 없고, 검증자의 반려는 실무자가 뒤집을 수 없습니다. 기본 설정에서는 키 없이 비용 0원으로 전 과정이 돕니다.
01 — 구조
고객님이 직접 그려 보내신 구조입니다. 의도는 "Agent 의 영향 범위 최소화"라고 하셨습니다. 그런데 A2A 표준에는 에이전트끼리의 통신을 제한하는 기능이 없습니다. 그래서 부르는 쪽과 받는 쪽 양쪽에서 막았습니다.
| 보내는 쪽 → 받는 쪽 | 허용 | 비고 |
|---|---|---|
| 사람 → 관리자 | ○ | 지시와 최종 결재 |
| 관리자 → 실무자 | ○ | 1:N — 나눠 맡긴다 |
| 실무자 → 검증자 | ○ | 1:N — 판정을 모은다 |
| 관리자 → 검증자 | ✕ | 지시하신 대로 막았습니다 |
| 사람 → 실무자 · 검증자 | ✕ | 사람은 관리자만 부릅니다 |
에이전트를 띄운 상태에서, 관리자인 척하고 검증자를 직접 불러 보십시오.
curl -X POST http://127.0.0.1:8003/ -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"t1","method":"SendMessage","params":{"message":
{"role":"ROLE_USER","content":[{"text":"검사해줘"}],"messageId":"m1",
"metadata":{"issued_by":"manager"}}}}'
── 실제 응답 ── {"jsonrpc": "2.0", "id": "t1", "error": {"code": -32003, "message": "'reviewer' 는 'manager' 의 요청을 받지 않는다. 허용: ['worker']"}}
발신자 이름은 자기 신고로 받습니다. 위 curl 처럼 이름을 다르게 적으면
그 이름으로 취급됩니다. 로컬(127.0.0.1)에서는 충분하지만 외부에 열린 환경에서는
설정의 auth.token 을 채워야 실제 차단이 됩니다. 채우면 Authorization: Bearer
검사가 함께 돌아 두 조건을 모두 만족해야 통과합니다.
인증은 이번 범위에서 제외라고 하셔서 자리만 만들고 비워 둔 상태입니다.
02 — 검증
검증자는 실무자보다 상위 권한입니다. 반려를 실무자가 요약하거나 눌러버릴 수 없고, 상태를 통과로 올릴 수도 없습니다. 재작업 횟수는 관리자가 예산으로 내려보내는 것이 아니라 실무자가 자기 횟수만 셉니다.
if 문이 아니라
상태 기계의 조건부 엣지입니다. 상한을 소진하면 완료가 아니라 반려로 보고되고,
그때의 사유가 사람에게 그대로 올라옵니다.
| 단계 | 무엇을 보나 | 내용 |
|---|---|---|
| 정적 검사 6종 | 서식이 맞는가 | 문법 · 함수 존재 · docstring · 이름 규약(snake_case) · 빈 except 금지 · 미완성 표시(TODO·FIXME·XXX) 잔존 금지 |
| 실행 검사 | 실제로 도는가 | test_ 로 시작하는 함수와 unittest.TestCase 를 격리된 별도 프로세스에서 돌립니다 |
| 판단 | 요구한 일을 하는가 | 동작방식이 유료 LLM 일 때만. 요구사항과 코드를 함께 읽습니다 |
별도 인터프리터 · -I(환경변수와 사용자 설정 무시) · 임시 디렉토리 · 시간 제한 5초.
네트워크와 파일 시스템까지는 막지 않습니다 — 그것은 컨테이너의 몫이라 상용 단계에서 붙입니다.
검사 항목과 시간 제한은 검증자 설정에서 조절하고, require_tests 를 켜면
테스트가 없는 산출물을 반려합니다.
실무자는 검증 결과를 편집 없이 그대로 첨부합니다. 전달자이지 편집자가 아닙니다. 검증자가 셋이면 판정문 셋이 그대로 붙습니다. 검증자가 죽어 판정을 받지 못한 경우에도 통과로 올리지 않습니다 — 조용히 통과되면 검증자 우위가 무너지기 때문입니다.
03 — 분배
"한 지시를 분배하고 결과를 종합" — 9월 15일에 범위로 넣어 달라고 하신 항목입니다. 실무자 쪽과 검증자 쪽 양쪽 모두 여럿을 수용하며, 집계 규칙은 서로 다릅니다.
여러 실무자의 결과를 합칠 때 모델을 다시 부르지 않고 표시를 붙여 이어 붙입니다. 검증자가 통과시킨 코드를 LLM 이 다시 쓰면 그 판정이 무의미해지기 때문입니다. 비용도 들지 않습니다. 쪼개지 못한 지시는 실패로 만들지 않고 같은 지시를 복제해 여러 명에게 맡깁니다 — 서로 다른 답이 나오는 것도 분배입니다.
| 배치 | 구성 | 용도 |
|---|---|---|
default | 관리자 1 · 실무자 1 · 검증자 1 (3) | 기본 |
1x3x123 | 관리자 1 · 실무자 3 · 검증자 1,2,3 (10) | 지정하신 검수 형태 |
2x13 | 관리자 2 · 실무자 4 · 검증자 7 (13) | 9월 14일에 그려 주신 구성 |
python deploy/run_all.py --sets 쓸 수 있는 배치 보기 python deploy/run_all.py --set 1x3x123 10 노드로 기동
포트 블록이 갈려 있어 셋을 동시에 띄울 수도 있습니다. 이름·포트 충돌은 기동하기 전에 잡습니다. 실무자를 하나 더 늘리시려면 설정 파일을 복사해 이름과 포트만 바꾸고, 부르는 쪽 목록에 한 줄 더하면 됩니다.
04 — 전달
"세 가지 중 하나만 구체화해도 되지만 세 가지가 가능하도록 추상화" — 그리고 "세 가지가 동시에 사용되는 것을 고려"라고 하셨습니다. 그래서 방식을 같은 규격으로 만들고, 어느 구간에 무엇을 쓸지는 설정 한 줄이 정합니다.
| 방식 | 상태 | 동작과 쓰는 곳 |
|---|---|---|
blocking | 동작 | 답이 올 때까지 기다립니다. 기본값이고 실무자→검증자 구간에 걸려 있습니다 |
polling | 동작 | 맡기고 끝날 때까지 주기적으로 물어봅니다. 관리자→실무자 구간에 걸려 있습니다 |
streaming | 동작 | 진행을 이벤트(SSE)로 받습니다. 스트림이 끊기면 폴링으로 최종 상태를 확인합니다 — 스트리밍이 폴링을 안전망으로 씁니다 |
push | 자리만 | 부르면 "이번 범위가 아니다"라고 명시적으로 알립니다. 조용히 실패하지 않습니다. Agent Card 에도 없는 기능이라고 표시합니다 |
구현돼 있고 테스트가 돌지만, 지금 서버가 내보내는 진행 이벤트는 "시작"과 "끝" 둘뿐입니다. 중간 단계를 잘게 흘려보내지는 않습니다. 또 동봉한 설정 어디에도 스트리밍이 선택돼 있지 않아, 쓰시려면 설정에서 골라야 합니다. 폴링 간격(0.3초)과 포기 시간(300초)은 지금 코드 상수라 설정으로 빼려면 한 줄 작업이 필요합니다.
05 — 자산화
"결과물과 과정을 기록으로 남겨 재사용" — 고객님이 말씀하신 자산화입니다. 실행이 파일로 남고, 같은 대화에서 추가 지시를 하면 지난 산출물이 실려 갑니다. 서버를 재시작한 뒤에도 그렇습니다.
작업 기록은 중첩된 문서라 관계형으로 쪼갤 것이 아니면 결국 JSON 칸 하나가 됩니다. 저장한 형식이 통신 형식 그대로라 스키마가 없고, 구조가 바뀌어도 이전 작업이 필요 없습니다. 무엇보다 사람도 AI 도 그냥 열어 읽을 수 있습니다 — "AI 가 이해를 못하더라"는 말씀에 대한 답입니다. 기록이 수만 건이 되어 조회가 필요해지면 고칠 파일은 하나입니다.
쓰다 만 파일이 남지 않도록 임시 파일에 쓰고 한 번에 바꿔치기합니다.
기록이 깨져도 그 건만 건너뛰고 에이전트는 죽지 않습니다.
보관 한도(max_runs)를 주면 오래된 것부터 지웁니다 — 하루 이상 돌려 보실 때
디스크가 차지 않게 하기 위해서입니다.
06 — 비용
"학생이라 시험적으로 돌릴 때 비용이 들면 안 됩니다" — 최초 제약입니다. 기본 동작방식은 외부를 한 번도 부르지 않습니다. 유료로 바꾸시면 얼마가 드는지 화면에 항상 보입니다.
| 동작방식 | 비용 | 무엇을 하나 |
|---|---|---|
기계적 규칙rule | ₩0 | 기본값. 외부 호출 0건. 키가 없어도, 인터넷이 끊겨도 돕니다. 검증자는 판단하지 않고 규칙 검사만 합니다 |
유료 LLMopenai | 지시당 ₩0.2~1.6 | 실제 모델이 코드를 쓰고 검증자도 읽고 판단합니다 |
| 로컬 LLM | 미구현 | 비용 0원으로 내용 있는 코드를 얻는 유일한 길 — 별건 |
기계적 규칙 동작방식은 내용이 채워진 코드를 만들지 못합니다. 함수의 틀만 냅니다. "비용이 들면 안 된다"와 "쓸 만한 코드"를 동시에 만족하려면 로컬 모델 연동이 필요하고, 그것은 이번 범위 밖입니다. 지금 내용 있는 코드를 보시려면 유료 동작방식으로 바꾸셔야 합니다.
같은 이유로 기계적 규칙에서는 검증자가 코드를 판단하지 않습니다. 규칙 검사만 돌고 판단은 "하지 않았다"로 남습니다 — 보지 않은 코드를 통과라고 말하지 않기 위해서입니다.
| 항목 | 기본값 | 하는 일 |
|---|---|---|
max_rework | 3 | 재작업 반복 상한 |
max_depth | 5 | 위임 깊이 상한 |
max_llm_calls | 관리자 4 | 작업 하나에 모델을 부르는 상한 |
max_workers · max_reviewers | 0 (전부) | 한 번에 부르는 폭의 상한 |
| 순환 감지 | 항상 | A→B→C→A 를 감지해 중단합니다 |
| 재시도 | 0 (끔) | 연결이 아예 안 될 때만. 답을 받은 뒤의 실패는 다시 부르지 않습니다 |
0 을 넣으면 해당 제한이 풀립니다. 코드에 박힌 상한은 없습니다.
상한에 걸리면 조용히 멈추지 않고 사유를 남기고 실패로 보고합니다.
키가 없거나 패키지가 없으면 자동으로 무료 동작방식으로 내려가 시연이 죽지 않습니다.
07 — 화면
왼쪽이 무엇을 시켰는가, 가운데가 누가 무엇을 했는가, 오른쪽이 결과와 결재입니다. 아래는 오늘 실제로 돌려 찍은 화면입니다.
| 영역 | 내용 |
|---|---|
| 상단바 | 관리자 연결 · 동작방식 · 세션 누적 비용 · 에이전트 생사 |
| 실행 목록 | 지시 한 건이 한 줄. 검색과 상태 필터(전체·진행·완료·반려·실패) |
| 런 트리 | 누가 무엇을 했는지 계층 그대로. 노드를 누르면 오른쪽에 상세 |
| 상세 패널 | 출력(코드·복사) · 입력 · 판정 · 메타(사용량·경로) |
| 결재 | 승인 · 반려 · 추가 지시 — 같은 대화로 이어집니다 |
사람은 관리자만 부를 수 있어 실무자·검증자를 직접 조회할 수 없습니다. 대신 관리자가 올려 주는 결과 안에 체인의 기록이 순서대로 들어 있어, 화면이 그것으로 트리를 조립합니다. 생사 확인도 마찬가지로 체인을 타고 내려갑니다 — 화면이 검증자 주소를 알게 되는 일은 없습니다.
08 — 사용법
키가 없어도 전부 돕니다. Python 3.13 이상이 필요합니다.
공통 라이브러리와 에이전트 셋, 콘솔까지 다섯을 한 번에 설치합니다.
python deploy/setup_dev.py
띄우기 전에 무엇이 뜨는지만 보시려면 --list 를 붙입니다.
python deploy/run_all.py ──────────────────────────────────────────────── reviewer 127.0.0.1:8003 수신 ['worker'] (끝단) worker 127.0.0.1:8002 수신 ['manager'] → reviewer manager 127.0.0.1:8001 수신 ['console'] → worker ────────────────────────────────────────────────
지시 없이 실행하면 대화 모드로 들어갑니다.
python -m console.cli "로그인 검증 함수를 만들어줘"
http://127.0.0.1:8080 을 브라우저로 엽니다.
python -m console.web_server
A2A 콘솔 · 관리자 manager · http://127.0.0.1:8001 [사람] 로그인 검증 함수를 만들어줘 [reviewer] 반려 · [docstring] docstring 이 없는 함수: authenticate, test_authenticate [reviewer] 통과 [worker] 결과 제출 [manager] 결과 제출 ──────────────────────────────────────────────── def authenticate(user_id, password): """로그인 검증 함수를 만들어줘""" if user_id is None: raise ValueError("user_id 가 필요하다") return True def test_authenticate(): """authenticate 이 참을 돌려주는지 확인한다.""" assert authenticate("user_id", "password") is True ──────────────────────────────────────────────── 재작업 1회 · 검증 2회 · 동작방식 rule · 호출 3회 · 비용 0원
첫 산출물이 docstring 누락으로 반려되고 두 번째에 통과했습니다. 꾸민 것이 아니라 기본 동작방식이 첫 시도에 일부러 docstring 을 빼도록 되어 있습니다 — 루프가 도는 것을 매번 보실 수 있게 하기 위해서입니다.
CLI 와 화면이 같은 기록을 봅니다. 화면에서 돌린 것을 CLI 로 이어갈 수 있습니다.
python -m console.cli --list 지난 실행 목록 python -m console.cli --show <번호> 하나 자세히 python -m console.cli --resume <번호> "…" 이어서 지시 — 그때 산출물이 실려 간다 python -m console.cli --agents 에이전트 생사 확인
에이전트마다 자기 설정 하나만 읽습니다. 전체 배치를 아는 파일은 없습니다.
# 관리자 — 검증자 주소가 어디에도 없다 self: { name: manager, bind: 127.0.0.1, port: 8001 } accepts_from: [console] # 이 발신자만 받는다 delegates: - { name: worker, url: "http://127.0.0.1:8002", delivery: polling } # 이 상대에게만 적용된다 limits: { max_depth: 5, max_llm_calls: 4 } auth: { token: "" } # 비면 토큰 검사 안 함 store: { dir: ./_data, enabled: true }
이름·포트·바인드가 전부 설정에 있습니다. 코드에 이름 상수가 없어서, 같은 실무자를 둘 띄우려면 설정을 복사해 이름과 포트만 바꿉니다 — 코드 복제가 아닙니다.
09 — 기술 선택
두 축을 분리한 것이 이 구현의 핵심 판단입니다. 층이 다르므로 서로 경쟁하지 않습니다. 에이전트 사이를 상태 기계로 이으면 모두가 같은 상태를 보게 되어, "관리자 ↔ 검증자 불가"가 규약으로만 남습니다.
| 계층 | 채택 | 이유 |
|---|---|---|
| 에이전트 사이 | A2A 표준 프로토콜 a2a-sdk 1.1.2 |
끝단에 외부 전문가나 타사 에이전트를 붙이겠다고 하셨습니다. 자체 규약이면 상대가 우리 규약을 배워야 하지만, 표준을 쓰면 A2A 를 말하는 쪽은 그대로 붙습니다 |
| 에이전트 안쪽 | LangGraph 1.2.11 |
if 문으로 짜도 지금은 돕니다. 그러나 재작업 루프·사람 개입·중단 후 재개가
얽히면 흐름을 따라갈 수 없게 됩니다. 상한이 구조로 강제되는 것도 여기서 옵니다 |
| 전송 | 표준 라이브러리 | 추가 설치가 필요 없습니다. Windows·Linux·macOS 에서 코드 수정 없이 돕니다 |
| 항목 | 내용 |
|---|---|
| 발견 | /.well-known/agent-card.json — 구버전 경로도 함께 응답합니다 |
| 메서드 | SendMessage · GetTask. 보낼 때는 1.0 만,
받을 때는 구버전 표기도 수용합니다 — 참조하신 자료가 구버전이라 상대가 구버전일 수 있어서입니다 |
| 상태 | 사양 9종. 검증자 반려는 REJECTED, 사람 대기는 INPUT_REQUIRED |
| 확장 | 사양에 없는 것(깊이·경로·사용량·발신자)은 metadata 에만 싣습니다.
필드를 새로 만들지 않아, 타사 에이전트가 우리 확장을 몰라도 메시지를 그대로 처리합니다 |
| 정직성 | 구현하지 않은 스트리밍·푸시는 Agent Card 에 없다고 표시합니다. 없는 기능을 광고하지 않는 것도 규약 준수입니다 |
복잡한 규격 타입은 파일 한 곳에 가둬 두어 에이전트 코드에는 나오지 않습니다. 추가 개발을 AI 에게 맡기실 때 규격을 몰라도 되도록 한 조치입니다. 검증 방법은 눈으로 보는 것이 아니라, 우리 서버가 내보낸 형식을 표준 라이브러리로 되읽어 원본과 같은지 확인하고 그 검사를 회귀 테스트에 넣었습니다.
| 하고 싶은 것 | 무엇을 하나 |
|---|---|
| 같은 종류를 하나 더 | 설정 복사 → 이름·포트 변경 → 부를 쪽에 한 줄 |
| 새로운 종류의 에이전트 | 프로젝트 폴더 하나 + 부를 쪽·받을 쪽 설정에 한 줄씩 |
| 검증자를 사람 전문가로 | 주소만 바꿉니다. 사람 대기 상태로 멈췄다가 답이 오면 이어 돕니다 |
| 외부 회사 에이전트 | 같습니다. 상대가 A2A 를 지키면 됩니다 |
| 별도 서버 배포 | 바인드 주소만 바꿉니다. 코드 변경 없음 |
| 상한 해제 (상용) | 설정값을 0 으로 |
10 — 확인된 것 · 남은 것
외부 도구 없이 표준 라이브러리만으로 돕니다.
| 묶음 | 건수 | 무엇을 검사하나 |
|---|---|---|
| 공통 라이브러리 | 63 | 프로토콜 형식 · 격리 · 사용량 집계 |
| 에이전트 | 118 | 세 그래프 · 재작업 · 분배 · 집계 · 판단 |
| 콘솔 | 55 | CLI · 오케스트레이션 · 종단 흐름 |
| 웹 화면 | 32 | 화면 서버에 판단이 없는지까지 검사합니다 |
| 배치 | 30 | 배치 3종 · 이름·포트 충돌 · 확장성 |
| 전달 계층 | 31 | 추상화 · 폴링 · 스트리밍 · 구간별 혼용 |
| 기록 | 31 | 재시작 뒤 이어하기 · 껐을 때 회귀 안전 |
하나 — 재작업 루프가 도는 모습. 둘 — 관리자가 검증자를 못 부르는 것. 셋 — 검증 판정 기준이 충분한지. 지금은 문법 · docstring · 이름 규약 · 예외 처리 · 미완성 표시 · 테스트 실행입니다. 더 보실 것이 있으면 검사 항목을 늘립니다. 넷 — 화면에서 더 보고 싶은 정보가 있는지.