C.W.K.
Stream
Lesson 04 of 05 · published

Side-effect 경계

~22 min · read-tool, write-tool, idempotency, human-in-the-loop

Level 0호기심 많은 독자
0 XP0/48 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

모든 tool 이 같지 않아. Read tool — 주문 목록, 고객 조회, 가격 확인 — 은 모델이 실수로 10 번 불러도 결과 없어. Write tool — 환불, 이메일 보내기, deploy 예약 — 은 모델이 흥분하면 누군가의 하루를 망쳐. Protocol 이 이 구분을 지켜주지 않아 — 네가 만들어 넣어야 해.

첫 습관은 이름으로 카테고리. Read tool 은 query 처럼 들려야 해: search_orders, get_customer, list_invoices. Write tool 은 동사+대상으로: refund_order, send_email, schedule_deploy. Naming 은 모델이 schema 너머에서 보는 몇 안 되는 신호 — write 에 동사 쓰는 건 정확성 쪽으로 조용한 bias.

둘째 습관은 write tool 의 idempotency key. 네트워크 끊김으로 같은 write 가 두 번 호출되면, 시스템이 감지해서 중복 거절해야 해. 부르는 API 들 보통 이미 지원 (Stripe 의 Idempotency-Key 같은 거); 네 tool 은 conversation context 에서 생성한 그런 key 를 받아 forward 해야 해.

셋째 습관은 위험한 가장자리에 human-in-the-loop. 되돌릴 수 없는 write — 돈 나가기, 메시지 보내기, infra 변경 — 에는 두 step tool 패턴: propose_refund 가 구조화된 proposal 반환, agent 가 사람한테 보여주고, 명시적 승인 후에야 agent 가 proposal ID 로 execute_refund 호출. Protocol 이 구조화된 proposal 을 줘 — 너는 user 한테 승인을 빚져.

이건 MCP 가 명시적으로 가져온 lens 이기도 해: spec 이 세상에 영향 줄 수 있는 tool 을 마크해서 client 가 user 한테 surface 하라고 요구해. Lens 자체는 좋은 API 디자이너들이 이미 하던 거 — MCP 는 그걸 contract 의 일부로 만들었을 뿐.

Code

Two-step write — propose 후 execute·python
{
  "name": "propose_refund",
  "description": "Build a refund proposal for human review. Does NOT execute.",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": {"type": "string"},
      "amount_cents": {"type": "integer"},
      "reason": {"type": "string"}
    },
    "required": ["order_id", "amount_cents", "reason"]
  }
},
{
  "name": "execute_refund",
  "description": (
    "Execute a previously-approved refund. Requires a proposal_id from "
    "propose_refund AND the human's explicit approval. Do not call without "
    "approval — the user-facing client will reject it."
  ),
  "input_schema": {
    "type": "object",
    "properties": {
      "proposal_id": {"type": "string"},
      "approved_by_user": {"type": "boolean"}
    },
    "required": ["proposal_id", "approved_by_user"]
  }
}
경계에서 idempotency·python
import hashlib, requests

def execute_refund(proposal_id, approved_by_user, conversation_id):
    if not approved_by_user:
        return {"error": "Refund requires explicit user approval."}
    idem_key = hashlib.sha256(f"{conversation_id}:{proposal_id}".encode()).hexdigest()
    r = requests.post(
        "https://api.stripe.com/v1/refunds",
        data={"payment_intent": proposal_id_to_intent(proposal_id)},
        headers={"Idempotency-Key": idem_key, "Authorization": "Bearer ..."},
    )
    return r.json()

External links

Exercise

네가 maintain 하는 (실제든 가상이든) 프로젝트의 모든 tool 적어. 각각 한 글자: pure read 면 R, write 면 W, 되돌릴 수 없는 destruction 이면 D. 모든 D 에 대해 propose/execute split 디자인. Two-step 없는 D 의 개수가 네 남은 숙제야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.