Format 패키지 편집하기
이 문서는 영문 원고를 AI로 번역한 내용이라 표현이 어색할 수 있습니다.
모든 커스텀 Format은 작은 파일 패키지입니다 — 진입 파일 SKILL.md와, 선택적으로
references/ 아래의 참고 문서들. 그리고 그 패키지에는 실제 히스토리가 남습니다. Contents API를
쓰면 API 키로 그 파일들을 읽고 변경을 커밋할 수 있습니다. 코딩 에이전트가 이미 저장소를 편집하는
방식 그대로 Format을 편집할 수 있다는 뜻입니다.
모양은 의도적으로 GitHub Contents API와 같습니다. gh api repos/{owner}/{repo}/contents/{path}를
안다면 이것도 아는 것입니다:
{handle}/{slug}는 Format을 호출할 때 쓰는 바로 그 주소입니다. 읽기는 formats:read, 쓰기는
formats:write가 필요하고, 커밋의 author는 항상 키의 소유자입니다 — 바디의 author나
committer는 무시됩니다.
이것은 실행이 아니라 작성입니다. 패키지를 편집해도 이미 진행 중인 run에는 영향이 없습니다. 각 run은 시작할 때의 패키지를 그대로 읽습니다.
Format 만들기
POST /v1/formats는 GitHub에서 POST /repos가 저장소를 여는 것처럼 Format과 그 패키지 저장소를
엽니다. formats:write가 필요하고, Format은 호출한 키의 워크스페이스에 만들어집니다 — 주소에
{handle}이 없으므로 다른 워크스페이스에 만들 방법 자체가 없습니다.
auto_init(기본값 true)은 유효한 최소 SKILL.md를 커밋합니다. 그래서 만들자마자 아래 엔드포인트로
읽고 쓸 수 있고, 그 파일을 실제 내용으로 교체하는 것이 다음 호출입니다. false는 400입니다. 모든
Format 패키지에는 SKILL.md가 있어야 하므로 빈 Format이라는 것은 존재할 수 없습니다. 바디로 패키지
파일을 받지는 않습니다. 그건 Contents API가 할 일이고, Format의 파일은 누가 썼든 하나의 규칙만
따라야 합니다.
응답의 package_sha와 contents_url을 보관하세요. 앞의 것은 다음 쓰기의 If-Match 전제 조건이고,
뒤의 것은 그 쓰기를 보낼 주소입니다. 워크스페이스에 이미 있는 slug는 덮어쓰지 않고 409입니다.
저장소는 카탈로그 행보다 먼저 열립니다. 패키지 히스토리에 닿지 못하면 커밋을 아무도 해석할 수
없는 Format이 생기는 대신 503 format_git_unavailable로 실패하고 Format은 만들어지지 않습니다.
패키지 읽기
경로 하나를 지정하면 파일 자체가 base64로 돌아옵니다:
sha를 보관하세요. 저장된 파일의 git blob sha이고, 모든 쓰기가 요구하는 전제 조건입니다.
디렉터리를 가리키는 경로(…/contents/references)는 그 디렉터리의 항목 목록을 돌려줍니다.
패키지 전체를 한 번에 읽기
루트를 나열하고 경로마다 다시 요청하면 파일 수만큼 왕복이 생깁니다. 필요한 것이 패키지 전체라면 — 예를 들어 에이전트 컨텍스트에 통째로 올리는 경우 — 한 번의 호출로 받으세요:
모든 행은 본문을 포함한 파일이고 path 순으로 정렬됩니다. dir 행은 없습니다 — 디렉터리는 경로
자체로 드러납니다. recursive=true도 됩니다. 그 밖의 값은, 아예 생략한 것과 마찬가지로 위의 기본
루트 목록입니다. 각 행의 sha는 쓰기가 요구하는 그 전제 조건이므로, 재귀 읽기 한 번이면 바로
편집을 시작할 수 있습니다.
이것은 목록 조회에만 적용됩니다. …/contents/{path}는 이 파라미터가 있든 없든 똑같이 답합니다.
변경 커밋하기
PUT은 파일 하나를 통째로 쓰고 커밋 하나를 만듭니다. patch가 아니라 replace이므로, 새 본문
전체를 base64로 보내세요.
commit.tree.sha는 Format의 새 패키지 identity이고, version은 대시보드에 표시되는 카운터입니다.
경로가 이미 있으면 sha를 보내고, 새 파일을 만들 때는 생략하세요. 두 에이전트가 같은 파일을
편집해도 서로를 조용히 덮어쓸 수 없습니다 — 나중 쪽의 sha가 더 이상 맞지 않기 때문입니다. 한
Format의 서로 다른 파일을 편집하는 경우는 If-Match를 보세요.
DELETE는 message와 sha를 요구하고, content: null로 답합니다:
여러 파일을 한 번에 커밋하기
폴더를 한 경로씩 편집하면 파일 수만큼 커밋과 왕복이 듭니다. 패키지 루트에 PUT하면 —
{path} 없이 — files 목록을 받아 전부를 커밋 하나, version 증가 하나, 새 package_sha
하나로 씁니다:
각 항목은 단일 파일 쓰기 규칙을 그대로 따릅니다. content는 base64로 인코딩한 파일 전체이고,
sha는 경로가 이미 있으면 필수, 새로 만들 때는 생략합니다. 응답의 content는 이 커밋이 쓴
파일들의 목록입니다.
files는 패키지가 아니라 변경 집합입니다. Format이 갖고 있지만 이 바디가 지목하지 않은
경로는 그대로 유지됩니다 — 12개 중 2개를 고치면서 나머지 10개를 잃을 일이 없습니다. 그래서 삭제는
여기서 표현할 수 없습니다. 파일 제거는 DELETE …/contents/{path}로 하세요. 삭제는 항상 요청한
결과여야 하고, 말하는 걸 잊어서 생기는 결과여서는 안 되기 때문입니다.
항목 중 하나라도 sha가 오래됐거나 결과 패키지가 규칙을 어기면 아무것도 커밋되지 않습니다.
배치는 통째로 반영되거나 통째로 실패합니다.
로 패키지 전체 지키기
파일별 sha는 "내가 읽은 뒤로 패키지가 움직이지 않았다"를 표현하지 못합니다. 두 에이전트가 서로
다른 파일을 편집하면 각자의 sha는 여전히 유효하므로 둘 다 반영되고, 나중 쪽은 이미 존재하지
않는 트리를 기준으로 편집을 계획한 셈이 됩니다.
그 틈을 막으려면 패키지 자신의 sha를 보내세요. 마지막 쓰기의 commit.tree.sha이고, Format
레코드가 package_sha로 보고하는 값과 같습니다:
패키지가 이미 움직였다면 쓰기는 409 format_package_sha_mismatch로 거부되고,
error.details.package_sha에 현재 값이 담겨 옵니다. 한 번의 왕복으로 다시 읽고 재시도할 수
있다는 뜻입니다. 이 헤더는 PUT …/{path}와 DELETE …/{path}에도 적용되며, 추가 조건입니다 —
파일별 sha 검사는 그대로 유효합니다.
여기서 If-Match는 불투명한 ETag가 아니라 40자 hex 문자열 그대로의 패키지 sha입니다.
package_sha가 생기기 전에 발행된 Format은 이 값이 없으므로, 헤더를 무시합니다 — 만족시킬 방법이
없는 409를 돌려주지 않기 위해서입니다.
패키지에 담을 수 있는 것
대시보드 편집기가 강제하는 규칙과 같습니다:
- 패키지 루트의
SKILL.md는 필수이고, frontmatter의name은 Format의 slug와 같아야 합니다. 삭제할 수 없습니다. - 파일은 루트에 있거나
references/또는agents/아래 한 단계까지만 둘 수 있습니다..., 절대 경로는 불가. - 파일 이름은
^[A-Za-z0-9][A-Za-z0-9._-]*$를 만족해야 합니다 — 앞에_나.은 불가. .md,.json,.yaml,.yml,.txt만 지원합니다.- 파일당 최대 100 MiB, 패키지당 최대 100 MiB. Contents 배치의
files[]는 최대 1000개 경로.
이 중 하나라도 어기는 패키지는 아무것도 커밋되기 전에 거부됩니다. 경로가 거부되면
skill_path_invalid가 허용 규칙 전체를 함께 돌려주므로, 첫 거부만으로 이름을 고칠 수 있습니다.
처리해야 할 에러
| Status | error.code | 무슨 일인가 |
|---|---|---|
403 | insufficient_scope | 키에 formats:read / formats:write가 없거나 service-account 키입니다. service-account 키는 패키지를 만들거나 편집할 수 없습니다. next_action은 authenticate입니다. 기존 키에 스코프를 덧붙일 수는 없으니 새 키를 만드세요. 빠진 스코프는 format_not_found가 아닙니다. |
409 | skill_slug_taken | 워크스페이스에 같은 slug의 Format이 이미 있습니다. 만들기는 덮어쓰지 않으니 다른 slug를 쓰세요. |
409 | skill_slug_reserved | Sume 제공 Format이 그 slug를 전역으로 쓰고 있습니다. 다른 slug를 쓰세요. |
404 | format_not_found | 알 수 없는 Format이거나, 호출한 키의 워크스페이스 밖입니다. 팀 Format에는 그 워크스페이스에서 만든 키가 필요합니다. |
404 | format_content_not_found | Format은 있지만 그 경로에는 아무것도 없습니다. |
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, … | 결과 패키지가 위 규칙을 어겼습니다. 아무것도 커밋되지 않았습니다. |
503 | format_git_unavailable | 패키지 히스토리가 커밋을 받지 못해서 아무것도 저장되지 않았습니다. 재시도하세요. |
마지막 항목은 의도된 설계입니다. 쓰기는 커밋되거나 실패하거나 둘 중 하나입니다. Format은 바뀌었는데 커밋은 없는 상태는 존재하지 않습니다.
이 API가 아닌 것
공개 git 엔드포인트도, clone URL도, 히스토리 writer에 직접 닿는 경로도 없습니다. 커밋은 이 API가 만들기 때문에 생깁니다. revert, blame, 히스토리 브라우징은 아직 이 surface에 없습니다.