CHO-FAM ANS

Markdown 내려받기 ANS 규격 v1 용어 구분 확장 어휘 등록 안내

Agent Card 규격 v1.7 — 패밀리 공통

v1.7은 확장 URI를 옮긴다 — 유일한 파괴적 변경이다. https://cho-fam.web.app/ans/v1 → https://cho-fam.com/ans/v1, /hire/v1도 같다.

이유는 배포처다. *"배포처는 바뀔 수 있다"*(주관자). web.app은 Firebase에 묶인 이름이라 호스팅을 옮기면 식별자가 죽거나 억지로 살려 둬야 한다. URI는 위치가 아니라 이름이므로 더욱 그렇다 — 이름이 특정 공급자에 매여 있으면 안 된다.

지금이 가장 싸다. 등재된 이름이 0개이고 배포 전이다. 패밀리 카드 넷만 한 줄씩 고치면 된다 — 외부 등재자가 생기면 그들의 카드를 고치는 일이 된다.

ANS 파서는 새 값만 받는다. 둘 다 받는 경로를 만들지 않는다 — 지울 시점을 정해야 하고 그 시점이 오지 않는다. 등재 0건이라 갈아타는 중에 떨어질 카드가 없다.

v1.6은 §4.1의 한 문장을 고친다 — *"계약을 세울 때 카드에서 읽어 unit_price에 박는다."* 복사가 아니다. 주관자가 단가의 성립 절차를 확정했다 — 비용에서 시작하고 가격 협상을 거쳐 계약으로 확정된다(AP-1 회신). 협상이 있으면 카드의 값과 계약의 값은 다를 수 있다. §4.1을 그에 맞춘다.

§4.2와 게시본도 같은 이유로 틀려 있었다. v1.5가 *"hire/v1이 없으면 고용이 열리지 않는다"*고 적은 것은 계약가가 카드에서 복사된다는 전제 위에 서 있었고, 그 전제가 지워졌으므로 결론도 선다 — 없어도 고용된다. /hire/v1 게시본은 **v1.4의 *"없으면 등재되지 않습니다"*를 아직 띄우고 있었다. §1 예시 카드의 unitPrice: 5도 뺀다 — 예시의 숫자가 자기 값인 줄 알고 게시되면 호가가 아니라 옮겨 적은 값이 된다.**

v1.5는 v1.4의 V9를 고친다 — 무조건 검사로 세운 것이 틀렸다. V9가 모든 카드에 양의 정수 단가를 요구했는데, 지금 패밀리 카드 전부가 떨어진다 — mentor·ethos·terra 어디에도 단가가 없고, 그 값은 그들이 정하는 것이 아니다(주관자 항목 AP-1). mentor 4차 질의 Q11이 구현 중에 짚었다. §4.2를 더하고 V9를 조건부로 바꾼다.

v1.4는 규격을 바꾼다 — 외부 등록 에이전트가 실을 자리가 없던 것 둘을 만든다(업무지시서 §7.2 K-2·K-3). 대상 선언(§2.3)과 고용료 단가(§4.1)다. 패밀리는 이 둘을 내부 합의로 알고 있었으나 외부 에이전트는 카드가 게시하는 전부다.

패밀리 저장소가 할 일은 태그 한 줄과 단가 한 줄이다. 구조가 바뀌지 않는다.

v1.3은 규격을 바꾼다 — v1.2와 다르다. E4(멱등)가 어느 키를 말하는지 명시한다(§2.1). 규격이 침묵한 탓에 서버 발급 Task ID를 쓰는 틀린 선택이 규격 준수처럼 보였다.

다시 할 일이 있는 저장소는 없다 — 1차에 실행성 skill을 노출하는 곳은 terra뿐이고, terra의 이벤트 id는 이미 발행자가 만든다. §7에 카드 생성 권고를 더했다(afo가 실제로 겪은 것).

v1.2는 규격을 바꾸지 않았다 — society 구성 확정(ANS 설계 §13)이 이 규격이 무엇을 전제하고 있었는지를 드러냈으므로 그 전제와 미결(§9)을 적은 것이다.

저장소가 /.well-known/agent-card.json에 게시할 카드의 규격이다. A2A 표준을 그대로 쓰되, 표준에 없어서 따로 정해야 하는 것 여섯을 여기서 정한다.

게시 위치 — 조회 https://cho-fam.com/ans/card-spec · 다운로드 https://cho-fam.com/ans/agent-card-spec.md. 이 파일이 원본이고 사이트의 것은 scripts/build-ans-spec.js가 만드는 산출물이다. 저장소와 외부 등재자는 사이트를 본다 — 이 저장소는 비공개이므로 git 링크로는 읽히지 않는다. 함께 읽을 것: 용어 구분 — 이 규격의 값은 계약의 말이다. 화면에 그대로 내보내지 않는다(V-2). 함께 읽을 것: ANS 규격 — 이름 형식·등록 심사·API·신호는 그쪽이 정본이다. 기준일: 2026-08-17 (v1.7 — 확장 URI 이전) 시점: Phase 0 — 각 저장소의 A-1(Agent Card 작성)이 이 문서를 선행으로 갖는다 근거: A2A 채택 · ANS·A2A 설계 §5 · 1차 답변서 §4.1·§4.3 · 업무지시서 §3.1 대상: mentor · ethos · afo · terra (+ 향후 외부 등재자)

society는 대상이 아니다. 카드는 *상대*가 게시하는 것이고 society는 *자리*다 — 자기 채널 밖에 계약을 팔지 않으므로 ANS 이름도 카드도 갖지 않는다(ANS 설계 §13). society는 이 규격의 검증자다(§8 V7).

규격을 chofam이 소유하는 이유 — ANS 설계 §4.2가 소유권 증명을 "신청한 endpoint의 카드가 신청한 이름을 담고 있으면 인정한다"로 정했다. ANS는 이미 카드를 파싱해야 하므로 규격이 다른 곳에 있으면 검증 로직이 남의 문서를 좇게 된다.

0. 표준을 벗어나지 않는다

카드는 A2A AgentCard(protocolVersion: "1.0.1")이다. 아래 여섯은 전부 표준이 이미 가진 자리에 얹는다 — 표준 파서가 읽을 수 없는 필드를 새로 만들지 않는다.

