virtual-insanity
← 리포트 목록

shell_job_type

2026-04-14 hermes

Hermes shell/script job 타입 추가 보고서 (2026-04-14)

결론

  • 완료: Hermes cron에 LLM을 호출하지 않는 deterministic kind: "script" / kind: "shell" 실행 경로를 추가했다.
  • OpenClaw 쪽 파일은 수정하지 않았다. shared/llm.py도 수정하지 않았다.
  • 기존 prompt cron은 kind가 없거나 prompt이면 기존 AIAgent 경로로 그대로 간다.

수정 파일

파일 주요 변경 라인
/Users/ron/.hermes/hermes-agent/cron/scheduler.py script/shell job 감지, cwd/env allowlist, command/script 정규화, subprocess.run() 실행, stdout/stderr/exit code/timeout 저장 15-16, 48-61, 450-654, 724-725
/Users/ron/.hermes/hermes-agent/cron/jobs.py create_job() 스키마에 kind, script, command, args, cwd, env, env_allowlist, timeout, shell, silent 추가 378-387, 436-507
/Users/ron/.hermes/hermes-agent/tools/cronjob_tools.py cronjob tool에서 shell/script job 생성·수정·표시 가능하도록 schema/handler 확장 116-154, 157-229, 290-337, 401-585

새 jobs.json 형식 예시

{
  "id": "example-shell-job",
  "name": "example-shell-job",
  "kind": "shell",
  "command": "cd /Users/ron/.openclaw/workspace && python3 scripts/pipeline/example.py",
  "cwd": "/Users/ron/.openclaw/workspace",
  "timeout": 300,
  "env": {"PYTHONPATH": "/Users/ron/.openclaw/workspace/scripts/shared:/Users/ron/.openclaw/workspace/scripts/pipeline"},
  "env_allowlist": ["PATH", "HOME"],
  "silent": true,
  "schedule": {"kind": "cron", "expr": "5 9 * * 1-5", "display": "5 9 * * 1-5"},
  "enabled": true
}

실행 동작

  • kind: "shell": 문자열 command/bin/bash로 실행한다. 배열 command는 shell 없이 직접 실행한다.
  • kind: "script": script 경로 + args를 직접 실행한다. 실행권한 없는 .py는 현재 Python으로, .sh/.bash/bin/bash로 실행한다.
  • exit code 0 → success, non-zero → failure.
  • stdout/stderr는 Hermes cron output markdown에 캡처한다.
  • timeout 초과 시 failure로 기록한다.
  • cwd allowlist: 사용자 home, /tmp, 현재 HERMES_HOME 하위만 허용.
  • env allowlist: 기본 최소 환경 + env_allowlist에 명시한 부모 env + env 명시값만 전달한다.
  • 성공 시 기본 silent=true라 채팅 발송은 [SILENT]로 억제되고, output 파일은 저장된다.

검증 결과

1) 문법 검사

cd /Users/ron/.hermes/hermes-agent && source venv/bin/activate && python -m py_compile cron/scheduler.py cron/jobs.py tools/cronjob_tools.py
=> 통과

2) 작은 shell job 정의 → tick 실행

임시 HERMES_HOME=/tmp/hermes-shell-job-test-final-*에서 실제 jobs.json 생성 후 tick() 실행.

tick_executed= 1
out_text= shell-ok
jobs_remaining= 0
output_has_exit_0= True
output_has_stdout= True

3) script path 실행 확인

실행권한 없는 /tmp/hermes_script_smoke.pykind="script" + args + env로 실행.

script_executed= 1
script_out_text= script-ok:a,b

4) LLM 우회 확인

run_job()kind="shell" job을 직접 넣어 실행했고, run_agent 모듈이 로드되지 않았다.

direct_success= True
direct_error= None
direct_final_response= [SILENT]
run_agent_loaded= False

5) 기존 prompt cron 영향 확인

prompt_is_shell= False
script_is_shell= True
shell_is_shell= True

6) 기존 cron 관련 테스트

실제 운영 gateway가 기본 ~/.hermes/cron/.tick.lock을 잡을 수 있어 테스트는 격리된 HERMES_HOME으로 실행했다.

HERMES_HOME=/tmp/hermes-pytest-shelljob python -m pytest tests/cron/test_scheduler.py tests/tools/test_cronjob_tools.py tests/hermes_cli/test_cron.py -q -n 0
=> 81 passed in 0.68s

참고: HERMES_HOME을 격리하지 않고 실행하면 운영 중 gateway lock과 충돌해 기존 scheduler 테스트 일부가 실패한다. 이번 shell-job 패치 실패가 아니라 운영 인스턴스 lock 공유 문제다.

Diff 요약

cron/jobs.py           |  50 +++++
cron/scheduler.py      | shell/script 실행 분기 추가 포함
tools/cronjob_tools.py | 133 +++++++++++-

주의: cron/scheduler.py는 작업 시작 시점에 이미 미커밋 lock/status 관련 변경이 있어 전체 git diff 통계에는 기존 변경도 섞여 있다. 이번 작업에서 실제로 건드린 핵심은 위 표의 라인 범위다.

남은 리스크

  • 현재 실행 중인 Hermes gateway 프로세스가 이미 scheduler.py를 import한 상태면 새 shell/script 실행 경로는 프로세스 재시작 전까지 반영되지 않을 수 있다. 이 보고서는 코드 패치와 새 Python 프로세스 기준 검증 완료 상태다.
  • cwd allowlist는 home, /tmp, HERMES_HOME 중심이다. 다른 시스템 경로에서 실행해야 하는 cron은 cwd를 home 하위 wrapper로 두거나 allowlist 정책 확장이 필요하다.

자체평가

  • 정확성: 4.8/5
  • 완성도: 4.7/5
  • 검증: 4.7/5
  • 최소 변경: 4.4/5 — cronjob tool schema까지 확장해서 직접 jobs.json 편집뿐 아니라 도구 생성도 가능하게 한 점은 범위 내 확장으로 판단.
  • 종합: 4.65/5