Tool Runtime은 함수 호출 목록이 아니다
메뉴
Moonshot Notes orbit notebook mark
Moonshot NotesAI 도구와 개발 워크플로우 기록하는 공간

AI Agent

Tool Runtime은 함수 호출 목록이 아니다

Claude Code CLI 분석을 바탕으로 AI agent tool runtime을 capability registry, schema validation, permission gate, 실행 오케스트레이션으로 설명합니다.

Tool Runtime은 함수 호출 목록이 아니다 hero image
Markdown약 1741 tokens

핵심 요약

  • 모델의 tool request는 제안일 뿐이며 실제 실행은 runtime이 통제해야 한다.
  • Tool runtime은 registry, schema validation, permission gate, executor, result formatter로 나뉜다.
  • read-only 도구는 병렬화할 수 있지만 write 도구는 순서를 보존해야 한다.
  • 오류와 거절도 model-visible result로 만들어 loop를 깨지 않게 해야 한다.

이번 글에서는 tool runtime을 보겠습니다.

AI agent에서 도구는 단순 함수 목록이 아닙니다. 모델이 “이 도구를 이 인자로 호출하고 싶다”고 요청하면, runtime은 그 요청이 실제로 가능한지 검증해야 합니다. 도구 이름이 맞는지, 입력 스키마가 유효한지, 현재 권한으로 실행 가능한지, 병렬로 실행해도 안전한지 확인해야 합니다.

Claude Code류의 agent를 runtime 관점으로 보면 tool execution은 모델 루프와 분리된 독립 계층입니다.

1. Tool request는 실행 명령이 아니라 제안이다

모델은 도구 호출을 “요청”할 수 있습니다. 하지만 실행 여부는 애플리케이션이 결정합니다.

모델은 다음 실수를 할 수 있습니다.

  • 존재하지 않는 도구 이름을 생성한다.
  • 필수 인자를 빠뜨린다.
  • 위험한 shell 명령을 요청한다.
  • 파일 쓰기 순서를 잘못 판단한다.
  • 외부 서비스로 민감 정보를 보내려 한다.

따라서 tool request는 실행 명령이 아니라 runtime이 검토해야 할 제안입니다.

2. Capability registry 설계

registry는 “무엇을 실행할 수 있는가”를 정의합니다.

필드설명
public name모델에게 노출되는 도구 이름
description모델이 선택할 때 참고하는 설명
input schema인자 검증 규칙
risk levelread, write, shell, network, secret 등 위험도
availability현재 mode/workspace/policy에서 사용 가능 여부
executor실제 실행 함수
result formatter결과를 model-visible message로 변환

도구가 많아질수록 registry와 executor를 분리해야 합니다. registry는 가능한 기능의 목록이고, executor는 이번 요청을 어떻게 처리할지 담당합니다.

3. Schema validation과 오류 메시지

도구 실행 전에는 반드시 입력 검증이 필요합니다. schema validation은 단순 타입 체크가 아니라 model feedback의 일부입니다.

잘못된 입력이 들어오면 runtime은 예외로 loop를 깨지 말고, 모델이 이해할 수 있는 오류 result를 만들어야 합니다.

# 읽는 법: 아래 항목은 동작 흐름을 빠르게 확인하기 위한 요약 예시입니다.요청: file_read(path=null)→ schema validation failed→ tool result: path는 문자열이어야 합니다→ model은 다른 인자로 다시 시도 가능

4. Permission gate와 실행 순서

schema가 통과되었다고 바로 실행하면 안 됩니다. 권한 판단이 필요합니다.

권한 판단은 도구 자체의 위험도, 입력값, 현재 permission mode, workspace trust, 사용자 규칙, 조직 정책을 함께 봐야 합니다.

위험도기본 처리
read-only local metadata자동 허용 가능
file readworkspace 범위 안에서 허용 가능
file write승인 또는 명시 정책 필요
shell command위험도 분류 후 승인 필요
network send목적지와 데이터 범위 확인 필요
secret access기본 거부

5. 병렬 실행과 직렬 실행

모든 도구를 병렬 실행하면 빠릅니다. 하지만 안전하지 않습니다. 읽기 전용 도구는 병렬화할 수 있지만, 파일 쓰기나 shell 실행은 순서가 중요합니다.

도구 성격실행 전략
read-only, no side effect병렬 실행 가능
write, state mutation직렬 실행 권장
shell command보수적으로 직렬 실행
external API mutation직렬 또는 transaction 필요
unknown risk직렬 실행 기본값

병렬화의 기준은 속도가 아니라 side effect입니다.

6. Result formatter와 재주입

도구 결과는 화면 출력과 model-visible message가 다릅니다. 화면에는 짧은 요약이 좋지만, 모델에게는 다음 추론에 필요한 구조화된 관찰값이 필요합니다.

도구 결과에는 최소한 다음 정보가 있어야 합니다.

  • 원래 tool request id
  • 성공/실패 여부
  • 모델이 읽을 수 있는 결과 요약
  • 오류가 있다면 원인
  • 민감 정보가 제거된 content
  • ledger에 남길 audit summary

7. 개념 코드로 보는 tool runtime

아래 코드는 설명용으로 새로 작성한 코드입니다.