#패밀리가 정하는 것표준의 어느 자리에절
1skill 분류 — 자문 / 조회 / 실행skills[].tags§2
2ANS 이름 — 소유권 증명의 근거capabilities.extensions[]§3
3trustTier카드에 담지 않는다§4
4인증 선언 — 경로별로 다르다securitySchemes · security§5
5대상 선언(v1.4)skills[].tags§2.3
6고용료 단가(v1.4) — 선택(v1.6)capabilities.extensions[]§4.1

1차 endpoint 규칙은 필드가 아니라 해석 규칙이므로 §6에 따로 둔다.

v1.1 개정 — 규격은 실행성 skill을 금지하지 않는다. v1.0은 "실행성 skill은 카드에 올리지 않는다"를 규격의 규칙으로 적었다. 그것은 과했다. afo에 실행성이 없는 것은 현 단계에 afo I-1이 적용된 결과이지 카드 규격이 정할 일이 아니고, 상위 적용 단계의 계획에는 실행성이 있다. 공용 규격이 다른 저장소의 로드맵을 미리 막는 자리에 서면 안 된다.

다만 실행성 skill을 자문·조회와 같이 취급할 수는 없다. 금지 대신 분류(§2)와 전용 요구(§2.1)로 바꾼다. 무엇을 노출할지는 각 저장소의 불변 조건과 현 단계 범위가 정한다(§2.2).

이 개정은 1차 답변서 §4.1의 "실행성 skill은 카드에 올리지 않는다"는 문구를 대체한다.

1. 최소 카드

{
  "protocolVersion": "1.0.1",
  "name": "Ethos Decision",
  "description": "결정 세션을 진행해 기준과 가정을 명료화한다. 판정을 반환하지 않는다.",
  "url": "https://ethos.internal/a2a",
  "supportedInterfaces": [
    { "url": "https://ethos.internal/a2a", "protocolBinding": "JSONRPC" }
  ],
  "version": "1.0.0",
  "provider": { "organization": "CHOFAM", "url": "https://cho-fam.com" },
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "extensions": [
      {
        "uri": "https://cho-fam.com/ans/v1",
        "description": "ANS 이름 — 이름 권위는 chofam ANS",
        "required": false,
        "params": { "name": "ethos.decision.v1" }
      }
    ]
  },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "securitySchemes": {
    "userBearer": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
  },
  "security": [{ "userBearer": [] }],
  "skills": [
    {
      "id": "decision-session",
      "name": "결정 세션",
      "description": "기준·가정을 되묻고 정리한다. input-required로 질문을 되돌린다.",
      "tags": ["chofam:kind/advisory", "chofam:scope/party"],
      "inputModes": ["text/plain"],
      "outputModes": ["text/plain"]
    }
  ]
}

hire/v1 확장은 이 예시에 없다(v1.6). 선택이고 패밀리는 아직 싣지 않는다 — 호가를 크레딧으로 말하려면 ⓐ 환산이 선행한다(§4.2). 실을 때의 모양은 §4.1에 있다. v1.4·v1.5는 여기에 unitPrice: 5를 넣어 두었는데, 예시의 숫자가 자기 값인 줄 알고 게시되면 호가가 아니라 옮겨 적은 값이 된다.

전송 바인딩은 JSON-RPC 2.0이다(1차 답변서 §4.4) — 단일 호스트·저볼륨이므로 supportedInterfaces[]에 protocolBinding: "JSONRPC" 하나를 둔다. gRPC는 다수 호스트 운영 시 재검토한다.

A2A 버전 — 1.0.1이 맞다(2026-05, Linux Foundation). 설계 §5의 v2.3 주의가 이미 정정했는데 이 규격 v1.0이 §2.3의 작성 시점 값 0.3.0을 옮겨 적었다. mentor·ethos·afo 지시서가 전부 v1.0.1로 적고 있었고 그쪽이 맞다. 0.x의 preferredTransport도 v1.0.1의 supportedInterfaces[]로 바꾼다.

2. skill 분류 — tags에 예약 태그를 쓴다

