먼저 필요한 권한을 고릅니다. 외부 통합의 안정된 경계라면 공개 훅, 요청 변경이나 실제 실행 래핑이라면 신뢰 미들웨어, 관측만 한다면 런타임 이벤트를 사용합니다. 내부 이벤트를 실행 제어에 사용하는 것은 지원 계약이 아닙니다.
1. 공개 훅 등록
HookRegistry는 13개의 HookName만 받습니다. 같은 훅 안에서 이름은 고유해야 하고 낮은 priority가 먼저 실행됩니다. handler는 HookDecision 또는 None을 반환합니다.
from core.hooks import HookAction, HookDecision, HookName
def require_ticket(invocation):
args = invocation.payload["arguments"]
if invocation.payload["tool_name"] == "run_bash" and "ticket" not in args:
return HookDecision(
action=HookAction.REQUEST_PERMISSION,
reason="run_bash requires an operator decision",
)
return HookDecision(action=HookAction.CONTINUE)
hook_registry.register(
HookName.PRE_TOOL_USE,
require_ticket,
name="require_ticket",
priority=50,
)rewrite는 payload의 실제 필드명을 사용합니다. 도구 인자를 바꾸려면 updates={"arguments": {...}}를 반환하며, GEODE가 변경된 요청을 다시 스키마 검증한 뒤 정책과 승인을 수행합니다.
파일시스템 RuntimeEvent 훅은 매니페스트를 먼저 읽습니다
.geode/hooks/<name>/hook.yaml은 실행 코드를 import하지 않고 읽을 수 있는 신뢰 경계입니다. 클래스만 둔hook.py 형식은 거부됩니다. 아래 매니페스트와 확장 신뢰 정책의hook:failure-metrics 승인이 모두 있어야 handler가 import됩니다. 이 플러그인은 3절의 내부 RuntimeEventBus를 구독합니다.
name: failure-metrics events: [tool_exec_failed] handler: handler.py priority: 50 capabilities: [events] resource_keys: []
handler 모듈은 기존 handle(event, data) 함수를 내보내거나 build_extension(context)에서 함수를 반환할 수 있습니다. 후자의 context에는 매니페스트와 정책이 함께 허용한 포트만 들어갑니다. 여기서는 events 하나뿐입니다. 이름 충돌과 매니페스트 오류는 import 전에 실패하고, 로드된 훅은 런타임 종료 때 등록 역순으로 해제됩니다.
2. 신뢰 미들웨어 등록
요청 변환과 실행 래핑을 섞지 않습니다. 아래 예시는 실제 provider 호출의 지연만 측정하고 요청은 그대로 전달합니다.
class LlmLatency:
async def llm_execution(self, request, next_call):
started = time.monotonic()
try:
return await next_call(request)
finally:
metrics.observe("llm", time.monotonic() - started)
middleware_registry.register_llm_execution(
LlmLatency(),
name="llm_latency",
priority=100,
)tool_request, tool_execution, llm_request, llm_execution마다 별도의 등록 메서드가 있습니다. execution에서 다른 요청을 next_call에 넘기거나 두 번 호출하면 fail-loud합니다.
| 결합점 | 기본 timeout | 실패 계약 |
|---|---|---|
tool_request | 10초 | 변환 실패 시 executor에 진입하지 않음 |
llm_request | 10초 | 변환 실패 시 provider에 진입하지 않음 |
tool_execution | 300초 | next_call 전 실패는 전파; 실행 완료 뒤 wrapper 실패는 완료 결과 보존 |
llm_execution | 900초 | next_call 전 실패는 전파; provider 완료 뒤 wrapper 실패는 결과를 보존해 재과금 방지 |
실행 미들웨어의 실패를 보고 같은 tool/provider 호출을 임의로 재시도하지 마세요. downstream 호출이 끝난 뒤 발생한 wrapper 오류는 런타임이 완료 결과를 보존합니다. llm_request가 cache-sensitive prefix를 바꾸려면 등록 시 allow_cache_invalidation=True와 요청 metadata의cache_invalidation_reason이 둘 다 필요합니다.
3. 내부 런타임 이벤트 구독
운영 메트릭이나 저장 sink처럼 제어권이 필요 없는 코드는 RuntimeEventBus를 구독합니다. prefix 구독은 내부 관측자용이며 공개 훅에는 없습니다.
from core.hooks import RuntimeEvent
events.subscribe(
RuntimeEvent.TOOL_EXEC_FAILED,
record_tool_failure,
name="tool_failure_metrics",
priority=60,
)4. 한 번만 소유하고 실제 경계를 검증
프로덕션에서는 SharedServices가 HookRegistry와 MiddlewareRegistry를 각각 한 번 만들고 ToolExecutor에 주입합니다. AgenticLoop는 executor의 같은 인스턴스를 공유합니다. 요청마다 새 registry를 만들면 등록이 보이지 않으므로 금지합니다.
- public hook은 해당 경계의 payload schema와 action 제한을 테스트합니다.
- tool middleware는 승인 전 변환과 승인 후 실행 순서를 함께 테스트합니다.
- LLM middleware는 모든 adapter call 경로와 retry마다 실행되는지 확인합니다.
- 관측 행은 run projection이 없어도 SQLite에 남고,
RunTimeline활성 시에만events.jsonl에도 남는지 확인합니다.
PostVerify 등록 시 주의
revise와 Stop continue는 빈 지시를 반환할 수 없습니다. 실패한 내장 검증에 accept를 반환하면 escalation으로 처리됩니다. 연속 시도는 기본 2회로 제한되며 이전 도구 부수 효과는 재생하지 않습니다.
참조: 훅과 미들웨어 계약, Agentic loop.