엔진만으로 돌리기

앱을 쓰신다면 이 장은 건너뛰셔도 됩니다. 부록입니다. 워커 화면에서 워커를 하나 추가하면 워커 파일 작성과 crontab 등록을 앱이 알아서 처리합니다. 정규 절차는 워커 장입니다.

여기부터는 .dmg 없이 git clone으로 엔진만 받은 경우입니다(설치 §앱 없이 엔진만 쓰기). 화면이 없으므로 워커 파일 작성과 crontab 등록을 직접 하고, 결과도 셸에서 확인합니다.

1. 워커 파일 하나 만들기

프로젝트에 dira를 연결하는 데 필요한 파일은 이것 하나뿐입니다. 반드시 있어야 하는 내용은 tick.sh를 source하는 한 줄뿐입니다. 이 파일이 놓인 위치가 곧 큐 루트입니다. <루트>/workers/w1.sh라면 workers의 부모 디렉터리가 루트이므로, 어느 설정값에도 루트 경로를 적을 필요가 없습니다.

mkdir -p ~/Projects/myproject/.dira/workers
cat > ~/Projects/myproject/.dira/workers/w1.sh <<'EOF'
#!/bin/bash
. "$HOME/Projects/dira/tick.sh"
EOF
chmod +x ~/Projects/myproject/.dira/workers/w1.sh
bash

tick.sh 자체는 직접 실행하지 않습니다. 실행해 보면 rc=2로 거절하는데, 워커 파일이 어느 위치에 놓였는지 모르면 루트도 알 수 없기 때문입니다. 실행의 진입점은 언제나 워커 파일입니다. 엔진 레포에 있는 worker.sh.example은 추가할 수 있는 설정값을 주석으로 전부 적어 둔 예시입니다. 위 두 줄 대신 그 예시를 복사해 쓰셔도 됩니다.

<루트>/
  tickets/<hash>.md          <- 큐. 평면이다
  personas/<이름>/PROFILE.md
  protocols/AGENTS.md
  ontology/                  <- 있으면 프롬프트가 자리만 알려 준다. 없어도 된다
  workers/w1.sh w2.sh        <- 워커. 여기 있는 파일 개수가 곧 동시성이다
  workers/runner.log logs/   <- 디스패치 기록·세션별 출력
text

페르소나와 프로토콜의 출발점이 필요하면 템플릿을 복사하세요. 템플릿이 없어도 엔진은 그대로 동작하고, 페르소나가 없는 평범한 에이전트가 티켓을 처리합니다.

