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는 항상 키의 소유자입니다 — 바디의 authorcommitter는 무시됩니다.

이것은 실행이 아니라 작성입니다. 패키지를 편집해도 이미 진행 중인 run에는 영향이 없습니다. 각 run은 시작할 때의 패키지를 그대로 읽습니다.

Format 만들기

POST /v1/formats는 GitHub에서 POST /repos가 저장소를 여는 것처럼 Format과 그 패키지 저장소를 엽니다. formats:write가 필요하고, Format은 호출한 키의 워크스페이스에 만들어집니다 — 주소에 {handle}이 없으므로 다른 워크스페이스에 만들 방법 자체가 없습니다.

auto_init(기본값 true)은 유효한 최소 SKILL.md를 커밋합니다. 그래서 만들자마자 아래 엔드포인트로 읽고 쓸 수 있고, 그 파일을 실제 내용으로 교체하는 것이 다음 호출입니다. false400입니다. 모든 Format 패키지에는 SKILL.md가 있어야 하므로 빈 Format이라는 것은 존재할 수 없습니다. 바디로 패키지 파일을 받지는 않습니다. 그건 Contents API가 할 일이고, Format의 파일은 누가 썼든 하나의 규칙만 따라야 합니다.

응답의 package_shacontents_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를 보세요.

DELETEmessagesha를 요구하고, 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가 허용 규칙 전체를 함께 돌려주므로, 첫 거부만으로 이름을 고칠 수 있습니다.

처리해야 할 에러

Statuserror.code무슨 일인가
403insufficient_scope키에 formats:read / formats:write가 없거나 service-account 키입니다. service-account 키는 패키지를 만들거나 편집할 수 없습니다. next_actionauthenticate입니다. 기존 키에 스코프를 덧붙일 수는 없으니 새 키를 만드세요. 빠진 스코프는 format_not_found가 아닙니다.
409skill_slug_taken워크스페이스에 같은 slug의 Format이 이미 있습니다. 만들기는 덮어쓰지 않으니 다른 slug를 쓰세요.
409skill_slug_reservedSume 제공 Format이 그 slug를 전역으로 쓰고 있습니다. 다른 slug를 쓰세요.
404format_not_found알 수 없는 Format이거나, 호출한 키의 워크스페이스 밖입니다. 팀 Format에는 그 워크스페이스에서 만든 키가 필요합니다.
404format_content_not_foundFormat은 있지만 그 경로에는 아무것도 없습니다.
409format_content_sha_required경로가 이미 존재하는데 sha를 보내지 않았습니다. 읽고 다시 시도하세요.
409format_content_sha_mismatchsha가 오래됐습니다 — 다른 쪽이 먼저 커밋했습니다. 다시 읽고 재시도하세요.
409format_package_sha_mismatchIf-Match 패키지 sha가 오래됐습니다. error.details.package_sha에 현재 값이 있습니다.
400skill_path_invalid, skill_frontmatter_invalid, skill_limit_exceeded, …결과 패키지가 위 규칙을 어겼습니다. 아무것도 커밋되지 않았습니다.
503format_git_unavailable패키지 히스토리가 커밋을 받지 못해서 아무것도 저장되지 않았습니다. 재시도하세요.

마지막 항목은 의도된 설계입니다. 쓰기는 커밋되거나 실패하거나 둘 중 하나입니다. Format은 바뀌었는데 커밋은 없는 상태는 존재하지 않습니다.

이 API가 아닌 것

공개 git 엔드포인트도, clone URL도, 히스토리 writer에 직접 닿는 경로도 없습니다. 커밋은 이 API가 만들기 때문에 생깁니다. revert, blame, 히스토리 브라우징은 아직 이 surface에 없습니다.