If-Match 낙관적 동시성: API로 Sume Format 파일 편집
다른 작성자의 변경을 덮어쓰지 않고 API로 Sume Format 파일을 편집하세요. 쓰기마다 파일의 blob sha를 보내고, If-Match가 패키지 전체를 지킵니다.

다른 사람의 변경을 덮어쓰지 않고 Sume Format의 파일을 편집하려면 GET /v1/formats/{handle}/{slug}/contents?recursive=1로 패키지를 읽은 뒤 PUT으로 커밋하세요. 이때 교체하는 파일마다 blob sha를 보내고, 패키지 sha는 If-Match 헤더에 담습니다. 읽은 뒤로 파일이나 패키지가 바뀌었다면 Sume는 409로 응답하고 아무것도 커밋하지 않습니다.
Contents API는 일부러 GitHub의 Contents API와 같은 모양으로 만들어졌습니다. 그래서 코딩 에이전트가 이미 저장소를 편집하는 방식 그대로 Format을 편집할 수 있습니다. 아래 내용은 2026-09-26에 확인한 Format 패키지 편집하기와 Sume API 레퍼런스에서 가져왔습니다. Format이 무엇인지는 Sume Format이란?을 참고하세요.
낙관적 동시성이란 무엇이고, Sume는 이를 어떻게 쓰나요?
낙관적 동시성은 쓰는 쪽이 잠금을 전혀 걸지 않는 방식입니다. 쓰기마다 기준으로 삼은 버전을 밝히고, 그 버전이 더 이상 최신이 아니면 서버가 쓰기를 거부합니다. Contents API에는 이런 검사가 두 가지 있고, 둘은 겹쳐서 적용됩니다.
- 파일별
sha는 저장된 파일의 git blob sha입니다. 기존 경로를 교체하거나 삭제하려면 이 값이 필요하므로, 같은 파일을 편집하는 두 에이전트가 서로의 변경을 조용히 덮어쓸 수 없습니다. If-Match에는 패키지 자체의 sha를 담습니다. 서로 다른 파일을 편집하는 두 에이전트는 각자 아직 유효한 파일별sha를 들고 있으므로, 두 쓰기가 모두 반영될 수 있습니다. 패키지 sha가 그 틈을 막습니다. 이 검사는 추가 조건이며, 파일별 검사도 그대로 적용됩니다.
Format의 파일은 어떻게 읽나요?
읽기에는 formats:read가 있는 API 키가 필요합니다. GET …/contents는 패키지 루트를 나열하고, GET …/contents/{path}는 파일 하나를 base64로 인코딩해 그 파일의 sha와 함께 돌려줍니다. 디렉터리를 가리키는 경로는 그 디렉터리의 항목 목록을 돌려줍니다.
루트 목록에 ?recursive=1(또는 recursive=true)을 붙이면 모든 파일을 본문과 함께 호출 한 번으로 받습니다. 행은 path 순으로 정렬되고 dir 행은 없습니다. 각 행의 sha가 쓰기에 필요한 전제 조건이므로, 재귀 읽기 한 번이면 바로 편집을 시작할 수 있습니다.
파일 하나를 커밋하거나, 여러 파일을 커밋 하나로 묶으려면 어떻게 하나요?
쓰기에는 formats:write가 필요합니다. PUT …/contents/{path}는 message, base64로 인코딩한 새 파일 전체인 content, 그리고 경로가 이미 있을 때는 sha를 받습니다. 새 파일을 만들 때는 sha를 생략하세요. 일부만 고치는 patch가 아니라 파일 전체를 바꾸는 교체입니다. DELETE …/contents/{path}는 message와 sha를 받으며, SKILL.md는 삭제할 수 없습니다. 커밋 작성자는 항상 키의 소유자이고, 본문에 author나 committer를 넣어도 무시됩니다.
패키지 루트에 PUT하면 files 목록을 받아 전부를 커밋 하나, version 증가 하나, 새 package_sha 하나로 씁니다. files는 변경 집합이므로 목록에 없는 경로는 그대로 유지됩니다. 항목 중 하나라도 sha가 오래됐거나 결과 패키지가 규칙을 어기면 아무것도 커밋되지 않습니다.
curl -sS -X PUT "https://api.sume.com/v1/formats/acme/product-promo/contents" \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "If-Match: $PACKAGE_SHA" \
-H "Content-Type: application/json" \
-d '{
"message": "edit plan, add a note",
"files": [
{ "path": "references/plan.md", "content": "IyBQbGFuCg==", "sha": "3f7b…" },
{ "path": "references/new-note.md", "content": "IyBOZXcK" }
]
}'If-Match는 ETag와 어떻게 다른가요?
표준 HTTP에서 If-Match는 서버가 이전 응답에서 돌려준 엔터티 태그(ETag)를 담습니다. Contents API에서는 불투명한 ETag가 아니라, 40자 hex 문자열 그대로인 패키지 sha입니다. 이 값은 Format 레코드의 package_sha나 마지막 쓰기 응답의 commit.tree.sha에서 읽으세요. 두 값은 같습니다.
- 오래된 값은
409 format_package_sha_mismatch로 거부되고error.details.package_sha에 현재 값이 담겨 오므로, 왕복 한 번으로 다시 읽고 재시도할 수 있습니다. - 이 헤더는 루트
PUT,PUT …/contents/{path},DELETE …/contents/{path}에서 모두 동작합니다. package_sha가 생기기 전에 게시된 Format에는 이 값이 없습니다. 이런 Format은 만족시킬 방법이 없는409를 돌려주는 대신 헤더를 무시합니다.
코드에서 어떤 오류를 처리해야 하나요?
쓰기는 커밋되거나 실패하거나 둘 중 하나입니다. Format은 바뀌었는데 커밋은 없는 경우는 생기지 않습니다.
| 상태 | `error.code` | 발생한 일 |
|---|---|---|
409 | format_content_sha_required | 경로가 이미 있는데 sha를 보내지 않음. 파일을 읽은 뒤 재시도 |
409 | format_content_sha_mismatch | sha가 오래됨. 다른 쪽이 먼저 커밋함. 다시 읽고 재시도 |
409 | format_package_sha_mismatch | If-Match의 패키지 sha가 오래됨. error.details.package_sha에 현재 값이 있음 |
400 | skill_path_invalid, skill_frontmatter_invalid, skill_limit_exceeded, … | 결과 패키지가 규칙을 어김. 아무것도 커밋되지 않음 |
404 | format_content_not_found | Format은 있지만 그 경로에 아무것도 없음 |
403 | insufficient_scope | 키에 스코프가 없거나, 패키지를 만들거나 편집할 수 없는 서비스 계정 키임. 새 키 발급 필요 |
503 | format_git_unavailable | 패키지 히스토리가 커밋을 받지 못해 아무것도 저장되지 않음. 재시도 |
Contents API가 하지 않는 일은 무엇인가요?
Contents API는 실행이 아니라 작성을 위한 것입니다. 각 실행은 시작할 때의 패키지를 읽으므로, 패키지를 편집해도 이미 진행 중인 실행에는 영향이 없습니다. 실행이 어느 편집본을 썼는지 확인하는 방법은 Sume Format 버전에서 다룹니다.
- 공개 git 엔드포인트도, clone URL도 없습니다. revert, blame, 히스토리 탐색은 아직 이 표면에 포함되지 않습니다.
- 부분 쓰기는 없습니다.
content는 항상 파일 전체입니다. - 배치 안에서는 삭제할 수 없습니다. 파일은
DELETE …/contents/{path}로 지우세요. - 배치 하나에는 경로를 최대 1000개까지 지정할 수 있고, 파일 하나와 패키지 전체는 각각 최대 100 MiB입니다. 파일 규칙은 SKILL.md 레시피 작성법에 있습니다.
출처
관련 글
작성자 Sume