Skip to content

Repository files navigation

Agent-CLI

License: MIT Python

ReAct 패턴 기반 에이전트 CLI. 멀티 프로바이더(OpenAI, Anthropic) 지원, on-premise LLM 최적화.

설치

Python 3.10+ 필요.

# 릴리스 설치 (태그된 버전)
pip install "git+ssh://git@github.com/dujeonglee/agent-cli.git@v2.0.0"

# 웹 UI 포함
pip install "agent-cli[web] @ git+ssh://git@github.com/dujeonglee/agent-cli.git@v2.0.0"

# 개발 모드
git clone git@github.com:dujeonglee/agent-cli.git
cd agent-cli
pip install -e ".[dev]"

설치 후 agent-cli 명령어가 PATH에 등록됩니다. 처음 실행하면 설정 마법사가 자동으로 시작됩니다. 버전 확인은 agent-cli --version. GitHub Release에 첨부된 wheel(pip install agent_cli-*.whl)로도 설치할 수 있습니다.

업데이트

agent-cli update          # 최신 릴리스 확인 → 확인 후 설치
agent-cli update --check  # 새 버전 있는지 확인만 (설치 안 함)

GitHub CLI(gh)로 최신 릴리스 태그를 확인하고(private repo 인증은 gh 로그인이 처리 — 토큰 설정 불필요), 릴리스에 첨부된 wheel을 받아 pip로 업그레이드합니다. 개발용 editable 설치(pip install -e .)에서는 덮어쓰지 않고 git pull을 안내합니다(--force로 우회).

빠른 시작

# OpenAI 호환 (기본 프로바이더, 로컬 omlx/vLLM 등)
agent-cli run "List files in the current directory"

# 특정 모델 지정
agent-cli run "Read README.md" -m gpt-4o

# OpenAI
agent-cli run "Analyze this project" -p openai

# Anthropic
agent-cli run "Read README.md and summarize" -p anthropic

# 직접 셸 명령 (LLM 없이)
agent-cli run "/sh ls -la"

# 스킬 실행
agent-cli run "/review-code src/auth.py"
agent-cli run "/summarize README.md"

# 도움말
agent-cli --help
agent-cli run --help
agent-cli web --help
agent-cli sessions --help

설정

첫 실행 (Setup Wizard)

설정 파일(config.json)이 없으면 자동으로 설정 마법사가 시작됩니다:

$ agent-cli run "hello"

No configuration found. Starting setup wizard...

╭─ Agent-CLI Setup ─────────────────────────────────────╮
│                                                        │
╰─ ReAct pattern agent CLI for on-premise LLMs ─────────╯

1. Select LLM Provider
   [1] OpenAI compatible (OpenAI, vLLM, LM Studio, omlx) — default
   [2] Anthropic

2. Connection → base URL, API key, 연결 테스트
3. Model → OpenAI 호환·Anthropic 모두 `/v1/models`(provider별 인증 헤더)로 자동 탐색 및 목록 선택, 실패 시 직접 입력(OpenAI 기본 `gpt-4o`, Anthropic 기본 `claude-sonnet-4-20250514`)
4. Review → 설정 확인 후 저장 위치 선택

수동으로 다시 실행:

agent-cli setup

config.json

설정은 JSON 파일로 저장됩니다:

{
  "provider": "openai",
  "base_url": "http://127.0.0.1:8000/v1",
  "api_key": "",
  "default_model": "gpt-4o"
}

Jira export (선택)

웹 UI의 Export 기능(아래)에서 Jira 코멘트로 내보냅니다. config 는 선택입니다 — config 없이도 웹 UI 에서 base_url·계정·토큰을 직접 입력해 게시할 수 있습니다. 자주 쓰는 사이트는 config 에 등록해두면 드롭다운으로 뜨고 URL 이 미리 채워집니다(여러 인스턴스 가능). config 에는 base_url 둡니다 — 자격증명은 서버에 저장하지 않고, 코멘트를 다는 각 사용자가 웹 UI에서 본인 계정으로 입력합니다(그래서 코멘트 작성자가 서버 계정이 아니라 그 사용자 본인이 됩니다):

{
  "jira": {
    "instances": {
      "work": {"base_url": "https://work.atlassian.net"},
      "dc":   {"base_url": "https://jira.corp.net", "deployment": "server"}
    },
    "default": "work"
  }
}
  • UI 에서 URL 직접 입력(옵셔널): config 에 등록된 인스턴스는 드롭다운으로 고르면 URL 이 채워지고, 그 자리에서 수정하거나 새 URL 을 직접 타이핑할 수도 있습니다(localStorage 에 마지막 URL·계정 기억). config 에 없는 직접 입력 URL 은 http://https:// 둘 다 허용하므로 사내 평문 HTTP Jira 도 그대로 쓸 수 있습니다 — 다만 http:// URL 을 입력하면 자격증명이 평문으로 전송됨을 알리는 ⚠️ 경고가 폼에 표시됩니다(차단이 아니라 정보성; 신뢰된 네트워크에서만 사용하세요). http/https 외 scheme(또는 scheme 없는 값)은 거부됩니다.
  • Cloud / Server·DC 자동 판별: deployment 을 생략하면 서버가 {base_url}/rest/api/2/serverInfo 를 프로브해 자동 판별하고(웹 UI가 알맞은 입력 필드를 미리 선택), "cloud" / "server" 로 명시해 프로브를 건너뛸 수도 있습니다. UI 의 토글로 사용자가 직접 바꿀 수도 있습니다.
  • 자격증명 입력: Cloud 는 email + API token(Atlassian 계정 설정에서 발급), Server/Data Center 는 username + password(또는 PAT). 입력값은 그 브라우저의 localStorage 에만 저장되어 다음 접속 때 자동 채워지고, 코멘트 POST 한 번에만 transient 하게 쓰입니다(서버 로그·세션에 남지 않음). ⚠️ 웹 UI 는 LAN 평문 HTTP 이므로 신뢰된 네트워크에서만 사용하세요.
  • Jira Cloud 무료 티어(≤10명)로도 동작합니다.

설정 우선순위

높은 게 낮은 걸 덮어씁니다 (필드 단위 병합):

우선순위 위치 용도
1 (최고) CLI 파라미터 (-p, -m, --base-url, --api-key) 임시 오버라이드
2 .agent-cli/config.json (프로젝트) 워크스페이스별 설정
3 ~/.agent-cli/config.json (사용자) 전역 기본 설정
4 (최저) 환경변수 시스템 레벨

예: ~/.agent-cli/config.jsongpt-4o가 기본이지만 -m gpt-4o-mini로 임시 실행:

agent-cli run "task" -m gpt-4o-mini

DIRECTIVE.md — 프로젝트 지시사항

에이전트가 항상 따라야 하는 규칙을 DIRECTIVE.md 파일에 작성하면 매 세션의 시스템 프롬프트에 자동 주입됩니다.

경로 용도
.agent-cli/DIRECTIVE.md 프로젝트별 규칙 (코딩 컨벤션, 테스트 정책 등)
~/.agent-cli/DIRECTIVE.md 사용자 전역 규칙 (응답 언어, 개인 선호 등)
  • 두 파일 모두 존재하면 둘 다 로드 (프로젝트 먼저, 유저 전역 뒤에)
  • 동일 내용이면 중복 제거
  • 파일당 최대 4,000자, 전체 최대 8,000자 (초과 시 잘림)

예시 (.agent-cli/DIRECTIVE.md):

# 코드 규칙
- 코드 수정 시 관련 유닛 테스트를 반드시 추가한다
- 웹 UI 행동 계약(SSE·confirm 흐름·칩 렌더링·연결 고갈)은 실브라우저 테스트로: `AGENT_CLI_BROWSER_TESTS=1 pytest tests/browser/` (playwright+chromium, 기본 스위트는 미수집)
- ruff check와 ruff format을 통과해야 한다
- Python 3.10+ 호환을 유지한다

스코프 마커 (5.1.0) — 지침을 main LLM 과 서브에이전트에 나눠 보낼 수 있습니다:

공통 규칙 (마커 이전 = main + 모든 서브에이전트)

## @main
- 사용자 보고는 한국어로, 결론 먼저

## @agents
- 결과는 영어 bullet 로 간결하게 반환
  • ## @main 블록은 main LLM 에만, ## @agents 블록은 모든 서브루프(agent run/spawn·skill)에만 주입됩니다. 마커 라인 자체는 프롬프트에서 제거됩니다.
  • 블록은 다음 ## @ 마커(또는 파일 끝)까지 — 블록 안에 일반 ## 헤딩 여러 개를 담을 수 있습니다. 마커 없는 파일은 종전과 완전히 동일하게 전체가 공통입니다.
  • 웹 Prompt Inspector 의 📝 에디터가 이 스코프 구조를 탭(공통/Main/서브에이전트)으로 그대로 편집하며, ✨ 버튼으로 대략적 의도를 LLM 초안으로 만들 수 있습니다.

환경변수

변수 config.json 키 설명
AGENT_CLI_PROVIDER provider LLM 프로바이더
AGENT_CLI_BASE_URL base_url API 엔드포인트
AGENT_CLI_API_KEY api_key API 키
AGENT_CLI_MODEL default_model 기본 모델
ANTHROPIC_API_KEY Anthropic API 키 (기존 호환)
OPENAI_API_KEY OpenAI API 키 (기존 호환)
AGENT_CLI_NO_READLINE readline 비활성화
AGENT_CLI_WORKSPACE_CONFINE 워크스페이스 경로 봉쇄 (기본 on). 0 으로 끄면 봉쇄 없음. 아래 참고
AGENT_CLI_WORKSPACE_ROOT 봉쇄 기준 루트 경로 override (기본: 프로세스 실행 디렉토리)
AGENT_CLI_DANGEROUS_SHELL_CONFIRM 위험 명령(rm/rmdir/mv) 확인 프롬프트 (기본 on). 0 으로 끄면 비활성

LLM 요청 재시도는 고정 상수로 동작한다(더 이상 env 로 조정 불가). 네트워크 에러(Timeout / ConnectionError)는 최대 10회, 일시적 게이트웨이 5xx(502/503/504)는 독립 카운터로 최대 3회, 재시도 간격 1초. 4xx·bare 500 은 무재시도. 스트리밍은 헤더 대기를 30초로 바운드하고 body 10분 연속 침묵 시 연결을 끊고 재전송한다. 자세한 동작은 agent_cli/providers/http.py 참고.

컨텍스트 컴팩션은 항상 켜져 있다(90% 예산 초과 시 LLM 요약; 실패/여전히 초과 시 플레인 FIFO drop 으로 폴백). 서브에이전트에도 전파된다.

워크스페이스 경로 봉쇄

에이전트의 파일 쓰기·수정·shell 이 워크스페이스(실행 디렉토리) 경로를 건드리면 사용자에게 확인을 물어봅니다 (기본 on). 실수로 홈 디렉토리나 시스템 파일을 덮어쓰는 사고를 막기 위한 가드입니다.

  • 대상: write_file, edit_file, shell. read_file 은 봉쇄하지 않습니다 — 드라이버/커널 작업은 커널 소스·툴체인·헤더를 워크스페이스 밖에서 대량으로 읽으므로, 읽기까지 물으면 프롬프트 폭풍이 되어 사용자가 "always" 를 남발하게 됩니다.
  • shell: 명령에서 절대경로·../ 탈출 토큰을 best-effort 로 추출해 검사합니다. $(...)·python -c "..."·셸 변수($FILE) 안에 숨은 경로는 못 잡습니다 — 이건 사고 방지용 speed bump 이지 샌드박스가 아닙니다(진짜 격리는 OS 샌드박스 필요). 확인 창에는 명령이 표시되고 워크스페이스 밖 경로가 강조됩니다 (위험 키워드 rm/rmdir/mv 를 강조하는 것과 동일 — CLI 볼드-레드, 웹 .danger 스팬).
  • 응답: y(이번만) / n(거부) / a(이 위치를 이번 세션 동안 항상 허용). a 는 해당 디렉토리 서브트리를 세션 allowlist 에 넣어 이후 같은 곳은 재프롬프트하지 않습니다.
  • 비대화형(TTY 없음·연결된 클라이언트 없음)에서 밖 경로를 건드리면 거부됩니다 (멈추지 않음). 배치/CI 에서는 AGENT_CLI_WORKSPACE_CONFINE=0 으로 끄세요.
  • 루트: 기본은 프로세스 실행 디렉토리. AGENT_CLI_WORKSPACE_ROOT 로 override. agent-board 는 각 인스턴스를 그 게시물 워크스페이스에서 spawn 하므로 그대로 맞습니다.

모델 권장 사양

에이전트 루프는 JSON 포맷 준수 + 도구 선택 + 멀티스텝 reasoning을 동시에 요구합니다. 모델 크기에 따라 성능 차이가 큽니다:

모델 크기 멀티스텝 태스크 단순 질의 비고
7B 이하 도구 혼동, JSON 포맷 불안정, 반복 실패 빈번
14-30B 간단한 도구 사용 가능, 복잡한 스킬은 불안정
32B+ 안정적 — 권장 최소 사양
70B+ ✅✅ agent 위임, 복잡한 스킬 등 고급 기능 안정

인라인 추론 태그 자동 격리 (5.10.0): 일부 모델(MiMo 등)이 응답 content 안에 <think>…</think> 류 태그로 긴 추론을 흘립니다 — provider 응답 조립 지점에서 이를 제거해 wire 파싱·컨텍스트를 보호하고, 제거분은 thinking 필드로 이동해 --verbose 에서 볼 수 있습니다 (닫히지 않은 <think> 는 그 지점부터 전부 추론으로 간주). 태그 4종: think/thinking/reasoning/reflection.

최소: 30B, 권장: 32B+

권장 사양은 OpenAI 호환 서버로 서빙하는 로컬/온프렘 모델 기준입니다. 32B+ 클래스 모델에서 멀티스텝 태스크가 안정적으로 동작합니다.

명령어

run — 단발 실행

