도는 세션에 말 걸기

턴이 끝나기를 기다리지 않으셔도 됩니다. 티켓 상세의 진행 기록에서 세션이 지금 하는 단계를 찾으면 그 안 맨 아래에 입력칸이 있습니다. 한 줄 넣고 보내면 그 문장이 세션의 stdin으로 들어갑니다. 세션은 하던 일을 중단하고 그 지시를 따릅니다. stdin은 실행 중인 프로그램이 바깥에서 글을 받는 통로입니다. 워커 화면의 스트림 다이얼로그에도 같은 칸이 있고, 그쪽은 단계별로 나뉘지 않으므로 상자 맨 아래에 있습니다.

진행 기록이 도구 호출을 한 줄씩 늘려 가는 동안 아래 입력창에 정렬은 최신순으로 해 줘를 치고 보내기를 누르자, 그 문장이 사람 말풍선으로 기록에 들어갑니다. 세션이 정렬 기준을 바꾸겠다고 답하고, 이어서 Edit 호출로 코드를 고칩니다.

검색 코드를 읽던 세션에 정렬은 최신순으로 해 줘 한 줄이 들어갑니다. 보낸 문장은 오른쪽 사람 말풍선으로 기록에 남고, 세션은 정렬 기준을 바꾸겠다고 답한 뒤 src/search/query.ts를 고칩니다.

  • 참견이 들어가는 입구는 세션마다 FIFO 하나입니다. FIFO는 프로그램끼리 글을 주고받으라고 만드는 특수한 파일이고 먼저 넣은 줄이 먼저 나갑니다. 티켓을 워커에 배정하는 디스패처가 티켓을 가져가는 순간 만들고, 세션이 끝나면 지웁니다. 이 경로가 티켓 frontmatter의 inbox: 값입니다(티켓이 지나는 상태 참고). 세션이 끝나면 값도 같이 비워집니다.
  • 스트림의 원본은 claude가 이미 쓰고 있는 트랜스크립트입니다. 화면을 위해 새로 남기는 파일이 없습니다.
  • 참견은 화면이 꾸며 낸 표시가 아닙니다. 트랜스크립트에 실제로 남은 줄입니다. 보낸 문장이 그대로 그 세션의 입력 기록에 들어갑니다.
  • 창은 세션이 실행되는 동안만 열려 있습니다. 턴이 끝나면 닫힙니다.

이 창은 스트리밍 입력을 받는 엔진에서만 열립니다(기본값 claude -p). 화면이 칸을 여는 것도 claude 워커입니다. 엔진이 codex처럼 argv로 프롬프트를 한 번에 받는 방식이면 이 입구 자체가 생기지 않습니다. argv는 프로그램을 띄울 때 이름 뒤에 붙여 넘기는 값이므로, 한 번 실행된 뒤에는 말을 더 넣을 방법이 없습니다. 어느 엔진으로 뜰지는 그 티켓의 페르소나가 정합니다 (페르소나 §디스패치 정책). 칸이 비활성이면 어느 엔진이라서 받지 못하는지를 그 자리가 알려 줍니다.

최초 프롬프트는 이 입구를 쓰지 않습니다. FIFO 대신 파일로 전달합니다. 프롬프트가 파이프 버퍼(64KB)를 넘으면 쓰는 쪽이 막히고 그 상태의 엔진은 시작조차 못 하고 멎습니다. 워커가 여섯 개쯤 되면 그 교착이 매번 발생합니다. 그래서 파일로 첫 프롬프트를 먼저 전달하고, 그다음 줄부터 FIFO가 stdin이 됩니다. 사람이 보내는 참견은 그 뒤에 오는 줄입니다.

파일 첨부

입력칸에는 파일도 첨부할 수 있습니다. 첨부 버튼으로 고르거나 그냥 붙여넣으시면(⌘V) 됩니다. 스크린샷을 찍어 바로 붙이는 것이 이 기능의 주 용도입니다. 파일을 고른 순간 업로드됩니다. 화면은 그 파일을 칩으로 보관하고 있다가 보낼 때 본문에 합칩니다.

첨부는 바이트를 실어 보내지 않습니다. 파일을 놓고 그 경로를 프롬프트에 적습니다. 이 칸이 보내는 것은 FIFO에 들어가는 한 줄이므로 이미지가 들어갈 여지가 없습니다. 대신 받는 쪽에 Read가 있으므로, 앱은 파일을 세션이 접근할 수 있는 위치에 놓고 그 절대경로를 프롬프트 끝에 붙입니다.

<사람이 쓴 본문>

첨부 파일 — 아래 경로를 Read로 읽어라:
/Users/.../myproject/.dira/attachments/ab12cd34-screenshot.png
text

