오픈소스 AI 에이전트를 내 컴퓨터에 띄울 때 터지는 에러 잡기
TuBrief 편집팀
2026년 9월 11일
0
컴퓨터/소프트웨어원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
유튜브 튜토리얼은 깃허브 스타 수천 개짜리 오픈소스 AI 도구를 가져와 몇 줄 치면 돌아가는 것처럼 보여줍니다. 실제로 터미널을 열고 클론해 보면 C++ 바이너리 충돌과 패키지 버전 꼬임부터 시작해서 에러 화면만 쏟아집니다. 실무 경력 1년 차 미만 주니어 개발자가 이 단계에서 막히는 이유는 파이썬과 런타임 환경 격리 기준을 잡지 않은 채 시스템 전역에 도구를 쏟아부었기 때문입니다. 프롬프트 문구를 다듬기 전에 로컬 프로세스 격리와 프록시 라우팅부터 손대야 밤샘 삽질을 멈출 수 있습니다.
오픈소스 AI 프로젝트는 C++ 빌드 도구가 얽힌 파이썬 라이브러리와 네이티브 바인딩을 부르는 Node.js 패키지가 섞여 있습니다. 전역 환경에 무심코 설치하면 파이썬 런타임에서 ImportError: dynamic module does not define module export function 같은 심볼 링크 에러가 납니다. Node.js 기반 프록시인 OmniRoute의 경우 package.json의 engines 필드에 Node 22와 Node 24~26을 지정하고 있어서 홀수 버전인 Node 23에서는 시작하자마자 뻗습니다.
가상환경을 만들고 엔진 잠금 파일을 기준으로 패키지를 설치해야 초반 빌드 에러를 막을 수 있습니다.
# 1. 파이썬 가상환경 생성 및 빌드 도구 갱신
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
# 2. 잠금 파일 기준 의존성 설치
if [ -f "poetry.lock" ]; then
poetry install --no-root
elif [ -f "requirements.txt" ]; then
pip install --no-cache-dir -r requirements.txt
fi
# 3. Node.js 런타임 버전 확인 및 설치
node -v # v22.x LTS 확인
pnpm install --frozen-lockfile
.env.example 파일을 복사해 .env를 수동으로 채울 때도 사소한 오타 하나 때문에 파싱 에러가 납니다. 오픈소스 에이전트 런타임인 DeepSeek Harness(dsh)는 일반 설정 파일(settings.yaml)과 실제 API 인증 키 파일(~/.dsh/.credentials.yaml)을 따로 둡니다. 키를 엉뚱한 위치에 적거나 경로 문법을 틀리면 에이전트가 돌지 않습니다.
| 환경변수 | 올바른 입력 예시 | 에러 원인 및 해결 방법 |
|---|---|---|
| OPENAI_API_BASE | http://localhost:20128/v1 | 주소 끝에 슬래시(/)를 붙여 404 라우팅 에러 유발, 슬래시 제거 |
| ANTHROPIC_API_KEY | sk-ant-api03-... | 도커 .env 값에 불필요한 따옴표("")를 둘러 인증 실패, 따옴표 제거 |
| DSH_HOME | /home/developer/.dsh | 물결표(~)를 그대로 써서 경로 권한 에러 유발, 절대 경로 명시 |
| SECRET_KEY | 32바이트 16진수 값 | 빈 값으로 방치해 세션 초기화 실패, openssl rand -hex 32로 발급 |
터미널 화면에 길게 늘어지는 에러 로그 전체를 붙잡고 씨# 로컬 오픈소스 AI 에이전트 구축할 때 터미널에서 터지는 오류 잡는 법
깃허브 스타 수가 수만 개에 달하는 오픈소스 AI 프로젝트를 클론하고 첫 실행 명령을 입력하는 순간, 화면은 온통 빨간색 에러 로그로 뒤덮입니다. 튜토리얼 영상 속 발표자는 엔터 한 번으로 깔끔하게 데모를 띄우지만, 내 터미널에는 C++ 컴파일 실패와 런타임 충돌만 남습니다. 모델의 지능 문제가 아닙니다. 파이썬 C-바이너리 충돌, 전역 환경 오염, 단일 고비용 API 엔드포인트에 묶인 구조가 원인입니다.
실무 1년 차 주니어 개발자가 로컬에서 AI 에이전트를 돌릴 때 겪는 환경 설정 병목을 없애고, 다중 모델 라우팅으로 API 비용을 덜어내는 구체적인 세팅 절차를 정리했습니다.
오픈소스 AI 프로젝트는 네이티브 C++ 라이브러리를 품은 파이썬 패키지와 네이티브 바인딩을 요구하는 Node.js 도구가 복잡하게 얽혀 있습니다. 시스템 전역 환경에 무심코 설치하면 라이브러리 심볼 링크가 깨지면서 ImportError: dynamic module does not define module export function 같은 골치 아픈 에러를 뱉습니다.
Node.js 기반 게이트웨이인 OmniRoute는 런타임 제약이 더 까다롭습니다. package.json의 engines 필드에 Node 22 및 24 이상 버전만 명시되어 있어, 홀수 버전인 Node 23 환경에서 부트스트랩을 시도하면 실행을 바로 차단합니다. 런타임 격리와 패키지 잠금 파일 동기화부터 끝내야 헛걸음을 줄입니다.
# 파이썬 가상환경을 생성하고 빌드 휠 도구를 먼저 갱신합니다
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
# 패키지 매니저 잠금 파일 기준으로 의존성을 설치합니다
if [ -f "poetry.lock" ]; then
poetry install --no-root
elif [ -f "requirements.txt" ]; then
pip install --no-cache-dir -r requirements.txt
fi
# Node.js 런타임 버전을 확인하고 잠금 파일 기준으로 설치합니다
node -v # v22 LTS 계열인지 확인
pnpm install --frozen-lockfile
환경변수 파일(.env) 설정도 자주 발목을 잡습니다. DeepSeek Harness(dsh)는 설정 파일(settings.yaml)과 실제 API 인증 키(~/.dsh/.credentials.yaml)를 물리적으로 분리합니다. 키 실수로 저장소에 커밋하는 사고를 방지하려는 의도입니다.
| 환경변수 명칭 | 표준 서식 및 예시 값 | 주요 결함 원인 및 방지 대책 |
|---|---|---|
| OPENAI_API_BASE | http://localhost:20128/v1 | URL 말단 슬래시(/) 중복이나 누락으로 인한 엔드포인트 404 호출 실패 |
| ANTHROPIC_API_KEY | sk-ant-api03-... | 도커 .env 안의 불필요한 따옴표 삽입으로 발생하는 문자열 파손 |
| DSH_HOME | /home/developer/.dsh | 물결표(~) 상대경로 입력으로 인한 비정상 디렉토리 생성 및 권한 에러 |
| SECRET_KEY | 32바이트 16진수 문자열 | 빈 값 방치로 인한 세션 암호화 실패 (openssl rand -hex 32로 생성) |
실행이 막히면 터미널 화면 위쪽에 지나간 긴 로그 대신 에러의 가장 밑바닥 심볼부터 확인해야 합니다. 터미널에서 grep -Ein "(error|exception|errno|fatal)" runtime.log | tail -n 15 명령으로 에러 코드를 뽑아낸 뒤, GitHub CLI를 통해 gh issue list -R deepseek-ai/deepseek-harness --search "EADDRINUSE is:closed"처럼 닫힌 이슈를 조회하면 삽질 시간을 크게 줄일 수 있습니다.
DeepSeek Harness는 Cordis 프레임워크를 기반으로 모델 어댑터와 도구를 독립 플러그인 형태로 조립하는 런타임입니다. 베이스캠프의 David Heinemeier Hansson(DHH)이 주도하는 오마치 OS(Omarchy)는 QuickShell과 Btrfs 스냅샷을 사용해 약 1분 30초 만에 개발 환경을 복원합니다. 이 조합을 쓰면 셋업 삽질에 버리는 반나절을 아낄 수 있습니다.
dsh 엔진에서 LLM에 들어가는 시스템 프롬프트는 코어 패키지인 dsh-system-prompt가 매 턴마다 조합합니다. 프롬프트 맨 앞단 텍스트를 고정하지 않고 동적으로 계속 바꾸면, 모델의 Key-Value(KV) 캐시가 첫 단락부터 깨집니다. 턴마다 입력 토큰 전체를 다시 계산하느라 응답 속도가 느려지고 비용도 샙니다.
# ~/.dsh/settings.yaml - 코어 설정 및 프롬프트 제어
llm-pi-ai:
providers:
local-omniroute:
type: openai-compatible
api:
baseURL: "http://127.0.0.1:20128/v1"
apiKeyEnv: "OMNIROUTE_API_KEY"
models:
- id: "auto/coding"
contextWindow: 128000
maxTokens: 8192
systemPrompt:
includeHarnessIdentity: false
persona: |
당신은 엄격한 TDD 원칙을 준수하는 엔지니어입니다.
1. 코드 수정 전 반드시 실패하는 단위 테스트를 먼저 작성합니다.
2. 부가 설명 없이 명확한 Git diff 형식과 표준 도구 명령만을 반환합니다.
시스템 프롬프트의 역할 정의는 고정 텍스트로 박아두고, 자주 바뀌는 파일 목록이나 작업 지시는 프롬프트 끝단이나 사용자 메시지로 넘겨야 공급자 수준의 프롬프트 캐싱 이득을 챙깁니다.
에이전트가 로컬 파일시스템을 탐색하거나 단위 테스트를 돌리다 외부 LLM API에서 504 Gateway Timeout이나 일시적 소켓 단절을 만나면 프로세스가 그대로 죽어버립니다. 헬스체크 엔드포인트를 감시하고 실패 시 재시도 간격을 점진적으로 늘리는 데몬 스크립트를 작성해 백그라운드로 띄워두어야 합니다.
#!/usr/bin/env bash
set -euo pipefail
export DSH_HOME="${HOME}/.dsh"
export OMNIROUTE_API_KEY="${OMNIROUTE_API_KEY:-sk-local-token}"
MAX_RETRIES=5
INITIAL_BACKOFF=2
PORT=3080
launch_agent_daemon() {
local retry_count=0
local backoff=${INITIAL_BACKOFF}
until curl -s -f "http://127.0.0.1:${PORT}/api/health" > /dev/null 2>&1; do
if [ ${retry_count} -ge ${MAX_RETRIES} ]; then
echo "[ERROR] 에이전트 런타임 기동 실패. 최대 재시도 횟수에 도달했습니다." >&2
exit 1
fi
echo "[INFO] DeepSeek Harness 구동 시도 ($((retry_count + 1))/${MAX_RETRIES})..."
npx --yes @deepseek-ai/dsh web --port ${PORT} --no-open >> "${DSH_HOME}/daemon.log" 2>&1 &
local pid=$!
sleep "${backoff}"
if kill -0 ${pid} 2>/dev/null; then
echo "[SUCCESS] DeepSeek Harness 정상 가동 (PID: ${pid})"
break
else
echo "[WARN] 프로세스 비정상 종료. ${backoff}초 후 재시도합니다."
retry_count=$((retry_count + 1))
backoff=$((backoff * 2))
fi
done
}
launch_agent_daemon
chmod +x daemon.sh로 권한을 주고 백그라운드로 돌려두면, API 일시 오류 때문에 터미널로 달려가 프로세스를 수동으로 다시 띄우는 수고를 덜 수 있습니다.
파이썬 기반 문서 파싱 파이프라인은 docx, xlsx, pdf 포맷마다 각기 다른 파이썬 라이브러리를 기워 붙여 쓰다가 표 셀 병합이 깨지거나 복잡한 수식이 통째로 날아가는 일을 겪기 쉽습니다.
Firecrawl이 공개한 AnyDoc은 외부 무거운 의존성 없이 단일 Rust 코어로 14가지 규격과 텍스트 PDF를 처리합니다. Firecrawl 벤치마크 기준 AnyDoc의 중앙값 변환 속도는 4.4~4.7ms 수준입니다. 헤드리스 LibreOffice가 평균 1,129ms 걸리는 것에 비하면 처리 속도 차이가 큽니다.
| 문서 변환 엔진 | 지원 포맷 수 | 중앙값 변환 속도 | 시스템 의존성 및 런타임 특성 | 복합 레이아웃(표·수식) 보존 수준 |
|---|---|---|---|---|
| Firecrawl AnyDoc | 14개 규격 + PDF | 4.4 ~ 4.7 ms | 외부 의존성 없음 (단일 Rust 바이트코드) | 상 (단일 직렬화 모델 정규화) |
| LibreOffice (Headless) | 12개 규격 | 1,129 ms | 무거운 시스템 패키지 (JVM, 폰트 팩) | 중 (포맷 간 변환 왜곡 잦음) |
| Mammoth (Python) | 1개 규격 (DOCX 전용) | 52 ms | 순수 파이썬 라이브러리 | 하 (병합 표 파손) |
| LangChain Unstructured | 다중 지원 (외부 래퍼) | 450 ~ 1,800 ms | Poppler, Tesseract 등 OS 레벨 의존성 | 중상 (변환 오버헤드 큼) |
AnyDoc은 텍스트 PDF와 각종 오피스 포맷을 바이트 시그니처 레벨에서 바로 판별합니다. 글자가 그림으로 굳어 있는 스캔 PDF가 들어오면 엉뚱한 텍스트를 꾸며내는 대신 NeedsOcrError 예외를 던집니다.
"""
AnyDoc 기반 다중 포맷 일괄 변환 및 이미지 경로 보정 스크립트
설치: pip install firecrawl-anydoc
"""
import os
import re
from pathlib import Path
import anydoc
class BatchDocumentConverter:
SUPPORTED_EXTENSIONS = {
'.docx', '.doc', '.docm', '.xlsx', '.xls', '.xlsm',
'.pptx', '.ppt', '.rtf', '.odt', '.ods', '.odp',
'.epub', '.csv', '.pdf'
}
def __init__(self, input_dir: Path, output_dir: Path):
self.input_dir = Path(input_dir)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
def execute_batch(self):
for root, _, files in os.walk(self.input_dir):
for file in files:
source_path = Path(root) / file
if source_path.suffix.lower() in self.SUPPORTED_EXTENSIONS:
self._process_single_document(source_path)
def _process_single_document(self, file_path: Path):
relative_path = file_path.relative_to(self.input_dir)
target_folder = self.output_dir / relative_path.parent / file_path.stem
target_folder.mkdir(parents=True, exist_ok=True)
assets_folder = target_folder / "assets"
try:
with open(file_path, "rb") as f:
raw_bytes = f.read()
format_hint = "csv" if file_path.suffix.lower() == ".csv" else None
doc_model = (anydoc.to_document(raw_bytes, format_hint)
if format_hint else anydoc.to_document(raw_bytes))
image_mapping = {}
if hasattr(doc_model, "assets") and doc_model.assets:
assets_folder.mkdir(exist_ok=True)
for idx, asset in enumerate(doc_model.assets):
mime_ext = asset.media_type.split("/")[-1] if hasattr(asset, "media_type") else "png"
img_name = f"extracted_img_{idx + 1}.{mime_ext}"
with open(assets_folder / img_name, "wb") as img_file:
img_file.write(asset.bytes)
image_mapping[getattr(asset, "id", f"asset_{idx}")] = f"./assets/{img_name}"
raw_markdown = anydoc.to_markdown(str(file_path))
normalized_markdown = self._sanitize_layout(raw_markdown, image_mapping)
result_path = target_folder / f"{file_path.stem}.md"
result_path.write_text(normalized_markdown, encoding="utf-8")
print(f"[성공] 변환 완료: {file_path.name} -> {result_path}")
except anydoc.NeedsOcrError:
print(f"[OCR 필요] 스캔 문서 감지: {file_path.name}. 호스티드 OCR 엔진으로 전달합니다.")
ocr_markdown = anydoc.to_markdown(str(file_path), ocr="hosted")
(target_folder / f"{file_path.stem}.md").write_text(ocr_markdown, encoding="utf-8")
except Exception as err:
print(f"[실패] {file_path.name}: {str(err)}")
def _sanitize_layout(self, content: str, img_map: dict) -> str:
lines = content.split("\n")
repaired_lines = []
for line in lines:
trimmed = line.strip()
if trimmed.startswith("|") and trimmed.endswith("|"):
line = re.sub(r"\s+", " ", line)
repaired_lines.append(line)
sanitized = "\n".join(repaired_lines)
for asset_id, local_rel_path in img_map.items():
sanitized = sanitized.replace(f"![{asset_id}]", f"")
return sanitized
if __name__ == "__main__":
converter = BatchDocumentConverter(Path("./raw_docs"), Path("./processed_md"))
converter.execute_batch()
AnyDoc은 파이썬 바인딩 동작 시 GIL(Global Interpreter Lock)을 해제합니다. 무거운 멀티프로세싱 라이브러리를 쓰지 않고 파이썬의 표준 ThreadPoolExecutor만 붙여도 수백 장의 사내 규정 문서를 병렬로 변환할 수 있습니다.
모든 프롬프트 호출을 최고가 플래그십 모델로 쏘는 습관은 사내 API 예산을 금방 바닥냅니다. 코딩 작업 중 절반 이상은 문법 오류 교정, 독스트링 생성, 단순 테스트 코드 작성처럼 상대적으로 가벼운 작업입니다.
UC 버클리와 LMSYS의 RouteLLM 연구 논문(2024)에 따르면, 작업 난이도를 판별해 모델을 동적으로 분기하는 방식을 취했을 때 MT-Bench 기준 GPT-4 수준 성능의 95%를 유지하면서 호출 비용을 85% 줄였습니다. 미국 통신사 AT&T 데이터 팀 역시 게이트웨이 프록시를 도입해 생성형 AI 운영 예산의 56%를 절감했습니다.
로컬 게이트웨이인 OmniRoute를 로컬 포트(20128)에 띄우면 작업 성격에 맞게 트래픽을 분기할 수 있습니다. 특정 벤더 API가 429(Rate Limit)를 반환하거나 타임아웃이 나면 1초 안에 백업 모델로 갈아타는 서킷 브레이커 기능도 지원합니다.
{
"name": "resilient-cost-saver",
"strategy": "priority",
"nodes": [
{
"provider": "anthropic",
"model": "claude-3-7-sonnet",
"priority": 1,
"timeoutMs": 10000
},
{
"provider": "deepseek",
"model": "deepseek-v4-pro",
"priority": 2,
"timeoutMs": 8000
},
{
"provider": "ollama-local",
"model": "qwen2.5-coder:32b",
"priority": 3,
"timeoutMs": 15000
}
],
"circuitBreaker": {
"errorThresholdPercentage": 50,
"recoveryTimeSec": 300,
"minimumRequests": 5
},
"compression": {
"enabled": true,
"engines": ["rtk", "caveman"]
}
}
10인 팀이 월 4억(400M) 토큰을 소비하는 환경을 가정하고, 단일 플래그십 호출 대비 OmniRoute 라우팅 규칙과 프롬프트 압축을 적용했을 때의 비용 차이를 계산했습니다.
| 라우팅 시나리오 | 모델별 트래픽 할당 비율 | 월간 토큰 소비량 | 백만 토큰당 유효 단가 | 월간 누적 지출액 | 비용 절감률 |
|---|---|---|---|---|---|
| 단일 플래그십 모델 고정 | 플래그십 100% | 400M | $15.00 | $6,000.00 | 기준선 (0%) |
| OmniRoute 분기 라우팅 | 단순 60%, 중간 25%, 고난도 15% | 240M (Haiku) |
100M (Sonnet)
60M (Opus) | $0.25
$3.00
$15.00 | $1,260.00 | 79.0% 절감 |
| 라우팅 + 프롬프트 압축 | 스마트 라우팅 + 토큰 30% 압축 | 280M (유효 토큰) | 가중 평균 환산 적용 | $882.00 | 85.3% 절감 |
브라우저에서 http://localhost:20128/dashboard로 들어가면 초당 요청 수와 서킷 브레이커 트립 여부, 잔여 쿼터를 실시간으로 모니터링할 수 있습니다.
엔지니어 Kevin Liu가 Three.js와 Vite로 구현한 3D 시뮬레이터 프로젝트 'Claude of Tanks'는 코드를 직접 치는 작업자 에이전트와 결과물 화면을 검증하는 평가자 에이전트를 직렬로 맞물린 구조로 화제가 되었습니다. 한편 프롬프트 지침에만 의존하지 않고 호스트 셸에서 파괴적 실행을 제어하는 Claudex 아키텍처는 에이전트 통제의 현실적인 기준점을 제시합니다.
UI나 그래픽스 개발에서 단일 에이전트는 작성한 코드가 문법적으로 맞아떨어지면 실제 화면에서 텍스처가 뭉개져도 문제를 눈치채지 못합니다. 코드를 작성하는 작업자 컨테이너와 헤드리스 브라우저로 렌더링 화면을 검증하는 비평가 컨테이너를 도커로 분리해야 작업물이 꼬이지 않습니다.
# docker-compose.yml - 멀티 에이전트 격리 실행 환경
version: '3.8'
services:
omniroute-core:
image: diegosouzapw/omniroute:latest
container_name: omniroute-core
ports:
- "20128:20128"
environment:
- PORT=20128
- NODE_ENV=production
volumes:
- omniroute-storage:/app/data
restart: unless-stopped
agent-worker:
image: node:22-bookworm-slim
container_name: agent-worker-node
working_dir: /workspace
depends_on:
- omniroute-core
environment:
- OPENAI_API_BASE=http://omniroute-core:20128/v1
- OPENAI_API_KEY=sk-local-dummy
- CLAUDE_CODE_SUBAGENT_MODEL=auto/coding
volumes:
- ./project_workspace:/workspace
- ./agent_hooks:/root/.claude/hooks:ro
- execution-logs:/workspace/.agent_logs
entrypoint: ["/bin/bash", "-c", "npm install -g @anthropic-ai/claude-code && tail -f /dev/null"]
agent-critic:
image: python:3.11-slim-bookworm
container_name: agent-critic-node
working_dir: /evaluator
depends_on:
- agent-worker
volumes:
- ./project_workspace:/workspace:ro
- ./evaluation_scripts:/evaluator
- execution-logs:/workspace/.agent_logs
entrypoint: ["python", "run_evaluator.py"]
volumes:
omniroute-storage:
execution-logs:
작업자 컨테이너에는 소스 코드 디렉토리 쓰기 권한을 주지만, 평가자 컨테이너에는 읽기 전용(:ro)으로 마운트합니다. 두 에이전트가 한 폴더에서 동시에 파일을 덮어쓰다 코드를 날려먹는 사태를 원천 차단하기 위해서입니다.
프롬프트에 "main 브랜치에 직접 커밋하지 마시오"라고 아무리 써붙여도 세션이 길어져 컨텍스트 압축이 일어나면 에이전트는 규칙을 잊어버립니다. 시스템 레벨의 훅 스크립트로 명령어를 중간에서 가로채는 물리적인 방어선이 필요합니다.
#!/usr/bin/env bash
# ~/.claude/hooks/pre-bash - 명령 실행 전 가로채기 훅
COMMAND="$1"
# 1. main 브랜치 직접 커밋 차단
if echo "${COMMAND}" | grep -qE "git[[:space:]]+commit.*(main|master)"; then
echo "[차단] main 브랜치 직접 커밋은 금지되어 있습니다. 작업 브랜치를 따서 진행하세요." >&2
exit 1
fi
# 2. 루트 및 상위 경로 강제 삭제 차단
if echo "${COMMAND}" | grep -qE "rm[[:space:]]+-rf[[:space:]]+(/|\.\.)"; then
echo "[차단] 상위 디렉토리 삭제 명령이 감지되어 중단합니다." >&2
exit 1
fi
# 3. 세션 복구를 위한 명령 로그 기록
LOG_PATH="${HOME}/.agent_logs/execution_trace.jsonl"
mkdir -p "$(dirname "${LOG_PATH}")"
echo "{\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"command\": \"${COMMAND}\"}" >> "${LOG_PATH}"
exit 0
이 스크립트를 걸어두면 에이전트가 실수로 메인 브랜치에 코드를 밀어 넣거나 프로젝트 상위 폴더를 날려먹기 직전에 셸 레벨에서 명령을 튕겨냅니다. 모든 도구 호출 기록은 추가 전용 JSONL 로그 파일에 남기 때문에 프로세스가 예기치 않게 뻗어도 직전 지점부터 작업을 이어받을 수 있습니다.