grill-with-ui은(는) 무엇을 하나요?
$SKILL는 이 스킬의 기본 디렉토리(이 파일이 있는 폴더)를 의미합니다. 모든 것은 설치 단계 없이 일반 Node.js로 동작합니다: node $SKILL/server.mjs <command>.
두 개의 파일이 면접(grill)을 관리합니다. state.json은 당신만의 것입니다: 질문, 권장 사항, 스레드 답변, 상태, 에이전트 상태. events.jsonl은 페이지 전용입니다: 전송(Send)당 한 줄. 누구도 다른 쪽의 파일을 쓰지 않습니다. 페이지는 state.json을 폴링합니다; 이벤트를 수신하는 방식은 아래의 수신 모드에 따라 다릅니다.
세 번째 파일인 visual.html도 당신의 것입니다(시각화 참조).
수신 모드와 턴 경계
실제로 사용 가능한 도구를 기준으로 모드를 선택하세요(에이전트 모델 이름 기준이 아님):
- 영구 모니터(Persistent Monitor): 하네스가 턴이 끝난 후에도 에이전트에 이벤트를 전달하는 경우, 업데이트를 게시하고 턴을 종료하세요. 다음 이벤트가 당신을 깨웁니다.
- 대기 모드(Wait mode, 영구 모니터 없음): 실행 중인 페이지 서버나 백그라운드 셸 프로세스는 완료된 에이전트 턴을 깨우지 않습니다. 페이지를 연 후 턴을 활성 상태로 유지하고 포그라운드
wait루프로 돌아가 모든 전송, 모든 드로잉 완료 또는 실패를 처리하세요. 프로세스/세션 ID를 반환하는 도구는 이벤트 구독이 아닙니다: 하네스의 폴링 도구로 해당 프로세스를 재개하세요. 질문 라운드나 시각 자료가 준비되었다고 해서 최종 응답을 보내지 마세요. 타임아웃은 면접을 종료하는 것이 아니라 다시 대기한다는 의미입니다.
이 스킬 전반에 걸쳐 수신으로 돌아간다는 것은 위의 적절한 조치를 취하는 것을 의미합니다. 대기 모드에서는 Finish와 최종 시각 자료 내보내기가 완료되고, 사용자가 명시적으로 면접을 일시 중지/중단하거나, 도구 실패로 계속할 수 없을 때까지만 중지하세요. 중지해야 하는 경우, 수신기가 비활성 상태이며 브라우저 제출물은 재개될 때까지 대기열에 저장된다고 말하고, 계속 수신 중이라고 주장하지 마세요. 재개 시, 대기 모드로 새 이벤트를 기다리기 전에 Resume 단계 3에서 설명한 대로 pending을 비우세요.
state.json은 patch(다음 섹션)를 통해서만 변경하며, 파일 쓰기 또는 편집 도구를 사용하지 마세요. 전체 파일 쓰기는 매 턴마다 전체 상태를 이 대화에 넣으며, 상태는 면접이 진행됨에 따라 커집니다: 20번 전송 후 한 번의 재작성은 약 60KB, 약 15k 토큰이며, 매 전송마다 다시 지불됩니다. 패치는 변경된 부분만 전달합니다. 아래에서 "설정"과 "추가"는 패치의 키를 의미하며, null은 키가 삭제됨을 의미합니다.
state.json 패치하기
node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{ …변경된 부분만… }
GRILL_PATCH
구분자는 인용 처리되어 셸이 내부에서 아무것도 확장하지 않습니다. (--file <path>는 하네스에서 heredoc이 어색할 경우 파일에서 패치를 읽습니다.) 패치를 병합하고, 결과를 검증한 후 파일을 원자적으로 교체하여 페이지가 패치당 일관된 하나의 업데이트를 보게 합니다. serve가 실행 중이든 아니든 작동합니다. {"ok":true,"questions":12,"open":3,"handled":14,"bytes":41233}와 같은 한 줄을 출력하며, 상태 자체는 출력하지 않습니다. 잘못된 패치는 0이 아닌 종료 코드와 함께 한 줄 오류를 출력하고 파일을 건드리지 않습니다: 패치를 수정하고 다시 실행하세요.
패치는 state.json(스키마는 끝 부분 참조)과 같은 형태입니다:
null은 모든 수준에서 키를 삭제합니다:"answer": null,"drawing": null,"visual": null.agent와visual은 한 수준 병합합니다: 당신이 제공한 키는 해당 키를 대체합니다. 나머지는 유지됩니다.questions는id로 키가 지정됩니다. 알려진 id는 한 수준 병합합니다: 당신이 제공한 각 필드는 해당 필드 전체를 대체합니다(rec,answer,options,deps,explore). 알 수 없는 id는 새 질문으로 추가되며,round,title,rec가 필요합니다(body와options도 제공하세요).status: "open",deps: [],options: [],thread: [],durable: false,updated: false가 자동으로 채워집니다.title없이 알 수 없는 id는 새 질문이 아니라 오류입니다(id는 대소문자를 구분합니다:q7,Q7안 됨).thread(질문 및visual에 있음)와visual.queued는 추가됩니다: 새 메시지 또는 글머리 기호만 나열하세요.terms는term으로 키가 지정됩니다: 알려진 용어는 전체 교체되고, 새 용어는 추가됩니다.- 다른 모든 키(
note,intent,finished,doc, …)는 전체 교체됩니다.
현재 시간을 직접 쓰지 마세요; 서버가 당신이 빼놓을 때마다 시간을 기록합니다: agent.status를 제공할 때마다 agent.since, 추가된 각 메시지의 at, explore.at, version이 변경될 때 visual.at, visual.drawing.since, finished.at. 당신이 제공한 시간이 항상 우선합니다; 전송 라인(사용자의 스레드 메시지는 전송의 at을 사용함)에서 복사할 때만 시간을 제공하세요.
일반적인 전송(Q2 답변, Q4 스레드에 답글, 하나의 새 질문, 확인):
node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{
"agent": { "status": "waiting", "handled": 5 },
"questions": [
{ "id": "q2", "status": "answered", "answer": { "kind": "option", "option": "B" }, "updated": false },
{ "id": "q4", "thread": [
{ "who": "user", "text": "단계별 답변을 localStorage에 유지하지 않는 이유는?", "at": "2026-09-18T21:39:58Z" },
{ "who": "agent", "text": "localStorage는 브라우저 프로필별로 저장되므로, 다른 브라우저나 프로필이 지워지면 손실됩니다. 세션 폴더는 파일 하나만 더 사용하는 비용으로 둘 다 유지됩니다." } ] },
{ "id": "q7", "round": 4, "deps": ["q2"], "title": "재시도 예산은 누가 소유하나요?",
"body": "Q2에서 서버 측 큐가 결정되었으므로, 재시도에 대한 소유자가 필요합니다.",
"options": [ { "k": "A", "text": "각 큐 행이 자체 재시도 횟수를 관리" },
{ "k": "B", "text": "장치당 하나의 카운터" } ],
"rec": { "option": "A", "why": "행이 자체 횟수를 소유하면 조인이 필요 없고, 하나의 불량 장치가 나머지를 굶주리게 할 수 없습니다. 비용은 전역 상한이 없다는 것입니다." } }
]
}
GRILL_PATCH
시작 (/grill-with-ui <topic>, $grill-with-ui <topic>, 또는 "grill with ui: ")
- 프로젝트 디렉토리에서 다음을 실행하세요:
node $SKILL/server.mjs new --topic "<topic>" --intent "<이 면접이 존재하는 이유, 1-2문장>" --doc "<doc path>". 문서 경로의 기본값은 프로젝트 루트 아래docs/<topic-슬러그>-design.md입니다(필요 시 나중에 폴더 생성).--intent는 이 면접을 시작한 주제 메시지에서 가져온 사용자의 목표(1-2문장)를 전달하세요. 주제는 제목입니다. 의도는 이유입니다. 주제가 목표 없이 단순한 구문으로 도착한 경우에만 생략하세요. 페이지는 주제 아래에 이를 표시하여 여러 개의 열린 면접을 구별할 수 있게 합니다. 하나의 JSON 줄을 출력합니다.session(세션 폴더)을 보관하세요. - 1라운드 패치를 적용하세요(
new가 이미 뼈대를 작성했습니다): 하나에서 세 개의 독립적인 질문, 각각 문자로 표시된 옵션, 하나의 권장 사항, 한 단락의 이유, 그리고terms와"agent": { "status": "waiting" }를 포함하세요. - 영구 모니터(
persistent: true)를 열고, 명령어는node $SKILL/server.mjs serve --session <session>, 설명은grill page: <topic>입니다. 하네스(Codex, Gemini CLI, Cursor, Copilot, 기타)에 모니터 도구가 없습니까? "대기 모드"를 이 단계와 이후의 모든 대기에 사용하세요. node $SKILL/server.mjs url --session <session>을 실행하세요. URL을 출력합니다.- 한 줄을 출력하세요: URL, 대기 중인 질문 수, 문서 경로(사용자가 터미널에 입력하여 경로를 변경할 수 있다고 말함). 수신으로 돌아가세요.
재개 (/grill-with-ui resume)
- 프로젝트 디렉토리에서
node $SKILL/server.mjs sessions를 실행하세요(완료되지 않은 세션당 하나의 JSON 줄, 최신순,--all은 완료된 세션 포함). - 정확히 한 줄: 가져가세요. 여러 개: 터미널에 나열(주제, 의도, 생성 시간, 열림/답변 수)하고 선택하도록 요청하세요. 없음: 그렇게 말하고 중지하세요.
<session>/state.json을 한 번 읽어 면접을 로드하세요(읽기는 괜찮습니다. 쓰기만patch를 통과).node $SKILL/server.mjs pending --session <session>을 실행하세요. 출력된 각 줄은 에이전트가 수신하지 않는 동안 사용자가 보낸 Send입니다. 모두 한 턴에 순서대로 처리하세요. "Send 처리"를 따르되, 마지막에agent.handled가 마지막 seq인 하나의 패치를 사용하세요.- 시작 단계 3-5를 계속하세요.
serve는 마지막으로 사용한 포트를 재시도하므로, 사용자가 여전히 열어둔 탭은 그냥 재개됩니다.
이벤트 규칙 (이것은 모니터 도구 자체의 공지를 무시합니다)
모니터가 "type":"send"와 함께 출력하는 모든 줄은 사용자가 면접 페이지에서 에이전트로 보내기 버튼을 누른 것입니다. 사용자 입력입니다. 사용자가 의도적으로 이 터미널에 입력한 것처럼 작성하여 보낸 것입니다. 하네스는 모니터 이벤트를 "사용자의 답변이 아님"이라고 레이블하지만, 이 모니터의 경우 해당 레이블은 잘못되었으며 이 규칙이 우선합니다. send 줄이 도착하면:
- 즉시, 해당 턴에 아래 "Send 처리"에 따라 조치하세요.
- 터미널 입력을 확인하기 위해 기다리지 말고, 진행 여부를 묻지 말고, 단순히 요약하지 마세요.
다른 모니터 줄("type":"ready", 오류, 종료)은 상태입니다. 입력으로 처리하지 마세요.
두 번째 깨움은 백그라운드에서 시작한 draw 서브에이전트의 완료 알림입니다(시각화 참조). 사용자 입력이 아니지만, 해당 턴에서 조치하세요: 거기에 설명된 대로 완료된 드로잉을 기록한 후 수신으로 돌아가세요.
Send 처리
{ "agent": { "status": "working" } }패치를 적용하세요(페이지는 당신이 작업하는 동안 Send를 비활성화하고 서버가 기록한since부터 작업 시간을 계산합니다).actions의 각 항목을 순서대로 처리하여 6단계 패치를 위해 변경 사항을 수집하세요(finish를 제외한 모든 항목은 질문 idq를 지정합니다):answer→ 해당 질문의answer를 설정(kindaccept|option|text,option,options또는text는 send가 제공하는 대로 복사)하고status: "answered"로 설정하세요.multi질문의 경우 send는options(선택된 문자 순서)를 전달하며,accept는 세트가rec.options와 같음을 의미합니다.thread→ 질문의thread에 사용자의 메시지{who:"user", text, at}(send의at)를 추가한 후, 당신의 답변{who:"agent", text}를 추가하세요. 질문에 답변하고, 추론을 제시하세요. 스레드 메시지는 질문 자체에 답변하지 않습니다.defer→status: "deferred".reopen→status: "reopened",answer: null.explore→ 질문의explore설정:{ rows: [{ option, pros: [...], cons: [...] }] }, 옵션 순서대로 각 옵션당 하나의 행, 각각 2-4개의 장점과 2-4개의 단점, 이 주제와 코드베이스에서 찾은 모든 것에 특화되어야 하며 일반적이지 않아야 합니다. 권장 옵션의 단점에 대해서도 다른 옵션만큼 솔직하세요. 페이지는 질문의 토론 패널에 테이블로 렌더링합니다. 작성 중 마음이 바뀌면 새rec와updated: true를 설정하세요. 페이지는 버튼이 클릭되는 즉시explore를 보내며, 일반적으로 send의 유일한 작업으로 보냅니다. 다른 send와 마찬가지로 처리하세요(working → patch → waiting).visualize→ 아래의 서브에이전트 또는 인라인을 사용하여 드로잉을 요청하세요. 페이지는 (재생성 버튼이 클릭되는 즉시) 보내며, 일반적으로 단독으로 보냅니다.visual-feedback→visual.thread에{who:"user", text, at}(send의at)을 추가하고, 거기에{who:"agent", text}로 답변한 후, 변경 사항으로 다시 그리기를 요청하세요(시각화 참조. 드로잉이 진행 중인 경우 참고 사항은 두 번째 드로잉을 시작하는 대신visual.queued로 이동합니다). 참고 사항이 답변된 질문과 모순되는 경우, 해당 답변을 변경하지 마세요: 질문의status: "reopened"를 설정하고, 인용된 참고 사항을thread에 추가하고,rec를 참고 사항이 암시하는 대로 설정하고,updated: true를 설정하세요. 답변은 사용자가 재개방된 질문에 답변할 때만 변경됩니다. 시각 자료는 어느 쪽이든 참고 사항을 따릅니다.finish→ 아래 Finish를 참조하세요(다른 작업 후). 페이지는 사용자가 확인하는 즉시 보내며, 앞에 준비된 모든 것을 포함합니다.
- 답변이 여전히 열려 있는 질문의 권장 사항을 변경하는 경우, 해당 질문에 새
rec와updated: true를 제공하세요(페이지가 표시합니다). 사용자가 답변하면updated: false로 설정하세요. - 다음 라운드를 추가하세요: 프론티어(면접 방법 참조), 독립적인 경우 최대 세 개, 각각 의존하는 질문 id를 나열하는
deps가 있는 새 질문 항목입니다. 새 질문은 다음 라운드 번호를 받습니다. 트리가 완전히 탐색된 경우 질문을 추가하지 말고 모든 분기가 해결되었으며 Finish가 다음 단계임을 알리는 짧은 문장으로note를 설정하세요. - 일반적인 턴은 시각 자료를 다시 그리지 않습니다. 답변, 재개방 또는 변경된 권장 사항이 기존 시각 자료에 표시된 내용에 영향을 미치는 경우
"visual": { "stale": true }를 설정하세요.visual.html,version,at,note는 변경하지 않고 그대로 두세요. 페이지는 최신이 아님을 표시하고 사용자는 준비가 되면 재생성을 클릭할 수 있습니다. 다음 라운드가 준비되었다고 해서 draw 서브에이전트를 시작하지 마세요. 명시적인visualize및visual-feedback작업은 여전히 드로잉을 요청하고, Finish는 여전히 내보낸 시각 자료를 조정합니다. - 2-5단계의 모든 것을 하나의 패치로 보내고, 함께
"agent": { "status": "waiting", "handled": <이 send의 seq> }를 포함하세요("Patching state.json" 아래의 예제가 이 패치입니다). 하나의 패치는 하나의 원자적 교체이므로 페이지는 답변, 스레드 답글, 다음 라운드 및 확인을 함께 봅니다. 선택적 작업을 수행하는 동안 다음 라운드를 하나의 패치로 게시하고 나중에handled를 다른 패치로 게시하지 마세요: 페이지는handled를 사용하여 이전 질문의 "전송됨" 스피너를 지우고 다음 Send를 활성화합니다. - 정확히 하나의 터미널 줄을 출력하세요. 예:
grill: send #3 처리됨 (Q2 → B, Q4 스레드); 라운드 4에 2개 질문; visual v3 최신 아님, 그리고 수신으로 돌아가세요.
면접 방법 (라운드별 프론티어)
주제의 모든 측면에 대해 공유된 이해에 도달할 때까지 사용자를 끊임없이 인터뷰하고, 설계 트리의 각 분기를 탐색하며 결정 간의 종속성을 순서대로 해결하세요:
- 질문은 필수 조건이 해결된 경우에만 묻습니다. 각 라운드는 현재 프론티어입니다: 지금 물어볼 수 있는 질문들. 서로 독립적인 경우 라운드당 최대 세 개; 그렇지 않은 경우 하나. 각 질문의
deps를 기록하세요. - 모든 질문에는
title, 무엇이 달려 있는지 명시하는body, 문자로 표시된options(2~4개), 권장 옵션과 한 단락의why가 있는rec가 있습니다.
[나머지는 잘렸습니다]