cp -r ~/Projects/dira/templates/* ~/Projects/myproject/.dira/
bash

2. 큐를 프로젝트 밖에 둘 때 - TICKET_CWD

루트가 프로젝트 안에 있어야 할 이유는 없습니다. 여러 사람이 같은 큐를 함께 쓴다면 마운트된 공유 드라이브(구글드라이브 등)에 workers/를 두고 cron이 그 경로를 가리키게 하세요.

세션은 기본적으로 루트의 부모에서 시작합니다(<프로젝트>/.dira가 루트면 <프로젝트>). 큐를 프로젝트 밖에 두면 이 기본값이 의미를 잃습니다. TICKET_CWD로 작업 디렉터리를 직접 지정하세요.

mkdir -p "/Volumes/TeamShare/myteam/.dira/workers"
cat > "/Volumes/TeamShare/myteam/.dira/workers/w1.sh" <<'EOF'
#!/bin/bash
TICKET_CWD="$HOME/Projects/myproject"
. "$HOME/Projects/dira/tick.sh"
EOF
chmod +x "/Volumes/TeamShare/myteam/.dira/workers/w1.sh"
bash

마운트가 연결돼 있지 않으면 워커 파일 자체가 없습니다. cron이 그 경로를 불러도 실행할 스크립트가 없으므로 아무 로그도 남기지 않고 조용히 실패합니다. 큐가 비어서 돌지 않는 것이 아니라 파일이 없어서 돌지 않는 것입니다. "빈 큐라 안 도는구나"로 넘기지 마시고 ls "/Volumes/TeamShare/myteam/.dira/workers"로 워커 파일이 실제로 보이는지부터 확인하세요.

3. 엔진 바꾸기 - TICKET_ENGINE

기본 엔진은 claude -p입니다. 워커는 프로젝트를 모르는 엔진 코드(tick.sh)를 불러 쓰는 역할이므로, 다른 CLI 에이전트로 바꾸는 작업도 이 파일에 한 줄만 추가하면 끝납니다.

#!/bin/bash
TICKET_ENGINE=(codex exec --json "{prompt}")
. "$HOME/Projects/dira/tick.sh"
bash

{prompt} 자리에는 실행 직전 조립된 프롬프트가 들어갑니다. 응답 JSON의 실제 session_id로 frontmatter를 정정하는 단계는 claude 전용이므로, 다른 엔진에서는 그 단계를 건너뜁니다. 추가할 수 있는 설정값 전체는 워커 환경변수에 있습니다.

4. cron에 걸기 전 손으로 한 번 돌려 보기

cron부터 걸면 첫 실패가 인증 문제와 겹쳐 원인이 흐려집니다. 먼저 셸에서 한 번 실행해 결과를 확인하세요.

dryrun은 세션을 실행하지 않고 선정 결과와 조립된 프롬프트만 출력합니다. 세션을 띄우기 전에 워커가 그 티켓을 실제로 고르는지, 프롬프트가 어떤 내용으로 채워지는지 여기서 직접 확인하시면 됩니다.

~/Projects/myproject/.dira/workers/w1.sh dryrun
bash

인자 없이 부르면 실제 1회 디스패치입니다. 워커가 티켓을 가져가고, 세션을 띄우고, 그 세션이 끝날 때까지 기다립니다.

~/Projects/myproject/.dira/workers/w1.sh
bash

list는 열린 티켓 큐 상태(대기, 할당됨, deps 대기)를 출력합니다. 티켓이 큐에서 사라졌다면 방금 실행한 세션이 그 티켓을 처리하고 끝낸 것입니다.

~/Projects/myproject/.dira/workers/w1.sh list
bash

dryrun과 list는 큐 전체를 보므로 같은 루트의 어느 워커 파일로 불러도 결과가 같습니다. 인자 없는 디스패치는 다릅니다. 티켓을 실제로 점유하는 명령이므로, 호출한 워커의 이름이 그 티켓의 owner에 기록됩니다(CLI 참고).

5. crontab 두 줄

여기까지는 사람이 그 자리에 앉아 직접 명령을 실행한 경우입니다. 1분마다 자동으로 실행되게 하려면 cron에 등록합니다. cron은 로그인 키체인에 접근하지 못하므로 이 시점부터 장기 토큰이 필요하고 (인증 §앱 없이 — 엔진만 쓸 때), 큐 루트가 클라우드 마운트라면 전체 디스크 접근 권한도 있어야 합니다.

* * * * * $HOME/Projects/myproject/.dira/workers/w1.sh >> $HOME/Projects/myproject/.dira/workers/cron.log 2>&1
* * * * * sleep 30; $HOME/Projects/myproject/.dira/workers/w1.sh >> $HOME/Projects/myproject/.dira/workers/cron.log 2>&1
text

crontab -e로 위 두 줄을 넣으세요. 워커 하나에 cron 두 줄입니다.

두 줄인 이유

cron이 지정할 수 있는 가장 짧은 시간 단위는 분입니다(man 5 crontab). 30초마다 큐를 보게 하려면 :00에 한 줄, :30에 한 줄이 필요합니다. 두 번째 줄은 sleep 30으로 30초를 기다린 뒤에 같은 워커를 실행합니다.

한 줄에 ;로 붙이면 안 되는 이유

워커는 동기 프로세스입니다. 셸은 w1.sh가 끝나야 sleep 30으로 넘어갑니다. 한 줄로 이으면 뒤쪽 호출이 앞 세션이 끝나기를 기다리게 됩니다.

# 하면 안 되는 것
* * * * * w1.sh; sleep 30; w1.sh
text

앞 호출이 티켓 하나를 30분짜리 세션으로 점유했다면, 뒤쪽 호출은 30초 뒤에 오지 않고 30분 뒤에야 실행됩니다. 30초 폴링이 결국 순차 실행으로 바뀌는 것입니다. 두 줄로 나눠야 cron 스케줄러가 각 줄을 독립된 프로세스로 :00과 :30에 각각 띄웁니다. 같은 분에 앞 실행이 아직 안 끝났으면 워커 락이 뒤 실행을 SKIP시키므로, 두 실행이 같은 티켓을 동시에 점유할 일은 없습니다.

중지

crontab에서 그 두 줄을 지우면 됩니다. 워커 파일까지 지우실 필요는 없습니다. 다시 등록할 때 그 파일을 그대로 씁니다.

로그

  • <루트>/workers/runner.log - 디스패치 진행 상황(선정, claim, SKIP 등)
  • <루트>/workers/logs/<시각>-<워커>-<해시>.log - 세션별 실제 출력

위 crontab 줄이 리다이렉트하는 cron.log는 워커 스크립트 자체의 표준출력과 표준에러입니다. 정상 동작 중에는 대개 비어 있고, 셸 자체에 문제가 생겼을 때만 내용이 남습니다. 티켓이 실제로 어떻게 처리됐는지는 runner.log와 logs/에서 확인하세요(로그 읽는 법).

안 도는 것 같으면 트러블슈팅을 보세요. 그 장은 화면에서 확인하는 방법을 먼저 안내하지만, 각 증상을 셸에서 확인하는 명령은 화면 없이도 그대로 쓸 수 있습니다.

다음은 워커 환경변수입니다.