대화와 큰 실행 결과는 transcript JSONL에, 조회 가능한 lifecycle은 project-local sessions.db의 hook_events에 남습니다. 같은 시각의 serve.log까지 맞추면 “가드가 정상 종료했는지”와 “외부 호출에서 실제로 매달렸는지”를 구분할 수 있습니다.
1. 최근 이벤트를 조회합니다
HookEventStore()는 현재 workspace의 database를 해석합니다. session key를 알고 있으면 아래처럼 최근 이벤트를 시간순으로 봅니다. 저장 payload는 raw prompt, user input, tool input/result를 포함하지 않습니다.
uv run python - <<'PY'
from core.observability.event_store import HookEventStore
store = HookEventStore()
try:
rows = store.read(limit=20, session_key="<session_key>")
for row in reversed(rows):
print(row.occurred_at, row.event, row.status, row.action)
finally:
store.close()
PY2. lifecycle pair를 확인합니다
llm_call_start뒤llm_call_end가 없으면 model adapter 대기를 확인합니다.tool_exec_start뒤tool_exec_end가 없으면 tool 실행 또는 process 종료 경계를 확인합니다.status=blocked는 interceptor가 의도적으로 막은 실행입니다.status=failed는 canonical terminal row에 실패가 반영된 경우입니다.
3. transcript와 daemon log를 맞춥니다
run_dir가 있는 실행은 transcript.jsonl과dialogue.jsonl을 확인합니다. 마지막 tool_call에 대응하는 tool_result가 없거나 동일 호출이 반복되면~/.geode/logs/serve.log에서 같은 timestamp의 traceback, timeout, credential 오류를 찾습니다.
4. 복구 후 확인합니다
원인을 고치고 재실행한 뒤 새 timeline이 canonical end와session_end까지 이어지는지 확인합니다. 이벤트 table은 보존 등급과 전체 행수 상한으로 자동 prune되므로 장기 보존이 필요한 증거는 별도 run artifact로 내보냅니다.