# 읽는 법: 실제 구현 복제가 아니라 runtime 경계를 설명하는 개념 코드입니다.class CapabilitySpec:    # 객체가 이후 단계에서 참조할 runtime 의존성과 상태 저장소를 초기화합니다.    def __init__(self, name, schema, risk, can_run, execute, format_result):        self.name = name        self.schema = schema        self.risk = risk        self.can_run = can_run        self.execute = execute        self.format_result = format_result # 단일 tool request를 검증, 권한 평가, 실행, 결과 포맷 단계로 처리합니다.async def execute_tool_request(request, runtime):    spec = runtime.capabilities.find(request.name)    if spec is None:        return ToolResult.error(request.id, "사용할 수 없는 도구입니다.")     parsed = spec.schema.validate(request.arguments)    if not parsed.ok:        return ToolResult.error(request.id, parsed.message)     if not spec.can_run(runtime.context):        return ToolResult.error(request.id, "현재 실행 모드에서 비활성화된 도구입니다.")     decision = await runtime.permissions.evaluate(spec, parsed.value)    await runtime.ledger.write("permission_decision", decision.summary())     if not decision.allowed:        return ToolResult.denied(request.id, decision.reason)     try:        raw_output = await spec.execute(parsed.value, runtime.execution_context)        return spec.format_result(request.id, raw_output)    except Exception as error:        return ToolResult.error(request.id, summarize_error(error)) # 읽기 전용 도구는 병렬로, side effect가 있는 도구는 순서대로 실행합니다.async def execute_tool_batch(requests, runtime):    readonly, ordered = split_by_side_effect(requests, runtime.capabilities)     for result in await run_parallel(readonly, lambda req: execute_tool_request(req, runtime)):        yield result     for req in ordered:        yield await execute_tool_request(req, runtime)

여기서 오류, 거절, 성공은 모두 ToolResult가 됩니다. loop를 깨는 것이 아니라 다음 모델 추론에 들어갈 관찰값을 만드는 것이 핵심입니다.

8. 실전 체크리스트

# 읽는 법: 아래 항목은 동작 흐름을 빠르게 확인하기 위한 요약 예시입니다.Tool Runtime 체크리스트 [ ] tool request를 실행 명령이 아니라 검토 대상 제안으로 본다.[ ] capability registry와 executor가 분리되어 있다.[ ] tool name lookup 실패도 result message를 만든다.[ ] schema validation 실패가 model-visible error로 돌아간다.[ ] permission check는 실제 실행 직전에 일어난다.[ ] read-only는 병렬화할 수 있고 write는 순서를 보존한다.[ ] tool result는 screen summary, model message, ledger summary로 나뉜다.[ ] unknown risk 도구는 보수적으로 직렬 또는 승인 필요로 처리한다.

실패 사례: 도구 이름만 맞추고 실행 정책을 빠뜨린 경우

가장 흔한 실패는 tool definition을 모델에게 넘겨주고 "이제 function calling이 된다"고 판단하는 것입니다. 초기 demo에서는 잘 작동합니다. 모델이 read_file을 고르고, 경로를 넣고, runtime이 파일을 읽어 결과를 돌려줍니다. 하지만 실제 제품에서는 곧 문제가 생깁니다. 읽기 도구와 쓰기 도구가 같은 queue에서 동시에 실행되고, permission check가 UI 버튼에만 있고 executor에는 없고, 실패한 tool call이 transcript에 남지 않습니다.

이 구조에서는 모델이 잘못된 인자를 만든 순간 runtime이 흔들립니다. 예를 들어 delete_cache 같은 도구가 optional scope 인자를 받는데 schema validation이 느슨하면 빈 scope가 전체 cache 삭제로 해석될 수 있습니다. 또는 read-only로 표시된 도구가 내부적으로 refresh token을 갱신해 side effect를 만들 수도 있습니다. 모델이 악의적이지 않아도 도구의 위험 등급이 runtime에 표현되지 않으면 운영 사고가 됩니다.

구현 예시: 실행 전 검문소를 명시하기

Tool runtime은 최소한 아래 순서를 가져야 합니다. 핵심은 모델 요청을 곧바로 함수 호출로 바꾸지 않는 것입니다.

ToolRequest  -> name lookup  -> schema validation  -> capability policy  -> permission check  -> concurrency planner  -> executor  -> normalized ToolResult
type Risk = "read" | "write" | "external" | "destructive"; type ToolCapability = {  name: string;  risk: Risk;  schemaVersion: string;  requiresApproval: boolean;}; function canExecute(capability: ToolCapability, mode: "auto" | "review") {  if (capability.risk === "destructive") return false;  if (capability.requiresApproval && mode === "auto") return false;  return true;}

이 정도의 작은 분리만 있어도 runtime은 도구를 병렬화할 수 있는지, 사용자 승인이 필요한지, 실패를 모델에게 어떻게 설명할지 판단할 수 있습니다. 중요한 점은 canExecute가 UI와 executor 양쪽에서 공유되는 정책이어야 한다는 것입니다. 화면에서 막았더라도 executor가 다시 확인하지 않으면 우회 경로가 생깁니다.

경계책임빠지면 생기는 문제
registry도구 이름과 capability 확인hallucinated tool이 런타임 예외로 끝남
schema입력 형식과 기본값 검증위험한 기본값이 조용히 실행됨
permission사용자·workspace 권한 확인UI 밖 실행 경로가 생김
result formatter성공·실패를 같은 타입으로 정규화모델이 실패 원인을 복구하지 못함

마무리

Tool runtime은 agent의 외부 세계 접점입니다. 모델이 요청했다고 바로 실행하지 않고, runtime이 검증하고 승인하고 기록해야 제품 수준 신뢰도가 나옵니다.

다음 글에서는 마지막 방어선인 권한, transcript, 비용을 다룹니다. 이 셋은 부가기능이 아니라 agent 제품성을 결정하는 핵심 계층입니다.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP