본문으로 건너뛰기

코드 실행(Programmatic Tool Calling)

execute_code 도구는 에이전트가 Hermes 도구를 프로그래밍 방식으로 호출하는 Python 스크립트를 작성하게 해, 여러 단계 워크플로를 하나의 LLM 턴으로 압축합니다. 스크립트는 에이전트 host의 child process에서 실행되며, Unix domain socket RPC로 Hermes와 통신합니다.

동작 방식

  1. 에이전트가 from hermes_tools import ...를 사용하는 Python 스크립트를 작성합니다.
  2. Hermes가 RPC 함수가 들어 있는 hermes_tools.py stub module을 생성합니다.
  3. Hermes가 Unix domain socket을 열고 RPC listener thread를 시작합니다.
  4. 스크립트가 child process에서 실행됩니다. 도구 호출은 socket을 통해 Hermes로 돌아갑니다.
  5. LLM에는 스크립트의 print() 출력만 반환됩니다. 중간 도구 결과는 context window에 들어가지 않습니다.
# The agent can write scripts like:
from hermes_tools import web_search, web_extract

results = web_search("Python 3.13 features", limit=5)
for r in results["data"]["web"]:
content = web_extract([r["url"]])
# ... filter and process ...
print(summary)

스크립트 안에서 사용할 수 있는 도구: web_search, web_extract, read_file, write_file, search_files, patch, terminal(foreground only).

에이전트가 이 도구를 쓰는 경우

에이전트는 다음 상황에서 execute_code를 사용합니다.

  • 3개 이상의 도구 호출 사이에 처리 로직이 필요할 때
  • 대량 데이터를 필터링하거나 조건 분기를 해야 할 때
  • 결과 목록을 반복 처리해야 할 때

핵심 이점은 중간 도구 결과가 context window에 들어가지 않는다는 점입니다. 마지막 print() 출력만 돌아오기 때문에 token 사용량을 크게 줄일 수 있습니다.

실제 예제

데이터 처리 파이프라인

from hermes_tools import search_files, read_file
import json

# Find all config files and extract database settings
matches = search_files("database", path=".", file_glob="*.yaml", limit=20)
configs = []
for match in matches.get("matches", []):
content = read_file(match["path"])
configs.append({"file": match["path"], "preview": content["content"][:200]})

print(json.dumps(configs, indent=2))

다단계 웹 조사

from hermes_tools import web_search, web_extract
import json

# Search, extract, and summarize in one turn
results = web_search("Rust async runtime comparison 2025", limit=5)
summaries = []
for r in results["data"]["web"]:
page = web_extract([r["url"]])
for p in page.get("results", []):
if p.get("content"):
summaries.append({
"title": r["title"],
"url": r["url"],
"excerpt": p["content"][:500]
})

print(json.dumps(summaries, indent=2))

대량 파일 리팩터링

from hermes_tools import search_files, read_file, patch

# Find all Python files using deprecated API and fix them
matches = search_files("old_api_call", path="src/", file_glob="*.py")
fixed = 0
for match in matches.get("matches", []):
result = patch(
path=match["path"],
old_string="old_api_call(",
new_string="new_api_call(",
replace_all=True
)
if "error" not in str(result):
fixed += 1

print(f"Fixed {fixed} files out of {len(matches.get('matches', []))} matches")

빌드와 테스트 파이프라인

from hermes_tools import terminal, read_file
import json

# Run tests, parse results, and report
result = terminal("cd /project && python -m pytest --tb=short -q 2>&1", timeout=120)
output = result.get("output", "")

# Parse test output
passed = output.count(" passed")
failed = output.count(" failed")
errors = output.count(" error")

report = {
"passed": passed,
"failed": failed,
"errors": errors,
"exit_code": result.get("exit_code", -1),
"summary": output[-500:] if len(output) > 500 else output
}

print(json.dumps(report, indent=2))

실행 모드

execute_code에는 두 가지 실행 모드가 있으며, ~/.hermes/config.yamlcode_execution.mode로 제어합니다.

모드작업 디렉터리Python 인터프리터
project(기본값)세션의 작업 디렉터리. terminal()과 동일합니다.활성 VIRTUAL_ENV / CONDA_PREFIX Python을 사용하고, 실패하면 Hermes 자체 Python으로 fallback합니다.
strict사용자 프로젝트와 격리된 임시 staging directorysys.executable(Hermes 자체 Python)

project를 유지하면 좋은 경우: import pandas, from my_project import foo, open(".env") 같은 import와 상대 경로가 terminal()에서와 같은 방식으로 동작하길 원할 때입니다. 대부분 이 모드가 맞습니다.

strict로 바꾸면 좋은 경우: 재현성을 최대화해야 할 때입니다. 사용자가 어떤 venv를 활성화했는지와 무관하게 매번 같은 interpreter를 쓰고, 상대 경로로 프로젝트 파일을 실수로 읽는 위험을 피하려면 strict mode가 적합합니다.

# ~/.hermes/config.yaml
code_execution:
mode: project # or "strict"

project mode의 fallback 동작: VIRTUAL_ENV / CONDA_PREFIX가 없거나, 깨졌거나, Python 3.8 미만을 가리키면 resolver는 sys.executable로 안전하게 fallback합니다. 에이전트가 작동 가능한 interpreter 없이 남지는 않습니다.

보안상 중요한 불변 조건은 두 모드에서 동일합니다.

  • 환경 변수 scrubbing(API key, token, credential 제거)
  • 도구 allowlist(script는 execute_code를 재귀 호출하거나, delegate_task 또는 MCP 도구를 호출할 수 없음)
  • 리소스 제한(timeout, stdout cap, tool-call cap)

모드를 바꾸면 스크립트가 실행되는 위치와 interpreter만 달라집니다. 볼 수 있는 자격 증명이나 호출 가능한 도구는 바뀌지 않습니다.

리소스 제한

리소스제한비고
Timeout5분(300초)SIGTERM으로 종료하고, 5초 grace 후 SIGKILL
Stdout50 KB[output truncated at 50KB] 안내와 함께 잘림
Stderr10 KBnon-zero exit일 때 debugging을 위해 output에 포함
도구 호출실행당 50회한도에 도달하면 오류 반환

모든 제한은 config.yaml에서 설정할 수 있습니다.

# In ~/.hermes/config.yaml
code_execution:
mode: project # project (default) | strict
timeout: 300 # Max seconds per script (default: 300)
max_tool_calls: 50 # Max tool calls per execution (default: 50)

스크립트 안의 도구 호출 방식

스크립트가 web_search("query") 같은 함수를 호출하면 다음 순서로 처리됩니다.

  1. 호출이 JSON으로 직렬화되어 Unix domain socket을 통해 parent process로 전송됩니다.
  2. parent가 표준 handle_function_call handler를 통해 dispatch합니다.
  3. 결과가 socket으로 다시 전송됩니다.
  4. 함수는 parse된 결과를 반환합니다.

따라서 스크립트 안의 도구 호출은 일반 도구 호출과 동일하게 동작합니다. 같은 rate limit, 같은 error handling, 같은 capability를 사용합니다. 유일한 제한은 terminal()이 foreground-only라는 점입니다. backgroundpty 매개변수는 사용할 수 없습니다.

오류 처리

스크립트가 실패하면 에이전트는 구조화된 오류 정보를 받습니다.

  • Non-zero exit code: stderr가 output에 포함되므로 에이전트가 전체 traceback을 볼 수 있습니다.
  • Timeout: 스크립트가 종료되고 에이전트는 "Script timed out after 300s and was killed." 메시지를 봅니다.
  • Interruption: 실행 중 사용자가 새 메시지를 보내면 스크립트가 종료되고 에이전트는 [execution interrupted - user sent a new message]를 봅니다.
  • Tool call limit: 50회 한도에 도달하면 이후 도구 호출은 오류 메시지를 반환합니다.

응답에는 항상 status(success/error/timeout/interrupted), output, tool_calls_made, duration_seconds가 포함됩니다.

보안

Security Model

child process는 최소 환경으로 실행됩니다. API key, token, credential은 기본적으로 제거됩니다. 스크립트는 RPC channel을 통해서만 도구에 접근하며, 명시적으로 허용되지 않는 한 환경 변수에서 비밀값을 읽을 수 없습니다.

이름에 KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL, PASSWD, AUTH가 포함된 환경 변수는 제외됩니다. PATH, HOME, LANG, SHELL, PYTHONPATH, VIRTUAL_ENV 같은 안전한 시스템 변수만 전달됩니다.

스킬 환경 변수 passthrough

스킬이 frontmatter에서 required_environment_variables를 선언하면, 해당 변수는 스킬이 로드된 뒤 execute_codeterminal child process에 자동으로 전달됩니다. 이를 통해 스킬은 임의 코드의 보안 자세를 약화하지 않고도 자신이 선언한 API key를 사용할 수 있습니다.

스킬이 아닌 일반 사용 사례에서는 config.yaml에서 변수를 명시적으로 allowlist할 수 있습니다.

terminal:
env_passthrough:
- MY_CUSTOM_KEY
- ANOTHER_TOKEN

전체 내용은 Security guide를 참고하세요.

Hermes는 실행할 스크립트와 자동 생성된 hermes_tools.py RPC stub을 임시 staging directory에 쓰고, 실행이 끝나면 정리합니다. strict mode에서는 스크립트도 그 디렉터리에서 실행됩니다. project mode에서는 세션의 작업 디렉터리에서 실행되며, import가 계속 동작하도록 staging directory가 PYTHONPATH에 남습니다. child process는 별도 process group에서 실행되므로 timeout이나 interruption 때 깨끗하게 종료할 수 있습니다.

execute_code와 terminal 비교

사용 사례execute_codeterminal
도구 호출이 섞인 다단계 워크플로아니오
단순 셸 명령아니오
큰 도구 출력 필터링/처리아니오
빌드 또는 테스트 suite 실행아니오
검색 결과 반복 처리아니오
interactive/background process아니오
환경 변수의 API key 필요passthrough를 통해서만대부분 전달

기준: Hermes 도구를 프로그래밍 방식으로 호출하고 호출 사이에 로직이 필요하면 execute_code를 사용하세요. 셸 명령, 빌드, 장기 실행 프로세스에는 terminal을 사용하세요.

플랫폼 지원

코드 실행은 Unix domain socket이 필요하므로 Linux와 macOS에서만 사용할 수 있습니다. Windows에서는 자동으로 비활성화되며, 에이전트는 일반적인 순차 도구 호출 방식으로 fallback합니다.