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설정 파일(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설정은 JSON 파일로 저장됩니다:
{
"provider": "openai",
"base_url": "http://127.0.0.1:8000/v1",
"api_key": "",
"default_model": "gpt-4o"
}웹 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.json에 gpt-4o가 기본이지만 -m gpt-4o-mini로 임시 실행:
agent-cli run "task" -m gpt-4o-mini에이전트가 항상 따라야 하는 규칙을 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+ 클래스 모델에서 멀티스텝 태스크가 안정적으로 동작합니다.
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 이전에 기록된 세션은 작성자 정보가 없어 화살표 없이 카드만 복원).
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 안내.
- 경로 오인 방지 (v4.51.1):
@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"}'agent-cli setup프로바이더, 접속 정보, 기본 모델을 대화형으로 설정합니다. 설정이 없을 때 자동으로 실행되며, 언제든 수동으로 다시 실행할 수 있습니다.
run과 web 모두 세션을 .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 컬럼, 읽기전용).
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로 정정.
특정 작업에 최적화된 재사용 가능한 프롬프트 템플릿. Claude Code 스킬 포맷과 호환.
패키지와 함께 배포되는 메타 스킬:
| 스킬 | 설명 |
|---|---|
/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 표시 시 인자 힌트 |
스킬 검색 경로:
.agent-cli/skills/*.md(프로젝트 로컬 플랫, 우선).agent-cli/skills/<name>/SKILL.md(프로젝트 로컬 디렉토리)~/.agent-cli/skills/*.md(사용자 전역 플랫)~/.agent-cli/skills/<name>/SKILL.md(사용자 전역 디렉토리)
같은 검색 경로 내에서 동일 이름의 플랫 파일과 디렉토리 스킬이 모두 존재하면 에러가 발생합니다.
에이전트 라이프사이클 전반에 걸쳐 동작하는 확장 시스템입니다. Python hook과 Shell hook 두 가지 방식을 지원합니다.
.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 건너뜀 (에이전트 루프 중단 없음)
| 이벤트 | 시점 | 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 (같은 이벤트 내)
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().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만)
스킬 frontmatter에서도 shell hooks를 정의할 수 있습니다 (해당 스킬 실행 중에만 활성):
---
name: safe-deploy
description: Deploy safely
hooks:
PreToolUse:
- matcher: shell
hooks:
- command: "./validate-deploy.sh"
---# 로컬/온프렘 서버 (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-4oexport ANTHROPIC_API_KEY="sk-ant-..."
agent-cli run "task" -p anthropicSystem 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/models의max_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로 거부.
- OpenAI 호환: context window를
- 런타임 감지도 실패하면 사용자에게 대화형으로 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이 자동으로 호출 가능) |
모든 builtin 도구가 flat-native입니다 (consolidation Step 3 완료) — action_input 에 표준 키를 그대로 씁니다: 파일 도구(read_file/write_file/edit_file)·code_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 라 그대로 전송하면 됩니다.
한 번에 파일 하나를 읽습니다 (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 아님).
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_file 을 read_file 재왕복 없이 바로 할 수 있는 ref 를 함께 줍니다(쓰기는 정상 수행, 거부 아님). 신규 파일·전면 재작성(≥30%)은 파일 전체 hashline echo 를 유지합니다(write→edit 직결용, diff 대상 없음).
LLM이 작업을 완료했을 때 호출하는 가상 도구입니다. result 필드에 최종 답변을 담습니다.
{"action": "complete", "action_input": {"result": "작업이 완료되었습니다. 파일을 생성했습니다."}}종료는 명시적 complete 만 (v8.4.0): 모델이 도구 호출 없이 산문만 내면 — 그것이 최종답변처럼 보여도 — 완료로 수용하지 않고 재시도 넛지를 보냅니다. 넛지 문구가 "방금 산문이 최종답변이었다면 그 내용을 complete 의 result 로 재방출하라"고 직접 안내해 한 턴 안에 수렴합니다. (v7.14 의 산문-완료 자동 수용은 실사용에서 "Now let me write the plan document:" 같은 전환 서술을 스킬 결과로 오인 종료하는 반례가 발견되어 제거 — 완료 의도 추론은 본질적으로 불안정하고, 조용히 틀립니다.)
이전 또는 현재 세션의 이력을 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 포함).
도구 관찰(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 병렬agentrun 팬아웃: 파일 라인수로~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>.txt로 lazy 저장(일반 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_context→LIMIT/컬럼 projection/substr(text,1,200)재쿼리,code_index→mode=fetch단일 심볼/search필터/max_bytes. (파일·팬아웃 얘기 없음.) - 사용자/어시스턴트 메시지는 캡 대상이 아닙니다(사람의 의도적 입력·모델 자신의 출력이라 거절/절단하지 않음).
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)에 답합니다.
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"}}아래는 각 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: 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.pyshim 이pysqlite3-binary휠로 자동 폴백. macOS / Windows 는 stdlibsqlite3가 사실상 항상 존재해서 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. 인덱스 폭주 방지.
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 동작)
같은 이름이 헤더의 prototype과 .cpp의 정의에 모두 있으면 fetch는 정의를 반환합니다. 선언만 있으면 그 선언을 [declaration] 표시와 함께 반환. Rust trait body의 method signature, Java interface method, C prototype 모두 is_definition=False 로 별도 기록됩니다.
fetch 결과의 body는 read_file과 동일한 hashline 포맷(LINE#HASH:content)으로 반환됩니다. 따라서 fetch 결과를 그대로 edit_file에 넘길 수 있고, 다시 read하지 않아도 됩니다.
| 언어 | 확장자 |
|---|---|
| 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++ 코드의 #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.
LLM이 추가 정보가 필요할 때 사용자에게 질문합니다. 배열로 여러 질문을 한 번에 할 수 있습니다.
{"action": "ask", "action_input": {"questions": ["어떤 파일을 수정할까요?"]}}
{"action": "ask", "action_input": {"questions": ["파일 경로는?", "사용할 언어는?"]}}셸 명령을 실행하고 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.sh나 echo "rm files" 같은 false positive는 안 잡지만 bash -c "rm x" 처럼 wrapper 안의 위험 명령은 놓칠 수 있음 (확인 시 모델에 알려서 풀어쓰게 유도).
새 파일을 생성하거나 기존 파일을 덮어씁니다. 작성한 content 는 hashline 포맷으로 반환되어 read_file 없이 바로 edit_file 수정이 가능합니다 — 기존 파일의 작은 변경은 전체 재작성 대신 edit_file 권장.
{"action": "write_file", "action_input": {"path": "output.txt", "content": "hello world"}}서브에이전트 단일 도구입니다 (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"}}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 가 소환한 워커 로스터(키)를 받아 peermessage로 배정→수집→리뷰→수리 루프를 자율 주행. 워커 회신은 orchestrator 에게만 라우팅되어 main 을 깨우지 않고, main 은 최종 보고/에스컬레이션만 받음. 스스로 spawn 불가 — 워커가 더 필요하면 main 에 message 로 요청).
- instant-agent (
instructions): 프로파일 파일 없이 인라인 텍스트로 즉석 전문가를 만듭니다 —{"mode":"spawn","instructions":"너는 이 레포의 wire-format 전문가다..."}(run 도 동일 지원).profile과 병용하면 파일 본문 뒤에 덧붙는 오버레이가 됩니다 (파일=일반 원칙, 인라인=세션 특정 지시). 상주 에이전트의 인라인 지시는 세션 내내, resume/부활 후에도 유지됩니다 (manifest 영속). /create-agent스킬: 새 프로파일 md 를 대화형으로 생성 (run/spawn 겸용).
한 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 하면 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.jsonmanifest — 역할 프롬프트도 저장돼 프로파일 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 처리됩니다.
등록된 스킬을 도구로 호출합니다. 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 가능).
- Stage 1:
json.loads()(직접 파싱) - Stage 2: JSON 복구 (깨진 JSON 자동 수정)
- 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 (중간 잘림 없음) │
└─────────────────────────────────────────────────────┘
context_window - max_output_tokens - 4000 (system prompt 예약)자동 계산- 메시지 단위로 eviction — 메시지 중간이 잘리는 일 없음
--max-context-tokens로 수동 override 가능 (0 = 자동)- 스킬/agent 서브에이전트는 부모 budget 상속
캐시가 budget의 90%를 넘으면 단순 FIFO drop 대신 LLM 요약 압축이 실행됩니다.
- 분할:
[system anchor][dynamic]— system prompt만 무조건 보존 - Evict 절반 (token-based): oldest 절반을 떼어냄
- LLM 요약: evict 묶음을 단일 호출로 요약. 요약은 구조화 섹션(TASK / STATE / DONE / PENDING / DECISIONS / FAILURES / FACTS)으로 만들어져, 에이전트가 요약만으로 작업을 이어갈 수 있게 남은 작업·실패한 시도·정확한 식별자(경로/명령/에러)를 보존합니다. 이전 요약이 있으면 prior summary를 같은 호출에 prepend (recursive single-call — 합치는 별도 단계 없음)
- 파일 경로 추출: evict 안의
read_file/write_file/edit_file/code_index호출과<agent:target>placeholder를 누적 file_list에 dedup 머지 - 재구성:
[system][summary][file_list][retained dynamic] - 영속화:
compaction.json(version, summary, file_list, dynamic_start_index 등) —--resume시 압축 상태 그대로 복원 - 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 최종 결과
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 서버의 도구를 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__가agent와run_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/ -vomlx_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