워커
워커는 30초마다 깨어나 큐를 훑고, 열려 있는 티켓 하나를 물어 세션을 띄우는 셸 스크립트입니다.
파일 하나가 워커 하나이고(<루트>/workers/<이름>.sh), 첫 프로젝트를 만들 때 w1이 이미
섰습니다.
워커 화면 — 한 줄이 말하는 것

왼쪽 내비의 워커. 한 줄이 워커 하나이고, 그 줄만 읽어도 지금 무슨 일이 벌어지는지 압니다.
열은 왼쪽부터 이름 · 상태 · 물고 있는 티켓 · 컨텍스트 · 마지막 활동 · pid ·
토큰(5시간) · 액션 여덟입니다.
- 상태는 넷입니다.
running은 세션을 띄워 놓고 끝나기를 기다리는 중,idle은 cron에 걸려 있고 지금은 아무것도 안 물고 있는 상태입니다.stopped은 crontab에서 빠져 있어 아예 깨어나지 않고(배지 옆에crontab 미등록이 붙습니다),stale은 세션 프로세스가 죽었는데 자리만 남았습니다. - 물고 있는 티켓은 지금 잡고 있는 티켓의 해시입니다. 누르면 그 티켓 상세로 갑니다.
- 컨텍스트는 이 워커가 세션마다 딸려 보내는 참조 문서의 수입니다. 숫자를 누르면 행이 펼쳐져
그 자리에서 항목을 고칩니다. 표 아래
공통 컨텍스트는 워커 전원이 같이 받는 목록입니다. - 마지막 활동은
runner.log에 이 워커 이름으로 마지막에 찍힌 줄 그대로입니다.DISPATCH면 방금 티켓 하나를 세션에 넘겼습니다.SKIP이면 깨어났는데 앞 세션이 아직 안 끝나 그냥 돌아갔습니다. 누르면 최근 20줄이 펼쳐집니다. - pid는 지금 그 워커를 돌리고 있는 프로세스의 번호입니다.
running과stale은 이 번호가 살아 있는지로 갈립니다(화면 소개 §워커). - **토큰(5시간)**은 최근 5시간 안에 끝난 세션이 쓴 토큰 합입니다. 한도는 워커별이 아니라 계정 하나에 붙어 있어서, 이 열이 한 통을 누가 얼마나 먹었는지 보여 줍니다.
30초 주기 — 깨어났다 죽는 스크립트
워커는 늘 떠 있는 프로세스가 아닙니다. cron이 30초에 한 번 그 스크립트를 처음부터 실행하고, 스크립트는 할 일을 마치면 죽습니다. 물 티켓이 없으면 큐를 훑고 그 자리에서 끝나며 로그에도 아무것도 안 남깁니다.
그래서 idle은 "차례를 기다리는 중"이 아닙니다. 지금 이 순간 그 워커의 프로세스가 아예
없다는 뜻이고, 다음 :00이나 :30에 새 프로세스가 하나 뜹니다. 그 30초는 crontab에 들어간 줄
둘이 만듭니다(아래 §새 워커에 딸려 오는 것 다섯). 화면에 조절할 칸이 없는 것도 그래서입니다.
앱과는 무관하게 돕니다. 앱을 닫아도 티켓은 계속 처리되고, 켜 둔다고 더 빨리 돌지도 않습니다.
반대로 중단으로 crontab 줄을 빼면 앱이 떠 있어도 그 워커는 다시 깨어나지 않습니다.
워커와 세션 — 한 번에 티켓 하나
티켓을 잡은 워커가 그다음에 하는 일은 세션을 띄우는 것입니다. 세션은 그 티켓을 들고 도는
에이전트 프로세스이고(기본은 claude -p), 페르소나 프로필과 협업 프로토콜에 어느 티켓을
잡았는지를 이어 붙인 프롬프트를 받습니다. 티켓 본문은 세션이 그 자리에서 직접 읽습니다.
워커는 그 세션이 끝날 때까지 같은 자리에서 기다립니다. 30분이 걸리는 티켓이면 30분을
기다립니다.
기다리는 동안에도 cron은 30초마다 그 워커를 부릅니다. 새로 뜬 쪽은 앞 실행이 아직 일하는 중인
것을 보고 SKIP 한 줄만 남기고 물러납니다. 한 워커가 세션 둘을 띄우는 일도, 같은 티켓이 두 번
디스패치되는 일도 그래서 없습니다. 그 자리를 막는 장치와 그것이 워커 수와 어떻게 이어지는지는
동시에 몇 개 돌릴까 §워커 락에 있습니다.
워커는 짧게 살고 세션은 오래 삽니다. running이 한참 서 있다면 그 워커가 띄운 세션이 아직
일하는 중이라는 뜻입니다. 지금 그 세션에 할 말이 있으면 도는 세션에 말
걸기로 가세요.
티켓을 무는 일 — 파일 이름 하나 바꾸기
"문다"는 비유가 아닙니다. 후보를 고른 워커가 실제로 하는 동작은 <해시>.md를 <해시>.wip.md로
만드는 것 하나입니다. 성공하면 그 티켓은 이 워커 것이 되고, 이미 다른 워커가 바꿔 놨으면 그
자리에서 실패해 다음 후보로 넘어갑니다.
워커 여럿이 같은 큐를 동시에 봐도 둘이 같은 티켓을 물지 않는 이유가 이것입니다. 확인하는 순간이 곧 잡는 순간이라 그 사이에 다른 워커가 끼어들 틈이 없습니다. 이름 바꾸기가 어떻게 락이 되는지는 티켓이 지나는 상태 §파일명이 곧 락인 이유에 있습니다.
후보는 열려 있고 선행 티켓(deps)이 다 끝난 티켓입니다. 그중 우선순위가 높은 것부터 집고 같은
값끼리는 만든 날짜가 오래된 쪽이 먼저입니다. 우선순위는 티켓 frontmatter의 priority:이고
1~5에서 5가 가장 높습니다. 안 적으면 3입니다.
열려 있는데도 안 물어가는 티켓이 있다면 우선순위 아래에 게이트가 둘 더 있습니다. 페르소나마다 동시에 물 수 있는 티켓 수에 상한이 걸릴 수 있고, 우선순위 1짜리는 진행중이 0건일 때만 후보입니다. 둘 다 동시에 몇 개 돌릴까 §워커 수보다 낮아지는 두 자리에 있습니다.
워커마다 트리가 따로인 이유
두 세션이 한 디렉터리에서 같은 브랜치에 커밋하면 서로의 작업을 덮습니다. 이 프로젝트에서 실제로 났던 사고입니다. 새 워커가 템플릿 워커의 작업 디렉터리 값을 그대로 물려받는 바람에 세 세션이 한 트리에서 한 브랜치에 커밋하고 있었고, 화면은 그걸 결함으로 보여주지도 못했습니다.
그래서 지금은 그 경로를 워커 이름에서 유도합니다. 이름이 곧 디렉터리라서 두 워커가 같은 트리를 가리키는 값이 애초에 생기지 않습니다.
프로젝트 폴더를 git이 관리하고 있지 않으면 트리는 만들지 않고 그 사실만 알립니다. 워커 파일도 crontab 등록도 그대로 유효합니다. 워커가 하나뿐인 배치에는 트리가 필요 없습니다.
워커 생성 한 번
워커 하나가 한 번에 티켓 하나만 뭅니다. 그래서 대기 레인에는 티켓이 쌓이는데 진행중 레인이 늘 한 장이면, 그 워커로는 부족해진 것입니다. 늘리는 자리는 워커 화면입니다(동시에 몇 개 돌릴까 참고).
우상단 워커 생성을 누르면 다이얼로그에서 정할 것이 이름 하나뿐입니다. w2처럼 짧게
지으세요. 영문·숫자·_·-만 되고, 이 이름이 그대로 파일명이자 작업 디렉터리 이름이 됩니다.
엔진이나 모델을 고르는 칸은 여기 없습니다. 새 워커는 기존 워커를 복사해서 만들어지므로 엔진 설정도 그대로 물려받습니다. 어느 CLI로 세션을 띄울지 고치는 자리는 아래 §엔진과 모델은 페르소나가 정합니다에 있습니다.
성공 화면에 등록 명령어가 없다는 것이 이 동작의 전부입니다. 만들었다는 문장과
crontab에 등록했습니다 — 30초 뒤부터 티켓을 물어갑니다.가 뜨고, 목록의 그 워커는
stopped가 아니라 idle로 섭니다. 셸을 열 일이 없습니다.
새 워커에 딸려 오는 것 다섯
- 워커 파일
<루트>/workers/<이름>.sh. 기존 워커를 템플릿으로 복사하고 755로 둡니다. - crontab 두 줄 —
:00과:30. cron은 정해 둔 시각마다 명령을 대신 실행해 주는 macOS의 기본 기능이고, crontab은 그 예약 목록입니다. 워커 하나에 두 줄인 이유는 cron이 다룰 수 있는 가장 잔 단위가 분이라서입니다. - git 워크트리
<루트>/worktrees/<이름>과 브랜치wt/<이름>. 브랜치는 같은 코드에서 갈라져 나온 작업 갈래고, 워크트리는 그 갈래를 따로 펼쳐 둔 폴더입니다. 워커마다 자기 폴더 안에서 자기 갈래로 일한다고 보시면 됩니다. .dira심링크 — 그 워크트리 안에서 큐를 가리키는 바로가기입니다. 세션이.dira/tickets/…를 상대경로로 쓰기 때문에 이게 없으면 워커가 자기 티켓을 찾지 못합니다.- 검증. 그 심링크를 되짚어 이 프로젝트의 큐로 풀리는지까지 확인합니다. 링크가 걸려 있다는 것만 보고 넘어가지 않습니다.
의존성 설치(npm install 같은 것)는 하지 않습니다. 이게 이 기능의 천장입니다. 새 워커의 작업
디렉터리에서 프론트엔드 티켓을 처음 물면 node_modules가 없습니다. 프로젝트마다 명령이 달라서
앱이 알 방법이 없습니다.
행 오른쪽 버튼 넷
행마다 reap · 스트림 · 중단(또는 재등록) · 삭제가 붙어 있습니다.
- reap은 스테일 수거를 그 자리에서 한 번 돌립니다. 세션이 죽었는데 진행중으로 남은 티켓을 대기로 되돌리는 일이고, 원래 워커가 30초마다 알아서 하는 것을 지금 당장 시키는 버튼입니다.
- 스트림은 도는 세션의 진행 기록을 엽니다.
running이면서 물고 있는 티켓을 알 때만 열리고, 참견도 그 안에서 합니다(도는 세션에 말 걸기). 못 여는 행에서도 버튼은 지워지지 않고 흐리게 남습니다. 진행 기록을 남기는 엔진은claude와grok뿐이라codex나agy로 도는 세션은 이 버튼이 끝내 열리지 않고, 참견은claude에서만 됩니다. 고장이 아니라 기능 집합이 다를 뿐이고, 티켓 수행은 같습니다.
나머지 셋이 건드리는 것은 서로 다릅니다.
| 버튼 | 하는 일 | 남는 것 |
|---|---|---|
중단 | crontab에서 그 워커 줄을 뺍니다 | 파일과 작업 디렉터리는 그대로입니다 |
재등록 | crontab 줄을 다시 넣습니다 | 중단의 정확한 역방향이고 그것뿐입니다 |
삭제 | crontab 줄을 뺀 뒤에 파일을 지웁니다 | 티켓은 하나도 지워지지 않습니다 |
중단과재등록은 같은 자리에 있고 동시에 뜨지 않습니다. 판정은 상태가 아니라 crontab이 합니다. 뺄 줄이 있으면중단, 없으면재등록입니다.중단은 도는 세션을 죽이지 않습니다. 지금 티켓을 물고 있으면 그 티켓이 끝난 뒤 멈춘다고 알려 줍니다.삭제는 순서가 정해져 있습니다. crontab 먼저, 파일 나중입니다. 뒤집으면 그 1분 사이에 cron이 없는 파일을 실행해 로그에 에러가 쌓입니다. crontab 해제가 실패하면 파일을 지우지 않고 멈춥니다.running인 워커는 삭제가 막힙니다. 돌고 있는 세션과 그 세션이 잡아 둔 자리가 붕 뜨기 때문입니다. 먼저 중단하시고, 물고 있는 티켓이 끝난 뒤에 지우세요.
엔진과 모델은 페르소나가 정합니다
워커 표에 엔진 열이 없습니다. 어느 CLI로 세션을 띄울지는 그 티켓의 persona:가 정하고,
고치는 자리는 페르소나 §디스패치 정책입니다. 엔진 값을 누르면
claude·codex·grok·agy 넷 가운데 하나와 그 안의 모델을 고릅니다. 목록에 없는 모델
이름은 직접 입력으로 넣습니다. 지정 없음으로 두면 그 티켓을 문 워커 자신의 엔진을 씁니다.
같은 절의 상한은 그 페르소나가 한 번에 쥘 수 있는 진행중 티켓 수입니다. 워커를 늘려도 그
페르소나만은 그 수를 안 넘습니다.
도중에 막혔을 때 — 남은 명령만 복사
crontab 쓰기는 macOS에서 앱 관리 승인이 걸려 있어 막힐 수 있고, 워크트리는 이름이 겹치거나
권한에서 걸릴 수 있습니다. 그래도 앱은 되돌리지 않습니다. 어디까지 갔는지와 사유를 그 자리에
보여 주고, 아직 남은 명령만 복사 버튼으로 줍니다. 이미 끝난 단계를 다시 돌리면 오히려 함정을
밟습니다.
그 명령을 이 문서에 적어 두지 않는 까닭도 같습니다. 성공하면 안 보이고, 실패한 화면이 그때의 정확한 경로를 채워서 주니까요.
앱 없이 워커를 손으로 만들고 cron에 직접 거는 절차는 부록 엔진만으로 돌리기에 있습니다.
다음은 동시에 몇 개 돌릴까입니다.