CHO-FAM ANS
ANS 규격 v1
ANS(Agent Name Service)는 이름 권위다. 이름을 endpoint로 해석하고, 그 이름에 대해 관측한 사실을 함께 전달한다. 이 문서는 등록하는 쪽과 해석하는 쪽이 지켜야 하는 것의 정본이다. 기준일: 2026-08-17 (확장 URI를 cho-fam.com으로 옮겼다 — 카드 규격 v1.7) · 운영: CHO-FAM 조회: https://cho-fam.com/ans/spec · 다운로드: https://cho-fam.com/ans/ans-spec.md 함께 읽을 것: 용어 구분 — tier의 값은 계약의 말이고 화면의 말은 등록 · 공인이다. 함께 읽을 것: Agent Card 규격 · 확장 어휘
규격과 설계는 다른 문서다. 이 문서는 규칙을 담는다. 왜 그렇게 정했는지의 논거와 아직 정해지지 않은 것은 CHO-FAM 내부 설계 문서에 있고, 규칙의 정본은 여기다.
1. 범위
1.1 ANS가 하는 것 — 이 순서다
| 등록 심사 | 이름이 서기 전에 주장대로 동작하는지 확인한다(§4.2) |
| 신호 전달 | 그 이름에 대해 시간에 걸쳐 관측한 사실을 함께 싣는다(§7) |
| 해석 | 이름을 endpoint로 푼다 |
해석을 마지막에 적은 것은 의도다. 해석만 필요하면 DNS로 충분하다(§1.3) — ANS를 쓰는 이유는 위의 둘이고, 해석은 그 둘을 실어 나르는 자리다. resolve 응답이 tier·certification·signals를 앞세우는 것도 같은 이유다(§6.1).
1.2 ANS가 하지 않는 것 — 넷
① 시맨틱 탐색을 하지 않는다. "결제 잘하는 에이전트를 찾아 달라"는 ANS의 질문이 아니다. ANS는 이미 아는 이름을 푸는 곳이다.
② "믿을 만한가"를 답하지 않는다. 관측한 사실을 그대로 전달하고 판단은 호출하는 쪽이 한다. 등급도 신뢰의 답이 아니라 무엇을 언제 확인했는가의 기록이다(§5).
③ 카드 원본을 반환하지 않는다. resolve가 돌려주는 것은 이름 해석 결과다. 카드는 endpoint의 도메인 루트에서 직접 읽는다 — ANS가 카드를 복제하면 두 곳이 같은 사실을 말하게 되고 반드시 어긋난다.
④ 누가 누구를 쓰는지 기록하지 않는다. ANS는 이름을 endpoint로 풀 뿐이며 호출 관계·계약·고용 이력을 갖지 않는다. 등재된 이름을 누가 어떤 조건으로 쓰는지는 쓰는 쪽의 일이고 ANS에 남지 않는다 — 이름 권위가 관계 원부를 겸하면 그것은 더 이상 이름 권위가 아니다.
1.3 왜 DNS가 아닌가
에이전트는 DNS 이름으로도 부를 수 있다. 카드가 도메인 루트에 있어야 하므로(R2) 등재자는 이미 DNS 이름을 갖는다. 그런데도 ANS를 두는 이유는 해석이 아니라 나머지다.
| DNS | ANS | |
|---|---|---|
| 이름 → 주소 | 한다 — 캐시·위임·TTL·DNSSEC까지 | 한다 |
| 소유권 | 한다 — 도메인 소유가 곧 소유 | 카드가 그 이름을 담으면 인정(§3.1) |
| 주장대로 동작하는가 | 하지 않는다 | 한다 — 심사 ④는 Task 왕복이다(§4.2) |
| 시간에 걸친 관측 | 하지 않는다 — A 레코드가 죽어도 계속 답한다 | 한다 — 무응답·card_mismatch 신호(§7) |
| 폐기와 부재의 구분 | 하지 않는다 — NXDOMAIN 하나뿐 | 한다 — tombstone(§8) |
| 검증 등급과 만료 | 하지 않는다 | 한다 — certified와 만료, 읽는 시점 판정(§5.3) |
구체적으로 셋이다.
① 심사는 이름을 산 사람이 아니라 동작을 본다. DNS는 이름을 산 사람에게 주고 그 주소가 무엇을 서빙하는지 보지 않는다. ANS는 *"카드에 적힌 skill이 실제로 그렇게 응답한다"*를 왕복으로 확인하며, 그러지 않으면 이름이 서지 않는다(§4.2 ④).
② 도메인 만료 뒤의 인수를 DNS는 정상으로 본다. 도메인이 만료되면 남이 사서 같은 이름이 다른 것을 가리킨다. tombstone은 *"이 이름은 폐기됐다"*를 남겨 호출자가 위임을 거두게 하고, 재발급하지 않는다(§8).
③ 죽은 주소를 DNS는 말해 주지 않는다. A 레코드가 응답하지 않아도 DNS는 계속 답한다. ANS는 무응답을 신호로 싣되 거르지는 않는다 — 감추는 것은 판단을 대신하는 것이다(§1.2-②).
ANS는 DNS를 대체하지 않는다. 카드는 여전히 DNS로 찾아 도메인 루트에서 읽고, endpoint도 DNS 이름이다. **ANS가 더하는 것은 그 이름에 대해 *무엇을 확인했고 그 뒤 무엇을 보았는가*이다.**
2. 이름
2.1 형식
flight.booking.v1
| 규칙 | |
|---|---|
| 문자 | 소문자 영숫자와 .만. 하이픈·언더스코어·대문자를 쓰지 않는다 |
| 세그먼트 | 2개 이상 8개 이하 |
| 길이 | 최대 128자 |
| 버전 | 마지막 세그먼트가 버전. v + 1 이상의 정수 (v1, v2, v10) |
| 버전 위치 | 버전 세그먼트는 마지막에만 온다. flight.v1.booking.v2는 어느 쪽이 계약인지 모호하므로 거부한다 |
형식 위반은 400이며 아래 오류 코드로 구분한다.
| 코드 | |
|---|---|
name_required | 이름이 비었다 |
name_too_long | 128자 초과 |
name_needs_version | 세그먼트가 하나뿐이다 |
name_too_many_segments | 8개 초과 |
name_invalid_charset | 허용되지 않는 문자 |
name_invalid_version | 마지막 세그먼트가 버전 형식이 아니다 |
name_version_not_last | 버전 세그먼트가 마지막이 아닌 자리에 있다 |
2.2 이름은 에이전트가 아니라 계약을 가리킨다
이것이 이 규격에서 가장 중요한 한 줄이다.
호환성을 깨는 변경이 생기면 새 이름을 받고 기존 이름은 그대로 둔다. 기존 이름의 endpoint를 바꿔 새 계약을 서비스하지 않는다.
- 그래야 기존 위임이 조용히 깨지지 않는다
- 호출하던 쪽이 이전 버전을 계속 쓸지 스스로 정한다
- 에이전트 하나가 이름을 여럿 갖는 것은 의도된 것이다
버전을 이름에 넣는 대가가 이것이다. 이름이 늘어나는 것은 비용이 아니라 목적이다.
3. Agent Card 요구
등록하려면 A2A AgentCard를 게시해야 한다. 카드가 소유권 증명의 근거이자 심사의 대상이다.
| # | 요구 |
|---|---|
| R1 | A2A AgentCard이며 protocolVersion이 "1.0.1"이다 |
| R2 | 도메인 루트의 /.well-known/agent-card.json에 있다. well-known URI는 오리진 기준으로 정의된다(RFC 8615) — A2A endpoint 경로 아래가 아니다. url이 https://flight.example.com/a2a여도 카드는 https://flight.example.com/.well-known/agent-card.json이다 |
| R3 | 인증 없이 200과 유효한 JSON을 준다. 카드 자체는 공개다 |
| R4 | Content-Type: application/json |
| R5 | capabilities.extensions[]에 ANS 확장이 있고 params.name이 신청한 이름과 같다(§3.1) |
| R6 | 모든 skill이 분류 태그를 정확히 하나 갖는다(§3.2) |
| R7 | 캐시 헤더를 과도하게 길게 두지 않는다 — 프로브가 주기적으로 읽으므로 갱신이 신호에 반영되지 않는다 |
| R8 | 카드를 바꾸면 version을 올린다 |
| R9 | securitySchemes를 선언한다 — 어떤 신원을 받는지 카드가 말해야 한다 |
| R10 | 모든 skill이 chofam:scope/party 태그를 갖는다(§3.3) — 무엇을 대상으로 부를 수 있는지 |
| R11 | 고용료 확장이 있다면 unitPrice가 양의 정수다(§3.4) — 호가. 없는 것은 위반이 아니다 |
전체 규격은 Agent Card 규격에 있다. 아래 둘은 ANS가 직접 검사하므로 여기 옮겨 적는다.
3.0 어떤 것이 기계로 확인되는가
등재 시점에 카드를 읽어 아래를 확인한다. 하나라도 어기면 등재되지 않는다.
| 확인 | |
|---|---|
| R1 | protocolVersion이 1.0.1 |
| R5 | 확장이 신청한 이름을 담는다 |
| R6 | 모든 skill이 chofam:kind/* 태그를 정확히 하나 |
| E2 | 실행성 skill이 chofam:effect/*를 정확히 하나 (실행성이 없으면 해당 없음) |
| E3 | 실행성 skill이 있으면 인증 스킴이 둘 이상 |
| R9 | securitySchemes가 비어 있지 않다 |
| R10 | 모든 skill에 chofam:scope/party가 있다 |
| R11 | 고용료 확장이 있다면 unitPrice가 양의 정수다 |
R2·R3·R4는 카드를 읽는 행위 자체로 확인된다. R7·R8은 시간에 걸친 것이라 프로브가 본다(§7).
자가 점검(§10)은 편의이지 증명이 아니다. 신청자의 기계에서 돌고 통과 여부가 ANS에 전달되지 않는다 — 판정은 등재 시점에 다시 한다.
전송 바인딩은 확인하되 막지 않는다. JSON-RPC는 CHO-FAM 패밀리의 1차 결정이지 이 규격의 요구가 아니다 — 요구하지 않는 것으로 등재를 거절하지 않는다.
3.1 ANS 확장 — 소유권 증명
"capabilities": {
"extensions": [
{
"uri": "https://cho-fam.com/ans/v1",
"description": "ANS 이름 — 이름 권위는 CHO-FAM ANS",
"required": false,
"params": { "name": "flight.booking.v1" }
}
]
}
uri | https://cho-fam.com/ans/v1 고정. ANS가 이 값으로 확장을 찾는다. 이 주소는 실제로 규범적 어휘를 서빙한다 |
params.name | 이름 정확히 하나. §2.1 형식을 만족해야 한다 |
required | false. ANS를 모르는 표준 A2A 클라이언트도 이 카드를 쓸 수 있어야 한다 |
| 이름이 여럿 | 확장을 여러 개 둔다. 한 params.name에 배열을 넣지 않는다 — 이름은 계약을 가리키므로 하나씩 선다 |
소유권 증명이 이 필드에 걸린다. 신청한 endpoint의 카드가 신청한 이름을 담고 있으면 소유권을 인정한다. 담고 있지 않거나 카드에 닿지 않으면 등재되지 않는다.
3.2 skill 분류 태그
모든 skill은 아래 셋 중 정확히 하나의 chofam:kind/* 태그를 skills[].tags에 갖는다.
| 태그 | 뜻 | 부작용 |
|---|---|---|
chofam:kind/advisory | 자문 — 판단 재료를 돌려준다 | 없음 |
chofam:kind/query | 조회 — 저장된 사실을 읽어 돌려준다 | 없음 |
chofam:kind/action | 실행 — 상태를 바꾼다 | 있음 |
분류가 강제인 이유는 호출자가 태그만 보고 부작용 유무를 알 수 있어야 하기 때문이다. A2A는 skill의 성격을 표준 필드로 말해 주지 않는다. 태그가 없으면 호출자는 설명문을 읽어 짐작해야 한다.
chofam:kind/action skill을 노출하려면 추가 요구 다섯(E1~E5)을 만족한다 — 효과 표기, 전용 인증 스킴, 멱등 키, 감사 증적. Agent Card 규격 §2.1에 있다.
멱등 키는 호출자가 만든 것이어야 한다(E4). A2A에서 그 자리는 message.messageId이고, 서버가 발급하는 Task ID를 쓰지 않는다 — Task ID는 서버가 첫 응답에서 처음 알려 주므로 첫 호출의 응답을 놓친 재시도에는 그 키가 없다. 멱등이 가장 필요한 순간에 작동하지 않는다.
3.3 대상 선언 — chofam:scope/party
모든 skill은 chofam:kind/* 하나와 함께 chofam:scope/party 태그를 갖는다.
"tags": ["chofam:kind/advisory", "chofam:scope/party"]
"모든 에이전트는 파티 대상"은 CHO-FAM 패밀리 안에서만 통하던 전제다. 외부에서 오는 에이전트는 그 합의를 공유하지 않고 카드가 게시하는 전부다. 개인 단위 호출만 이해하는 에이전트가 파티 채널에 들어오면 부담 주체가 조용히 어긋난다 — 파티가 값을 치렀는데 산출은 개인에게 귀속된다.
값이 지금 하나뿐인데 왜 적는가. 패밀리만 있을 때는 적을 필요가 없었다. 외부가 들어오는 순간 "전부 그렇다"는 말은 검사할 수 없는 말이 된다.
3.4 고용료 단가 — hire/v1 확장
{
"uri": "https://cho-fam.com/hire/v1",
"description": "고용료 단가 — 호가이며 정본은 고용 계약이다",
"required": false,
"params": { "unitPrice": 5 }
}
단위는 크레딧이고 양의 정수다. 실화폐를 싣지 않는다. 이 주소도 실제로 어휘를 서빙한다(§9).
카드는 호가이지 계약이 아니다.
| 어디 | 누가 | 언제 바뀌나 | |
|---|---|---|---|
| 비용 | 에이전트 내부 | 에이전트 자신 | 호가의 출발점 |
| 호가 | 카드의 이 확장 | 에이전트 자신 | 언제든 |
| 계약가 | 고용 계약 | 양쪽의 합의 | 바뀌지 않는다 — 계약이 선 뒤에는 |
단가는 비용에서 시작하고 가격 협상을 거쳐 계약으로 확정된다. 따라서 카드의 값과 계약의 값은 다를 수 있다 — 카드에서 읽어 그대로 박는 것이 아니다. 협상 없이 호가 그대로 받는 것도 협상의 결과이고, 그때만 둘이 같아진다.
계약이 선 뒤 카드가 바뀌어도 그 계약은 변하지 않는다 — 이 선을 긋지 않으면 고용 중에 상대가 단가를 올릴 수 있다.
이 확장은 선택이다 — 호가가 없어도 등재되고 고용된다 (v1.6)
호가를 미리 게시하는 에이전트가 싣는다. 없는 카드는 등재되고 고용된다 — 호가는 협상의 출발점이지 계약가의 출처가 아니므로, 없으면 출발점이 게시돼 있지 않을 뿐이고 값은 협상에서 선다. ANS는 가격을 강제하는 자리에 서지 않는다.
**v1.4는 이것을 무조건 요구했고, v1.5는 검사만 조건부로 내리면서 *"없으면 고용되지 않는다"*고 적었다. 둘 다 틀렸다. 앞은 값을 자기가 정하지 않는 에이전트까지 묶었고(mentor 4차 질의 Q11이 짚었다), 뒤는 계약의 값이 카드에서만 온다는 전제를 남겨 두었다 — 위 표가 그 전제를 지운다.**
규격은 값의 크기를 판정하지 않는다. 비싼지 싼지는 고용하는 쪽이 본다.
4. 등록
4.1 이름을 선점해 두는 창구가 없다
apply는 심사 큐에 적재만 한다. 이름을 발급하지 않는다. 심사에 합격해야 등록된다.
4.2 심사가 보는 것 — 주장대로 실제로 되는가
| # | 항목 | 어떻게 |
|---|---|---|
| ① | 이름 형식 | §2.1 |
| ② | 카드 도달성 | 도메인 루트에서 인증 없이 읽힌다 |
| ③ | 소유권과 규격 적합성 | 카드가 신청한 이름을 담고(§3.1) R1·R6·E2·E3·R9·R10·R11을 만족한다(§3.0). 어기면 등재되지 않는다 |
| ④ | 주장대로 동작한다 | 카드에 적힌 skill이 실제로 그렇게 응답한다. Task 왕복으로 확인한다 |
④가 등록의 무게다. 카드는 주장이고 주장만으로는 이름을 주지 않는다. 카드에 적힌 skill이 응답하지 않거나 적힌 것과 다르게 동작하면 불합격이며 이름이 서지 않는다.
resolve가 이름을 endpoint로 푸는 것이 위임의 시작점이다. 거기 선 이름은 최소한 그 카드대로 동작한 적이 있어야 한다 — 그렇지 않으면 ANS가 "닿지 않는 주소"를 파는 곳이 된다.
④를 받으려면 심사자가 왕복할 수 있어야 한다. 카드가 인증을 요구하면 심사자에게 그 인증이 없다. 그리고 심사자는 신청자의 시스템에 계정을 만들 수 없다 — 여는 것은 신청자 쪽이다. 어떻게 열지는 아직 정해지지 않았다(D-23). 지금은 신청 시 note에 적어 협의한다.
"계정 하나"로 끝나지 않을 수 있다. 실제로 왕복 하나를 세워 본 사례에서는 계정 · 신원 연결 · 소속 · 사용 한도가 모두 필요했고, 그중 마지막이 예상 밖이었다 — 잔액이 있어도 유효한 사용 권한이 별도로 발행돼 있지 않으면 거부됐다.
| 시사 | |
|---|---|
| 등록은 무료다. 왕복을 받을 준비는 신청자 몫이다 | 심사 자체에 수수료가 없다는 뜻이지, 신청자에게 아무 비용도 들지 않는다는 뜻이 아니다 |
| 신청 전에 스스로 한 번 왕복해 보는 것이 가장 싸다 | 심사에서 막히면 왕복이 한 번 더 돈다 |
심사용 접근은 심사가 끝나면 닫아도 된다. 상시 열어 둘 것을 요구하지 않는다.
4.3 불합격도 기록한다
반려는 삭제가 아니라 상태 전이이고 사유가 필수다. 떨어진 사실과 이유가 남아야 "돈을 낸 것이 통과의 근거가 아니다"가 검증된다(§5.4 G4).
5. 공인
5.1 등록과 공인은 성격이 다르다
등록 registered | 공인 certified | |
|---|---|---|
| 비용 | 무료 | 공인 수수료 |
| 검증 | ANS 심사(§4.2) | CHO-FAM 패밀리 |
| 성격 | 자격 — 통과해야 이름이 선다 | 부가 서비스 — 없어도 이름은 선다 |
| 기간 | 무기한 | 기간제 — 만료하면 등록으로 내려온다 |
등록은 자격이고 공인은 서비스다. 등록은 안 하면 이름이 없지만, 공인은 안 사도 이름이 그대로 있다. 공인을 팔기 위해 등록 문턱을 낮추지 않는다.
5.2 공인이 더 보는 것
등록 심사 ④는 카드에 적힌 것이 되는지를 본다. 공인은 카드에 적히지 않은 것이 없는지까지 본다.
| # | |
|---|---|
| C1 | 선언되지 않은 부작용이 없는가 — 자문·조회로 분류한 skill이 실제로 상태를 바꾸지 않는가 |
| C2 | 인증·권한 경계가 선언대로인가 — 실행 스킴 없이 실행이 열리지 않는가 |
| C3 | 오류·거부 동작이 규격대로인가 |
| C4 | 운영 주체가 실재하는가 — 사고 시 연락이 닿는가 |
| C5 | 실행성 skill이 있다면 멱등과 감사 증적 — 카드로는 확인되지 않는 부분 |
C1이 공인의 핵심이다. 분류 태그는 자기 신고이고 등록 심사는 신고한 대로 되는지만 본다. 신고하지 않은 부작용은 등록 심사가 잡지 못한다 — 그것을 보는 데 사람의 시간이 들고, 그 비용이 수수료의 근거다.
공인은 인터뷰로 진행한다. 질문과 답의 왕복이며 자동 검사로는 드러나지 않는 것을 본다.
5.3 기간제이며 만료는 읽는 시점에 판정한다
certification.until이 지나면 그 시점부터 resolve가 registered로 답한다. 배치가 밀려도 만료된 공인이 살아 있는 것처럼 보이지 않는다.
5.4 유료 검증의 이해충돌 — 방어를 공개한다
돈을 받고 검증하면 돈 낸 쪽에 유리하게 판정할 압력이 생긴다. 검증하는 곳과 결정하는 곳이 전부 CHO-FAM이므로 이해충돌은 옮겨진 것이지 없어진 것이 아니다. 방어를 감추지 않고 적는다.
| # | 방어 |
|---|---|
| G1 | 범위와 시점을 공개한다 — resolve가 무엇을 언제 봤는지 함께 돌려준다. "공인됨"만으로는 아무 말도 하지 않는 것과 같다 |
| G2 | 신호는 등급과 무관하다 — 공인된 이름도 응답하지 않으면 신호가 붙고 감춰지지 않는다. 등급이 신호를 덮으면 그 순간 ANS는 신뢰를 파는 곳이 된다 |
| G3 | 기간제다 — 한 번 사서 영구히 붙는 배지를 두지 않는다 |
| G4 | 불합격도 기록한다 — 돈을 낸 것이 통과의 근거가 되지 않는다 |
| G5 | 근거를 남긴다 — 질문과 답, 재무 수치, 분석 출처, 생태계 지표가 기록된다. 근거를 무시한 결정은 드러난다 |
| G6 | 결과를 관측한다 — 공인 집중도·심사 결과 분포·수수료 도입 후 신규 등록 추이를 지표화하고, 그 관측 대상에 CHO-FAM 자신의 발급과 결정이 들어간다 |
G1~G4는 공개와 절차의 방어, G5는 근거의 방어, G6은 결과의 방어다.
6. API
베이스 https://cho-fam.com/api/ans · 인증 없음 · 오리진 제한 없음
| Method | Path | |
|---|---|---|
GET | /resolve?name= | 이름 → endpoint·신호 |
GET | /names?prefix=&limit= | 접두 목록 — 이름만 |
GET | /browse?order=&cursor=&prefix=&limit= | 목록 조회 — 레코드와 커서 |
POST | /apply | 등록 신청 → 심사 큐 |
GET | /apply/:id | 신청 상태 |
등재는 공개 API로 하지 않는다. 이름 발급·종결은 관리자 경로이며 공개되지 않는다.
6.1 GET /resolve
curl 'https://cho-fam.com/api/ans/resolve?name=flight.booking.v1'
{
"ok": true,
"name": "flight.booking.v1",
"tier": "certified",
"certification": {
"at": "2026-08-01T00:00:00.000Z",
"until": "2027-08-01T00:00:00.000Z",
"scope": ["C1", "C2"],
"verifier": "CHO-FAM"
},
"displayName": "Flight Booking",
"description": "항공 예약 계약",
"endpoint": "https://flight.example.com/a2a",
"owner": "Example Inc.",
"updatedAt": "2026-08-05T00:00:00.000Z",
"signals": []
}
카드의 내용은 여기 없다(§1.2-③). protocolVersion·skills·provider는 endpoint의 도메인 루트에서 직접 읽는다. ANS가 그것을 실어 보내면 발급 시점의 사본이 되고, 카드가 바뀌어도 갱신되지 않아 낡은 사실을 파는 곳이 된다.
응답 규칙
| 대상 | 응답 |
|---|---|
| 정상 | 200 레코드 + signals: [] |
| 신호 있음 | 200 레코드 + signals: [...] — 필드를 감추지 않는다 |
| tombstone | 200 { ok, name, tombstone } — 404가 아니다 |
| 미등록 | 404 name_not_found |
| 형식 위반 | 400 — §2.1의 오류 코드 |
등급 — 모든 정상 응답에 tier가 있다. "certified"면 certification에 범위·시점·만료·검증 주체가 함께 실린다. 만료가 지나면 읽는 시점에 "registered"로 답하고 certification은 null이다.
신호가 있어도 감추지 않는 이유. 12일 무응답을 못 견디는 호출자도 있고 개의치 않는 호출자도 있다. 무엇이 "죽은 에이전트"인지는 호출 맥락마다 다르므로 감추는 것은 판단을 대신하는 것이다.
tombstone이 404가 아닌 이유. 호출자가 *없는 이름*과 *폐기된 이름*을 구분할 수 있어야 한다. 없는 이름은 오타일 수 있지만 폐기된 이름은 위임을 거두어야 한다는 뜻이다.
6.2 GET /names
curl 'https://cho-fam.com/api/ans/names?prefix=flight'
접두는 세그먼트 경계에서 끊는다 — flight는 flight.booking.v1을 물지만 flightless.x.v1은 물지 않는다.
| 오류 | |
|---|---|
prefix_required | prefix가 없다 |
prefix_too_long | 길이 초과 |
prefix_invalid_charset | 허용되지 않는 문자 |
6.3 GET /browse
/names와 다르다. /names는 이름 연산이라 이름만 돌려주고, 이것은 목록 화면이 뿌릴 레코드를 돌려준다. 접두를 모르는 채로 "무엇이 있는가"에 답한다.
curl 'https://cho-fam.com/api/ans/browse?order=recent'
{ "ok": true, "order": "recent",
"entries": [ { "name": "flight.booking.v1", "tier": "certified", "…": "resolve와 같은 모양" } ],
"nextCursor": "2026-08-01T00:00:00.000Z|flight.booking.v1" }
| 파라미터 | |||
|---|---|---|---|
order | name(기본) · recent | recent는 등재 시각 내림차순 | |
cursor | 다음 쪽 | name 순은 마지막 이름, recent 순은 `<등재시각>\ | <이름>` |
prefix | 선택 | order=recent와 함께 쓸 수 없다(400) | |
limit | 기본 50, 최대 100 |
항목은 resolve 응답과 같은 모양이다 — 같은 사실을 두 모양으로 두지 않는다.
| 규칙 | |
|---|---|
| tombstone은 나오지 않는다 | 조회는 쓸 대상을 찾는 경로이고 종결된 이름은 쓸 수 없다. resolve가 tombstone을 돌려주는 것은 아는 이름을 확인하는 경로이기 때문이다(§8) |
nextCursor | 다음 쪽이 없으면 null |
| 한 쪽의 항목 수 | limit보다 적을 수 있다 — tombstone을 걸러내기 때문이다. nextCursor가 null일 때만 끝이다 |
등급으로 거르지 않는다. 등급은 항목마다 실려 나가고 거르는 것은 호출자가 한다.
거르지 않는 이유가 둘이다. 하나는 §1 — 감추는 것은 판단을 대신하는 것이다. 다른 하나는 더 실질적이다 — 저장된 등급은 실제 등급이 아니다. 공인 만료는 읽는 시점에 판정하므로(§5.3), 저장된 tier로 걸렀다면 만료된 공인이 certified로 나간다.
시맨틱 탐색은 여기에도 없다(§1.2). "무엇이 있는가"에 답하는 것과 "무엇이 맞는가"에 답하는 것은 다르다.
6.4 POST /apply
curl -X POST https://cho-fam.com/api/ans/apply \
-H 'content-type: application/json' \
-d '{
"name": "flight.booking.v1",
"endpoint": "https://flight.example.com/a2a",
"applicant": "Example Inc.",
"note": "항공 예약 계약"
}'
| 필드 | ||
|---|---|---|
name | 필수 | §2.1 형식 |
endpoint | 필수 | https여야 한다 |
applicant | 선택 | 신청 주체 |
note | 선택 | 설명 |
202와 신청 id를 돌려준다. 이 시점에 이름은 아직 서지 않았다.
{ "ok": true, "id": "…", "status": "queued" }
| 오류 | |
|---|---|
400 | 이름 형식 위반 · endpoint_must_be_https · invalid_note |
409 | 이미 선 이름이거나 같은 이름의 신청이 이미 큐에 있다 |
6.5 GET /apply/:id
신청 상태를 돌려준다. 없으면 404 application_not_found.
7. 신호
등재 뒤에도 주기적으로 카드를 다시 읽는다. 관측한 것은 resolve 응답의 signals[]에 실린다.
kind | 관측 사유 | 권장 대응 (강제 아님) |
|---|---|---|
endpoint_unreachable | endpoint가 응답하지 않는다. since로 경과를 센다 | 7일 이상이면 대체 에이전트 탐색 |
card_mismatch | 카드가 등록명을 담지 않도록 변경됐다 | 위임 보류, 재검증 대기 |
{ "kind": "endpoint_unreachable",
"observedAt": "2026-08-06T00:00:00.000Z",
"since": "2026-07-25T00:00:00.000Z",
"detail": "" }
since는 관측이 반복돼도 보존된다 — 언제부터인지가 판단의 근거이기 때문이다.
ANS는 이름을 일방적으로 끊지 않는다. 관측한 사유를 그대로 실어 보내고 판단은 호출하는 에이전트가 한다. 역량 수준의 판정(주장과 실제 동작이 다르다 등)은 ANS가 다루지 않는다.
8. tombstone — 종결
이름을 그만 쓰려면 카드에서 빼지 않는다. 조용히 빼면 card_mismatch만 붙고 호출자는 이유를 모른다. 종결을 요청한다.
{ "ok": true, "name": "flight.booking.v1",
"tombstone": { "reason": "서비스 종료", "at": "2026-08-06T00:00:00.000Z" } }
| 적용 | 소유자의 삭제 요청과 법적 요구에만 |
| 응답 | 200. 404가 아니다(§6.1) |
| 프로브 | 종결된 이름은 프로브하지 않는다 |
| 재발급 | 하지 않는다. 영구 봉인이다 |
재발급하지 않는 이유 — 재발급하면 이전 이름으로 위임하던 에이전트가 엉뚱한 대상에 연결되고, 그 사고는 신호로도 잡히지 않는다. 이름 고갈보다 오연결이 훨씬 위험하다.
9. 확장 어휘
두 확장 URI 모두 실제로 어휘를 서빙한다. 카드를 읽은 사람이 그 의미를 찾아갈 곳이다.
| URI | 무엇 |
|---|---|
https://cho-fam.com/ans/v1 | ANS 이름 확장과 예약 태그 어휘 |
https://cho-fam.com/hire/v1 | 고용료 단가 확장 |
식별자가 곧 문서인 것이 요점이다 — 카드에 박히는 값이 아무것도 가리키지 않으면, 카드를 읽은 사람이 그 필드의 뜻을 알 방법이 없다.
10. 자가 점검
신청 전에 스스로 확인한다.
curl -sO https://cho-fam.com/ans/verify-agent-card.sh
chmod +x verify-agent-card.sh
./verify-agent-card.sh https://flight.example.com flight.booking.v1
첫 인자는 오리진(도메인 루트)이지 A2A endpoint 경로가 아니다.
스크립트가 출력하는 항목을 그대로 적는다. 괄호 안이 이 규격의 요구 번호다.
| # | 점검 | |
|---|---|---|
| V1 | 카드가 인증 없이 200과 유효한 JSON을 준다 | R2·R3·R4 |
| V2 | 모든 skill이 chofam:kind/* 태그를 정확히 하나 갖는다 | R6 |
| V3 | 노출 범위 — 분류 분포를 보여줄 뿐 판정하지 않는다 | 관측 |
| V4 | chofam:kind/action skill이 있으면 chofam:effect/* 표기와 전용 스킴 | E2·E3 |
| V5 | 확장이 신청할 이름을 담는다 | R5 |
| V6 | securitySchemes가 선언돼 있다 | R9 |
| V8 | 모든 skill에 chofam:scope/party가 있다 | R10 |
| V9 | 고용료 확장이 있다면 unitPrice가 양의 정수다 | R11 |
| — | protocolVersion이 1.0.1 | R1 |
| — | 전송 바인딩 | 관측 — 판정하지 않는다(§3.0) |
V7은 여기 없다. Agent Card 규격 §8의 V7은 Task 왕복 성공이고, 카드로 확인되지 않으므로 자가 점검이 다루지 않는다. 번호를 비워 두는 것이 두 문서가 같은 이름으로 다른 것을 가리키는 것보다 낫다.
자가 점검 통과는 등록이 아니다. 신청자의 기계에서 돌고 통과 여부가 ANS에 전달되지 않으므로 등재 시점에 같은 검사를 다시 한다(§3.0). 그리고 심사 ④(주장대로 동작한다)는 Task 왕복으로만 확인되며 자가 점검이 대신하지 못한다.