agent-cli run "task description" [options]
옵션 설명 기본값
-p, --provider openai / anthropic openai
-m, --model 모델 ID 프로바이더 기본값
--base-url API 엔드포인트 프로바이더 기본값
--api-key API 키 (환경 변수 자동 감지)
-n, --max-turns 최대 턴 (0=무제한) 0
--max-context-tokens 컨텍스트 윈도우 토큰 상한 (0=모델에서 자동 결정) 0
--max-depth 중첩 깊이 (agent + skill 합산). 한계 도달 시 두 도구 모두 자동 비활성. 2
--agent-timeout 서브에이전트 타임아웃 (초) 300
--result-file 최종 답변(원문)을 지정 경로에 기록 — 렌더러 장식 없는 기계 소비용(스크립팅). @profile 실행도 관찰 래퍼(STATUS/RESULT)를 벗긴 원문만 기록. 실패 시 파일 미생성 (없음)
-v, --verbose 원시 LLM 응답 + thinking 블록 + 컨텍스트 덤프 표시
--style 렌더러 스타일 (minimal 또는 커스텀 — agent_cli/render/<name>.py 플러그인. 커스텀 렌더러의 필수 구현은 9개(출력 코어 7 + 입력 2, v4.50.0)로 축소 — 디버그/장식 메서드는 안전한 기본값) minimal
--record-turns / --no-record-turns 세션 디렉토리에 turns.jsonl 기록 (회복률 통계용 메타데이터; prompt·응답 본문 미포함) --record-turns
--response-format Wire format 플러그인 이름. 빌트인: json_fc (기본 — 산문 reasoning + flat {action, params} op 들의 bare JSON 배열로 한 턴에 여러 독립 도구 호출, 종료는 complete op. md_array 의 리네임+리셰이프 후계 — 마크다운 헤더 제거, v6.0.0 bakeoff A/B 140run 에서 구형과 동등 확인. 구 ## Thought/## Action emission 도 drift 로 관용), xml_fc (태그-파라미터 <tool_call><function=X><parameter=k>v</parameter></function></tool_call> — 파라미터 값이 raw 텍스트라 파일 본문/최종 답변에 JSON escaping 불필요. <tool_call> XML 프라이어 모델용. 2026-07-17 Qwen 실측: 27B=natively 동등, 35B-A3B=구제 하니스(lenient+foreign, 무-왕복)로 완주 100%·실재시도 0.06/run — 양쪽 바인딩 가능(기본은 json_fc) — docs/multi-wire-format/PHASE2.md §8). agent_cli/wire_formats/에 모듈을 추가하면 자동 등록. 미등록 이름은 LLM 호출 전에 즉시 실패. 미지정 시 해석 체인: resume 세션의 기록 포맷 > models.json 모델별 wire_format 바인딩 > json_fc (해석 체인)

| --resume <id> | 이전 세션을 로드해 복원된 컨텍스트 위에 QUERY 를 이어지는 요청으로 실행. web --resume 과 같은 on-disk 세션이라 run↔web 상호 이어가기 가능 (v4.46.0) | (새 세션) |

run 실행 후 세션이 자동 저장됩니다. run --resume <id> 또는 web --resume <id>로 이어서 작업할 수 있습니다:

$ agent-cli run "Analyze the project structure"
# ... 실행 결과 ...
# Session 1774752167 saved. Resume with: agent-cli run/web --resume 1774752167

$ agent-cli run "이어서 테스트도 작성해" --resume 1774752167   # CLI 로 이어서
$ agent-cli web --resume 1774752167   # 또는 브라우저 UI 로 이어서

resume 시 web은 이전 대화(turn)를 UI에 그대로 재생해 어디서 끊겼는지 바로 확인할 수 있습니다 (중간 도구 호출/관찰 포함). Team 뷰의 활동 막대(스킬 밴드·일회성 run·팀원 작업 구간)도 함께 복구됩니다 — 이 이벤트는 대화 히스토리에 없어 세션 디렉토리의 scopes.jsonl 사이드카에서 재생됩니다. 사용자 레인과 ✓ 회신 화살표도 복구됩니다 (히스토리 user 레코드의 작성자 + complete 귀속을 재생에서 동일 규칙으로 재도출; v8.2.0 이전에 기록된 세션은 작성자 정보가 없어 화살표 없이 카드만 복원).

web — LAN 웹 UI (실험)

pip install 'agent-cli[web]'   # 옵션 의존성 설치 (FastAPI, uvicorn)
agent-cli web [-p openai] [-m model] [--port N] [--token <hex>]
agent-cli web --resume <session_id>    # 이전 세션 이어서 시작

agent-cli 인스턴스 하나를 단일 세션으로 브라우저 UI 에 노출. 기본 바인드는 loopback(127.0.0.1) — LAN 노출은 --host 0.0.0.0 opt-in(웹 UI 가 셸을 구동할 수 있으니 신뢰된 네트워크에서만). 자동 토큰 생성(또는 --token 지정) — 토큰은 시작 URL 에 한 번 실려 오고, 서버가 그걸 HttpOnly 쿠키로 교환한 뒤엔 브라우저 URL 에서 사라집니다(주소창·로그·Referer 누출 방지). 다중 뷰어 (모두 동등): 모든 탭이 스트림을 보고 모두 입력·큐 가능(controller/observer 구분 없음). 각 탭은 접속 시 재미있는 기본 닉네임이 채워진 입력으로 이름을 정할 수 있고(✕로 기본값 유지, 한 번 정하면 다음 접속에 기억), 접속자 로스터에 닉네임이 표시됩니다. 닉네임은 접속 후에도 로스터 옆 ✎ 버튼으로 언제든 다시 열어 변경할 수 있습니다(현재 닉네임이 채워진 채 재노출 → 즉시 로스터에 반영). 메시지 큐: 에이전트가 실행 중이어도 메시지를 보내면 큐에 쌓여 실시간 표시되고, 매 턴 종료 시 하나씩 디큐되어 대화에 주입(steering)됩니다. 자기가 큐한 메시지는 처리 전 ✕로 취소 가능. 모든 사용자 요청은 닉네임 라벨로 task 로그에 누적됩니다. 시작 시 토큰 포함 URL이 출력되고, 로컬 bind 일 때만 브라우저 자동 오픈(원격 --host <ip> 면 생략 — 아래 표 참조).

Team 뷰가 기본 표면 (v8.2.0): 웹 UI 의 주 화면은 팀 스윔레인입니다 — 사용자·main·에이전트들의 활동과 메시지 흐름이 한눈에 보이는 개요이자 네비게이터. 상세한 대화(타임라인)는 ▤ 상세 대화 버튼(또는 스윔레인의 막대·화살표·응답 독 클릭)으로 오른쪽에서 열리는 온디맨드 드로어입니다 — 클릭한 항목의 카드로 스크롤+하이라이트되어(같은 task_id/이벤트 시각으로 연결) 바로 맥락을 볼 수 있습니다. 👤 사용자 레인: 맨 왼쪽 레인 하나에 모든 웹 사용자가 multiplex 되어 표시됩니다 — 각 메시지 마크 옆에 보낸 사람의 닉네임이 붙고, 요청은 실선 화살표로 main 에 꽂힙니다. ✓ 회신 화살표: main 이 complete 하면 사용자 레인으로 파선 화살표가 그려지고, 라벨에 그 런이 처리한 모든 요청자(런을 시작한 사람 + 실행 중 조향 주입한 사람들, 예: ✓ Bob·두정)가 표시됩니다 — hover 하면 답 요약. 🤝 에이전트 보고가 깨운 런도 원 요청 승계로 귀속됩니다 (v8.5.0): main 이 에이전트에게 일을 시킬 때 그 시점 요청자들이 요청에 스냅샷되고, 회신이 이를 되실어 와 회신을 접은 런의 최종답이 원 요청자에게 화살표로 연결됩니다 — 응답 전에 다른 사용자의 요청이 끼어들어도 요청↔회신 쌍에 묶여 교차 오염이 없습니다. 승계할 요청자가 없는 런만 무화살표(독에 무귀속 표기). 응답 독: 입력창 위에 최신 답변이 3줄 미리보기로 상시 표시됩니다 — 귀속(→ [Bob, 두정]) 과 시각이 함께 뜨고, 새 답 도착 시 펄스로 알립니다. 답이 3줄을 넘을 때만 펼치기 버튼이 나타나 그 자리에서 전문을 읽을 수 있고, 본문을 클릭하면 드로어가 그 답의 카드 위치로 열립니다. ask/confirm 응답 표면은 기존 그대로 입력창 옆입니다. Team 뷰는 팀 활동을 세로 시퀀스 다이어그램으로 그립니다 — 시간이 아래로 흐르고 에이전트는 세로 컬럼(상단에 이름 헤더가 고정), 작업 구간은 세로 막대, 에이전트끼리 주고받은 메시지는 컬럼 사이 가로 화살표로 요청·회신 왕복이 모두 보입니다. 세로축은 균등한 시간축이 아니라 이벤트 기준입니다 — 요청/회신/작업 시작·종료 같은 각 이벤트가 고정 간격의 한 행을 차지하므로, 실제로 오래 걸린 구간이 화면을 잡아먹지 않고 에이전트들이 어떻게 피드백을 주고받았는지가 한눈에 보입니다(왼쪽에 각 이벤트의 실제 시각 표시, 실제 수행 시간은 막대에 마우스를 올리면 툴팁으로). skill(🪄)·일회성 run(⟲)은 호출자가 그동안 블록되므로 호출자 컬럼 안에 다른 색 구간으로 표시됩니다. 스킬 안에서 다시 스킬/에이전트를 실행하면(중첩) 자식이 부모 바로 오른쪽 칸에 그려지고 가는 선으로 부모와 이어집니다 — 깊이는 툴팁에 표시됩니다. 차례로 실행되는 형제는 같은 칸을 재사용하므로 스킬을 여러 개 연달아 돌려도 폭이 늘지 않고, 한 턴에 여러 에이전트를 동시에 띄운 경우(병렬 배치)만 옆 칸으로 갈라지며 ⋔ 포크와 동시 개수(⋔3)로 표시됩니다(동시 시작이라 같은 행에서 출발하고, 끝나는 시각은 각자). 오른쪽 타임라인에서도 자식 카드가 부모 카드 안에 들어갑니다 — 부모는 기본 접힘이라, 안에서 뭔가 돌고 있으면 부모 헤더에 ▸ … 실행 중 배지가 뜨고, 스윔레인에서 중첩된 막대를 클릭하면 부모 카드들이 자동으로 펼쳐진 뒤 해당 카드로 이동합니다. 각 컬럼 맨 아래에는 현재 상태가 표시됩니다 — 작업 중인 에이전트는 도는 스피너, 쉬는 에이전트는 녹색 체크(✓), main 도 응답 생성 중이면 스피너가 돕니다(타임라인이 드로어 안이라 기본 화면의 유일한 '응답 중' 단서). 이벤트가 도착할 때만 갱신되므로 주기적 폴링은 없습니다. 색은 현재 테마에 반응하고, 에이전트 없는 단순 대화도 사용자·main 두 레인으로 흐름이 그려집니다.

옵션 설명 기본값
--host bind 주소. 기본 loopback; LAN 노출은 --host 0.0.0.0 (opt-in) 127.0.0.1
--port listen 포트. 생략 시 0xC0DE(49374) 우선 시도 후 사용 중이면 OS가 빈 포트 자동 할당. 명시(--port 9090) 시 그 포트로만 바인딩 (충돌 시 에러). 실제 URL은 시작 시 출력됨. 자동 (0xC0DE=49374 → 빈 포트)
--token 인증 토큰 자동 생성 (32 byte URL-safe)
--no-browser 브라우저 자동 open 비활성 false
--idle-timeout N초 동안 접속자 0 + 진행 중 작업 없음이면 스스로 종료 (0 = 끄기). 온디맨드로 인스턴스를 띄우는 오케스트레이터용 — 다음 접속에 --resume 으로 재기동. 워커가 작업 중(LLM 턴·도구·질문 대기)이면 안 죽음(긴 작업 끊김 방지). 0 (끄기)
--trust-local loopback(127.0.0.1/::1) 요청은 토큰 인증 생략. 앞단에 인증을 직접 하는 신뢰된 로컬 게이트웨이/프록시를 둘 때 — 게이트웨이가 토큰을 매 요청 주입할 필요 없음. localhost 바인드(--host 127.0.0.1)일 때만 안전. 비-loopback 요청과 끈 상태에선 토큰 그대로 요구. false
--base-path 리버스 프록시가 /<prefix>/* 를 이 인스턴스로 라우팅(+prefix strip)할 때의 URL 경로 prefix (예 /s/doom). UI 의 모든 URL 은 상대경로이고 serve 되는 index.html 에 <base href="<prefix>/"> 가 주입돼 prefix 하위로 resolve 됩니다. 기본 '' = 루트(<base href="/">, 기존과 동일). '' (루트)

브라우저 자동 오픈은 로컬 bind 일 때만 일어납니다 — 기본 127.0.0.1(과 0.0.0.0/localhost/::/::1)처럼 이 머신에서 localhost 로 접속 가능한 경우. --host <원격IP> 처럼 특정 IP 에 바인드하면(원격 서버) 자동 오픈을 생략하고 접속 URL 만 출력합니다(서버에서 브라우저를 띄워봐야 소용없으므로). --no-browser 는 로컬에서도 끄고 싶을 때.

상태 사이드카(status.json)에는 v7.10.0부터 상주 에이전트 요약도 실립니다{"agents": {"alive", "working", "list": [...]}} (additive; 에이전트 없으면 필드 생략). board 가 행에 🤖 칩과 "에이전트 작업 중" 상태를 그리는 소스이고 /api/health 도 동일 요약을 반환합니다. --idle-timeout 자가 종료도 에이전트가 작업 중이거나 미처리 요청이 있으면 발동하지 않습니다 (main 유휴여도 백그라운드 작업 보존).

오케스트레이션(인스턴스 파일): web 은 시작 시 .agent-cli/sessions/<id>/web.json({session_id, host, port, token, pid})을 기록하고 종료 시 제거합니다. --idle-timeout 의 자가종료와 짝을 이뤄 — 외부 서비스가 인스턴스를 온디맨드로 띄우고, 프로세스를 추적·종료하지 않고도, 파일 하나로 "이 세션 web 떠 있나/어디로" 를 알아 접속(또는 --resume 재기동)할 수 있습니다. | --resume <id> | 이전 세션 이어서 실행 (agent-cli sessions로 ID 확인) | — | | 기타 (-p, -m, -n, --max-depth 등) | run과 동일 | |

UI 기능:

  • 좌측 어시스턴트 카드 (markdown 렌더링: 헤더 #/##/###, GFM 파이프 표, 순서/비순서 리스트, bold/italic, 인라인 코드, 펜스 코드 블록) + 우측 사용자 bubble
  • 도구 호출(action) / 결과(observation) 인라인 카드, ✓/✗ 상태 표시
  • 각 카드 모서리에 시각 표시 (YYMMDD HH:MM:SS, 마우스 hover 시 전체 날짜+밀리초). agent/skill 내부 카드도 동일. --resume 로 이어서 보면 카드들은 실제 발생 시각(history 기록 기준)으로 표시되어 재접속 시점과 혼동되지 않음
  • 실시간 스트리밍 (점선 카드로 토큰 누적 → 최종 카드로 교체)
  • 서브에이전트(delegate) 작업은 접히는 그룹 카드로 격리 — 헤더 아무 곳이나 클릭해 접기/펼치기, 긴 카드를 펼쳐 스크롤해도 헤더가 상단에 고정(sticky)돼 어느 위치에서든 접을 수 있고, 본문 여백 클릭으로도 접힘 (중첩 카드·텍스트 선택은 방해 안 함)
  • 컨텍스트 동기화: 오래된 turn이 LLM 컨텍스트에서 밀려나거나 compaction으로 요약되면 UI에서도 제거
  • 여러 탭/PC가 접속하면 모두 동등하게 입력 가능 (접속자는 닉네임 로스터에 표시). 같은 승인(confirm)/질문(ask)에 두 명이 동시에 답하면 먼저 도착한 답만 수용 — 늦은 답은 거절(409)되어 다음 프롬프트를 오염시키지 않고, 해당 화면의 stale 다이얼로그는 자동으로 접힘
  • 연결 고갈 방어는 board 열기 게이트가 담당 (운용 전제: 방은 항상 board 를 통해 열기) — 각 탭은 board 카운트용 BroadcastChannel 비콘에만 응답. 페이지 자체 입장 게이트(v7.5~7.6 Web Locks 파킹)는 제거됨: Web Locks 는 secure context 전용이라 LAN http 배포에서 동작하지 않고, URL 직접 진입 케이스는 운용 정책상 범위 밖. 주의: 브라우저 세션 복원으로 방 탭 5개 이상이 한꺼번에 재진입하면 origin 당 6연결 한도 포화가 재현될 수 있음 — 그때는 탭을 닫으면 됨
  • 승인(y/n/a) 버튼 클릭 후 3초 내 서버 응답이 없으면 "연결 정체" 경고를 표시 — 브라우저의 origin 당 동시연결 제한(HTTP/1.1, 6개)에 SSE 탭들이 몰려 클릭이 조용히 대기열에 갇히는 상황을 보이게 함 (안 쓰는 탭을 닫으면 회복)
  • 재접속/새로고침 시 트랜스크립트 재생은 최근 5,000개 이벤트 윈도우로 제한 — 아주 긴 세션은 맨 위에 "이전 N개 생략" 노티스가 표시됩니다 (전체 기록은 세션 히스토리에 보존, --resume 은 무관). 라이브로 보고 있던 탭은 영향 없음
  • ANSWERING 모드: ask 도구 호출 시 질문 텍스트가 입력창 위에 표시되어 스크롤 없이 답변
  • 부재 중 질문 대기 (v7.8.0): ask/위험 shell 승인/워크스페이스 밖 접근 승인은 지금 보는 사람이 없어도 대기합니다 — 즉시 "(no response)"로 포기하지 않고, 세션 상태가 awaiting_input 이 되어 board 목록에 "답변 필요"로 표시되며, 나중에 접속하면 대기 중이던 질문이 그대로 떠서(재접속 replay) 답할 수 있습니다. 다중 방 운용에서 다른 방을 보는 사이 도착한 질문을 놓치지 않음. CLI(터미널 없음=답 불가)는 종전대로 즉시 폴백
  • 칩 헤더 (v7.1.0): 상단 바가 의미 칩 3개로 수렴 — 모델 칩(max 28ch, hover=provider·전체 이름) · ctx 게이지 칩(사용률 % + 미니 게이지; 클릭하면 팝오버에 토큰 상세 ctx 5.2K/256K (2%) · ↑5.2K ↓320 · Σ↓1.8K + 컴팩션 임계 슬라이더 + Agents 상한이 열림) · 워크스페이스 칩(📋 …/마지막/2세그먼트, hover=전체 경로, 클릭=경로 복사 — LAN http 에서도 동작, 복사 후 1초 ✓). 어떤 창 폭·모델명·경로에서도 헤더 한 줄 보장.
  • 토큰 현황: ctx 칩과 팝오버가 매 turn 갱신, 새로고침 후에도 유지(SSE snapshot). CLI(run)도 동일 정보를 매 turn 한 줄로 표시(in: … | out: … | ctx: … | Σout: …). usage.input_tokens(서버 실측)를 받아 renderer.token_usage 로 추상화 → CLI/web 공통
  • 압축 임계 슬라이더 (5.14.0): 토큰 현황 옆 압축 80% 슬라이더로 컨텍스트 compaction 목표 비율세션 한정으로 조절합니다(50~95%, 5% 스텝). 가용 컨텍스트(context_window − system − output)가 이 %에 닿으면 압축이 걸립니다 — 낮출수록 일찍/자주 압축(컨텍스트 적게 유지, 오버플로 여유↑), 높일수록 늦게 압축(컨텍스트 많이 유지). web 과 loop 이 같은 ContextManager 를 공유하므로 슬라이더를 놓는 즉시 다음 LLM 콜에 반영되고, 여러 뷰어의 슬라이더는 sticky 브로드캐스트로 동기화됩니다. main 대화 전용이며, 서브에이전트는 spawn/resume 시점의 값을 상속합니다(이미 도는 서브에 새 값을 적용하려면 재spawn). GET/POST /api/compaction, 토큰 인증. (재시작하면 기본 80% 로 복귀 — 영속 아님.)
  • Send → Stop → Stopping… 토글: 사용자 메시지 전송 후 worker 가 응답을 처리하는 동안 Send 버튼이 빨간 Stop 버튼으로 바뀝니다. 클릭하면 즉시 Stopping…(비활성)으로 바뀌어 중복 클릭을 막고, 진행 중인 turn 을 안전하게 중단 (CLI 의 Ctrl+C 와 같은 stop_event 경로 → POST /api/stop). LLM 생성 도중이면 스트림을 즉시 끊고 미완성 응답을 폐기하며, 도구 실행 중이면 그 스텝을 마친 뒤 멈춥니다. turn 이 끝나면 다시 Send 로 복귀. 중단은 [interrupt] observation 으로 기록되어 다음 입력에서 이어집니다. 두 번째 메시지가 in-flight turn 에 끼어드는 것도 자연히 차단 (Enter 는 stop 을 트리거하지 않음 — 버튼 전용). 새로고침 / 재접속 후에도 상태 유지 (서버가 last worker state 를 SSE snapshot 에 prepend). prompt 모드(ask 답변)에선 항상 Send, confirm 모드는 별도 버튼. 일반 chat·/skill·@<profile>(agent run) 실행 모두 Stop 으로 중단됩니다 (run 팬아웃은 병렬 worker 가 같은 stop_event 를 공유).

🎨 테마 피커: 헤더의 🎨 버튼으로 드롭다운을 열어 5가지 큐레이션 테마(각 항목에 색 스와치 + 현재 ✓)를 고릅니다 — Amber(기본, 따뜻한 차콜+호박색), Slate(중성 다크), Midnight(딥 블루), Terminal(틸/near-black), Light. 선택은 즉시 적용 + 브라우저에 기억되고(localStorage), 첫 페인트 전에 적용되어 깜빡임이 없습니다. 다크 테마들은 반투명 헤어라인 테두리로 부드러운 경계를 씁니다. 모든 색은 CSS 디자인 토큰(:root 베이스 + [data-theme="…"] 오버라이드) 한 곳에서 파생되어, 테마 추가 = 토큰 블록 하나입니다. 버튼 체계 (5.5.0): 변형 4종 — btn-primary(화면당 주행동 하나: Send/저장/다운로드) / btn-ghost(hairline 기본값: 취소·보조 동작) / btn-danger(Stop·삭제 — 평시엔 조용, hover 에서만 채움) / btn-icon(헤더 도구·✕ 닫기) — 이 전부이며, 전역 베이스가 모든 버튼에 투명 배경+테마 글자색을 강제해 스타일 누락 버튼이 브라우저 기본 배경으로 '유령'이 되는 사고를 차단합니다. amber/terminal 은 밝은 accent 위 대비를 위해 어두운 잉크 글자(--on-accent)를 씁니다.

⚡ Prompt Inspector: 헤더의 ⚡ 버튼으로 우측 드로어를 열면 현재 턴에 실제로 전송된 시스템 프롬프트를 섹션별로 확인할 수 있습니다. 상단의 토큰 예산 스택바(섹션별 색·비율), 섹션 아코디언(이름·토큰 뱃지·본문), 검색 필터를 제공합니다. 열 때마다 최신 LLM 호출의 스냅샷을 가져오며(GET /api/debug/prompt, 토큰 인증), 훅이 주입한 동적 섹션도 Hook: <이름>으로 표시됩니다. 컨텍스트 압축(compaction)이 일어나면 그 요약과 파일 목록도 ⊙ Compaction summary / Files touched (user-injected) 섹션으로 함께 보여, 모델이 실제로 받는 압축 컨텍스트를 검사할 수 있습니다. 정적 시스템 프롬프트 아래에는 ── 동적 컨텍스트 (대화 · 관찰) ── 구분선과 함께 현재 컨텍스트 윈도우에 든 대화·관찰(ctx.get_messages() 의 system 제외분)이 메시지별 섹션으로 표시되어, 시스템 프롬프트뿐 아니라 LLM 이 실제로 받는 전체 입력을 검사할 수 있습니다(메인 스코프 한정). 첫 메시지 전에도 채워집니다 — 시작 시 시스템 프롬프트를 미리 캡처하고(Hook: 동적 섹션은 첫 LLM 호출 후 채워짐), --resume 면 복원된 대화도 드로어를 열자마자 보입니다. 에이전트별 스코프: 서브에이전트(agent run)가 돌면 드로어 상단에 [Main] [explorer·1] [coder·2] 칩 row가 나타나, 칩을 클릭하면 해당 서브에이전트가 실제로 받은 시스템 프롬프트로 전환되고 Main을 누르면 메인으로 돌아옵니다. 끝난 서브에이전트의 프롬프트도 사후 검사를 위해 남아 있으며, 칩의 ✕로 개별 제거할 수 있습니다(Main은 제거 불가). 📝 Directives 에디터 (청중 스코프 탭, 5.4.0): 드로어 상단에서 프로젝트 .agent-cli/DIRECTIVE.md 를 편집합니다 — 에디터 구조 = 파일 구조: 🌐 공통(main+모든 서브에이전트) / 🎯 Main(## @main) / 🤖 서브에이전트(## @agents) 세 탭이 파일의 스코프 블록과 1:1 로 대응하고(분해·조립은 서버의 U-C 파서 단일 출처), 내용 있는 탭엔 ● 뱃지가 붙습니다. 각 탭은 직접 편집하거나, 아래 입력줄에 넣고 싶은 내용을 대략적으로 적고 ✨ 생성을 누르면 LLM 이 그 청중용 directive 초안을 써서 탭에 반영합니다(기존 내용이 있으면 병합/개정) — 초안은 미저장 상태로 들어와 검토 후 저장. 생성은 산문 직접 호출(provider 1콜 + 구조적 새니타이저)입니다 — Qwen3.6 세대에서 CoT 누출 무발생을 재실측(16/16)한 뒤 단순화(5.7.0). 메인 대화/워커와 무접촉이라 타임라인에 흔적이 없고, 탭별로 동시에 여러 생성을 걸 수 있습니다. 새니타이저는 <think> 블록·코드 펜스만 벗기는 포장 제거 전용(내용 필터 없음)이라 미래 모델 교체로 누출이 재발해도 조용히 오염되지 않습니다. 저장하면 세 탭이 하나의 파일로 조립되어 기록되고 다음 LLM 호출부터 적용(KV 캐시 prefix 리셋), 여러 인스펙터 간 브로드캐스트 동기화. (구 성격/업무/지침 3축·프리셋 라이브러리·📋 템플릿은 5.4.0 에서 폐지 — "모든 방 공유" 지침은 전역 ~/.agent-cli/DIRECTIVE.md 가 담당합니다.) API: GET/POST /api/directives(scopes 계약) · POST /api/directives/generate, 토큰 인증, 프로젝트 파일 전용.

📤 Export: 헤더의 📤 버튼으로 선택 모드에 들어가면 각 대화 카드 좌측에 체크박스가 나타납니다(기본 전부 해제). 원하는 카드를 고르거나 All로 전체 선택한 뒤(하단 액션바에 선택 개수 표시), 두 가지로 내보낼 수 있습니다:

  • ⬇ HTML — 선택한 대화를 self-contained HTML 파일로 다운로드(스타일 인라인, 어디서나 열림).
  • Jira… — 선택한 대화를 한 개의 Jira 코멘트로, 본인 계정으로 게시. 인스턴스 드롭다운(설정 시) + base URL(설정값 prefill, 직접 입력·수정 가능) + Cloud/Server 토글 + 본인 계정·토큰(또는 username·password) + issue key(예: PROJ-123) 입력 후 Send. URL·자격증명은 브라우저 localStorage 에 기억됩니다. Cloud 는 ADF, Server/DC 는 wiki 마크업으로 전송. 설정은 위 Jira export 참조.

📁 Workspace files (다운로드 + 업로드 + 삭제): 헤더의 📁 버튼으로 우측 드로어를 열면 워크스페이스 파일 트리가 나타납니다(디렉토리는 ▶로 펼쳐 하위 탐색). 파일·디렉토리 옆에 크기 표시 — 디렉토리는 하위 전체를 합산한 재귀 크기이고, 맨 위 루트 행(📁 /)에는 워크스페이스 총 크기가 표시됩니다. 한 드로어에서 다운로드·업로드·삭제를 모두 합니다.

  • 다운로드: 원하는 파일/디렉토리를 체크하거나 트리 맨 위 루트 행(📁 /)의 체크박스로 워크스페이스 전체를 선택한 뒤 ⬇ Download zip 을 누르면 선택 항목이 임시 zip으로 압축되어 다운로드됩니다(전송 후 서버의 임시 파일은 삭제). 디렉토리를 고르면 그 하위 전체가, 파일을 고르면 그 파일만 담깁니다. 트리에서 폴더를 클릭하면 그 폴더가 업로드 대상이 됩니다(루트 행 클릭 = 루트).
  • 업로드: 드로어 안으로 파일이나 폴더를 드래그-드롭하거나 파일/폴더 버튼으로 선택하면 업로드됩니다(파일당 한 요청). 폴더를 드롭/선택하면 하위 구조를 그대로 보존해 재귀로 업로드합니다(중간 디렉토리는 서버가 자동 생성). 업로드 위치는 트리에서 클릭한 폴더(클릭하면 하이라이트되고 "⬆ 업로드 대상" 에 표시), 클릭하지 않으면 워크스페이스 루트입니다. 결과(✓ 경로, 덮어쓴 경우 표시) 후 트리가 자동 갱신됩니다.
  • 삭제 (🗑): 체크한 파일/디렉토리를 🗑 Delete 로 삭제합니다. 영구 삭제라 먼저 확인(confirm) 창이 뜨고, 디렉토리는 하위까지 재귀 삭제됩니다. 삭제 후 트리 자동 갱신(개수·실패 보고).

보안 가드: 워크스페이스(서버 실행 디렉토리) 밖 경로는 서버가 차단하고, 업로드는 경로의 모든 세그먼트에서 ../절대경로 금지(traversal 차단)·resolve 후 워크스페이스 하위 재검증·대상 경로 존재 확인·파일당 50MB·토큰 인증을 적용합니다. 삭제는 가장 파괴적이라 가드가 가장 강합니다 — 워크스페이스 하위만, traversal 차단, 워크스페이스 루트 자체는 삭제 거부, 토큰 인증. (드래그로 파일을 브라우저 밖으로 끌어내는 다운로드는 브라우저 제약상 미지원 — zip 버튼이 전 브라우저·디렉토리 경로입니다.)

종료 (Ctrl+C): 한 번의 Ctrl+C로 깨끗하게 종료됩니다. uvicorn의 lifespan shutdown 훅이 활성 SSE 연결을 정리하고, 백그라운드 worker는 SHUTDOWN sentinel로 깨어나 빠져나가며, 세션이 자동 저장됩니다. agent-cli web --resume <session_id>로 이어서 실행하면 이전 turn들이 SSE snapshot으로 재생되어 UI에 그대로 복원됩니다.

CLI parity 명령어:

  • /help — 웹 모드 명령어 안내
  • /sh <command> — LLM 우회 셸 실행
  • /skills — 사용 가능한 스킬 목록
  • /<skill> <args> — 스킬 직접 실행 (예: /optimize ./)
    • 경로 오인 방지 (v4.51.1): /Users/me/proj 분석해줘 처럼 경로로 시작하는 메시지는 명령이 아니라 일반 요청으로 LLM 에 전달됩니다 — 첫 토큰에 두 번째 / 가 있거나, 실존 경로(/etc, /tmp 등)거나, 명령 문법(영숫자·-·_)이 아니면 스킬 디스패치를 건너뜁니다. 오타(/no-such-skill)는 여전히 not-found 안내.
  • @agents — 사용 가능한 에이전트 목록
  • @<agent> <task> — 에이전트에게 작업 위임

curl로도 직접 사용 가능:

curl -N "http://localhost:49374/api/stream?token=<TOKEN>"
curl -X POST "http://localhost:49374/api/input?token=<TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"kind":"chat","content":"hello"}'

setup — 설정 마법사

agent-cli setup

프로바이더, 접속 정보, 기본 모델을 대화형으로 설정합니다. 설정이 없을 때 자동으로 실행되며, 언제든 수동으로 다시 실행할 수 있습니다.

sessions — 세션 관리

runweb 모두 세션을 .agent-cli/sessions/{session_id}/에 자동 저장합니다. 세션 종료 시 컨텍스트 윈도우 내용이 요약으로 저장됩니다. --resume으로 이전 세션을 이어서 작업할 수 있습니다.

# 현재 워크스페이스의 세션 목록
agent-cli sessions

# 특정 워크스페이스의 세션 목록
agent-cli sessions --workspace /path/to/project

# 이전 세션 이어서 작업
agent-cli web -p openai -m gpt-4o --resume <session_id>

각 세션은 id·시각과 함께 마지막 사용자 요청(↳)마지막 결과(→) 를 한눈에 보여줍니다 (아직 끝나지 않은 run 은 → (in progress)). 이 요약은 세션의 history.jsonl 에서 마지막 user↔complete 페어를 읽어 만듭니다 (별도의 query 메타 필드는 제거됨).

Sessions for /path/to/project:

  1774752167 2026-06-04 10:31:02
      ↳ Analyze the project structure
      → src/, tests/, docs/ 3개 최상위 디렉토리로 구성된…
옵션 설명 기본값
-w, --workspace 워크스페이스 경로 필터 현재 디렉토리

--resume 없이 시작할 때: web--resume 없이 실행하면, 가장 최근 세션을 위 포맷으로 보여주고 Resume it? [y/N] 를 묻습니다. y 면 그 세션을 이어가고, 그 외(Enter 포함)는 새 세션으로 시작합니다 (안전한 기본값). 파이프/비대화 환경(stdin 이 TTY 아님)에서는 묻지 않고 항상 새 세션입니다.

LLM은 read_context 도구로 현재 또는 이전 세션의 이력을 SQL 로 질의할 수 있습니다 (history 테이블에 SELECT — kind/tools/files/author/turn/text 컬럼, 읽기전용).

세션 메모리 (memory 도구)

LLM 이 세션 중 중대한 실패·중요한 발견·결정·메모를 명시적으로 기록하고 필요할 때 꺼내 쓰는 도구입니다. read_context(raw 이력 SQL 질의)와 달리 LLM 이 큐레이션한 durable salience 로, 컨텍스트 압축(compaction)에도 유실되지 않습니다.

  • 왜 필요한가: 컨텍스트가 예산의 90% 를 넘으면 오래된 대화가 요약/드롭됩니다. "왜 이 빌드가 깨졌는지" 같은 중요한 정보가 이때 사라집니다. 메모리는 롤링 컨텍스트 밖 (<session_dir>/memory.jsonl)에 저장돼 압축 대상이 아니고, --resume 로 복원됩니다.
  • 상시 인덱스: 기록된 메모리의 요약(id·타입·한 줄)이 시스템 프롬프트의 ## Session Memory 섹션에 항상 노출되어, LLM 이 "무엇을 기록했는지" 잊지 않습니다. 전체 내용은 memory(mode=get, id=N) 으로 필요할 때만 꺼냅니다(토큰 절약 + recall 보장).
  • 타입 4종: failure ⚠(반복 회피) · discovery 💡(재사용) · decision 🔀(근거) · note 📝(일반).
  • 모드: add(type+summary, 선택 detail/tags → id) · get(id) · update(id + 바꿀 필드) · delete(id) · list(type/tag 필터). 발견이 나중에 틀리면 update/delete 로 정정.

스킬 (Prompt Skills)

특정 작업에 최적화된 재사용 가능한 프롬프트 템플릿. Claude Code 스킬 포맷과 호환.

패키지 내장 스킬 (built-in)

패키지와 함께 배포되는 메타 스킬:

스킬 설명
/create-skill <name> 새 스킬 파일을 대화형으로 생성 (SKILL.md + scripts/)
/create-agent <name> 새 에이전트 정의 파일을 대화형으로 생성
/plan <feature> 기능 요청을 작업 분해 + 의존성 + 범위 추정으로 구조화하여 plan/ 에 저장
/orchestrate <task> 자율 에이전트 팀 부트스트랩 — 계획 수립 후 워커들을 idle 로 소환만 하고(일을 시키면 회신이 main 을 깨움), 로스터(키)를 orchestrator 에이전트에게 인계 후 즉시 반환. 조율(배정→리뷰→수리)은 orchestrator 가 peer message 로 자율 진행, main 은 최종 보고만 자동 수신

사용자가 같은 이름의 스킬을 .agent-cli/skills/에 만들면 built-in을 오버라이드합니다.

프로젝트 스킬 예시

# 코드 리뷰
agent-cli run "/review-code src/auth.py"

# 파일 요약
agent-cli run "/summarize README.md"

# 유닛 테스트 생성
agent-cli run "/test src/utils.py"

# 코드 최적화 분석
agent-cli run "/optimize ./agent_cli"

# 대화형(web) 모드에서도 사용 가능
/review-code src/auth.py
/skills   # 사용 가능한 스킬 목록

커스텀 스킬 작성

/create-skill my-skill 명령으로 대화형 생성하거나, .agent-cli/skills/my-skill.md 파일을 직접 만들면 /my-skill 명령어가 자동 등록됩니다:

---
name: my-skill
description: What this skill does
allowed-tools: [read_file, shell]
max-turns: 5
argument-hint: "<file_path>"
---

Your custom prompt template here. Use $ARGUMENTS for user input.
$0, $1 or $ARGUMENTS[0], $ARGUMENTS[1] for individual arguments (0-based).
${CLAUDE_SKILL_DIR} for the skill's directory path.
${SESSION_ID} for the current session ID.
!`command` for dynamic context injection (shell command output).
Frontmatter 필드 설명 필수
name 슬래시 명령어 이름 (미지정 시 파일명)
description 스킬 설명
allowed-tools 허용 도구 리스트 (미지정 시 전체)
max-turns 최대 턴 (미지정 시 글로벌 설정 사용)
model 스킬 실행 시 모델 오버라이드 (미지정 시 현재 모델 사용)
context fork이면 독립 컨텍스트에서 실행 (부모 대화 히스토리 없음)
hooks 스킬 스코프 lifecycle hooks (PreToolUse, PostToolUse 등)
disable-model-invocation true이면 LLM 자동 호출 금지 (사용자만 /명령으로 호출 가능)
user-invocable false이면 /skills 메뉴에서 숨김 (LLM만 호출 가능)
argument-hint /skills 표시 시 인자 힌트

스킬 검색 경로:

  1. .agent-cli/skills/*.md (프로젝트 로컬 플랫, 우선)
  2. .agent-cli/skills/<name>/SKILL.md (프로젝트 로컬 디렉토리)
  3. ~/.agent-cli/skills/*.md (사용자 전역 플랫)
  4. ~/.agent-cli/skills/<name>/SKILL.md (사용자 전역 디렉토리)

같은 검색 경로 내에서 동일 이름의 플랫 파일과 디렉토리 스킬이 모두 존재하면 에러가 발생합니다.

Hooks

에이전트 라이프사이클 전반에 걸쳐 동작하는 확장 시스템입니다. Python hookShell hook 두 가지 방식을 지원합니다.

Python Hooks

.agent-cli/hooks/*.py (프로젝트) 또는 ~/.agent-cli/hooks/*.py (유저 전역):

# .agent-cli/hooks/00_memory.py
EVENTS = ["OnSessionStart", "OnTurnEnd", "OnSessionEnd"]

def on_session_start(ctx):
    """세션 시작 시 관련 메모리 로드."""
    results = ctx.search_memory("project context")
    if results:
        ctx.inject_system_section("Memory", format_memories(results))

def on_turn_end(ctx):
    """중요 결정을 메모리에 저장."""
    if ctx.messages:
        last = ctx.messages[-1].get("content", "")
        if "edit_file" in last:
            ctx.store_memory([{
                "name": f"change_turn_{ctx.turn}",
                "entityType": "file_change",
                "observations": [last[:200]],
            }])

def on_session_end(ctx):
    """세션 요약 저장."""
    ctx.store_memory([{
        "name": f"session_{ctx.session_dir.name}",
        "entityType": "session",
        "observations": [f"Completed {ctx.turn} turns"],
    }])

파일 규칙:

  • 숫자 prefix 순서 실행 (00_10_20_)
  • 프���젝트 hooks가 유저 hooks보다 먼저
  • EVENTS 리스트로 구독할 이벤트 선언
  • 에러 시 해당 hook 건너뜀 (에이전트 루프 중단 없음)

Hook 이벤트 (11개)

이벤트 시점 Python Shell
OnSessionStart 세션 시작
PreLLMCall LLM 호출 직전 (매 턴)
PostLLMCall LLM 응답 수신 후
PreToolUse 도구 실행 직전 ✓ block/modify ✓ exit 2=block
PostToolUse 도구 실행 직후
OnTurnEnd ��� 종료 후
OnAgentStart agent run 실행 직전
OnAgentEnd agent run 완료 후
OnSkillStart skill 실행 직전
OnSkillEnd skill 완료 후
OnSessionEnd 세션 종료

실행 순서: Python hooks → Shell hooks (같은 이벤트 내)

HookContext

Python hook 함수가 받는 컨텍스�� 객체:

def pre_llm_call(ctx):
    ctx.event          # 이벤트 이름 ("PreLLMCall")
    ctx.messages       # 현재 cache messages (읽기/쓰기 — compaction/FIFO 후 상태)
    ctx.turn           # 현재 턴 번호
    ctx.session_dir    # 세션 디렉토리 Path
    ctx.tool_name      # PreToolUse/PostToolUse 시 도구 이름
    ctx.tool_input     # PreToolUse 시 도구 입력
    ctx.tool_result    # PostToolUse 시 ToolResult
    ctx.llm_response   # PostLLMCall 시 LLM 응답 텍스트

    # Context 조작
    ctx.inject_message("system", "remember this")
    ctx.inject_system_section("Memory", "facts...")
    ctx.remove_system_section("Memory")

    # 도구 제어 (PreToolUse only)
    ctx.block("reason")
    ctx.modify_input({"command": "ls"})

    # MCP 메모리 (MCP memory 서버 연결 시)
    ctx.store_memory([{"name": "...", "entityType": "...", "observations": [...]}])
    ctx.search_memory("query")
    ctx.read_memory()

Shell Hooks (기존 방식)

.agent-cli/hooks.json:

{
  "PreToolUse": [
    {
      "matcher": "shell",
      "hooks": [{"command": "./block-dangerous.sh", "timeout": 30}]
    }
  ],
  "PostToolUse": [
    {
      "matcher": "edit_file",
      "hooks": [{"command": "ruff format $(cat | jq -r '.tool_input.path')"}]
    }
  ]
}
  • stdin으로 JSON 전달: {"hook_event_name", "tool_name", "tool_input", "tool_result"}
  • matcher: 도구 이름 regex (빈 문자열 = 모든 도구)
  • exit 0 = 통과, exit 2 = 차단 (PreToolUse만)
  • stdout JSON의 updatedInput으로 도구 인자 수정 가능 (PreToolUse만)

스킬 내 hooks

스킬 frontmatter에서도 shell hooks를 정의할 수 있습니다 (해당 스킬 실행 중에만 활성):

---
name: safe-deploy
description: Deploy safely
hooks:
  PreToolUse:
    - matcher: shell
      hooks:
        - command: "./validate-deploy.sh"
---

프로바이더 설정

OpenAI 호환 (기본)

# 로컬/온프렘 서버 (omlx, vLLM, LM Studio 등)
agent-cli run "task" --base-url http://127.0.0.1:8000/v1 -m my-model

# 호스티드 OpenAI
export OPENAI_API_KEY="sk-..."
agent-cli run "task" -p openai --base-url https://api.openai.com/v1 -m gpt-4o

Anthropic

export ANTHROPIC_API_KEY="sk-ant-..."
agent-cli run "task" -p anthropic

System prompt에 자동으로 prompt cache(cache_control: ephemeral)가 적용된다. 한 세션에서 두 번째 콜부터 시스템 프롬프트가 캐시 히트하여 입력 비용을 90% 절감한다. 캐시 hit/write 토큰은 turn summary에 표시된다.

모델 레지스트리

models.json에 모델별 능력치를 정의합니다:

{
  "models": {
    "gpt-4o": {
      "provider": "openai",
      "context_window": 32768,
      "max_output_tokens": 4096,
      "supports_structured_output": true,
      "supports_thinking": true,
      "thinking_budget": 4096,
      "supports_strict_schema": false,
      "thinking_format": "think"
    }
  }
}

파일 위치 및 정책

우선순위 위치 역할 자동 저장
1 .agent-cli/models.json 프로젝트 로컬 오버라이드 안 함 (읽기만)
2 ~/.agent-cli/models.json 사용자 전역 설정 새 모델 자동 저장
3 agent_cli/default_models.json 패키지 기본값 안 함 (읽기만)
  • 미등록 모델은 런타임 자동 감지 → ~/.agent-cli/models.json에 저장
    • OpenAI 호환: context window를 /v1/modelsmax_model_len에서 읽고, 값이 없으면 컨텍스트 오버플로 프로브로 추정. 추가로 thinking 지원 여부를 프로브.
    • Anthropic: 동일한 로직을 Anthropic API(/messages, x-api-key+anthropic-version)로 프로브 — context window는 /models 메타(omlx) → /messages 오버플로 프로브 → 128K 폴백, structured-output 은 프롬프트 기반 JSON 검사. (/v1/models 메타가 없는 실 Anthropic 모델은 레지스트리/폴백 사용.)
    • Thinking 감지: 프로브 프롬프트 → message.thinking 필드 또는 <think> 태그 확인 (하드코딩 없이 자동)
    • 자동 산출 규칙: max_output = context_window // 4. context window가 16K(MIN_CONTEXT_WINDOW) 미만이면 UnsupportedModelError로 거부.
  • 런타임 감지도 실패하면 사용자에게 대화형으로 context window, thinking 지원 여부, wire format 바인딩(auto = 기본 해석 체인; 등록된 포맷명만 수용, 오타는 재질문)을 질문 → ~/.agent-cli/models.json에 저장
  • 이미 등록된 모델은 덮어쓰지 않음 (사용자 설정 보호)
  • 다음 실행 시 저장된 설정에서 로딩 (프로브/질문 재실행 없음)
필드 설명
context_window 컨텍스트 윈도우 크기 (토큰)
max_output_tokens 최대 출력 토큰
supports_thinking Thinking/reasoning 지원
thinking_budget Thinking 토큰 예산
thinking_format Thinking 블록 태그 ("think", "")
wire_format (선택) 이 모델의 wire format 바인딩 (json_fc, xml_fc). 모델마다 학습된 tool-call 포맷 프라이어가 다를 때 사용 — 지정하면 --response-format 미지정 시 이 포맷으로 실행되고, 서브에이전트도 자기 모델의 바인딩을 따릅니다 (프로필 model 오버라이드 포함 — main 과 다른 포맷으로 도는 서브에이전트 가능). 미등록 이름은 부트/spawn 시 즉시 실패. 설정 경로: 손편집 / 대화형 모델 등록 프롬프트("Wire format [auto]") / agent-board ⚙ admin 모델 행 드롭다운. 자동 감지 refresh 에도 보존됩니다

설정 우선순위: .agent-cli/models.json (프로젝트) > ~/.agent-cli/models.json (전역) > default_models.json (패키지) > 런타임 감지 > 보수적 기본값

도구

LLM이 사용할 수 있는 도구 목록:

도구 설명
read_file 파일 읽기 (hashline 태그, flat-native — 한 op=한 파일). 여러 파일은 멀티-op 으로 read_file op 을 여러 개 emit
write_file 파일 생성/덮어쓰기. 작성 content 를 hashline 으로 반환 (read_file 없이 edit_file 직결)
edit_file hashline 기반 정밀 편집 (퍼지 매칭 지원)
shell 셸 명령 실행
fetch 웹 페이지를 가져와 마크다운으로 변환 (재귀 fetch 지원)
agent 서브에이전트 (일회성 run + 상주 spawn/request/status/resume/kill). run: 한 op=한 task, 연속 run op = 병렬. spawn: 컨텍스트 유지 상주 — 회신은 관찰로 자동 배달(도착 시 main 이 깨어남 — status 폴링 불필요; working/미처리 요청이 보이는 status 응답에는 "complete 로 턴을 마치고 기다려라" 힌트가 붙음). 배달된 회신 말미에는 그 에이전트의 배달-시점 잔여 상태 한 줄(working · N queued 또는 idle — ready)이 동봉되어 밀린 작업량을 바로 알 수 있음
read_context 세션 이력 SQL 질의 (history 테이블 SELECT: kind/tools/files/author/turn/text)
memory 세션 메모리 — 중대한 실패·발견·결정·메모를 기록/조회 (compaction 무관, resume 복원, 상시 인덱스). 모드: add/get/update/delete/list
code_index tree-sitter 기반 SQLite 코드 인덱스 (읽기 전용, flat-native — 한 op=한 query). 여러 query 는 멀티-op 으로 (모드 섞기 가능). lazy build + sha1 incremental + edit/write post-hook 자동 갱신. 10 mode: list/fetch/lookup/kind/file/refs/callers/callees/slice/build. Python/JS/TS/C/C++/Go/Rust/Java/Markdown
complete 작업 완료 신호 (최종 결과 반환)
ask 사용자에게 질문하고 대기 (대화형; 질문 하나=op 하나, 여러 질문은 ask op 여러 개로 배치 — read_file 식)
run_skill 등록된 스킬 실행 (LLM이 자동으로 호출 가능)

action_input 키 네이밍 규칙

모든 builtin 도구가 flat-native입니다 (consolidation Step 3 완료) — action_input 에 표준 키를 그대로 씁니다: 파일 도구(read_file/write_file/edit_filecode_index{path/mode, ...}, agent{mode, task, ...}, shell{command, ...}, 제어 도구(complete/ask/run_skill)도 표준 키. 한 op = 한 대상이고, 여러 대상(여러 파일 읽기, 여러 쿼리, 여러 병렬 서브에이전트)은 한 턴에 op 을 여러 개 냅니다.

어떤 builtin 도 wire-key prefix 를 쓰지 않습니다. wire-key prefix({tool}_{param}) 메커니즘 — 모델이 action 이름을 빠뜨려도 키 모양으로 도구를 복구(dropped-action recovery) — 은 미래 prefixed 도구/포맷용 latent seam 으로 코드에 남아 있고, 현재 어떤 builtin 도 활성화하지 않습니다. MCP/외부 도구는 prefix-less(자체 bare 스키마)라 이 메커니즘과 무관합니다.

아래 예시들은 각 도구의 표준 키 그대로입니다 — flat-native 라 그대로 전송하면 됩니다.

read_file — 파일 읽기

한 번에 파일 하나를 읽습니다 (flat-native). 모드(부분 범위 / 검색 / stat)를 고를 수 있고, 각 줄에 LINE#HASH:content hashline 태그가 붙습니다. 여러 파일은 멀티-op 포맷에서 read_file op 을 여러 개 emit 해 한 턴에 읽습니다 (op 배열이 곧 배치 — 중첩 배열 없음).

{"action": "read_file", "action_input": {"path": "src/main.py"}}

부분 범위 / 검색 / stat 모드:

{"action": "read_file", "action_input": {"path": "src/main.py", "line_start": 100, "line_end": 200}}
{"action": "read_file", "action_input": {"path": "agent_cli/loop.py", "search": "_handle_ask", "context": 3}}
{"action": "read_file", "action_input": {"path": "config.py", "stat": true}}
  • 필드 (path 만 필수):
    • path: 읽을 파일 경로.
    • line_start / line_end: 1-based, inclusive. 둘 다 생략하면 full read.
    • search: 정규식 패턴. 매칭 줄 + 주변 context 줄을 반환 (기본 5).
    • stat: 파일 크기/총 줄 수 + 앞 20줄 (메타데이터 조회, read 아님).

edit_file — Hashline 편집

read_file에서 받은 hashline 태그를 사용하여 정밀 편집합니다. 한 op = 한 편집 (flat-native):

1#VR:def hello():
2#KT:    return "world"
3#ZZ:

각 op 의 action_input (path + 편집 필드):

{"path": "app.py", "op": "replace", "pos": "2#KT", "lines": ["    return \"hello\""]}
{"path": "app.py", "op": "replace", "pos": "1#VR", "end": "3#ZZ", "lines": ["def greet():", "    pass"]}
{"path": "app.py", "op": "append", "pos": "1#VR", "lines": ["    # comment"]}

같은 파일 다중편집도 한 턴에 보낼 수 있습니다 — 연속된 같은-path edit_file op 들은 하나의 원본 read 기준으로 함께 적용됩니다(모든 ref 를 mutate 전에 해석 → overlap 사전거부 → 줄번호 내림차순 bottom-up 적용 → 1회 쓰기). 그래서 앞 편집이 줄을 밀어도 뒤 편집의 ref 가 어긋나지 않습니다. ref 는 마지막 read 기준 그대로 쓰면 됩니다(다른 편집을 의식해 줄번호를 미리 보정하지 말 것). all-or-nothing: 한 편집이라도 overlap/해시불일치면 그 파일은 손대지 않고 배치 전체가 거부됩니다(모델이 전부 재시도). 겹치는 범위는 턴을 나눠 사이에 re-read 하세요. 다른 파일 편집은 같은 턴에 여러 op 로 보내도 됩니다(서로 독립).

해시 불일치 시 퍼지 매칭으로 자동 보정합니다 (공백/따옴표/대시 정규화 — 같은 줄번호 한정).

편집이 성공하면 결과에 unified diff 와 함께 Updated region 블록(바뀐 영역 ±3줄의 fresh hashline 태그)이 붙습니다 — 방금 편집한 자리를 이어서 다시 편집할 때 read_file 재왕복 없이 이 ref 를 바로 쓸 수 있습니다(write_file 의 hashline echo 와 같은 목적). 줄번호는 편집 후 실제 파일 기준(절대)이고, 파일 대부분을 갈아엎은 편집은 이 블록을 상한(~100줄)까지만 내보내고 나머지는 read_file 로 안내합니다.

edit_file 성공 시 응답에 변경 사항 unified diff 가 포함됩니다 (+ 녹색 / - 빨강, 100줄 초과 시 truncate). write_file 성공 시엔 작성한 content 가 hashline(LINE#HASH:content) 포맷으로 반환되어, read_file 없이 방금 쓴 파일을 바로 edit_file 로 수정할 수 있습니다 (write→edit 직결 — 작은 변경 시 전체 재작성 대신 부분 edit 유도). 또한 write_file기존 파일을 덮어쓰는데 바뀐 줄이 30% 미만이면(작은 overwrite), 관찰에 "~N% of lines changed … edit_file costs only the changed lines" 넛지가 붙고 echo 가 파일 전체 hashline 대신 unified diff + 바뀐 영역의 fresh hashline 태그(Updated region 블록) 로 바뀝니다 — edit_file 성공 echo 와 동일한 조립(공용 render_change_echo)이라, churn 케이스의 컨텍스트 점유를 줄이면서도 "edit 했어야 할 줄"을 보여주고 후속 edit_fileread_file 재왕복 없이 바로 할 수 있는 ref 를 함께 줍니다(쓰기는 정상 수행, 거부 아님). 신규 파일·전면 재작성(≥30%)은 파일 전체 hashline echo 를 유지합니다(write→edit 직결용, diff 대상 없음).

complete — 작업 완료

LLM이 작업을 완료했을 때 호출하는 가상 도구입니다. result 필드에 최종 답변을 담습니다.

{"action": "complete", "action_input": {"result": "작업이 완료되었습니다. 파일을 생성했습니다."}}

종료는 명시적 complete 만 (v8.4.0): 모델이 도구 호출 없이 산문만 내면 — 그것이 최종답변처럼 보여도 — 완료로 수용하지 않고 재시도 넛지를 보냅니다. 넛지 문구가 "방금 산문이 최종답변이었다면 그 내용을 complete 의 result 로 재방출하라"고 직접 안내해 한 턴 안에 수렴합니다. (v7.14 의 산문-완료 자동 수용은 실사용에서 "Now let me write the plan document:" 같은 전환 서술을 스킬 결과로 오인 종료하는 반례가 발견되어 제거 — 완료 의도 추론은 본질적으로 불안정하고, 조용히 틀립니다.)

read_context — 세션 이력 조회

이전 또는 현재 세션의 이력을 SQL 로 질의합니다. LLM이 context window 밖으로 evict/compaction된 정보가 필요할 때 자발적으로 사용합니다. history.jsonl 이 인메모리 history 테이블로 적재되고, LLM이 SELECT 를 작성합니다(읽기전용).

테이블 history (레코드 1개 = 1행): session, loc, seq, kind(query/action/observation/final/raw/system), turn, ts, tools(툴명), files(조작 파일 경로), author(닉네임), text(각 레코드의 전체 내용).

결과는 잘림 없이 그대로 반환됩니다 (이전의 200자 셀 절단·공백 collapse 제거). 결과를 작게 유지하려면 직접 projection/limit 하세요 — 먼저 미리보기로 훑고(SELECT loc, substr(text,1,200) … LIMIT 30), 원하는 한 행만 전체(SELECT text …)를 가져옵니다. 과대한 결과는 통째 덤프되지 않고 "좁히라"는 nudge 로 거절됩니다(아래 과대 출력 캡 참조).

// 스키마 + 예시 + 세션 목록 보기 (query 생략)
{"action": "read_context", "action_input": {}}

// auth.py 를 건드린 관측만 — 먼저 미리보기로 훑기
{"action": "read_context", "action_input": {"query": "SELECT loc, turn, substr(text,1,200) FROM history WHERE kind='observation' AND files LIKE '%auth.py%' LIMIT 30"}}

// 특정 사용자(웹 멀티유저)의 질문만
{"action": "read_context", "action_input": {"query": "SELECT text FROM history WHERE kind='query' AND author='두정'"}}

// 키워드 + 턴 범위 + 정렬/제한
{"action": "read_context", "action_input": {"query": "SELECT loc, text FROM history WHERE text LIKE '%인증%' AND turn>=5 ORDER BY turn LIMIT 20"}}

// 다른 세션까지 — sessions 로 적재 범위 지정(all 또는 특정 id)
{"action": "read_context", "action_input": {"query": "SELECT DISTINCT session FROM history", "sessions": "all"}}

읽기전용: SELECT/WITH 만 허용(쓰기·DDL 거부). 행/셀 수 캡은 없습니다 — 결과를 작게 유지하는 건 모델 몫(LIMIT/substr/조건). sessions 미지정 시 현재 세션(run/skill subdir 포함).

과대 출력 캡 (oversized observation)

도구 관찰(observation)이 컨텍스트 윈도우의 1/10 을 넘으면, 그 전체 출력은 컨텍스트에 들어가지 않고 "좁혀서 다시 받아라"는 nudge 로 대체됩니다(도구 호출 자체는 성공). 거대 출력은 추론 공간을 잠식해 응답 품질을 떨어뜨리기 때문에, 모델을 라인범위/심볼/LIMIT/grep 같은 surgical 회수로 자연스럽게 유도합니다. 전체가 꼭 필요하면 파일로 빼서(… | tee /tmp/out.txt) 부분만 read_file 하면 됩니다.

  • 도구별 제어 (Tool 추상화 표면 3개): Tool.render_observation(result, args) 가 결과 → 관찰 본문 렌더(기본=성공 output·실패 error); Tool.apply_oversized_cap(기본 True)으로 캡 적용 여부를 도구별로 끔; Tool.render_oversized(result, args, *, body, tokens, ctx)캡에 걸렸을 때 무엇을 낼지를 도구가 소유(기본=제네릭 nudge). per-call 루프 컨텍스트는 RunContext(frozen: session_dir·oversized_cap·tools_available) 하나로 묶여 실행(run/_run)·렌더(render_oversized) 두 표면에 동일하게 전달됩니다 — 새 per-call 값이 생겨도 시그니처를 다시 안 늘리고 필드 하나만 추가하면 됩니다. ctx.tools_available(현재 루프에서 호출 가능한 도구 집합)을 받아, 그 도구가 실제로 쓸 수 있는 복구만 안내합니다.
  • 디스크-기반 분산 유도 (read_file·shell·agent·fetch 공통): 큰 내용이 파일 <path> 에 있으면 복구는 한 패턴으로 수렴합니다(공유 헬퍼 on_disk_oversized_nudge) — (a) 특정 부분read_file '<path>' line range/search, (b) 전체 분석/검색N-way 병렬 agent run 팬아웃: 파일 라인수로 ~k 섹션을 계산해 한 턴에 run op 여러 개(agent-cli 가 동시 실행)를 내는 구체 범위 예시(agent(mode="run", task="read_file '<path>' lines 1-N …") × k)를 제시 → 서브에이전트마다 한 섹션만 read 해 요약 반환, 부모는 distilled 만 병합. 팬아웃 줄은 agent 가 실제 호출 가능할 때만 표시(depth 한계 서브에이전트에선 자동 생략).
    • read_file: <path>=원본 파일(이미 디스크). 추가 갈래 read_symbols. 또한 stat 모드(메타데이터 조회)의 후속 안내가 cap-aware — 전체 읽기가 캡을 넘을 파일이면 "full read" 미끼를 빼고 처음부터 range/search/run 팬아웃으로 유도(작은 파일은 기존대로 full read 제안), 캡에 걸릴 왕복 한 번을 아낍니다.
    • shell: over-cap 일 때만 출력을 session_dir/shell-output-<hash>.txtlazy 저장(일반 shell 호출엔 디스크 쓰기 0) 후 그 파일을 가리킴. headless(세션 없음)면 tee 폴백.
    • agent (run): 서브에이전트 답변은 이미 <run_dir>/result.md 에 저장돼 있어 그 파일을 가리킴 + 더 좁은 재위임(근본 원인 교정) 옵션 추가.
    • fetch: over-cap 일 때만 내용을 session_dir/fetch-output-<hash>.txt 로 lazy 저장 후 가리킴 + 더 좁은 URL/얕은 depth 옵션 추가.
  • 비-파일 도구 (read_context·code_index): 출력이 파일이 아니라(SQL 결과·심볼 목록) 제자리 재-narrow 가 정답이므로 별도 헬퍼(narrow_oversized_nudge)로 그 도구 파라미터만 안내 — read_contextLIMIT/컬럼 projection/substr(text,1,200) 재쿼리, code_indexmode=fetch 단일 심볼/search 필터/max_bytes. (파일·팬아웃 얘기 없음.)
  • 사용자/어시스턴트 메시지는 캡 대상이 아닙니다(사람의 의도적 입력·모델 자신의 출력이라 거절/절단하지 않음).

code_index — SQLite 코드 인덱스

tree-sitter로 프로젝트 전체를 파싱해 <project_root>/.agent-cli/code_index.db에 영구 SQLite 인덱스를 만듭니다. 첫 query 시 lazy build, 이후 query마다 sha1 비교로 변경 파일만 incremental rebuild. edit_file / write_file 성공 시 자동 post-hook이 인덱스 갱신 → 모델이 직접 mode='build' 호출할 필요 거의 없음.

read_file이 텍스트(line range)에 답한다면 code_index는 의미 단위(symbol)와 cross-file 관계(refs/callers/callees)에 답합니다.

한 op = 한 query (flat-native)

code_index 는 읽기 전용(파일 안 씀)이고, 한 op 가 한 query를 돌립니다 (flat). 여러 query 는 멀티-op 으로 code_index op 을 여러 개 보냅니다(읽기전용이라 순서/상태 의존 없음 — 모드 섞기 OK).

// 단일 query
{"action": "code_index", "action_input": {"mode": "fetch", "path": "agent_cli/loop.py", "name": "AgentLoop._call_llm"}}

10 mode (각 op 의 action_input)

아래는 각 op 의 action_input 형태입니다 — 한 op 가 하나의 query:

// 1. 파일 outline (read_file:stat의 구조 인지 대안)
{"mode": "list", "path": "agent_cli/loop.py"}

// 2. 단일 심볼 body (hashline 포맷 → edit_file 직결)
{"mode": "fetch", "path": "agent_cli/loop.py", "name": "AgentLoop._call_llm"}
{"mode": "fetch", "path": "README.md", "name": "## Setup"}

// 3. 이름으로 심볼 찾기 (인덱스 전역)
{"mode": "lookup", "name": "AgentLoop"}
{"mode": "lookup", "name": "Setup", "symbol_kind": "section"}

// 4. 특정 kind 심볼 전부 (function/type/variable/constant/section)
{"mode": "kind", "symbol_kind": "function"}

// 5. 한 파일의 모든 심볼 (재파싱 없이 인덱스 조회)
{"mode": "file", "path": "agent_cli/loop.py"}

// 6. 참조 사이트 (call=호출, name=콜백/포인터, type=타입 위치)
{"mode": "refs", "name": "AgentLoop._call_llm", "ref_kind": "call"}

// 7. 누가 호출하나
{"mode": "callers", "name": "process"}

// 8. 무엇을 호출하나
{"mode": "callees", "name": "process"}

// 9. LLM 컨텍스트용 markdown blob (정의 + 선택적 callees/callers/types/macros)
{"mode": "slice", "name": "process", "with_callees": true, "with_types": true, "depth": 2}

// 10. 전체 rebuild 강제 (드묾 — 보통 lazy build + post-hook으로 충분)
{"mode": "build"}

인덱스 root 결정 + 가지치기

  • Root: cwd 또는 가장 가까운 조상 디렉토리 중 .agent-cli/ 가 있는 곳. 없으면 cwd 사용 (.agent-cli/ 자동 생성).
  • DB: <root>/.agent-cli/code_index.db. .gitignore 기본 패턴이 이미 .agent-cli/ 를 덮음.
  • SQLite 백엔드 자동 폴백 (Linux): stdlib sqlite3 가 없는 CPython 빌드 (예: --without-sqlite 로 빌드된 잠금 서버) 에선 agent_cli/code_index/_sqlite.py shim 이 pysqlite3-binary 휠로 자동 폴백. macOS / Windows 는 stdlib sqlite3 가 사실상 항상 존재해서 wheel 설치 X — pyproject 의 sys_platform == 'linux' marker 가 Linux 에서만 폴백 휠을 끌어들임.
  • 자동 prune 디렉토리: .git/.hg/.svn, .agent-cli/.claude, .venv/venv/env, __pycache__/.pytest_cache/.ruff_cache/.mypy_cache, node_modules, build/dist/target, .tox. 인덱스 폭주 방지.

Scope 경계 — index-scoped vs per-file

  • lookup / kind / file / refs / callers / callees / slice: 인덱스 root 안에서만 동작. 바깥 path 주면 명시적 에러.
  • list / fetch: root 안이면 인덱스 조회, root 바깥이면 on-demand parse fallback (한 파일만 즉석 파싱, DB 갱신 없음). /tmp/scratch.py 같은 ad-hoc 파일도 동작.

표기 관습

  • Python·JS·TS: Class.method
  • C·C++·Rust: namespace::Class::method (또는 Type::method)
  • Markdown: Setup 또는 ## Setup (양쪽 다 fetch 동작)

선언 vs 정의

같은 이름이 헤더의 prototype과 .cpp의 정의에 모두 있으면 fetch는 정의를 반환합니다. 선언만 있으면 그 선언을 [declaration] 표시와 함께 반환. Rust trait body의 method signature, Java interface method, C prototype 모두 is_definition=False 로 별도 기록됩니다.

hashline 출력 (mode='fetch')

fetch 결과의 body는 read_file과 동일한 hashline 포맷(LINE#HASH:content)으로 반환됩니다. 따라서 fetch 결과를 그대로 edit_file에 넘길 수 있고, 다시 read하지 않아도 됩니다.

지원 언어 (9개)

언어 확장자
Python .py, .pyi
JavaScript .js, .jsx, .mjs, .cjs
TypeScript .ts, .tsx
C .c, .h
C++ .cpp, .cc, .cxx, .hpp, .hh, .hxx, .h++
Go .go
Rust .rs
Java .java
Markdown .md, .markdown (heading → kind='section')

C/C++는 dedicated grammar 각각 사용. 그 외 형식은 read_file 으로.

C/C++ 전처리 — defconfig (kernel/driver 필수)

C/C++ 코드의 #define / #ifdef 분기 처리는 번들된 pure-Python _unifdef.py 가 기본 수행합니다 — 별도 설치 불필요. 시스템에 unifdef 바이너리가 있으면 (brew install unifdef / apt install unifdef) 자동으로 그것을 우선 사용 (battle-tested C 구현).

기능상 차이 없음 — 두 백엔드는 ifdef/elif/else/endif + defined()/논리/비교/산술 표현식에서 byte-identical 출력 (parity 테스트로 보장).

함수 시그니처가 #ifdef CONFIG_X 로 분기되는 코드 (커널 드라이버 등에서 흔함):

#ifdef CONFIG_SOMETHING
void foo(int x)
#else
static void foo(int x)
#endif
{ ... body ... }

은 tree-sitter가 ERROR로 파싱해 정의가 인덱스에서 누락됩니다. 이 경우 <project_root>/.agent-cli/defconfig#define/#undef 줄을 적으면 unifdef -b 가 분기를 사전에 잘라 정의가 살아납니다:

# .agent-cli/defconfig
#define CONFIG_SOMETHING
#undef CONFIG_LEGACY

파일은 사용자가 직접 작성합니다 (LLM이 추측하면 잘못된 분기를 인덱싱할 위험). code_index tool은 첫 query 시 이 파일이 있으면 자동으로 unifdef 에 전달합니다 — 별도 옵션 불필요. mode='build' 출력에 defconfig: 라인으로 적용 여부가 보입니다.

C/C++ 사용자도 시스템 unifdef 설치 선택 사항 — 번들된 pure-Python 으로 동일 동작. Python/JS/TS/Go/Rust/Java/Markdown 만 쓰는 사용자는 전처리 단계 자체가 no-op.

ask — 사용자에게 질문 (대화형 web 전용)

LLM이 추가 정보가 필요할 때 사용자에게 질문합니다. 배열로 여러 질문을 한 번에 할 수 있습니다.

{"action": "ask", "action_input": {"questions": ["어떤 파일을 수정할까요?"]}}
{"action": "ask", "action_input": {"questions": ["파일 경로는?", "사용할 언어는?"]}}

shell — 셸 명령 실행

셸 명령을 실행하고 stdout/stderr를 반환합니다. 타임아웃 기본 30초.

{"action": "shell", "action_input": {"command": "find agent_cli -name '*.py' | wc -l"}}

shell 출력은 자르지 않고 그대로 LLM에 전달됩니다. find / / grep -r 같은 큰 명령을 호출하면 컨텍스트가 그만큼 차지되니, 필요한 부분만 받도록 좁히는 명령을 권장 (tail -n 100, grep ERROR, head -c 4096 등). 누적 컨텍스트가 budget의 90%를 넘으면 compaction이 발동해 오래된 절반을 LLM 요약으로 흡수하고, 그 단계에서도 안 들어가면 플레인 FIFO로 떨어뜨립니다.

위험 명령 확인. rm / rmdir / mv 가 명령에 포함되면 실행 전 사용자에게 묻습니다. 실행될 명령이 별도 줄로 표시되고, 확인을 유발한 위험 키워드만 강조됩니다 (CLI 는 볼드-레드, 웹 다이얼로그는 .danger 스팬):

  $ rm -rf /tmp/build        ← 'rm' 강조
⚠ Dangerous command detected (`rm`). Allow? (y=once, n=deny, a=always allow `rm` this session)
  [y/n/a, optional comment after]:

강조 범위는 서버에서 계산한 문자 span 이라 CLI·웹이 같은 소스를 칠하며, 유발 토큰만(정확히 그 명령을 위험하게 만든 rm) 표시됩니다 — rm-helper.sh 같은 유사 문자열은 강조하지 않습니다. 응답 첫 토큰이 결정 (y 이번만 / n 거부 / a 이 세션 동안 같은 키워드 자동 허용), 뒤에 선택적 코멘트 추가 가능:

  • y and also cleanup /tmp/cache next — 명령 실행 + 코멘트가 출력 끝에 [User note when approving: ...] 로 붙어서 LLM 이 다음 액션에 반영
  • n the path is wrong, try /tmp/build instead — 거부 + 이유가 에러 메시지에 들어가서 LLM 이 다른 경로 탐색
  • a only inside /tmp — 세션 allowlist 추가 + 코멘트 전달
  • 빈 응답 / 인식 안 되는 첫 토큰 → 거부 (전체 입력은 코멘트로 보존)

첫 토큰은 별칭도 인식합니다 — y(yes/ok/okay/yep/yeah/sure), a(always/allow), n(no/nope) — 자연스러운 긍정이 안전 기본값 거부로 오인되지 않게. (allow 는 옵션 라벨이 "always allow" 이므로 a 로 매핑.)

누가/왜 묻는지 표시. 서브에이전트(agent run)에서 올라온 확인/질문에는 에이전트 라벨 + reasoning(thought)(확인은 실행하려는 action까지)이 함께 표시됩니다 — CLI는 프롬프트 위 ↳ from [explorer] · 💭 … · ⚡ … 헤더, 웹은 확인 다이얼로그/답변 영역에 같은 정보. 메인 에이전트는 thought/action 이 이미 인라인이라 헤더 생략.

기본 활성. 비활성하려면 AGENT_CLI_DANGEROUS_SHELL_CONFIRM=0. 확인을 띄울 수 있는지는 렌더러가 판단합니다 — CLI는 터미널(TTY), 웹은 연결된 브라우저(SSE 다이얼로그, TTY 불필요). 어느 쪽으로도 물어볼 수 없는 무인 환경(TTY 없는 CI/배치 + 미접속)에서는 자동 거부 — 위험 명령이 silent 실행되는 일 없음. 병렬 run 팬아웃처럼 여러 작업이 동시에 도는 경우에도 확인은 직렬화되어 한 번에 하나만 떠서, 응답이 엉뚱한 작업으로 새지 않음. shlex 토큰 단위 매칭이라 rm-helper.shecho "rm files" 같은 false positive는 안 잡지만 bash -c "rm x" 처럼 wrapper 안의 위험 명령은 놓칠 수 있음 (확인 시 모델에 알려서 풀어쓰게 유도).

write_file — 파일 생성

새 파일을 생성하거나 기존 파일을 덮어씁니다. 작성한 content 는 hashline 포맷으로 반환되어 read_file 없이 바로 edit_file 수정이 가능합니다 — 기존 파일의 작은 변경은 전체 재작성 대신 edit_file 권장.

{"action": "write_file", "action_input": {"path": "output.txt", "content": "hello world"}}

agent — 서브에이전트 (일회성 run + 상주 spawn)

서브에이전트 단일 도구입니다 (5.0.0 에서 구 delegate/teammate 통합). mode 로 두 수명 모델을 오갑니다:

mode 수명 용도
run 일회성 — 답변 반환 후 소멸 독립적인 일회성 작업, 병렬 팬아웃
spawn / request / status / resume / kill 상주 — 답변 후에도 컨텍스트 유지 반복 협업, 역할별 장기 작업
{"action": "agent", "action_input": {"mode": "run", "task": "Read /tmp/data.csv and count rows"}}
{"action": "agent", "action_input": {"mode": "run", "task": "Fix the bug we found", "context": "fork"}}
{"action": "agent", "action_input": {"mode": "run", "task": "Review this code", "profile": "code-reviewer"}}
{"action": "agent", "action_input": {"mode": "spawn", "profile": "researcher", "task": "레포 구조를 파악해줘"}}
{"action": "agent", "action_input": {"mode": "request", "key": "agt-3f2a1b9c", "message": "그래서 진입점이 어디야?"}}
{"action": "agent", "action_input": {"mode": "status"}}
{"action": "agent", "action_input": {"mode": "resume", "key": "agt-3f2a1b9c", "task": "이어서 진행해"}}
{"action": "agent", "action_input": {"mode": "kill", "key": "agt-3f2a1b9c"}}

공통 — 프로파일 (profile)

run 과 spawn 이 같은 프로파일 파일을 씁니다. YAML frontmatter(description/allowed-tools/model/hooks/auto-spawn) + 본문(역할 — 서브에이전트 시스템 프롬프트의 Role 섹션을 통째로 교체). 검색 경로: 프로젝트(.agent-cli/agents/) → 유저 전역(~/.agent-cli/agents/) → 패키지 내장(agent_cli/agents/builtin/).

  • 프로파일 발견: 사용 가능한 프로파일 목록(이름+description)이 시스템 프롬프트 ## Agent Profiles 섹션에 광고되어 모델이 스스로 적합한 전문가를 골라 run/spawn 합니다 (disable-model-invocation: true 로 숨김 가능). description 이 발견 표면이니 "무엇의 전문가인지"를 명확히.
  • 내장 프로파일 — 범용 워커 5종(오케스트레이션은 main 이 담당). 모두 격리된 private memory(세션·compaction·resume 를 넘어 자기 지식 축적, 서로 못 봄)를 가집니다:
    • code-writer(구현 — 파일 스코프 규율: 담당 파일만 수정, Files touched: 보고, 에러/정리 경로·검증 전 정직 보고).
    • code-reviewer(읽기 전용 리뷰 — 정확성·보안 우선, 구체적 실패 시나리오로 검증된 결함만 severity·file:line·CONFIRMED/PLAUSIBLE 으로 보고; 스타일 nitpick 배제).
    • code-analyst(읽기 전용 분석 — "어떻게 동작하나": 콜패스·등록 간접·실행/동시성 컨텍스트·수명 추적, file:line 인용. 결함 판정은 안 함=reviewer 몫).
    • unittest-writer(테스트 — 뮤테이션으로 무는지 증명: 코드 뒤집으면 실패해야, observable effect 검증, 의존성 페이크로 격리, 실행 검증).
    • log-analyst(읽기 전용 — 로그·스택트레이스·크래시에서 근본 원인: bottom-up 프레임·cascade 첫 도미노·트리거 조건, 증거 인용).
    • orchestrator(spawn 전용 조율자 — /orchestrate 가 소환한 워커 로스터(키)를 받아 peer message 로 배정→수집→리뷰→수리 루프를 자율 주행. 워커 회신은 orchestrator 에게만 라우팅되어 main 을 깨우지 않고, main 은 최종 보고/에스컬레이션만 받음. 스스로 spawn 불가 — 워커가 더 필요하면 main 에 message 로 요청).
  • instant-agent (instructions): 프로파일 파일 없이 인라인 텍스트로 즉석 전문가를 만듭니다 — {"mode":"spawn","instructions":"너는 이 레포의 wire-format 전문가다..."} (run 도 동일 지원). profile 과 병용하면 파일 본문 뒤에 덧붙는 오버레이가 됩니다 (파일=일반 원칙, 인라인=세션 특정 지시). 상주 에이전트의 인라인 지시는 세션 내내, resume/부활 후에도 유지됩니다 (manifest 영속).
  • /create-agent 스킬: 새 프로파일 md 를 대화형으로 생성 (run/spawn 겸용).

mode:"run" — 일회성 위임

한 op = 한 task (flat-native). 컨텍스트 모드로 서브에이전트가 부모 맥락을 얼마나 알지 제어합니다:

모드 동작
none (기본) 독립 실행. task에 모든 정보 포함 필요
fork 부모 컨텍스트를 복사하여 실행. 맥락 인지 + 독립
  • 병렬 실행: 한 턴에 run op 을 여러 개 내면 독립 서브에이전트가 동시에(threading) 실행됩니다 — 루프가 연속된 run op 들을 모아 병렬 디스패치합니다 (mode-aware 배칭: 상주 모드 op 이 섞인 턴은 순차). 독립 작업일 때만 여러 개를 내고, task B가 task A의 결과에 의존하면 A만 먼저 낸 뒤 다음 턴에 그 결과로 B를 호출하세요.
  • tools 파라미터로 서브에이전트가 사용할 수 있는 도구를 제한할 수 있습니다.
  • 서브루프에서도 사용 가능: run 은 main 뿐 아니라 서브에이전트(run/spawn/skill) 안에서도 쓸 수 있습니다 — 서브에이전트가 자기 작업을 다시 병렬 팬아웃하는 경로 (--max-depth 로 중첩 제한, 상주 모드는 main 전용). 서브루프의 도구 설명은 run 만 문서화된 축소판으로 렌더됩니다.
  • 산출물 포맷: run 결과는 구조화된 형식으로 반환됩니다:
STATUS: success
RESULT:
(서브에이전트 출력)

[Subagent activity]
- iter 1: read_file auth.py
- iter 2: shell pytest
- iter 3: edit_file auth.py

[Files touched]
- Read: auth.py, config.py
- Modified: auth.py

[Duration: 12.3s] [Subagent used 3 iterations]

실패 시에는 [Last actions before failure] 섹션이 추가되어 디버깅에 필요한 마지막 액션과 에러 메시지를 확인할 수 있습니다. 결과는 세션의 run_{name}_{hash}_{ts}/ subdir 의 result.md에 자동 저장됩니다.

상주 모드 — spawn / request / status / resume / kill

spawn 하면 key 를 돌려받고, 그 key 로 몇 번이고 이어서 요청할 수 있습니다. 상주 에이전트는 자기 컨텍스트(이전 문답 전부)를 유지한 채 살아 있습니다.

  • 비동기 + 자동 배달: request 는 즉시 반환되고, 에이전트는 자기 스레드에서 작업합니다. 회신이 준비되면 harness 가 다음 턴 경계에서 관찰로 자동 배달합니다 — 모델이 status 를 폴링하거나 블록할 필요가 없습니다 (main 이 idle 이어도 자동 재기동으로 배달).
  • 양방향 문답 (ask 라우팅): 상주 에이전트가 작업 중 막혀서 ask 를 부르면 질문이 main 의 mailbox 로 옵니다 (사용자가 아니라 main LLM 이 그 에이전트의 "사용자"). main 은 같은 request 로 답하고, 에이전트는 답을 받을 때까지 waiting_ask 상태로 대기합니다. run 서브에이전트의 ask 는 종전대로 사용자에게 갑니다.
  • 에이전트↔에이전트 메시징 (5.11.0): 모든 상주 에이전트는 message 도구(커널 기본 탑재)로 다른 상주 에이전트나 main 에게 직접 메시지를 보낼 수 있습니다 — {"to":"<key>","text":"..."} (또는 "to":"main"). 비동기입니다: 발신자는 블록하지 않고 계속 일하며, 대상의 회신은 새 메시지로 발신자의 inbox 에 도착합니다(도착하면 idle 이어도 깨어나 이어서 처리 — main 의 자동 배달과 동형). 배달된 회신은 terminal(되받아치지 않음)이라 핑퐁이 없고(안전망 hop 상한), 데드락은 비동기라 구조적으로 불가합니다. 회신을 받은 에이전트는 그걸로 자기 작업을 계속하고, 마무리되어 요청자(예: main)에게 보고할 결과가 있으면 다시 message 로 보내며, 없으면 그냥 complete 합니다. spawn/kill 같은 생명주기는 여전히 main 전용 — 서브에이전트는 메시지만. 요청은 언제나 명시적·directed 이라 상시 관찰 채널(구독)이 필요 없습니다.
  • 다중 인스턴스: 같은 프로파일을 여러 명 스폰할 수 있습니다 — coder 3명이 서로 다른 파일을 병렬 개발하는 식. spawn 의 name(예: ui/api)으로 인스턴스를 구분하며 광고·대화창·회신 헤더에 agt-x (coder · ui) 로 표시됩니다 (주소는 항상 key).
  • Live Agents 광고: 현재 상주 중인 에이전트 목록(key·프로파일·인스턴스명·전문영역)이 main 시스템 프롬프트 ## Live Agents 섹션에 상시 광고됩니다 — compaction 이 spawn 관찰을 지워도, auto-spawn/resume 처럼 관찰이 없어도 모델이 자기 팀을 잊지 않습니다. 멤버십 변화(spawn/kill/사망) 때만 재조립되어 KV 캐시 영향 최소(busy/idle 같은 휘발 상태는 미포함 — 활동은 관찰이 운반). 프롬프트 인스펙터에서 그대로 확인 가능합니다. 상주 에이전트에게도(5.11.0) 자기 자신을 제외한 동료 로스터가 같은 ## Live Agents 섹션으로 주입되어(단, message 도구 안내 문구), 누가 돌고 있고 어떤 역할인지 알고 서로 부를 수 있습니다.
  • 🤝 대화창 resume 복원 (5.13.0): 웹 UI 의 상주 에이전트 대화창은 그 에이전트와 주고받은 요청/회신/질문을 agents/<key>/conversation.jsonl 에 남깁니다. agent-cli 를 재시작하고 세션을 resume 하거나 dead 에이전트를 mode:"resume" 으로 되살리면, 이 로그를 재생해 대화창이 원래 시각 그대로 복원됩니다 (에이전트 내부 작업 상세는 종전대로 메인 채팅의 접히는 작업 카드). kill 하면 대화창을 즉시 비우고(정리), resume 하면 다시 채웁니다 — "kill=정리 / resume=재생" 대칭. 로그 파일은 kill 후에도 남아 언제든 부활 시 복원됩니다.
  • auto-spawn: frontmatter auto-spawn: true 프로파일은 세션 시작 시 자동 상주합니다 (resume 재생성분과 중복 스폰 없음).
  • worker 사망 통지: worker 가 비정상 종료(ctx 생성 실패·내부 기계 예외)하면 main 에 DIED 관찰로 즉시 통지됩니다 — status 를 조회할 필요 없음. kill/세션 종료 같은 의도된 종료는 통지하지 않습니다.
  • 부활 (mode: "resume"): kill 되거나 사망한 에이전트를 이전 컨텍스트 그대로 되살립니다 — history 가 디스크에 보존되므로 부활한 에이전트는 죽기 전 문답을 전부 기억합니다 (task 로 즉시 이어서 요청 가능, 회신 파일 번호도 이어감). 웹 대화 창의 dead 칩에서 ↻ 버튼으로도 부활할 수 있습니다. 부활 후에는 세션 resume 의 자동 재생성 대상으로 복귀합니다. 죽은 에이전트의 칩/기록이 남아 있는 이유가 바로 이것 — 사후 검사 + 부활 가능성.
  • 스코프: 상주 모드는 main 세션 전용 — 서브에이전트 안에서 spawn/request 등을 부르면 run 안내와 함께 거부됩니다. 동시 생존 상한 기본 10, 웹 UI 헤더의 숫자 입력으로 세션 한정 조절(무제한 체크박스 = 상한 없음; 상한을 낮춰도 기존 에이전트는 안 죽고 새 spawn 만 막힘).
  • 수명: main 의 Stop/Ctrl+C 는 상주 에이전트를 죽이지 않습니다(백그라운드 계속). 종료는 kill 또는 세션 종료 시 일괄 정리. 회신 전문은 agents/<key>/replies/ 에 항상 저장됩니다.
  • 세션 resume 시 자동 재생성: --resume 하면 이전 세션에서 살아있던 상주 에이전트가 자기 대화 이력을 전부 기억한 채 자동으로 되살아납니다 (agents.json manifest — 역할 프롬프트도 저장돼 프로파일 md 파일이 지워져도 무관). 미배달 회신도 보존되어 첫 턴에 배달됩니다 (답변 대기 중이던 질문은 STALE 로 표시 — 재시작으로 더 이상 블록 상태가 아님을 안내). 명시적으로 kill 한 에이전트는 되살아나지 않습니다.
  • 인스펙터: 웹 Prompt Inspector 에 상주 에이전트 스코프 칩이 상시 표시되어 살아있는 동안 시스템 프롬프트·동적 컨텍스트를 실시간 검사할 수 있습니다. 요청 처리 과정은 run 과 같은 접이식 카드(🤝)로 표시됩니다.
  • 사용자 직접 명령 (@ 구문): 채팅박스(웹)나 CLI 에서 —
    • @agents — 프로파일 카탈로그 + live roster 통합 목록.
    • @<profile> <task> — 일회성 run (명시적으로 @<profile>-run 도 동일).
    • @<profile>-spawn <task> — 상주 스폰 (+초기 task). 하이픈 포함 프로파일명도 안전 — 전체 토큰이 실존 프로파일이면 그것이 우선.
    • @agt-<key> <메시지> — 특정 상주 에이전트에게 직접 전송 (@agt-<key> 만 치면 상태). 사용자 발신이므로 회신은 main LLM 대화에 섞이지 않고 — 웹은 🤝 창으로, CLI 는 콘솔 라인(🤝 [agt-x → user])으로 도착합니다.
  • 웹 대화 창 + 인간 개입 (🤝): 웹 헤더의 🤝 버튼으로 에이전트 대화 창을 엽니다 — roster(상태 뱃지·✕ 종료)와 선택한 에이전트의 대화 스트림(요청 in / 회신 out / 질문 ❓)이 실시간으로 흐릅니다. 창 하단 입력으로 사람이 직접 메시지를 보낼 수 있고(닉네임 attribution), 에이전트가 질문(ask) 대기 중이면 그 메시지가 답으로 소비됩니다 (main 과 선착순). 인간↔에이전트 문답은 창에만 표시되고 main 컨텍스트에는 배달되지 않습니다 (오염 방지).
  • idle 자동 재기동 (웹): main 이 입력 대기(idle) 중에 회신/질문이 도착하면 harness 가 run 을 자동으로 깨워 배달합니다 — 사용자가 다음 메시지를 보낼 때까지 회신이 잠들지 않습니다. 이미 다른 run 이 배달을 끝냈으면 빈 run 을 열지 않습니다.
  • CLI run 커맨드에서도 동작 (큐 펌프): run 커맨드는 web 과 같은 공용 입력 큐 위에서 돕니다 — 모델이 complete 해도 상주 에이전트가 아직 일하는 중이거나 미배달 회신이 있으면 프로세스가 끝나지 않고, 회신이 도착하면 자동으로 이어서(🤝 wake) 처리한 뒤 조용해졌을 때(quiescence) 종료합니다. 상주 에이전트를 안 쓰면 종전과 동일하게 1회 실행 후 즉시 종료. 답변 없는 질문만 남은 경우(교착)는 경고 후 종료하며 resume 시 STALE 처리됩니다.

run_skill — 스킬 실행

등록된 스킬을 도구로 호출합니다. LLM이 스스로 판단하여 적절한 스킬을 선택합니다. 시스템 프롬프트에 사용 가능한 스킬 목록이 안내됩니다.

{"action": "run_skill", "action_input": {"name": "optimize", "arguments": "./"}}
{"action": "run_skill", "action_input": {"name": "summarize", "arguments": "README.md"}}

스킬 내부에서는 별도 ReAct 루프가 실행되며, 결과가 artifact로 저장됩니다. disable-model-invocation: true인 스킬은 LLM이 호출할 수 없습니다.

결과 포맷:

STATUS: success
RESULT:
SKILL: summarize(./)
The agent-cli directory contains a ReAct pattern-based agent CLI...

[Internal skill calls during this execution:]
- run_skill(optimize): Task completed: Analysis done.

SKILL: 헤더로 실행된 스킬과 인자를 식별하고, [Internal skill calls]로 내부에서 호출된 스킬 이력을 표시합니다.

스킬 스택 (재귀 방지)

스킬 내부에서 다른 스킬을 호출할 수 있지만 재귀는 차단됩니다:

  • A→B 허용: summarize 내부에서 optimize 호출 가능
  • A→A 차단: summarize 내부에서 summarize 호출 불가
  • A→B→A 차단: 순환 호출 불가
  • Execution Context: 시스템 프롬프트에 call stack 표시 + 재귀 호출 억제 지시

핵심 기능

도구 호출 방식

모든 프로바이더가 텍스트 파싱 방식을 사용합니다 (ReAct JSON 출력 → 파싱 → 도구 실행).

프로바이더 JSON 출력 보장 파싱
OpenAI-compat response_format: json_object (basic JSON mode) JSON 파싱
Anthropic tool calling 3단계 폴백 파싱

Strict JSON Schema(OpenAI json_schema)는 사용하지 않습니다. 일부 온프렘 서버/모델 조합에서 strict 스키마가 깨지는 이슈가 있어, 확장성을 위해 basic JSON mode만 사용. ReAct 구조 강제는 시스템 프롬프트 + 3단계 파서가 담당합니다 (32B+ 권장 사양에서 실질 품질 손실 거의 없음, 7-14B는 포맷 drift 가능).

3단계 파싱 폴백

  1. Stage 1: json.loads() (직접 파싱)
  2. Stage 2: JSON 복구 (깨진 JSON 자동 수정)
  3. Stage 3: Regex 추출 (최후 수단)

Thinking 모델(<think>...</think>)은 파싱 전 자동 분리됩니다.

세션 & 컨텍스트 관리 시스템

토큰 budget 기반 컨텍스트 관리. 평상시는 FIFO eviction, 90% 임계 초과 시 LLM 요약 압축(compaction)으로 전환되며, 모든 변경은 history.jsonl에 영속화됩니다.

컨텍스트 윈도우 레이아웃

┌─────────────────────────────────────────────────────┐
│                  System Prompt                       │
│  ┌───────────────────────────────────────────────┐  │
│  │ Role / Task Guidelines / Format Rules         │  │  ← Primacy (고정, KV cache hit)
│  │ Available Tools (inline guides)               │  │
│  │ Available Skills / Available Agents           │  │  ← Middle (참조용)
│  │ Execution Context (call stack)                │  │  ← 스킬/agent 실행 시만
│  │ Directives (DIRECTIVE.md)                     │  │
│  │ Environment (CWD, date, platform)             │  │  ← Recency (현재 맥락)
│  │ Context Recovery Guide                        │  │
│  │ [Hook Dynamic Sections]                       │  │  ← hook이 주입한 동적 섹션
│  └───────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────┤
│            Messages (Compaction + FIFO)              │
│  ┌───────────────────────────────────────────────┐  │
│  │ [evicted — history.jsonl에만 존재]            │  │  ← 90% 초과 시 oldest half가 요약으로 흡수
│  │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  │  │
│  │ [Compaction summary]    ← LLM 요약 (recursive)│  │
│  │ [Touched files] a.py, b.py, <agent:foo>       │  │
│  │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  │  │
│  │ user: "hooks.py 분석해줘"                     │  │
│  │ assistant: thought → action: read_file(...)   │  │  ← 자연어 변환
│  │ user: [read_file] Observation: 1#PS:...       │  │  ← 도구 결과 전문
│  │ assistant: thought → complete(분석 완료)      │  │
│  └───────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────┤
│          Token Budget (자동 계산)                     │
│  budget = context_window - max_output - 4K reserve  │
│  예: 262K model → ~254K token budget                │
│  메시지 단위 eviction (중간 잘림 없음)               │
└─────────────────────────────────────────────────────┘

토큰 Budget 관리

  • context_window - max_output_tokens - 4000 (system prompt 예약) 자동 계산
  • 메시지 단위로 eviction — 메시지 중간이 잘리는 일 없음
  • --max-context-tokens로 수동 override 가능 (0 = 자동)
  • 스킬/agent 서브에이전트는 부모 budget 상속

Context Compaction (90% 임계)

캐시가 budget의 90%를 넘으면 단순 FIFO drop 대신 LLM 요약 압축이 실행됩니다.

  1. 분할: [system anchor][dynamic] — system prompt만 무조건 보존
  2. Evict 절반 (token-based): oldest 절반을 떼어냄
  3. LLM 요약: evict 묶음을 단일 호출로 요약. 요약은 구조화 섹션(TASK / STATE / DONE / PENDING / DECISIONS / FAILURES / FACTS)으로 만들어져, 에이전트가 요약만으로 작업을 이어갈 수 있게 남은 작업·실패한 시도·정확한 식별자(경로/명령/에러)를 보존합니다. 이전 요약이 있으면 prior summary를 같은 호출에 prepend (recursive single-call — 합치는 별도 단계 없음)
  4. 파일 경로 추출: evict 안의 read_file/write_file/edit_file/code_index 호출과 <agent:target> placeholder를 누적 file_list에 dedup 머지
  5. 재구성: [system][summary][file_list][retained dynamic]
  6. 영속화: compaction.json (version, summary, file_list, dynamic_start_index 등) — --resume 시 압축 상태 그대로 복원
  7. Belt-and-braces fallback: LLM 호출 실패 또는 재구성된 cache가 여전히 budget 초과면 플레인 FIFO drop으로 떨어뜨림 — 무한 트리거 루프 방지

압축은 항상 켜져 있으며, LLM 요약이 실패하거나 재구성된 cache가 여전히 budget을 초과하면 belt-and-braces 플레인 FIFO drop으로 안전하게 폴백합니다. 이 동작은 agent·skill 서브에이전트에도 그대로 전파됩니다.

압축이 일어나면 CLI는 한 줄 상태로, 웹은 대화창 인라인 시스템 라인(⊙ 컨텍스트 압축됨 X→Y tok)으로 진행을 표시합니다. 압축으로 흡수된 요약·파일 목록은 웹의 ⚡ Prompt Inspector 에서도 확인할 수 있습니다(위 참조).

디렉토리 구조

{project}/.agent-cli/
  sessions/
    {session_id}/
      session.jsonl                     # 메타데이터 (1줄: id, workspace, updated_at, query)
      history.jsonl                     # 전체 대화 기록 (JSON Lines, append-only)
      run_{name}_{hash}_{ts}/         # agent run subdir
        history.jsonl                   #   run 내부 대화
        result.md                       #   run 최종 결과
      skill_{name}_{hash}_{ts}/         # skill subdir
        history.jsonl                   #   skill 내부 대화
        result.md                       #   skill 최종 결과

Context Recovery

FIFO에서 밀려난 과거 메시지가 필요할 때:

  • System prompt에 history.jsonl 경로 안내 (Context Recovery Guide)
  • LLM이 read_file(history.jsonl) 실행하여 과거 맥락 복구
  • history.jsonl 내 artifact 경로로 run/skill 상세 결과 접근 가능

세션 관리

agent-cli sessions                     # 세션 목록
agent-cli web --resume <session_id>   # 이전 세션 이어서 작업

MCP (Model Context Protocol) 지원

외부 MCP 서버의 도구를 agent-cli에서 사용할 수 있습니다.

설정

.agent-cli/mcp.json 또는 ~/.agent-cli/mcp.json에 서버를 정의합니다:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    },
    "remote-api": {
      "url": "http://localhost:8080",
      "transport": "sse"
    }
  }
}
  • stdio: command + args — 로컬 프로세스로 실행
  • SSE: url + transport: "sse" — HTTP 원격 연결
  • ${VAR} — 환경 변수 참조
  • 프로젝트 설정이 유저 설정보다 우선

사용

세션 시작 시 자동 연결됩니다 (run·web 양쪽 — web 은 v4.46.0 부터). MCP 도구는 {server}.{tool} 형식으로 LLM이 자동 사용:

{"action": "github.list_issues", "action_input": {"repo": "owner/repo"}}

스트리밍 출력

모든 프로바이더(OpenAI, Anthropic)에서 LLM 응답을 실시간 스트리밍합니다:

  • ASCII-art 말하는 얼굴 애니메이션 + 누적 토큰 카운터 (한 줄, 시간 기반 throttle)
  • 스트리밍 완료 후 thought/action을 Markdown 렌더링
  • 토큰 throughput 표시: ttft: 200ms | in: 1024 tok (892 tok/s) | out: 156 tok (201 tok/s)
  • TTFT (Time-to-First-Token) 모든 프로바이더에서 클라이언트 측정

안전장치

  • 반복 호출 감지: 동일 도구를 같은 파라미터로 3회 연속 호출 시 자동 중단
  • echo-as-final: echo로 답하는 소형 모델 패턴 자동 감지 → complete 도구 호출로 변환
  • 잘린 JSON 복구: LLM 응답이 잘릴 때 JSON repair 후 마지막 불완전한 edit 라인 제거, 적용된 edit 수 리포트
  • Execution Context: 스킬/agent 실행 시 call stack + depth N/M 을 시스템 프롬프트에 표시, 재귀 호출 억제. 한계 도달 시 그 사실도 명시.
  • agent_stack / skill_stack: 런타임 재귀 방지 (A→B→A 차단). 블록 메시지에 3가지 recovery option 포함 (다른 접근 / complete / ask).
  • max_depth: skill + agent 합산 중첩 깊이 제한 (기본 2). 한계 도달 시 AgentLoop.__init__agentrun_skill 둘 다 tool list 에서 제거 (대칭). dispatch 단계 belt-and-suspenders check 도 동일 메시지 출력.

프로젝트 구조

agent_cli/
├── main.py              CLI 명령어 (run, web, setup, sessions, update)
├── loop.py              AgentLoop 클래스 + ReAct 에이전트 루프
├── config.py            config.json 3레이어 로딩 + models.json 레지스트리
├── setup.py             SetupWizard (첫 실행 설정 마법사)
├── constants.py         공유 상수 (타임아웃, 임계값)
├── hooks/               Hook 시스템 (Python + Shell 라이프사이클 훅 11개 이벤트)
├── render/              플러그인 렌더링 시스템 (minimal — 커스텀 추가 가능)
├── input_history.py     readline 히스토리 영속화
├── providers/           LLM 프로바이더 (Anthropic, OpenAI)
├── wire_formats/        wire format 플러그인 (json_fc/xml_fc 내장, 추가 가능; 파서·복구·history 표현 self-contained)
├── tools/               도구 (read/write/edit/shell/agent/context)
├── context/             컨텍스트 관리 (compaction + FIFO + history.jsonl + 세션 메타)
├── prompts/             조건부 시스템 프롬프트
├── skills/              프롬프트 스킬 시스템 (로더, 실행기, 모델)
├── agents/              에이전트 정의 (builtin: explorer)
└── mcp/                 MCP 통합 (config, client, adapter)

상세 아키텍처: docs/ARCHITECTURE.md

테스트

# 전체 유닛 테스트 (통합 테스트는 서버 미가용 시 자동 skip)
pytest tests/ -v

omlx 통합 테스트 (실 서버 필요)

omlx_integration 마커가 붙은 E2E 테스트는 실제 OpenAI 호환 omlx 서버를 대상으로 run_loop·툴 사용·스킬·agent run·런타임 capability 감지를 검증합니다. 서버가 없으면 자동으로 skip되므로 pytest tests/는 항상 green입니다.

# 실 서버를 띄운 뒤 실행
pytest tests/ -m omlx_integration -v

# 연결/모델 override (기본: http://127.0.0.1:8000/v1, Qwen3.6-27B-MLX-8bit)
OMLX_BASE_URL=http://192.168.0.44:8000/v1 \
INTEGRATION_MODELS="Qwen3.6-27B-MLX-8bit" \
  pytest tests/ -m omlx_integration -v
환경변수 기본값 설명
OMLX_BASE_URL http://127.0.0.1:8000/v1 omlx OpenAI 호환 엔드포인트
OMLX_API_KEY (없음) 필요 시 API 키
INTEGRATION_MODELS Qwen3.6-27B-MLX-8bit 테스트 모델 (콤마 구분, 가용 모델만 실행)

환경 변수

설정 우선순위 및 전체 환경변수 목록은 설정 섹션을 참조하세요.

라이선스

MIT License

About

Simple cli project

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages