# 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`](https://cho-fam.com/hire/v1) 게시본은 **v1.4의 *"없으면 등재되지 않습니다"*를 아직 띄우고 있었다.** §1 예시 카드의 `unitPrice: 5`도 뺀다 — **예시의 숫자가 자기 값인 줄 알고 게시되면 호가가 아니라 옮겨 적은 값이 된다.**
>
> **v1.5는 v1.4의 `V9`를 고친다** — **무조건 검사로 세운 것이 틀렸다.** `V9`가 모든 카드에 양의 정수 단가를 요구했는데, **지금 패밀리 카드 전부가 떨어진다** — mentor·ethos·terra 어디에도 단가가 없고, **그 값은 그들이 정하는 것이 아니다**(주관자 항목 `AP-1`). [mentor 4차 질의 `Q11`](https://github.com/hs9147/mentor)이 구현 중에 짚었다. **§4.2를 더하고 `V9`를 조건부로 바꾼다.**
>
> **v1.4는 규격을 바꾼다** — **외부 등록 에이전트가 실을 자리가 없던 것 둘을 만든다**([업무지시서 §7.2](work-orders.md) `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](ans-a2a-design.md))이 **이 규격이 무엇을 전제하고 있었는지**를 드러냈으므로 그 전제와 미결(§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 링크로는 읽히지 않는다.
> **함께 읽을 것**: [용어 구분](vocabulary.md) — 이 규격의 값은 **계약의 말**이다. 화면에 그대로 내보내지 않는다(`V-2`).
> **함께 읽을 것**: [ANS 규격](ans-spec.md) — 이름 형식·등록 심사·API·신호는 그쪽이 정본이다.
> **기준일**: 2026-08-17 (v1.7 — 확장 URI 이전)
> **시점**: Phase 0 — **각 저장소의 A-1(Agent Card 작성)이 이 문서를 선행으로 갖는다**
> **근거**: [A2A 채택](implementation-plan-build.md) · [ANS·A2A 설계 §5](ans-a2a-design.md) · [1차 답변서 §4.1·§4.3](work-orders-answer.md) · [업무지시서 §3.1](work-orders.md)
> **대상**: mentor · ethos · afo · terra (+ 향후 외부 등재자)
>
> **society는 대상이 아니다.** 카드는 *상대*가 게시하는 것이고 society는 *자리*다 — 자기 채널 밖에 계약을 팔지 않으므로 ANS 이름도 카드도 갖지 않는다([ANS 설계 §13](ans-a2a-design.md)). society는 이 규격의 **검증자**다(§8 V7).

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

---

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

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

| # | 패밀리가 정하는 것 | 표준의 어느 자리에 | 절 |
|---|---|---|---|
| 1 | **skill 분류** — 자문 / 조회 / 실행 | `skills[].tags` | §2 |
| 2 | **ANS 이름** — 소유권 증명의 근거 | `capabilities.extensions[]` | §3 |
| 3 | **`trustTier`** | **카드에 담지 않는다** | §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](work-orders-answer.md)의 "실행성 skill은 카드에 올리지 않는다"는 문구를 대체한다.

---

## 1. 최소 카드

```json
{
  "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](work-orders-answer.md)) — 단일 호스트·저볼륨이므로 `supportedInterfaces[]`에 `protocolBinding: "JSONRPC"` 하나를 둔다. gRPC는 다수 호스트 운영 시 재검토한다.

> **A2A 버전 — `1.0.1`이 맞다**(2026-05, Linux Foundation). [설계 §5의 v2.3 주의](ans-a2a-design.md)가 이미 정정했는데 이 규격 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을 카드에 노출하려면 아래 다섯을 만족한다.**

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

```json
{
  "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](https://github.com/hs9147/ethos)가 구현 중에 짚었다.
>
> **키가 서버 발급이면 멱등은 재시도가 이미 성립한 뒤에만 작동한다.** 첫 호출이 나갔는데 응답을 못 받은 클라이언트는 Task ID를 모르고, 그 상태에서 다시 보내면 **키 없는 두 번째 실행**이 된다. 막으려던 사고가 바로 그것이다.
>
> **`message.messageId`는 클라이언트가 만들므로 첫 호출부터 있다.** 규격이 요구하는 성질은 "고유함"이 아니라 **"재시도할 때 손에 있음"**이다.
>
> **terra는 이미 만족한다** — 인입 이벤트의 `id`는 발행자(society)가 만들어 보내는 값이므로 재전송에도 같은 값이 실린다. **다시 할 일이 없다.**

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

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

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

| 저장소 | **1차 노출** | 노출하지 않음 | 근거 |
|---|---|---|---|
| **mentor** | 자문(지식 질의) · 조회(탐색 API) | — | — |
| **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/party` (v1.4)

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

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

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

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

> **왜 새 기계장치를 만들지 않는가.** §2가 이미 예약 태그로 *"호출자가 태그만 보고 알 수 있어야 하는 것"*을 나르고 있다. 대상은 정확히 그 종류다 — **설명문을 읽어 짐작하게 두면 안 되는 사실**이다.
>
> **값이 지금 하나뿐인데 왜 적는가.** 패밀리만 있을 때는 적을 필요가 없었다. **외부가 들어오는 순간 "전부 그렇다"는 말은 검사할 수 없는 말이 된다.**

---

## 3. ANS 이름 — `capabilities.extensions[]`

```json
"capabilities": {
  "extensions": [
    {
      "uri": "https://cho-fam.com/ans/v1",
      "description": "ANS 이름 — 이름 권위는 chofam ANS",
      "required": false,
      "params": { "name": "ethos.decision.v1" }
    }
  ]
}
```

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

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

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

---

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

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

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

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

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

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

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

```json
{
  "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](work-orders.md)).

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

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

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

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

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

---

---

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

[1차 답변서 §4.3](work-orders-answer.md)이 경로별로 확정했다. **카드의 `securitySchemes`는 그 경로가 실제로 받는 것을 그대로 적는다.**

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

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

```json
"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을 노출하는 카드는 그 경로 전용 스킴을 따로 선언한다.**

```json
"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](ans-a2a-design.md), `D-16(ANS)`). **카드로도 Gateway로도 막히지 않으므로 가시 범위 자체를 정해야 한다.**

---

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

**1차는 패밀리 에이전트가 ANS를 런타임 경로로 쓰지 않는다**([ANS 설계 §11.4](ans-a2a-design.md)). 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 | **인증 없이 읽힌다.** 카드 자체는 공개다 |
| G3 | `Content-Type: application/json` |
| G4 | ANS 프로브가 주기적으로 읽으므로 **캐시 헤더를 과도하게 길게 두지 않는다** — 갱신이 신호에 반영되지 않는다 |
| 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-spec.md)). 자가 점검은 편의이지 증명이 아니다 — 신청자의 기계에서 돌고 통과 여부가 ANS에 전달되지 않는다. **패밀리 시드도 같은 검사를 거친다.**

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

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

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

**ANS 이름 배정**([업무지시서 §3.2](work-orders.md)) — `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의 결정**이다. 여기 적는 것은 **규격이 그것을 전제하고 있다**는 사실을 남기기 위해서다.
