AI 에이전트 기반 CI/CD 자동화 — Hermes Agent Crons의 state.db 구조와 격리 실행 동작 원리
YAML 들여쓰기 오류 하나로 빌드가 통째로 깨지거나, 외부 SaaS 계정 권한 문제로 반나절을 낭비한 경험 — 저도 여러 번 겪었고, 다들 한 번씩은 있으실 겁니다. GitHub Actions의 on: 블록 문법을 또 찾아보며 "이걸 왜 외우고 있어야 하지?" 싶었던 그 순간들 말이죠. 기존 YAML 기반 워크플로가 답답하게 느껴졌던 적이 있으셨다면, NousResearch가 만든 자가 진화형 AI 에이전트 프레임워크 Hermes Agent의 Crons 시스템이 꽤 흥미롭게 느껴지실 겁니다. "every night at 12am, push changes to GitHub" — 이 한 문장이 야간 자동 커밋 파이프라인 전체를 정의합니다. 처음 봤을 때 솔직히 좀 당황했습니다.
핵심은 이겁니다: LLM이 직접 배포 여부를 판단하는 구조인데도, 잡 간 상태 오염 없이 격리 실행을 보장하는 메커니즘이 이미 내장되어 있습니다. 이게 그냥 "멋진 자연어 인터페이스"가 아니라 실제로 CI/CD 대안으로 진지하게 고려해볼 만한 이유입니다.
이 글에서는 Hermes Crons의 내부 저장소 state.db가 어떤 구조인지, 격리 실행이 어떻게 보장되는지를 뜯어봅니다. CI 헬스 모니터링, 야간 코드 리뷰 요약, 멀티스텝 배포 파이프라인까지 실제 코드와 함께 살펴봅니다.
TL;DR
- Hermes Agent Crons는 자연어 프롬프트 한 문장으로 스케줄 작업을 정의하고, LLM이 실행 방식을 판단합니다.
- 모든 실행 이력은
~/.hermes/state.db(SQLite WAL + FTS5)에 자동 저장되며, 잡마다 완전히 새로운 세션이 생성됩니다.- LLM 비용 없이 스크립트만 실행하는
no_agent모드를 활용하면 비용 효율적인 하이브리드 파이프라인을 구성할 수 있습니다.
핵심 개념
Hermes Agent Crons가 CI/CD와 다른 이유
기존 CI/CD 파이프라인은 "무엇을 실행할지"를 YAML로 미리 명시해야 합니다. 테스트 실패 원인을 분석하거나, 배포를 중단할지 계속할지 맥락을 보고 판단하는 로직은 쉘 스크립트나 복잡한 조건문으로 직접 구현해야 하죠. 반면 Hermes의 Crons는 LLM이 잡의 실행 주체가 되기 때문에, 프롬프트 한 문장에 담긴 의도를 에이전트가 해석하고 실행 방식을 스스로 결정합니다.
본격적인 예시를 보기 전에 Skills 개념을 먼저 짚어둘 필요가 있습니다. Skills는 크론 잡에 첨부 가능한 도구 모음입니다. git-ops, docker-build 같은 내장 또는 커스텀 Skills를 잡에 연결하면, 에이전트가 해당 도구를 실제로 호출해 Git 커밋이나 Docker 이미지 빌드 같은 작업을 수행할 수 있습니다. "배포해줘"라는 프롬프트가 실제로 동작하는 건 이 Skills 덕분이죠. Skills 자체를 직접 작성하는 방법은 이 글의 범위를 넘어서므로 별도로 다루겠지만, 일단 "에이전트가 쓸 수 있는 도구 세트"라고 이해하면 충분합니다.
{
"name": "nightly-deploy",
"schedule": "0 2 * * *",
"skills": ["git-ops", "docker-build"],
"script": "~/.hermes/scripts/pre_check.sh",
"workdir": "/srv/myapp",
"delivery": {"channel": "slack", "target": "#deploys"}
}전통적인 CI 잡과 크게 달라 보이지 않을 수 있습니다. 차이는 script 필드의 stdout이 프롬프트에 주입된 뒤, 에이전트가 그 내용을 읽고 "배포를 지금 진행해도 되는지"를 스스로 판단한다는 점입니다. YAML 조건문이 아니라 LLM의 추론이 결정 로직을 담당합니다.
state.db — Hermes의 모든 실행 기록이 모이는 곳
Hermes는 ~/.hermes/state.db라는 단일 SQLite 파일에 모든 세션과 메시지를 저장합니다. WAL(Write-Ahead Logging) 모드로 운용되기 때문에 동시 읽기/쓰기가 안정적으로 처리되고, 파일 기반임에도 프로덕션 수준의 신뢰성을 제공합니다.
| 테이블 | 역할 |
|---|---|
sessions |
세션 메타데이터 — 플랫폼, 모델, 시작/종료 시각, 토큰 수, 비용, 제목 |
messages |
전체 메시지 히스토리 — role, content, tool_calls, 추론 토큰 포함 |
messages_fts |
FTS5 가상 테이블 — content·tool_name·tool_calls 자동 인덱싱 |
schema_version |
마이그레이션 버전 추적 단일 행 |
messages_fts는 FTS5(Full-Text Search 5) 가상 테이블로, 메시지가 기록될 때 트리거로 자동 동기화됩니다. "content table" 방식이라 인덱스만 별도로 저장해 DB 용량을 최소화하면서도 수개월치 실행 로그를 밀리초 단위로 검색할 수 있습니다. 에이전트 자신도 내장 session_search 도구로 과거 대화 전체를 전문 검색할 수 있죠. 저도 처음엔 "SQLite에 이런 게 된다고?" 싶었는데, 실제로 써보니 수만 개 메시지도 순식간에 검색되더군요.
WAL 모드(Write-Ahead Logging): 쓰기 작업을 별도 WAL 파일에 먼저 기록한 뒤 체크포인트 시 메인 DB에 반영합니다. 읽기와 쓰기가 서로 블로킹하지 않아 동시성이 대폭 향상됩니다.
한 가지 중요한 주의사항이 있습니다. 공식 문서는 state.db를 외부에서 직접 쿼리하는 것을 명시적으로 금지합니다. 릴리스 간 내부 스키마가 변경될 수 있기 때문입니다. 감사 목적으로 데이터가 필요하다면 공식 API나 session_search 도구를 통해 접근하는 것이 안전합니다.
격리 실행 메커니즘 — 잡 간 상태 오염이 없는 이유
실무에서 크론 잡을 운용하다 보면 "이전 잡의 상태가 다음 잡에 영향을 줬나?"라는 의심을 하게 되는 상황이 생깁니다. Hermes는 이 문제를 구조적으로 차단합니다.
스케줄러가 잡을 실행할 때마다 대화 히스토리와 컨텍스트가 완전히 비어있는 fresh AIAgent 세션을 새로 생성합니다. 이전 잡이 어떤 상태였든, 어떤 실패를 겪었든 다음 잡에는 전혀 영향을 주지 않습니다. 컨테이너 기반 CI 러너가 매 실행마다 새 이미지를 띄우는 것과 유사한 보장을 파일 시스템 수준에서 제공하는 셈이죠.
중복 실행 방지는 ~/.hermes/cron/.tick.lock 파일 락으로 처리합니다. 크로스 프로세스 파일 락이라 동일 머신에서 여러 Hermes 인스턴스가 뜨더라도 같은 잡이 동시에 두 번 실행되는 상황을 막아줍니다.
~/.hermes/
├── state.db # 전체 세션·메시지 영속 저장소
├── cron/
│ ├── .tick.lock # 중복 실행 방지 파일 락
│ ├── jobs/ # 잡 정의 JSON 파일들
│ └── output/
│ └── {job_id}/
│ └── {timestamp}.md # 잡별 실행 로그
└── scripts/ # no_agent 모드용 쉘 스크립트격리(Isolation): 각 잡이 독립적인 세션에서 시작되므로, 잡 A의 실패나 사이드 이펙트가 잡 B의 실행 환경을 오염시키지 않습니다.
실전 적용
예시 1: CI 헬스 모니터링 게이트 (no_agent 워치독 모드)
v0.13.0 Tenacity 릴리스에서 도입된 no_agent 옵션은 솔직히 제가 가장 반가웠던 기능입니다. LLM 추론이 필요 없는 단순 체크 잡에서 API 비용을 아예 안 쓸 수 있거든요. JSON 설정에서는 "no_agent": true로, Python API에서 직접 잡을 생성할 때는 no_agent=True로 지정하면 됩니다.
#!/bin/bash
# ~/.hermes/scripts/ci_check.sh
# jq -r 플래그로 따옴표 없는 raw 문자열로 추출
STATUS=$(curl -s https://api.github.com/repos/org/repo/actions/runs \
| jq -r '.workflow_runs[0].conclusion')
[ "$STATUS" != "success" ] && echo "CI FAILED: $STATUS"
# stdout이 비어있으면 묵음 처리 — 성공은 알림 없음{
"name": "ci-health-watchdog",
"schedule": "*/5 * * * *",
"script": "~/.hermes/scripts/ci_check.sh",
"no_agent": true,
"delivery": {
"channel": "slack",
"target": "#alerts",
"only_if_output": true
}
}| 구성 요소 | 역할 |
|---|---|
"no_agent": true |
LLM 호출 없이 스크립트 stdout만 직접 전달 |
only_if_output: true |
빈 stdout은 묵음 처리 — "문제 있을 때만 알림" 패턴 |
*/5 * * * * |
5분마다 실행 (최소 단위는 60초) |
이 패턴의 강점은 API 비용이 0이라는 점입니다. CI 상태 확인, 디스크 용량 경보, 서비스 헬스체크처럼 스크립트로 해결되는 잡은 "no_agent": true로 등록해두면 비용 걱정 없이 높은 빈도로 돌릴 수 있습니다.
예시 2: 야간 자동 코드 리뷰 요약
매일 자정 PR 목록을 조회하고 변경 사항을 요약·위험 항목을 분류해 Slack으로 전달하는 잡입니다. workdir을 레포지토리 루트로 지정하면 AGENTS.md나 CLAUDE.md가 자동으로 주입되어 에이전트가 프로젝트 컨텍스트를 파악합니다.
{
"name": "nightly-pr-review-summary",
"schedule": "0 0 * * *",
"skills": ["github-mcp"],
"workdir": "/path/to/myrepo",
"prompt": "오늘 열린 PR 목록을 조회하고, 각 PR의 변경 사항을 요약한 뒤 배포 위험도가 높은 항목을 분류해서 Slack #dev-review 채널에 전송해줘",
"delivery": {
"channel": "slack",
"target": "#dev-review"
}
}기존 CI/CD로 이걸 구현하려면 GitHub API 호출 → diff 파싱 → 위험도 분류 로직 → Slack 메시지 포맷팅을 전부 스크립트로 작성해야 합니다. Hermes에서는 프롬프트 한 문장이 그 모든 로직을 대신합니다. LLM API 비용이 발생하지만, 이 정도 작업을 직접 스크립트로 구현하는 개발 시간과 비교하면 충분히 합리적인 트레이드오프라고 생각합니다.
예시 3: context_from 체인으로 의존 잡 연결
context_from 필드를 활용하면 잡 A의 마지막 실행 stdout을 잡 B의 프롬프트 앞에 자동 삽입해서 순차 파이프라인을 구성할 수 있습니다. 주입되는 내용은 직전 잡의 전체 텍스트 출력이며, 길이 제한이 있으므로 앞 잡의 출력이 지나치게 길면 잘릴 수 있다는 점을 감안해야 합니다.
[
{
"name": "run-tests",
"schedule": "0 1 * * *",
"skills": ["pytest-runner"],
"prompt": "테스트 스위트 전체를 실행하고 실패 항목과 커버리지를 요약해줘"
},
{
"name": "deploy-decision",
"schedule": "30 1 * * *",
"context_from": "run-tests",
"skills": ["git-ops", "docker-build"],
"workdir": "/srv/myapp",
"prompt": "테스트 결과를 바탕으로 프로덕션 배포 진행 여부를 판단하고, 배포가 안전하다면 실행해줘"
}
]| 필드 | 동작 |
|---|---|
context_from: "run-tests" |
run-tests 잡의 마지막 실행 stdout을 프롬프트 앞에 자동 삽입 |
"30 1 * * *" |
첫 번째 잡(01:00) 완료 예상 시간에 여유분을 더해 30분 뒤(01:30)로 설정 |
이 패턴이 흥미로운 건, 배포 여부 판단 로직을 "테스트 실패가 0개이고 커버리지가 80% 이상이면 배포"처럼 하드코딩하는 대신, 에이전트가 테스트 결과의 맥락 — 어떤 모듈이 실패했는지, 임시 스킵된 테스트가 있는지 등 — 을 읽고 판단한다는 점입니다. 저도 처음엔 "이게 실제로 정확하게 판단하나?"라는 의심이 있었는데, 구조화된 프롬프트와 테스트 리포트를 함께 주입하면 생각보다 정확하더군요.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 설정 단순성 | YAML 없이 자연어 프롬프트 한 문장으로 스케줄 작업 정의 |
| LLM 판단력 | 테스트 실패 원인 분석, 배포 중단 여부 등 규칙 기반 CI/CD가 처리 못하는 맥락 판단 가능 |
| 외부 의존 없음 | 서버리스·SaaS 계정 불필요, ~/.hermes/ 단일 디렉터리로 완결 |
| 격리 신뢰성 | 잡 간 상태 오염 없음, 파일 락으로 중복 실행 방지 보장 |
| 멀티채널 전달 | Slack·Discord·Email 등 다채널 결과 전송 내장 |
| FTS5 감사 추적 | 모든 실행 이력이 state.db에 전문 검색 가능한 형태로 자동 보존 |
| no_agent 비용 최적화 | 단순 스크립트 잡은 LLM 호출 없이 실행 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| state.db 스키마 불안정 | 릴리스 간 내부 스키마 변경 가능, 직접 쿼리 공식 금지 | 공식 API와 session_search 도구만 사용 |
| 60초 최소 단위 | 스케줄러 틱 간격이 60초 — 초 단위 정밀도 불가 | 초 단위 실행이 필요한 잡은 전통 크론 유지 |
| FTS5 의존성 | 일부 Python 3.11 macOS 빌드에서 SQLite FTS5 누락 이슈 | Docker 공식 이미지 사용으로 환경 통일 |
| 단일 머신 한계 | 기본 구성이 로컬 파일 시스템 기반 — 분산 팀 협업 어려움 | Pluggable SessionDB RFC(#23717) 완성 대기 또는 Docker Compose로 공유 환경 구성 |
| LLM 비용 | no_agent 외 모든 잡은 LLM API 호출 발생 |
고빈도 단순 잡은 "no_agent": true 적극 활용 |
| 디버깅 가시성 | 실패 로그가 ~/.hermes/cron/output/{job_id}/{timestamp}.md에만 존재 |
Web Dashboard 또는 별도 로그 집계 도구 연동 권장 |
FTS5(Full-Text Search 5): SQLite에 내장된 전문 검색 확장 모듈입니다. LIKE 검색보다 훨씬 빠르고, 토큰화·랭킹·구문 검색을 지원합니다. 단, 컴파일 시 활성화되어 있어야 사용 가능하며 일부 패키지 빌드에서 누락될 수 있습니다.
실무에서 가장 흔한 실수
-
state.db를 직접 쿼리해서 감사 로그를 뽑으려는 시도 — 스키마가 언제든 바뀔 수 있고 공식적으로도 금지된 사용법입니다. 감사 추적이 필요하다면
session_search도구나 Web Dashboard의 세션 뷰어를 활용하는 것이 안전합니다. -
모든 잡을 LLM 에이전트로 돌리는 설계 — 디스크 체크나 HTTP 핑처럼 단순한 잡에도
"no_agent"옵션을 빠뜨리면 API 비용이 불필요하게 쌓입니다. 판단이 필요 없는 잡은 처음부터"no_agent": true로 분류해두는 습관이 중요합니다. -
context_from체인의 타이밍 미스 — 앞 잡이 아직 실행 중인데 뒤 잡의 스케줄이 트리거되면 이전 실행의 출력을 주입받게 됩니다. 앞 잡의 완료 예상 시간에 10~15분 이상 여유를 더해 뒤 잡의 스케줄을 잡아두는 것이 좋습니다.
마치며
Hermes Agent의 Crons 시스템은 "YAML로 무엇을 실행할지 명시"하는 CI/CD 패러다임에서, LLM이 직접 배포 여부를 판단하는 에이전트 실행 구조로의 전환을 실제 프로덕션 수준에서 시도해볼 수 있는 도구입니다. state.db의 FTS5 기반 감사 추적과 잡별 완전 격리 세션이라는 두 가지 기반 덕분에, 기존 CI/CD가 제공하는 재현성과 추적 가능성을 포기하지 않으면서도 LLM의 판단력을 파이프라인에 녹여낼 수 있습니다.
지금 바로 시작해볼 수 있는 3단계:
- 먼저
no_agent워치독 잡 하나를 등록해보는 것을 권장합니다.pip install hermes-agent로 설치 후,~/.hermes/scripts/에 간단한 헬스체크 스크립트를 작성하고,~/.hermes/cron/jobs/디렉터리에"no_agent": true로 설정한 JSON 잡 파일을 올려보는 것입니다. LLM API 비용 없이 크론 시스템의 동작 방식을 직접 확인할 수 있습니다. hermes dashboard명령어로 Web Dashboard를 띄워보면 도움이 됩니다. 크론 매니저·라이브 로그 뷰어·세션 관리가 통합된 로컬 대시보드에서~/.hermes/cron/output/하위 로그 파일을 UI로 확인할 수 있습니다.- 기존 CI에서 가장 자주 손이 가는 잡 하나를 LLM 에이전트 잡으로 포팅해보는 것도 좋습니다.
context_from없이 단독 잡으로 먼저 시작하고, 동작이 안정적으로 확인되면context_from체인으로 연결해나가는 순서가 디버깅하기 편합니다.
참고 자료
공식 문서
- Scheduled Tasks (Cron) | Hermes Agent 공식 문서
- Cron Internals | Hermes Agent 개발자 가이드
- Script-Only Cron Jobs (No LLM) | Hermes Agent
- Sessions | Hermes Agent
소스코드 & 릴리스
- hermes_state.py 소스코드 — NousResearch/hermes-agent
- Release v0.13.0 Tenacity — NousResearch/hermes-agent
- RFC: Pluggable SessionDB Provider — GitHub Issue #23717
심층 분석 & 커뮤니티