Hermes API server 안정화 + health 정확화
결론
hermes cron status는 이제 HTTP API alive + Scheduler tick alive가 둘 다 정상일 때만 running으로 판단한다.18789 LISTEN만으로는 running 처리하지 않고, scheduler evidence를 함께 본다./v1/health핸들러는 HTTP API 상태와 scheduler 상태를 분리해 반환하도록 수정했다./api/jobs/{id}/run은 마이그레이션 job id(ocA-src-reg형태)를 받을 수 있게 수정했다.
확인한 사실
1. API server 코드
대상 파일:
- /Users/ron/.hermes/hermes-agent/gateway/platforms/api_server.py
확인 결과:
- 기존 /health, /v1/health는 {status: ok, platform: hermes-agent}만 반환했다.
- 기존 /api/jobs/{job_id}/run은 내부적으로 cron.jobs.trigger_job(job_id)를 호출해 다음 scheduler tick에 실행되도록 예약한다.
- 기존 job id 검증식은 12자리 hex만 허용해서 ocA-src-reg, ocB-*, ocC-* 같은 마이그레이션 id를 거부할 수 있었다.
수정:
- /v1/health 응답에 http_api와 scheduler를 분리했다.
- scheduler는 cron.scheduler.get_scheduler_health()로 확인한다.
- job id 검증식을 [A-Za-z0-9][A-Za-z0-9_-]{0,80}로 완화했다.
2. Scheduler tick health
대상 파일:
- /Users/ron/.hermes/hermes-agent/cron/scheduler.py
수정:
- scheduler tick마다 ~/.hermes/cron/scheduler_state.json에 last_tick_at, last_tick_epoch, 실행/예약 개수, 오류를 기록한다.
- .tick.lock 존재와 갱신 시각도 함께 본다.
- 이미 떠 있는 구버전 gateway가 아직 scheduler_state.json을 쓰지 못하는 경우를 위해, .tick.lock 갱신 시각을 호환용 tick 신호로 사용한다.
현재 확인값:
- scheduler health: alive=true
- lock: 존재
- 마지막 tick 기록: 2026-04-14T14:32:39+09:00
- 마지막 tick 오류: 없음
3. hermes cron status
대상 파일:
- /Users/ron/.hermes/hermes-agent/hermes_cli/cron.py
- /Users/ron/.hermes/hermes-agent/hermes_cli/gateway.py
수정:
- 기존 ps aux 기반 탐지는 이 환경에서 권한 문제로 실패했다.
- 새 기준은 다음을 결합한다:
- API server 포트/listen 또는 /v1/health
- gateway runtime status의 api_server 연결 상태
- scheduler health
- 둘 다 정상일 때만 running으로 표시한다.
검증 결과:
✓ Gateway is running — cron jobs will fire automatically
HTTP API: 127.0.0.1:18789 healthy
Scheduler: tick healthy
4. /api/jobs, /api/jobs/{id}/run 코드 검증
실제 HTTP curl은 현재 Codex 실행 샌드박스에서 localhost 연결이 차단되어 실패했다. 대신 handler 단위 검증으로 다음을 확인했다.
run_status 200
run_body {"job": {"id": "ocA-src-reg", "next_run_at": "test-now"}}
job_id_regex True
확인 의미:
- /api/jobs/{id}/run 핸들러가 200 응답을 만들 수 있음
- ocA-src-reg 형태 id가 더 이상 400으로 거부되지 않음
- 실제 trigger 함수 연결 지점은 cron.jobs.trigger_job() 그대로 유지됨
검증 명령
/Users/ron/.hermes/hermes-agent/venv/bin/python -m py_compile \
gateway/platforms/api_server.py cron/scheduler.py hermes_cli/cron.py hermes_cli/gateway.py
HERMES_HOME=/Users/ron/.hermes API_SERVER_HOST=127.0.0.1 API_SERVER_PORT=18789 \
/Users/ron/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main cron status
HERMES_HOME=/Users/ron/.hermes API_SERVER_HOST=127.0.0.1 API_SERVER_PORT=18789 \
/Users/ron/.hermes/hermes-agent/venv/bin/python - <<'PY'
import asyncio, sys
sys.path.insert(0, '/Users/ron/.hermes/hermes-agent')
from gateway.config import PlatformConfig
from gateway.platforms.api_server import APIServerAdapter
class Req:
headers = {}
query = {}
match_info = {'job_id': 'ocA-src-reg'}
async def main():
adapter = APIServerAdapter(PlatformConfig())
adapter._cron_trigger = lambda job_id: {'id': job_id, 'next_run_at': 'test-now'}
resp = await adapter._handle_run_job(Req())
print(resp.status, resp.text)
asyncio.run(main())
PY
제한/남은 리스크
curl http://127.0.0.1:18789/v1/health는 이 Codex 샌드박스에서 localhost 연결이 차단되어 실패했다.launchctl kickstart와kill도Operation not permitted로 거부되어, 실행 중인 gateway 프로세스에는 새/v1/health코드가 아직 로드되지 않았다.- 파일 수정은 완료됐으므로, 다음 gateway 재시작 후
/v1/health는 새 구조로 응답해야 한다.
변경 파일
/Users/ron/.hermes/hermes-agent/gateway/platforms/api_server.py/Users/ron/.hermes/hermes-agent/cron/scheduler.py/Users/ron/.hermes/hermes-agent/hermes_cli/cron.py/Users/ron/.hermes/hermes-agent/hermes_cli/gateway.py
금지 범위 확인
shared/llm.py수정 없음~/.openclaw/*파일 수정 없음