virtual-insanity
← 리포트 목록

Hermes API server health 정확화 감사

2026-04-14 hermes [hermes, api-server, cron, health, ops]

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_apischeduler를 분리했다. - 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.jsonlast_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 kickstartkillOperation 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/* 파일 수정 없음