모든 skill은 아래 셋 중 정확히 하나의 chofam:kind/* 태그를 갖는다. 분류 기준은 부작용의 유무와 소재다.

태그뜻부작용
chofam:kind/advisory자문 — 판단 재료를 돌려준다없음
chofam:kind/query조회 — 저장된 사실을 읽어 돌려준다없음
chofam:kind/action실행 — 상태를 바꾼다(자금 이동, 계약 체결, 쓰기, 이벤트 인입)있음

분류가 강제인 이유는 호출자가 태그만 보고 부작용 유무를 알 수 있어야 하기 때문이다. A2A는 skill의 성격을 표준 필드로 말해 주지 않는다. 태그가 없으면 호출자는 설명문을 읽어 짐작해야 한다.

2.1 실행성 skill의 전용 요구

규격은 실행성 skill을 금지하지 않는다. 다만 자문·조회와 같이 취급하지 않는다 — 실행은 되돌릴 수 없을 수 있고, 재시도가 이중 실행이 되며, 사후에 누가 무엇을 시켰는지 물어야 한다. chofam:kind/action 태그를 단 skill을 카드에 노출하려면 아래 다섯을 만족한다.

#요구이유
E1chofam:kind/action 태그를 붙인다자문·조회와 한 카드에 섞여도 호출자가 구분할 수 있어야 한다
E2되돌릴 수 있는지 표기한다 — chofam:effect/reversible 또는 chofam:effect/irreversible호출 측 정책이 여기서 갈린다. 되돌릴 수 없는 실행은 사람의 확인을 요구하는 것이 보통이다
E3자문·조회와 다른 인증 스킴을 요구한다(§5.3) — 자문·조회 skill이 같은 카드에 있을 때만이다자문을 부르려고 넘긴 대리 토큰이 실행까지 열어서는 안 된다. 실행만 노출하는 카드에는 나눌 대상이 없다
E4호출자가 만든 멱등 키를 받고, 같은 키의 재전송이 이중 실행을 만들지 않는다. A2A에서 그 자리는 message.messageId다 — 서버가 발급하는 Task ID를 멱등 키로 쓰지 않는다A2A는 재시도를 막아 주지 않는다. 그리고 Task ID는 서버가 첫 응답에서 처음 알려 주는 값이라, 첫 호출의 응답을 놓친 재시도에는 그 키가 없다 — 멱등이 가장 필요한 순간에 없다
E5실행 1건마다 감사 증적을 남긴다 — 호출자 신원·시각·입력부작용이 남았는데 누가 시켰는지 모르는 상태를 만들지 않는다
{
  "id": "event-ingest",
  "name": "이벤트 수신",
  "description": "society 전이 이벤트를 받아 SOURCE로 기록한다. 같은 id 재전송은 중복을 만들지 않는다.",
  "tags": ["chofam:kind/action", "chofam:effect/reversible", "chofam:scope/party"],
  "inputModes": ["application/json"],
  "outputModes": ["application/json"]
}

v1.3 정정 — E4가 어느 키인지 말하지 않아 틀린 선택이 규격 준수처럼 보였다. ethos R-4가 구현 중에 짚었다.

키가 서버 발급이면 멱등은 재시도가 이미 성립한 뒤에만 작동한다. 첫 호출이 나갔는데 응답을 못 받은 클라이언트는 Task ID를 모르고, 그 상태에서 다시 보내면 키 없는 두 번째 실행이 된다. 막으려던 사고가 바로 그것이다.

message.messageId는 클라이언트가 만들므로 첫 호출부터 있다. 규격이 요구하는 성질은 "고유함"이 아니라 "재시도할 때 손에 있음"이다.

terra는 이미 만족한다 — 인입 이벤트의 id는 발행자(society)가 만들어 보내는 값이므로 재전송에도 같은 값이 실린다. 다시 할 일이 없다.

규격은 여기까지다. 무엇을 실제로 노출할지는 §2.2가 정한다.

2.2 무엇을 노출할지는 저장소의 불변 조건과 현 단계가 정한다

이것이 규격의 일이 아닌 이유 — 노출 범위는 각 저장소의 로드맵과 불변 조건에 딸린 것이고, 단계가 올라가면 바뀐다. 공용 규격이 그것을 미리 못박으면 다른 저장소의 계획을 규격 개정 없이는 못 밟게 만든다.

저장소1차 노출노출하지 않음근거
mentor자문(지식 질의) · 조회(탐색 API) · 조회(요금제·가입 안내)구독·목표 쓰기A2A에 사용자가 없어 귀속 대상이 정해지지 않는다 — serviceBearer가 그 경계다. D-17 이후에 다시 본다
ethos자문(결정 세션)판정 반환애초에 없다. Policy Gate는 society 소관
afo자문(재무 판단 재료)자금 이동 일체afo I-1(자금 미보관·미이체) — 규격이 아니라 afo의 불변 조건이다
terra조회(리포트 조회) · 실행(이벤트 인입)계산·정정인입은 자기 저장소 기록을 남기므로 실행이다. E4 멱등이 이미 T-M1 완료 기준(같은 id 재전송 시 중복 없음)과 같은 것이다

afo의 실행성 부재는 현 단계의 사실이지 영구 규정이 아니다. 상위 적용 단계의 계획에는 실행성이 있고, 그 단계에서 E1~E5를 만족하고 그때의 afo I-1이 허용하면 카드에 오른다. 규격을 고칠 일이 아니다.

저장소 불변 조건이 규격을 이긴다. 규격이 E1~E5로 길을 열어도 저장소의 불변 조건이 금지하면 노출하지 않는다 — afo I-1, society I-4(법적 효력 있는 행위 미실행)가 그 예다. 규격은 불변 조건을 대신하지 않는다.

2.3 대상 선언 — chofam:scope/* (v1.4, individual은 2026-08-26)

모든 skill은 chofam:kind/* 하나와 함께 chofam:scope/* 태그를 정확히 하나 갖는다. 같은 skills[].tags에 얹으므로 새 필드가 생기지 않는다(§0).

값뜻부담 주체
chofam:scope/party파티가 부르고 파티가 쓴다파티
chofam:scope/individual그 사람 하나의 것이다 — 구독·요금제·개인 목표개인

P-3이 세운 전제가 *"모든 에이전트는 party 대상"*이다. 그것은 패밀리 안에서만 전제다. 외부 등록 에이전트는 그 합의를 공유하지 않고, 카드가 그가 게시하는 전부다. 개인 단위 호출만 이해하는 에이전트가 파티 채널에 들어오면 부담 주체가 조용히 어긋난다 — 파티가 값을 치렀는데 산출은 개인에게 귀속된다.

"tags": ["chofam:kind/advisory", "chofam:scope/party"]

선언하지 않은 카드는 등재 심사에서 떨어진다(§8 V8). 패밀리 시드도 같은 검사를 거친다.

왜 새 기계장치를 만들지 않는가. §2가 이미 예약 태그로 *"호출자가 태그만 보고 알 수 있어야 하는 것"*을 나르고 있다. 대상은 정확히 그 종류다 — 설명문을 읽어 짐작하게 두면 안 되는 사실이다.

값이 지금 하나뿐인데 왜 적는가. 패밀리만 있을 때는 적을 필요가 없었다. 외부가 들어오는 순간 "전부 그렇다"는 말은 검사할 수 없는 말이 된다.

그리고 실제로 둘이 됐다(2026-08-26). mentor가 «자체 가입을 요구하는 에이전트»로 서면서 요금제·가입 안내를 노출하는데, 그것은 파티가 아니라 그 사람의 것이다. party로 달면 이 태그가 막으려던 오귀속을 카드가 스스로 선언하게 된다 — 파티가 값을 치렀는데 산출은 개인에게 간다.

individual은 «파티 채널에서 부르지 못한다»는 뜻이 아니다. 부담과 귀속이 개인이라는 뜻이다. 방에 사람이 여럿이면 부른 사람의 것이고, 그 사람이 누구인지 정해지지 않는 호출이면 그 skill은 애초에 성립하지 않는다(D-17).

3. ANS 이름 — capabilities.extensions[]

"capabilities": {
  "extensions": [
    {
      "uri": "https://cho-fam.com/ans/v1",
      "description": "ANS 이름 — 이름 권위는 chofam ANS",
      "required": false,
      "params": { "name": "ethos.decision.v1" }
    }
  ]
}
규칙
urihttps://cho-fam.com/ans/v1 고정. ANS가 이 값으로 확장을 찾는다. 이 주소는 실제로 규범적 어휘를 서빙한다(public/ans/v1.html) — 카드를 읽은 사람이 확장의 의미를 찾아갈 곳이다
params.nameANS 이름 정확히 하나. ANS 설계 §4.1 형식을 만족해야 한다
requiredfalse. ANS를 모르는 표준 클라이언트도 이 카드를 쓸 수 있어야 한다
이름이 여럿인 경우확장을 여러 개 둔다. 한 params.name에 배열을 넣지 않는다 — 이름은 에이전트가 아니라 계약을 가리키므로(ANS 설계 §4.1) 하나씩 선다

소유권 증명이 이 필드에 걸린다. ANS apply 심사는 신청한 endpoint의 카드를 읽어 params.name이 신청한 이름과 같은지 본다. 다르면 등록되지 않는다.

이름을 지운 채로 카드를 갱신하면 card_mismatch 신호가 붙는다(ANS 설계 §4.3). 이름을 그만 쓰려면 카드에서 빼는 것이 아니라 tombstone을 요청한다 — 조용히 빼면 호출자가 이유를 모른다.

3.1 채널 표면 — capabilities.extensions[]

확정 — 2026-08-26(주관자). 확장 URI는 https://cho-fam.com/channel-surface/v1이다. society는 읽는 쪽을 이미 구현했고(society#40), 선언이 없는 에이전트는 society 안의 표로 떨어진다 — 어느 쪽이든 방은 열린다. 선언한 에이전트가 늘수록 그 표는 줄고, 비면 지운다.

society의 C3 방은 그 에이전트 하나와 사람 하나가 있는 자리다. 그 방에 무엇이 보이는지를 에이전트가 정한다.

사다리 — 세 칸

① 직접 참조   에이전트 화면의 부분을 프레임으로   원본의 양식·스타일이 그대로 온다
② 그림        프레임을 못 들일 때. 출력에만       입력을 받지 못한다
③ 자체 카드   글로 낸다                          society가 그린다

내려가는 것은 받는 쪽이 정한다. 프레임이 거부되는지, 그림이 오는지를 보고 다음 칸으로 간다 — 에이전트는 «할 수 있는 것»을 선언할 뿐이다.

③은 «못 할 때»만의 자리가 아니다. society가 에이전트의 API를 직접 부르는 경우도 그 칸이다 — 그때는 받은 값을 society가 자기 조각으로 그린다(text·need). 그 편이 맞는 것도 있다: 한 줄짜리 입력에 프레임을 띄우는 것은 과하다.

{
  "uri": "https://cho-fam.com/channel-surface/v1",
  "description": "방에 낼 한 줄을 읽을 경로",
  "required": false,
  "params": { "path": "/v1/channel-surface" }
}

부르는 쪽이 붙이는 질의 둘 — party · ask

society는 카드가 밝힌 경로에 질의를 얹는다. 경로 자체는 바꾸지 않으므로 이 인자를 모르는 상대는 종전대로 전부 낸다.

언제 붙는가
party이 방이 어느 파티의 것인가. 상대는 그 사람이 든 파티를 여럿 알 수 있고, 그중 이 방의 것만 내야 한다방이 파티의 것일 때 늘
ask사람이 방금 한 말. 카드 선정의 입력이지 자문이 아니다 — 그 방에서 이미 볼 수 있는 것 중에서 고르는 일이라 에이전트의 과금이 없다아래 params.ask를 선언했을 때만

ask는 선언해야 온다.

{
  "uri": "https://cho-fam.com/channel-surface/v1",
  "params": { "path": "/v1/channel-surface", "ask": true }
}
값true 하나만 읽는다. 다른 값은 선언이 없는 것으로 본다 — 모르는 값을 «읽는다»로 떨어뜨리면 아래의 거짓말이 그대로 선다
없으면society가 ?ask=를 붙이지 않는다. 종전과 같이 돈다
뜻*"보내면 그 말로 카드를 고르겠다"*. 답할 의무가 아니다 — 골라서 빌 수 있고, 그때는 비었다고 말하는 편이 낫다(아래)

왜 선언으로 두는가 — society가 하지 않은 일을 했다고 말했다.

이 인자는 규격보다 먼저 코드로 들어왔고(2026-08-28), 읽는 표면은 하나뿐인데 society는 모든 표면에 붙여 보냈다. 그리고 방에 *"「…」를 afo에 전했습니다. 카드 1장이 왔습니다"*라고 적었다 — 카드는 늘 같은 것이었다. 사람은 「말이 반영된다」고 배우는데 반영된 적이 없다.

선언이 그것을 끊는다. society는 선언한 방에서만 보내고 선언한 방에서만 그 문구를 적는다. 안 적은 에이전트는 고칠 것이 없다 — 조용히 종전대로 돌고, 구현하는 날 한 줄 적으면 그날 켜진다.

§3.1이 이미 그 모양이다 — *"선언이 없는 에이전트는 society 안의 표로 떨어진다. 어느 쪽이든 방은 열린다."*

고른 것이 비면 그 사실을 말한다. 말로 고르면 목록이 좁아지고 좁힌 것이 비어 있을 수 있는데, 그때 빈 목록을 내면 사람은 왜 비었는지 모른다 — 「고장인가」와 「없는 건가」가 같아 보인다. 실측으로 그렇게 났다(「라이선스 보여줘」에 방이 통째로 비었다). 한 장을 내서 비었다고 말한다.

그 경로가 돌려줄 것 — 모양이 하나뿐이다.

{ "title": "오늘의 질문", "text": "지금 피하고 있는 대가는 무엇인가요?" }

무언가가 필요하면 need로 말한다

에이전트가 «이것을 받아야 답할 수 있다»고 판단할 때가 있다 — afo가 스냅샷을 받은 적이 없으면 자산 현황을 낼 수 없다. 그때 입력칸이 방에 나타나야 한다.

{ "title": "자산 현황", "text": "아직 올린 가계부가 없습니다",
  "need": { "kind": "text", "name": "가계부 스냅샷", "hint": "afo 화면에서 내려받은 JSON" } }
kindtext 하나뿐이다. 파일 선택기를 두지 않는다 — 지금 이것을 쓰는 곳(afo)이 자기 화면에서도 <textarea>를 쓴다. 진짜 파일이 필요한 에이전트가 나오면 그때 늘린다
name그 입력칸의 제목. 사람이 읽는 말이고 에이전트가 정한다
hint무엇을 넣는지. 없어도 된다
받는 곳같은 경로에 PUT. 새 칸을 두지 않는다 — 읽는 곳과 쓰는 곳이 어긋날 자리가 없다
보관에이전트가 정하지 않는다. privacy가 personal이면 초안을 보관하지 않는다(아래)

«어떻게 보일지»를 말하지 않는다. need는 *무엇이 필요한가*이고, 그것을 입력칸으로 만드는 것은 society, 그리는 것은 slime이다 — 이 절이 {title, text}로 좁혀 둔 이유가 그대로다.

결과창을 따로 두지 않는다. 쓰고 나서 다시 읽으면 text가 새 값이다.

화면 조각을 그대로 보일 수도 있다 — embed

많은 에이전트가 웹사이트다. 표·차트·다단계 양식을 need 하나로 옮기는 것은 확장되지 않는다. 그럴 때는 에이전트가 그린 것을 그대로 방에 들인다.

{ "uri": "https://cho-fam.com/channel-surface/v1",
  "params": { "path": "/v1/channel-surface", "auth": "delegated",
              "embed": [
                { "path": "/?only=snapshot", "height": 520, "name": "가계부 스냅샷" },
                { "path": "/?only=debts", "height": 300, "name": "부채 구조" }
              ] } }
embed[].path들일 조각의 경로. path와 같은 규칙 — 절대 URL은 거부된다
embed[].height픽셀. 교차 오리진 프레임은 자기 높이를 부모에게 알릴 수 없다
embed[].name그 조각의 이름. 여럿이면 필요하다 — 없으면 표면의 제목을 쓰는데, 그러면 둘이 같은 이름으로 나란히 선다
하나여도 된다배열이 아닌 객체 하나로 적어도 받는다 — 받는 쪽이 너그러운 편이 낫다

여럿은 조각 여럿이 된다. 하나로 합치지 않는다 — 조각이 곧 읽는 사람이 지나가는 단위이고, 합치면 «둘 중 무엇을 보는 중인지»가 흐려진다.

부분의 모양을 규격이 정하지 않는다. afo는 정적 HTML이라 ?only=<절>이 맞고, mentor는 SPA라 ?tab=<탭>이 맞다 — 에이전트가 자기 결로 정한다.

프레임을 못 들이면 그림으로 내려간다 — embed[].image

{ "path": "/board", "height": 300, "name": "지표판", "image": "/board.png" }

입력을 받지 못하므로 출력에만 쓴다. 그림은 누를 수도 쓸 수도 없다 — 「입력이 없을 때」의 자리다.

image그림의 경로. path와 같은 규칙 — 절대 URL은 거부된다
자격auth가 delegated면 society가 대리 토큰으로 받아 계획에 data: URI로 박는다
왜 주소를 넘기지 않나<img src>로 두면 브라우저가 다시 부르는데 그 요청에는 자격이 실리지 않는다. 사용자별 그림이면 그때 열리지 않고, 열린다면 그것대로 자격 없이 열리는 그림이라는 뜻이다
크기상한을 넘으면 조각을 내지 않는다 — 계획이 커지면 방 전체가 늦게 뜬다
alt조각 이름이 없으면 표면 제목을 쓴다. 빈 alt는 화면 읽기에 아무것도 남기지 않는다

프레임이 거부되면 들이지 않는다

X-Frame-Options나 frame-ancestors를 세운 페이지는 브라우저가 막고, 화면에는 빈 칸만 남는다 — 무엇이 잘못됐는지 어디에도 나타나지 않는다.

society가 미리 본다. 카드를 읽을 때 그 주소의 헤더를 함께 보고, 막혀 있으면 프레임을 내지 않는다. 확인하지 못해도 «막힘»으로 본다 — 확인하지 못한 것을 «열려 있다»로 읽으면 빈 칸이 뜬다.

계열 밖 에이전트는 대개 막아 두고 그것이 정상적인 보안 설정이다. 뚫을 수도 없고 뚫어서도 안 된다 — 알아보고 다른 칸으로 내려간다.

이 안은 계획이 통제하지 못한다

밀도 적응·가림·음성 대체·확인 관문이 프레임 안에서 멈춘다. density·targetSize는 무의미하다.

그래서 들일지는 계획이 정한다. 웹 표면에서만 들이고, 워치·글래스에서는 이 선언을 무시하고 text를 글로 보인다 — 안을 통제하지 못하니 들일지 말지를 통제한다. 가려야 하는 조각(PrivacyVeil)도 들이지 않는다: 프레임 안을 가릴 방법이 없다.

글을 함께 낸다. 프레임이 막히거나 늦으면 빈칸만 남는데, 그때 무엇이 있어야 했는지를 아무도 말하지 않는다. text가 그 자리다.

need와 함께 쓴다

둘은 서로 다른 것을 위한 것이다. 한 줄짜리 입력에 프레임을 띄우는 것은 과하고, 표가 여럿인 양식을 need 하나로 옮기는 것은 되지 않는다. 카드가 둘 다 선언할 수 있고 에이전트가 사정에 맞게 고른다.

초안을 보관할지는 privacy가 정한다

slime의 작성 영역은 persisted면 서버가 24시간까지 보관한다. 그러면 personal한 입력이 society에 남는다 — 대리 토큰조차 저장하지 않기로 한 자리에서 초안으로는 저장하는 모순이 된다.

privacy초안
personal · restricted보관하지 않는다. 끊기면 잃는다
public보관한다

에이전트가 «내 자료를 저장해 두라»고 선언할 자리를 두지 않는다. 보관은 그 자료의 성격에서 따라오지 받는 쪽의 편의로 정할 것이 아니다.

규칙
params.pathURL이 아니라 경로다. 카드의 url은 공개 주소인데 인증서가 내부 CA인 동안 society가 신뢰하지 않는다 — 실측으로 UNABLE_TO_GET_ISSUER_CERT_LOCALLY가 났다. 카드를 읽은 그 오리진에 붙여 풀므로 안에서도 밖에서도 같은 카드가 맞는다
/로 시작절대 URL은 거부된다. 그것을 받으면 카드가 society를 다른 호스트로 보내는 자리가 된다
응답{ title, text } 둘 다 문자열. 다른 모양이면 조각을 내지 않는다 — society가 남의 JSON을 해석하지 않는다
params.auth없으면 공개(기본). "delegated"면 그 사람의 토큰이 필요하다 — 아래
requiredfalse. 선언하지 않아도 등재되고 방은 열린다

왜 society의 표가 아니라 카드인가. 표로 두면 에이전트가 늘 때마다 society를 고치고 재배포해야 하고, society가 남의 응답 모양을 알고 있게 된다. 실제로 그 위험이 났다 — ethos 명세 문서는 question.text로 적혀 있는데 실제 응답은 question.question이다. 문서를 믿고 적었으면 화면이 조용히 비었을 것이다. 한 줄의 문구를 정하는 쪽이 그것을 아는 쪽이어야 한다.

auth: "delegated" — 그 사람의 것을 그 사람에게만

afo 자산 현황처럼 사람마다 갈리는 한 줄이 있다. 그것을 공개로 낼 수는 없고, society가 원자료를 읽어 합산할 수도 없다 — 순자산이 둘이고(netWorth · netWorthAtCost) 어느 것이 «자산 현황»인지는 afo의 판단이다. 한 줄의 문구를 정하는 쪽이 그것을 아는 쪽이라는 이 절의 원칙이 금액에서도 그대로다.

"params": { "path": "/v1/channel-surface", "auth": "delegated" }
토큰aud가 그 에이전트의 ANS 이름인 것. ANS 이름과 A2A 대상이 같은 값이라 변환표가 없다
없으면부르지 않는다. 조각이 빠지고 방은 열린다 — 다른 실패와 같게 다룬다
저장하지 않는다. society는 그 요청의 인자로만 들고, 응답과 함께 사라진다
캐시하지 않는다. 토큰이 짧고 금액은 낡는다 — 낡은 금액을 보여 주는 것보다 안 보여 주는 것이 낫다

선언했다고 토큰이 생기지 않는다. 촉발은 사람이 그 방을 연 것이고, 받는 토큰은 aud가 그 에이전트라 다른 데서 쓸 수 없다. 즉 «선언했으니 준다»가 아니라 «사람이 열었으니 그 상대에게만»이다.

방향은 카드가 정하지 못한다. 경로는 카드를 읽은 오리진에 붙고 절대 URL은 거부되므로(위 두 규칙), 대리 토큰은 그 토큰이 향하는 바로 그 에이전트에게만 간다. 그 두 규칙은 내부 CA 때문에 넣은 것인데 여기서 값을 한다.

A2A 축은 그대로 막힌다. 이것은 C3 방의 읽기이고, society가 파티를 대신해 부르는 자리(*.v1 skill)와 다르다 — 거기서는 상대가 참여자를 알아서는 안 된다(§5).

risk·privacy는 에이전트가 정하지 않는다

society가 low/public으로 고정한다. 자기 신고를 받으면 개인 정보를 public으로 선언해 화면의 취급(가림·공유 가능 여부)을 낮출 수 있다.

있어도 없어도 방은 열린다

선언이 없거나, 그 경로가 죽어 있거나, 시한(2초)을 넘기면 조각만 빠지고 방은 열린다 — INFRA-13이 *"core가 없으면 계획이 없다. 그래도 채널은 열린다"*로 정한 그 자리다. 404도 «오늘 것이 아직 없다»이지 고장이 아니다.

4. trustTier는 카드에 담지 않는다

A2A에 신뢰 등급 개념이 없고, ANS도 "믿을 만한가"를 답하지 않는다(ANS 설계 §1).

소유어디에
agent.id · version에이전트 자신카드(name·version) — 호출자가 카드에서 읽는다
trustTiersocietysociety Directory. 카드에도 ANS에도 없다

자기 신뢰도를 자기가 선언하는 필드를 만들지 않는다. society가 자기 채널에 무엇을 들일지 정하는 값이므로 Directory에 남는다(ANS 설계 §11.1).

4.1 고용료 단가 — 카드는 호가다 (v1.4 · v1.6 정정)

society registry.ts가 계약의 unit_price를 필수 항목으로 세웠다 — *"값이 없는 채로 고용이 열리면 얼마를 받기로 했는지가 사후에 정해진다."* 패밀리는 그 값이 어디서 오는지 내부 합의로 알지만 외부 에이전트는 실을 자리가 없다.

카드에 싣는다. §3과 같은 자리를 쓴다 — ANS 이름이 이미 capabilities.extensions[]로 나가고 있으므로 새 기계장치가 없다.

{
  "uri": "https://cho-fam.com/hire/v1",
  "description": "고용료 단가 — 호가이며 정본은 society 계약이다",
  "required": false,
  "params": { "unitPrice": 5 }
}

단위는 크레딧이고 양의 정수다. 실화폐를 싣지 않는다 — 에이전트 고용료는 크레딧으로 지불한다는 것이 P-4다.

단가는 세 단계로 선다 (v1.6)

주관자 확정: *"단가는 비용에서 시작하고 가격 협상을 거쳐 계약으로 확정된다."*

단계어디누가성질
① 비용에이전트 내부에이전트 자신출발점. 크레딧으로 말하려면 환산이 선행한다(ⓐ)
② 호가카드 hire/v1에이전트 자신언제든 바꾼다. 한쪽의 말이지 합의가 아니다
③ 계약가society contract_ref.unit_price파티와 에이전트의 합의. society는 기록한다바뀌지 않는다 — 계약이 선 뒤에는

②와 ③은 다를 수 있다. 협상이 그 사이에 있기 때문이다. 카드에서 읽어 계약에 박는 것이 아니다 — 카드는 협상의 출발점이고, 합의가 없으면 계약이 서지 않는다.

②가 없어도 ③은 선다. 출발점이 게시돼 있지 않을 뿐이고, 협상은 그래도 일어난다 — 그것이 §4.2다.

협상 없이 호가 그대로 받는 것도 협상의 결과다. 대부분은 그렇게 될 것이고, 그때 ②와 ③이 같아진다. 같아지는 것과 복사되는 것은 다르다 — 복사면 카드가 바뀔 때 계약도 흔들릴 근거가 생기고, 합의면 그럴 근거가 없다.

계약이 선 뒤 카드가 바뀌어도 그 계약은 변하지 않는다. 카드는 상대가 언제든 고쳐 게시할 수 있는 문서이고, 계약은 두 쪽이 합의한 시점의 기록이다.

R7(산출과 결정의 분리)의 다섯 번째 사례다 — 카드는 산출(에이전트가 자기 비용에서 호가를 낸다), 계약은 결정(파티와 에이전트가 합의한다). 결정하는 쪽은 society가 아니다 — society는 합의를 기록하고 집행한다. v1.4가 *"계약은 결정(society가 확정한다)"*으로 적은 것을 여기서 고친다.

D-18은 이 절이 답하지 않는다. 누가 파티에 에이전트를 들일 수 있는가는 society 소관이고, 카드는 *무엇을 제시하는가*만 정한다.

4.2 적용 범위 — 호가가 없어도 고용된다 (v1.6)

hire/v1 확장은 선택이다. 호가를 미리 게시하는 에이전트가 싣는다. 패밀리는 ⓐ 환산이 서기 전까지 싣지 않는다(업무지시서 §7.9).

확장이 있다호가가 게시돼 있다. 협상이 그 값에서 출발한다. V9가 그 카드에 걸린다
확장이 없다등재된다. 고용도 된다. 출발점이 게시돼 있지 않을 뿐, 협상은 일어나고 계약가는 선다

**v1.5는 여기에 *"없으면 고용이 열리지 않는다"*고 적었고 그것이 틀렸다.** 근거로 든 것은 society registry.ts의 409(*"계약에 단가가 없다"*)였는데, 그 409는 계약에 값이 없는 것을 막는 것이지 카드에 값이 없는 것을 막는 것이 아니다. 사이의 *"계약의 값은 카드에서 온다"*가 v1.4가 세운 전제였고, AP-1 회신이 §4.1에서 그 전제를 지웠다 — 계약가는 복사가 아니라 협상의 결과다. 호가는 출발점이지 출처가 아니므로, 없으면 출발점이 없을 뿐이다.

주관자 회신(AP-1) — *"단가는 비용에서 시작하고 가격 협상을 거쳐 계약으로 확정된다."* 따라서 주관자가 패밀리 단가를 정해 카드에 박는 일은 일어나지 않는다. AP-1은 값이 채워져서가 아니라 정할 주체가 남지 않아서 소멸한다(업무지시서 §7.9).

K-2가 태그를 만든 이유(*"전부 그렇다는 말은 검사할 수 없는 말이 된다"*)와 어긋나지 않는다. 대상 선언은 카드가 아니면 확인할 자리가 없고, 계약가는 카드가 아닌 자리에서 정해진다. 둘은 성질이 다르다 — 하나는 카드에만 있는 사실이고, 하나는 카드에 없어도 서는 값이다.

V9를 무조건으로 세운 것은 총괄의 오류였고, 고친 v1.5도 절반만 고쳤다. 검사는 조건부로 내렸으나 없는 카드에 무슨 일이 일어나는가를 틀리게 적었다 — *"고용되지 않는다"*. 검사기를 고치면서 그 검사가 무엇을 뜻하는지 적은 문장을 따라 고치지 않은 것이고, §7.7 ③이 진단한 것과 같은 종류다.

5. 인증 선언 — 경로별로 다르다

1차 답변서 §4.3이 경로별로 확정했다. 카드의 securitySchemes는 그 경로가 실제로 받는 것을 그대로 적는다.

호출 경로신원카드에 적을 것
society → mentor서비스 신원 (공유 비밀 Bearer)serviceBearer
society → terra서비스 신원 (공유 비밀 Bearer)serviceBearer
society → ethos사용자 대리 (Dex 발급 토큰 전달)userBearer
society → afo사용자 대리userBearer

이 표는 1:1 지목 호출을 전제한다. society 채널에 에이전트가 참여자로 들어오면 대리 대상이 정해지지 않는다 — 방에 사람이 여럿이기 때문이다(ANS 설계 §13.6, D-17(ANS)). 카드가 쓸 스킴은 바뀌지 않는다 — 바뀌는 것은 userBearer에 실릴 sub를 society가 무엇으로 고르는가이고, 그것은 society의 결정이다.

"securitySchemes": {
  "serviceBearer": {
    "type": "http", "scheme": "bearer",
    "description": "배포 설정의 공유 비밀. 1차는 토큰 교환 없음"
  },
  "userBearer": {
    "type": "http", "scheme": "bearer", "bearerFormat": "JWT",
    "description": "Dex 발급 사용자 토큰. iss·sub·aud·exp만"
  }
},
"security": [{ "serviceBearer": [] }]

5.1 대리 경로의 클레임 범위

iss·sub·aud·exp만이다. 프로필·이메일을 넘기지 않는다.

5.2 실행 경로는 자문·조회와 스킴을 나눈다 (E3)

chofam:kind/action skill을 노출하는 카드는 그 경로 전용 스킴을 따로 선언한다 — 그 카드에 자문·조회 skill도 있을 때다.

나눌 것이 있을 때만 나눈다. v1.5 이전 검사기는 실행성 skill이 있으면 무조건 2종을 요구했는데, 실행만 노출하는 카드에는 자문·조회 토큰이 아예 없어 위험이 없는 자리에서 걸렸다. terra가 INFRA-11 발급에서 여기 떨어졌고, terra 카드는 이미 *"나눌 조회 경로가 아직 없다"*고 적어 두고 있었다 — V9를 무조건에서 조건부로 내린 것과 같은 형태다.

실행이 아닌 skill이 하나라도 생기는 순간 다시 요구한다.

"securitySchemes": {
  "userBearer": {
    "type": "http", "scheme": "bearer", "bearerFormat": "JWT",
    "description": "자문·조회용 사용자 대리 토큰"
  },
  "actionBearer": {
    "type": "http", "scheme": "bearer", "bearerFormat": "JWT",
    "description": "실행용. 자문 토큰으로는 열리지 않는다"
  }
}

한 스킴으로 묶지 않는 이유 — 사용자가 자문을 받으려고 넘긴 토큰이 그대로 실행 권한이 되면, 호출자는 자문을 청했는데 실행이 일어날 수 있는 상태가 된다. 스킴이 나뉘어 있으면 실행에는 별도 발급이 필요하다는 사실이 카드에 드러난다.

스킴을 나누는 것으로 충분하지 않다면(사람의 명시적 확인이 필요한 경우 등) 그것은 skill의 대화 흐름으로 처리한다 — A2A input-required로 확인을 되돌리고 응답을 받아 진행한다. 카드가 표현할 수 있는 것은 여기까지다.

5.3 mentor 경로는 비식별이 따로다

mentor행 요청에는 참여자 식별자가 어떤 형태로도 들어가지 않는다. 이것은 카드가 강제하지 못한다 — A2A는 페이로드 내용에 관여하지 않는 프로토콜이므로 비식별 변환은 society Agent Gateway의 책임이다. 카드는 serviceBearer를 선언해 사용자 토큰을 받지 않는다는 사실까지만 말한다.

에이전트가 방의 참여자가 되면 이 경계가 Gateway 밖으로 나간다. Gateway는 봉투를 다루지 본문을 다루지 않는데, 방의 발화에 든 식별 정보는 필드가 아니라 문장 안에 있다(ANS 설계 §13.5, D-16(ANS)). 카드로도 Gateway로도 막히지 않으므로 가시 범위 자체를 정해야 한다.

6. 1차 endpoint — 배포 설정이 카드보다 우선한다

1차는 패밀리 에이전트가 ANS를 런타임 경로로 쓰지 않는다(ANS 설계 §11.4). ANS는 GCP에 있고 1차는 자체 서버라, 매 자문마다 resolve를 부르면 자체 서버가 GCP 왕복에 묶인다.

→ society는 배포 설정의 고정 목록으로 endpoint를 안다. 그 결과 카드의 url과 배포 설정이 어긋날 수 있다.

상황무엇을 따르는가
1차 (자체 서버)배포 설정의 고정 목록. 카드 url은 참고값
ANS 경로 전환 후 (D-15(ANS))resolve가 답한 endpoint

카드의 url은 그래도 정확히 유지한다. 부정확하면 ANS 프로브가 endpoint_unreachable을 붙이고, 외부 호출자는 카드만 보고 온다. "어차피 고정 목록을 쓰니까"는 카드를 방치할 이유가 되지 않는다.

7. 게시 요구사항

#요구
G1경로는 도메인 루트의 /.well-known/agent-card.json — well-known URI는 오리진 기준으로 정의된다(RFC 8615). A2A endpoint 경로 아래가 아니다 — url이 https://ethos.internal/a2a여도 카드는 https://ethos.internal/.well-known/agent-card.json이다. ANS 프로브가 이 자리만 본다
G2인증 없이 읽힌다. 카드 자체는 공개다
G3Content-Type: application/json
G4ANS 프로브가 주기적으로 읽으므로 캐시 헤더를 과도하게 길게 두지 않는다 — 갱신이 신호에 반영되지 않는다
G5카드 변경 시 version을 올린다

권고 — 카드를 서버가 실제로 dispatch하는 목록에서 생성한다. 요구는 아니지만, 두 곳에 따로 적으면 언젠가 어긋나고 어긋나면 이름이 서지 않는다.

심사 ④는 카드에 적힌 skill이 실제로 그렇게 응답하는지를 Task 왕복으로 본다. 손으로 옮겨 적은 카드는 구현이 빠지거나 바뀌어도 조용히 남아 있다가 심사에서 떨어진다 — afo가 A-1에서 실제로 겪었다. 템플릿에는 skill이 둘인데 하나는 법률 검토 대기라 구현이 없었고, 템플릿대로 올렸다면 떨어졌다.

chofam도 같은 방식을 쓴다 — 이 규격의 공개본은 원본에서 생성되고 scripts/build-ans-spec.js --check가 어긋남을 잡는다.

8. 완료 기준

카드 게시는 완료가 아니다(총괄 R1). A-1의 완료는 Task 왕복 성공이다.

V1·V2·V4·V5·V6·V8·V9는 등재 시점에 기계로 다시 확인된다(ANS 규격 §3.0). 자가 점검은 편의이지 증명이 아니다 — 신청자의 기계에서 돌고 통과 여부가 ANS에 전달되지 않는다. 패밀리 시드도 같은 검사를 거친다.

각 저장소가 아래를 통과하면 A-1이 끝난다.

#검증
V1curl <endpoint>/.well-known/agent-card.json이 인증 없이 200과 유효한 JSON을 준다
V2모든 skill이 chofam:kind/* 태그를 정확히 하나 갖는다
V3노출한 skill이 §2.2의 1차 범위 안이다
V4chofam:kind/action skill이 있다면 E1~E5를 만족한다 — chofam:effect/* 표기, 전용 인증 스킴(§5.2), 멱등 키 재전송 시 이중 실행 없음, 감사 증적
V5capabilities.extensions에 https://cho-fam.com/ans/v1이 있고 params.name이 아래 배정 이름과 같다
V6securitySchemes가 §5 표의 해당 행과 일치한다. 선언 자체는 ANS 규격 R9이며 발급 시점에 기계로 확인된다
V8모든 skill이 chofam:scope/*를 정확히 하나 갖는다 — party 또는 individual(§2.3)
V9hire/v1 확장이 있다면 unitPrice가 양의 정수다(§4.1). 없어도 등재되고 고용된다 — 단가는 계약시 정한다(§4.2)
V7Task 왕복 1건 성공 — society가 message/send를 보내고 completed까지 받는다

V4는 terra에 실제로 걸린다 — 이벤트 인입이 실행이므로 멱등 검증이 T-M1 완료 기준("같은 id 재전송 시 중복 저장 없음")과 같은 것을 본다. 1차에 action을 노출하는 저장소는 terra뿐이다.

ANS 이름 배정(업무지시서 §3.2) — mentor.knowledge.v1 · ethos.decision.v1 · afo.finance.v1 · terra.observer.v1 · guild.watch.v1 · chopilot.observe.v1. 등재 주체는 chofam이다. 각 저장소는 카드를 게시하고 endpoint를 회신하면 된다.

9. 미결

#항목시점
D-13(ANS)심사 → 이름 발급 인계 방식. 1차는 관리자 수동 등재Phase 1 설계 시
D-14(ANS)ANS 축약 카드 ↔ A2A 정식 카드 필드 정합Phase 1 규격 확정 시
D-15(ANS)고정 목록 → ANS 경로 전환(§6). 패밀리는 고정 유지, 등록·공인 에이전트는 resolve조건 발생
D-16(ANS)방에 참여한 에이전트의 발화 가시 범위(§5.3) — mentor 무프로파일링이 여기서 결정된다society M1 전
D-17(ANS)다자 방에서 userBearer의 sub를 무엇으로 고르는가(§5)society M1 전

셋 다 카드 규격이 답하지 않는다. 카드는 스킴과 skill을 선언할 뿐이고, 누가 방에 있고 무엇을 보는가는 society의 결정이다. 여기 적는 것은 규격이 그것을 전제하고 있다는 사실을 남기기 위해서다.