세션이 읽는 것은 위 블록 그대로입니다. 그런데 보낸 뒤 그 글을 화면에서 다시 읽을 때는 안내 줄과 경로 줄이 보이지 않고 그 자리에 첨부한 파일이 미리보기로 뜹니다. 그림 파일(png jpg jpeg gif webp avif)은 축소판으로 뜹니다. svg를 포함한 나머지는 클립 표시가 붙은 파일명 칩입니다. 축소판을 누르면 화면 위에 창이 열리고 그림이 원본 크기 그대로 보입니다. 줄여서 맞추지 않으므로 큰 스크린샷의 작은 글자도 읽을 수 있고, 그림이 화면보다 크면 창 안에서 가로와 세로로 스크롤해 끝까지 봅니다. 창은 Esc나 바깥 클릭, X로 닫고, 닫아도 보던 화면에 그대로 있습니다. 브라우저에서 확대하거나 저장하려면 창 머리 줄의 새 탭에서 열기를 누릅니다. 칩은 누르면 원본이 바로 새 탭에서 열립니다. 예외는 글을 고치는 편집 칸입니다. 그 칸에서는 위 블록이 종전처럼 원문 그대로 보이고 이미 붙은 첨부는 그 줄들을 지워서 제거합니다.

  • 파일이 놓이는 위치는 <큐 루트>/attachments/입니다. 파일명 앞에 8자리 16진수가 붙어서, 같은 이름을 두 번 붙여도 덮어쓰지 않습니다. 자동 삭제는 없습니다. 티켓 본문과 트랜스크립트가 이 경로를 영구히 참조하므로 지우면 이력이 깨진 링크가 됩니다. 용량 정리는 사람이 합니다.
  • 상한은 1건 20MB, 한 번에 10개입니다. 상한을 넘으면 그 파일만 거절하고 사유를 칩 자리에 남깁니다. 종류 제한은 없습니다. Read가 못 여는 형식이면 세션이 그렇게 알려 줍니다.
  • 파일 내용을 본문 안에 그대로 옮겨 적지 않습니다. 텍스트 파일이라도 경로만 적습니다. 그러지 않으면 티켓 본문이 로그 한 벌만큼 불어납니다.
  • 첨부할 수 있는 칸은 넷입니다. 요구 접수, 티켓 발행, 여기(참견과 이어받기), 프로젝트 홈 질의이고, 표기는 넷이 같습니다.
  • 스크린샷을 두 장 붙이면 칩 둘이 다 image.png입니다. 크롬이 붙이는 이름이 그것입니다. 디스크에서는 앞의 8자리가 둘을 구분해 줍니다.
  • 드래그 앤 드롭은 없습니다.

에이전트가 스스로 거는 잠금

추측으로 진행하는 대신 되묻습니다. 티켓 본문에 질문을 붙이고 존재하지 않는 파일을 deps로 걸어 아무 워커도 그 티켓을 가져가지 못하게 만듭니다. 앞에서 본 deps는 아직 안 끝난 티켓을 기다리는 용도였는데(자세한 것은 티켓 직접 발행하기에 있습니다), 여기서는 아예 없는 해시를 건다는 점이 다릅니다. 답을 쓰면 그 파일이 생겨 잠금이 저절로 풀립니다.

  • 잠긴 티켓은 큐에서 보입니다. 화면에서 답할 수 있다는 뜻입니다.
  • 사람이 상태를 되돌리지 않습니다. 잠금과 해제가 같은 규칙입니다.
  • 같은 이유로 죽는 티켓이 세션을 무한히 소모하지도 않습니다. reap이 주워 온 회차와 요청 자체가 잘못돼 죽은 회차는 2회까지만 자동 회수하고 3회째에는 질문이 됩니다. 그 밖의 실패는 10회까지 회수하고 그 뒤로는 질문을 만들지 않고 그 티켓을 잠시 쉬게 했다가 다시 디스패치합니다.
  • 본문 마지막 절이 ## 블록이면 횟수와 무관하게 바로 질문이 됩니다. 세션이 더 진행할 수 없어서 스스로 멈춘 것이므로, 다시 실행해도 얻을 것이 없기 때문입니다.

이 네 줄을 실제로 실행하는 것은 reap이고, 죽은 세션이 남긴 티켓을 거둬들이는 절차입니다. 세션이 죽은 채로 남은 티켓을 발견하면 우선 attempts를 올리며 자동으로 회수해 다시 큐에 돌려놓습니다. 그런데 같은 이유로 두 번 더 죽으면, 즉 3회째부터는 회수 대신 본문에 ## 질문 n을 붙이고 아직 없는 해시를 deps(와 awaiting:)에 걸어 열림으로 되돌립니다. attempts와 무관하게 곧바로 질문이 되는 경로도 있습니다. 되돌린 티켓 본문의 마지막 ## 절이 ## 블록이면 1회째든 2회째든 그 즉시 같은 답변 요청으로 올립니다(tickets.py의 reclaim). 답변 파일이 생기기 전에는 deps가 미충족이므로 어떤 워커도 그 티켓을 가져가지 않습니다. 답이 달리면 attempts는 0에서 다시 셉니다.

다음은 터미널과 파일과 소스 컨트롤입니다.