트리거 리허설
개요
이벤트에 스스로 깨어나는 에이전트는 트리거 규칙만큼만 안전합니다. 에이전트 자체가 아무리 조심해도 하루 예산을 모두 소진할 수 있습니다. 문제는 보통 한 단계 위에 있습니다:
- 규칙이 자신의 작업이 생성한 이벤트에 반응하여 계속해서 다시 실행됩니다.
- 두 규칙이 두 번째 시스템을 통해 서로를 피드백합니다 (에이전트가 댓글을 달고, 댓글이 동기화되며, 동기화가 웹훅을 발생시킴).
- 시끄러운 소스가 2분 안에 10개의 편집을 보내고 각각이 새로운 실행을 시작합니다.
- "가끔 확인"을 의미하는 크론 라인이 하루에 288번 실행됩니다.
- 두 규칙이 동일한 이벤트를 처리하여 작업을 두 번 수행합니다.
- 환불, 삭제 또는 메시지를 보내는 규칙이 누구의 허락도 없이 실행됩니다.
이러한 문제는 에이전트를 하나의 예제로 테스트할 때 나타나지 않습니다. 실제 트래픽이 도착할 때 나타납니다. Trigger Rehearsal은 실제 트래픽을 일찍, 종이 위에서 만듭니다: 규칙을 하나의 중립적인 형식으로 작성하고, 하루 또는 일주일 분량의 이벤트를 재생하며, 아무것도 활성화되기 전에 타임라인을 읽습니다.
사용 시기
- 사람이 프롬프트를 입력하지 않고 에이전트가 작업을 시작하도록 허용하는 규칙을 켜기 전에.
- 기존 규칙 집합에 새 규칙을 추가할 때 (새 규칙이 이전에 열려 있던 루프를 닫을 수 있음).
- 백그라운드 에이전트가 예상보다 훨씬 자주 또는 훨씬 적게 실행될 때.
- 디바운스 윈도우, 병렬 제한 및 일일 상한을 선택할 때, 추측 대신 숫자를 원할 때.
- 리뷰어가 "이 Repo에 한 시간에 50개의 이슈가 들어오면 어떻게 되나요?"라고 물을 때.
사용하지 않는 경우: 에이전트가 실행 내부에서 무엇을 하는지 확인 (프롬프트 또는 스킬 질문), 또는 이벤트 소스 자체를 부하 테스트.
규칙 시트
모든 것은 rules.json으로 변환됩니다. 번역을 정직하게 유지하세요: 실제 규칙당 하나의 시트 규칙, 그리고 실제로 존재하는 가드(보호 장치)를 작성하고, 추가하려는 가드는 작성하지 마세요.
{
"tz": "Asia/Shanghai",
"run_s": 180,
"external_sources": ["github"],
"budget": {"runs_per_day": 40, "cost_per_run": 0.15, "cost_per_day": 6},
"rules": [
{
"name": "triage-new-issue",
"on": {"source": "github", "types": ["issues.opened", "issues.edited"]},
"where": {"repo": "me/app"},
"key": "subject",
"debounce_s": 300,
"parallel": 1,
"max_per_day": 20,
"unless_actor": ["my-bot"],
"unless_label": "triaged",
"emits": [{"source": "github", "types": ["issues.labeled"], "actor": "my-bot", "labels": ["triaged"]}],
"sensitive": [],
"asks": false
},
{"name": "morning-digest", "on": {"cron": "0 9 * * 1-5"}, "max_per_day": 1}
]
}
| 필드 | 의미 |
|---|---|
on.source + on.types | 규칙을 시작하는 이벤트. types를 생략하면 해당 소스의 모든 유형과 일치합니다. |
on.cron | 시트의 tz에 있는 5필드 크론 라인. 규칙은 source 또는 cron 중 하나를 가집니다. |
where | 값과 같아야 하거나 (또는 목록 중 하나여야 하는) 이벤트 필드. |
key | 주제 (이슈 번호, 티켓 ID, URL)를 명명하는 이벤트 필드. 반복 병합에 사용됩니다. |
debounce_s | 한 주제에 대해 마지막 이벤트 후 이 시간만큼 기다린 다음 한 번 실행합니다. |
parallel | 이 규칙의 동시 활성 실행 수. 초과 실행은 대기합니다. |
max_per_day | 로컬 일 기준 상한. 초과 실행은 드롭됩니다. |
run_s | 일반적인 실행 시간, 대기열에 사용됩니다. 시트 값으로 대체된 다음 120으로 대체됩니다. |
unless_actor, unless_label | 가드: 이러한 액터가 만들거나 이 레이블이 있는 이벤트를 건너. |
emits | 실행 자체가 생성하는 이벤트 (댓글, 레이블, 상태 변경, 후속 이벤트). 포함할 액터와 레이블을 작성하세요. |
sensitive + asks | 사람이 필요한 작업 (삭제, 지출, 재시작, 보내기). asks: true는 실행이 중지되고 먼저 묻는다는 의미입니다. |
external_sources | 외부 세계에서 공급되는 소스. 다른 모든 것은 규칙에 의해 방출되어야 합니다. |
budget | 판결에 사용되는 선택적 일별 실행 횟수, 실행당 비용 및 일별 비용. |
단계별 가이드
- 실제 규칙 수집. 설명이 아닌 실제 구성을 읽으세요. 에이전트 허브 규칙 파일, 저장된 루틴, CI 워크플로우
on:블록, crontab 라인, 코드리스 자동화 화면. 다른 규칙이 감시하는 소스에 다시 쓰는 모든 것을 기록하세요. rules.json으로 변환.python3 scripts/rehearse.py new rehearsal/로 시작하세요. 모든 규칙에 대해 세 가지 질문을 하고 필드를 채우세요: 무엇이 깨우는가, 한 번의 실행이 무엇을 다시 쓰는가, 그리고 자신의 쓰기로 인해 깨어나는 것을 막는 것은 무엇인가. 세 번째 질문에 대한 답이 "없음"이면 가드를 비워두세요. 리허설은 그것을 잡기 위한 것입니다.- 린트.
python3 scripts/rehearse.py lint rehearsal/rules.json. 재생하기 전에 모든 HOLD를 수정하세요. REVIEW 항목은 숫자를 기다릴 수 있습니다. - 이벤트 수집. 가장 좋은 방법: 소스에서 실제 바쁜 하루를 내보내세요 (웹후크 전송 로그, 이슈 타임라인, 헬프데스크 감사 로그)를
events.jsonl로, 한 줄에 하나의 JSON 객체로, 최소t,source,type을 포함하고key와where가 사용하는 필드를 포함하세요. 기록이 없으면 의도적으로 짧은 합성 버스트를 작성하세요: 3분 안에 10개의 편집, 한 번에 두 개의 주제, 자신의 봇이 만든 이벤트. - 전체 일별 재생.
python3 scripts/rehearse.py replay rehearsal/rules.json rehearsal/events.jsonl --from 2026-11-02T00:00:00Z --to 2026-11-03T00:00:00Z --md rehearsal/report.md.--from/--to를 전달하여 짧은 창으로 인해 일별 숫자가 부풀려지지 않도록 하세요. - 판결만이 아닌 타임라인 읽기. 몇 분 간격으로 같은 주제에 대한 실행을 찾으세요 (디바운스 누락 또는 너무 짧음), 깊이가 증가하는 체인 (루프), 긴 대기 (병렬 수준 너무 낮음), 드롭 (실제 작업에 비해 상한이 너무 낮거나 규칙이 노이즈에 실행된다는 신호).
- 조정 및 반복. 한 번에 하나의 노브를 변경하고 동일한 이벤트를 재생하여 변화가 명확히 해당 변경에서 비롯되도록 하세요.
- 결정. 현실적인 하루에 대한 마지막 재생이 ARM인 경우에만 활성화하세요. 최종
rules.json, 이벤트 파일 및report.md를 규칙이 켜진 이유에 대한 기록으로 함께 보관하세요.
판결
- ARM: HOLD 또는 REVIEW 발견 사항 없음. 작성된 대로 활성화해도 안전합니다.
- TRIM: 실행을 낭비하거나 작업을 건너뛸 수 있음 (키 없음, 상한 없음, 중복, 상한이 실제 이벤트를 드롭함, 예산 근접). 트리밍 후에만 활성화하거나, 서면 사유와 함께 활성화하세요.
- HOLD: 활성화하지 마세요. 자기 루프, 보호되지 않은 사이클, 깊이 5를 넘어 실행된 체인, 묻지 않고 수행되는 민감한 작업, 예산 초과 또는 깨진 크론 라인.
종료 코드는 판결을 따릅니다 (0, 1, 2; 3은 잘못된 입력을 의미). 따라서 리허설은 CI에서 구성 변경을 게이트할 수 있습니다.
예제
지원 팀이 에이전트가 헬프데스크 티켓에 응답하도록 하려고 합니다. 그들의 시트 (examples/loopy/)에는 답변을 게시하는 규칙이 있으며, 이는 티켓을 업데이트하고, 이는 동일한 규칙과 일치합니다. 평범한 하루의 재생 결과는 다음과 같습니다:
HOLD self-loop answer-ticket: 규칙의 자체 출력이 unless_actor/unless_label 가드 없이 자체 트리거와 일치
HOLD runaway answer-ticket: 체인이 깊이 5를 넘어 계속 재실행됨 (3회 차단)
HOLD sensitive-unasked refund-check: 민감한 작업 ['refund'] 선언했지만 'asks'가 true가 아님
HOLD over-runs-budget -: 100.0/일 (예산 30/일 대비)
REVIEW cron-tight inbox-poll: 최대 720회/일 실행
VERDICT HOLD
세 번의 티켓 업데이트가 18번의 답변 실행으로 이어졌습니다. 수정은 간단합니다: 답변 규칙에 "unless_actor": ["support-bot"]을 추가하고, 환불 규칙에 "asks": true를 설정하며, 2분 폴링을 받은 편지함이 실제로 변경된 경우에만 inbox.changed를 발생시키는 스크립트로 대체하세요 (examples/loopy/rules.fixed.json 참조). 같은 날의 다음 재생은 100회 실행에서 4회로 줄어들고 판결은 TRIM으로 이동합니다. 남은 발견 사항 하나는 응답과 환불 확인 모두 환불 티켓을 본다는 것인데, 이는 의도된 것이므로 보고서 옆에 한 줄의 서면 사유와 함께 수락됩니다.
팁
- 가장 좋은 단일 루프 방어는 실행 후에 거짓이 되는 조건입니다: 실행이 추가하고 규칙이 제외하는 레이블, 또는 규칙이 더 이상 일치하지 않는 상태.
- 교차 시스템 루프는 놓치기 쉽습니다. 에이전트가 시스템 A에 쓰고 시스템 A가 B로 동기화되면, 동기화된 이벤트를 표시할 액터와 함께
emits아래에 나열하세요. - 디바운스는 설계상 지연을 추가합니다. 사람이 편집 버스트를 완료하는 데 걸리는 시간에서 창을 선택하고, 누군가가 답변을 기다리는 곳에서는 짧게 유지하세요.
- 디바운스는 작업 중복 제거가 아닙니다. 되돌릴 수 없는 작업의 경우, 실행 자체는 작업 전에 여전히 "이미 완료되었나?"를 확인해야 합니다.
- 일일 상한은 퓨즈이지 계획이 아닙니다. 보통 하루 재생이 상한에 도달하면 규칙이 너무 광범위합니다.
- 몇 분마다 폴링하는 것은 일반적으로 상태를 비교하고 변경 시 하나의 이벤트를 발생시키는 작은 스크립트로 더 저렴합니다. 에이전트는 새로운 것이 있을 때만 깨어납니다.
- 규칙이 추가될 때마다 리허설을 다시 실행하세요. 대부분의 루프는 첫 번째 규칙이 아니라 두 번째 규칙에 의해 생성됩니다.