에이전트로 만들기

코딩 에이전트에게 Tiffy 앱 개발을 맡기는 방법, AGENTS.md가 강제하는 규칙, 자주 하는 실수.

대부분의 앱 개발자는 코드를 직접 치기보다 Claude Code나 Codex 같은 코딩 에이전트에게 앱을 맡길 것입니다. tiffy-app init이 만드는 AGENTS.md(그리고 같은 내용의 CLAUDE.md)가 그 에이전트를 위한 규칙서입니다.

AGENTS.md가 담은 것

  • 절대 규칙: @tiffy/app-sdk와 순수 npm 라이브러리만 import한다, 네트워크는 ctx.fetch로만 나간다, 비밀은 ctx.secrets에만 두고 로그에 남기지 않는다, 되돌릴 수 없는 도구는 irreversible: true와 approval: "required"를 함께 적는다, 도구와 트리거는 예외 대신 { ok: false, error }를 돌려준다, 문구는 i18n/ko.json과 i18n/en.json에 같은 키로 둔다, 화면은 선언형 JSON만 고친다, 매니페스트를 바꾸면 pnpm check를 통과시키고 버전을 올린다.
  • 폴더 구조: tiffy-app.json, src/index.ts, src/tools/*.ts, src/triggers/*.ts, schemas/*.json, surfaces/*.json, i18n/{ko,en}.json, test/*.test.ts.
  • ctx 요약표와 필요한 scope.
  • 도구/트리거를 만드는 절차: 스키마 먼저, 매니페스트에 등록, 핸들러 작성, 성공/실패 테스트 각 한 개.
  • 제출 전 확인 목록과 자주 하는 실수 목록.
  • "문서와 타입이 다르면 타입이 맞다"는 원칙. node_modules/@tiffy/app-sdk/dist/index.d.ts를 읽으라고 안내합니다.

에이전트에게 줄 프롬프트 예시

com.example.myapp 폴더에서 작업한다. 먼저 AGENTS.md를 읽고 그 규칙을 따라라.

만들 것: mail.draftReply 도구 하나.
- 입력: { messageId: string }
- 출력: { draft: string }
- ctx.notes.search로 관련 노트를 찾고, ctx.model.complete로 답장 초안을 만든다.
- 실패는 예외 대신 { ok: false, error }로 돌려준다.
- schemas/draftReply.in.json과 .out.json을 먼저 쓰고 매니페스트 tools에 등록해라.
- 성공 케이스와 네트워크 실패 케이스 테스트를 createMockContext로 써라.
- 다 쓰면 pnpm test와 pnpm check를 돌려서 통과를 확인해라.

AGENTS.md가 이미 규칙 대부분을 담고 있으므로 프롬프트는 "무엇을 만들지"에 집중하면 됩니다. 규칙을 프롬프트에 다시 나열할 필요가 없습니다.

tiffy-app check가 잡아내는 것

  • 매니페스트 스키마 오류(필수 필드 누락, irreversible: true인데 approval: "none"인 경우 등).
  • @tiffy/core, @tiffy/db, node:fs, node:child_process, node:net, process.env import.
  • tools/triggers의 매니페스트 키와 defineApp 코드 키 불일치.
  • i18n/ko.json과 i18n/en.json의 키 불일치.
  • surfaces/settings.json, surfaces/home-card.json의 허용되지 않은 필드 타입.
  • 번들(또는 src/) 크기 5MB 초과, 아이콘 파일 누락, 스크린샷 폴더 비어 있음(경고).
  • ctx.notes, ctx.fetch 같은 호출에 대응하는 scope가 선언되지 않은 경우(경고만).
  • AGENTS.md에 적힌 SDK 버전이 설치된 @tiffy/app-sdk와 다른 경우(경고).

check가 못 잡는 것도 있습니다. 동적으로 만든 import 경로나 문자열 결합으로 만든 URL은 정규식 검사를 피해 갑니다. 진짜 권한 경계는 실행 중 호스트가 강제하는 것입니다(앱 만들기 참고).

자주 하는 실수

  • 도구 안에서 fetch를 직접 부른다 → ctx.fetch로 바꾸고 호스트를 scopes에 network: 형태로 선언한다.
  • 트리거가 매번 전체 목록을 이벤트로 낸다 → dedupeKey와 ctx.kv에 저장한 커서로 새 항목만 낸다.
  • 예외를 던진다 → { ok: false, error: { code, message, retryable? } }로 돌려준다.
  • 설정값을 코드 상수로 박는다 → surfaces/settings.json 필드로 받아 ctx.kv나 ctx.secrets에서 읽는다.
  • 문구를 코드에 직접 쓴다 → ctx.i18n.t(key)와 i18n 파일의 키로 뺀다.
  • 매니페스트 version을 안 올린다 → 다음 발행 전에 항상 올린다(발행 파이프라인이 갖춰지면 검증됩니다. 게시 참고).
  • 테스트에서 scopes 옵션을 빼먹는다 → ctx.fetch가 항상 성공해서 scope 검사 로직에 버그가 있어도 테스트를 통과한다. 실제 매니페스트의 scopes를 테스트에도 넘겨라.