| name | agent-config |
| description | agent-config 담당자의 운영 면(operating surface) — 스킬·정체성·정렬을 여러 하네스(pi / entwurf Claude / Claude Code / Codex / Antigravity / Copilot / Kiro)로 펼치는 repo에서 실제로 손을 쓸 때. AGENTS.md가 '정신'을 담고 run.sh가 '로컬 명령'을 담는다면, 이 스킬은 그 둘이 못 가진 삽질 지식을 담는다: 스킬을 추가/수정해서 모든 하네스에 제대로 뜨게 하는 법, 새 기기 setup, '내 스킬이 안 보여요' 진단, .bak 함정, 바이너리-from-sibling-repo 패턴, 스킬 테스트 공백, git-hooks 안전벽. 트리거: 'agent-config', '스킬 추가', '스킬 안 떠', '스킬 링크', 'run.sh setup', '새 기기 셋업', '하네스 펼침', 'setup:links', '담당자 스킬', 'repo-local skill', 'consumer skill 이주', 'copilot skills', 'kiro skills', 'kiro-cli'. |
| user_invocable | true |
agent-config — 담당자의 운영 면
Repo: ~/repos/gh/agent-config. 이 집이 무엇인지(정신)는 AGENTS.md가, 한 줄 명령은
run.sh가 가진다. 이 스킬은 그 둘이 못 가진 것 — 스킬을 만지고 펼칠 때마다 다시
당하는 삽질 — 을 담는다. AGENTS.md는 일부러 spec이 되길 거부하므로(정신은 한글로, API는
영어로) 운영 노하우가 들어갈 자리가 없다. 그 빈자리가 여기다.
⚠️ 먼저 자리를 붙들어라: 이 repo는 두 번째 하네스가 아니다. 하네스는 pi다.
agent-config는 그 위에서 도구·기록·정체성·정렬을 관리하는 자리. 스킬 목록을 늘리는 게
발전이 아니다. (AGENTS.md 담당자의 자리 참조 — 만지기 전에 그 섹션부터.)
멘탈 모델 — 한 SSOT가 N개 하네스로 펼쳐진다
skills/<name>/SKILL.md (+바이너리) ← SSOT (이 repo)
│ ./run.sh setup → setup_links
▼
┌─ ~/.pi/agent/skills/pi-skills/<name> (pi, 개별 링크)
├─ ~/.pi/agent/claude-plugin/skills/<name> (entwurf Claude, 개별 — SDK 격리)
├─ ~/.claude/skills → skills/ (Claude Code, 디렉토리 통링크)
├─ ~/.codex/skills/<name> (Codex, 개별 — .system/ 빌트인 때문)
├─ ~/.gemini/antigravity-cli/skills → skills/ (Antigravity, 디렉토리 통링크)
└─ ~/.copilot/skills → skills/ (Copilot CLI, 디렉토리 통링크)
└─ ~/.kiro/skills → skills/ (Kiro CLI, kiro-cli 설치 시)
핵심 비대칭(이게 삽질의 근원): 어떤 하네스는 디렉토리 통째 링크, 어떤 하네스는 스킬마다
개별 링크다. 개별 링크 하네스(pi / claude-plugin / codex)는 setup:links를 다시 돌려야
새 스킬이 잡힌다. 통링크 하네스(claude/antigravity/copilot/kiro)는 skills/에 디렉토리만
생기면 자동으로 보인다.
Copilot 소유 경계: skills만 agent-config. ~/.copilot/settings.json · birth plugin ·
statusLine 은 entwurf #82 (install-copilot-bridge / install-copilot-statusline).
settings를 여기서 링크하면 agy 회귀와 같은 공동소유 파괴가 난다. Gemini CLI legacy
(~/.gemini/skills) 는 2026-08-06 에 은퇴 — 바이너리 없음.
Kiro도 skills만 agent-config다. kiro-cli가 PATH에 있을 때만
~/.kiro/skills를 연결한다. ~/.kiro/settings/, agents/, sessions/는 Kiro runtime
소유이며, Kiro는 의도적으로 entwurf citizen surface에 넣지 않는다.
OpenCode 는 쓰지 않는다 — run.sh 에 분기가 없고 ~/.config/opencode/skills 도 만든 적이
없다. 문서에만 있던 하네스라 2026-07-14 에 걷어냈다.
./run.sh setup 전체 순서: refresh_self → preflight → repos(clone/pull) → build → links → npm → git-hooks. 스킬만 다시 펼치려면 빌드 없이 ./run.sh setup:links 한 방.
스킬을 추가/수정한다 — 두 종류
스킬은 두 패턴이 공존한다. 어느 쪽인지부터 판별해야 한다.
A. script 스킬 (SSOT가 여기) — botlog, agenda, entwurf-peek …
mkdir -p skills/<name>/scripts
$EDITOR skills/<name>/SKILL.md
./run.sh setup:links
./run.sh env
B. 바이너리 스킬 (코드 SSOT가 sibling repo) — denotecli, bibcli, gitcli, lifetract, gogcli, dictcli
경계는 하나다: 형제 repo는 코드를, agent-config는 스킬면(SKILL.md + 배포 바이너리)을 소유한다.
형제 repo는 자기 SKILL.md를 갖지 않고, skills/<name>/에 아무것도 쓰지 않는다. 바이너리는
.gitignore에 박혀 있다 — 산출물이지 SSOT가 아니다. 소스는 형제 repo에서 고친다:
$EDITOR ~/repos/gh/gitcli/...
git -C ~/repos/gh/gitcli commit ...
./run.sh setup:build
- denotecli/gitcli/lifetract/bibcli =
go_build. gog = 글로벌 upstream(nixos-config).
dictcli = GraalVM native-image + Kiwi(dictcli/run.sh build) — 게이트 밖이고 배포 운영을
우리가 진다. 아래 전용 절 참조.
- 바이너리는 머신별 네이티브 빌드(oracle=aarch64, 나머지=x86_64). 기기 옮기면 재빌드 필수.
2026-07-14: lifetract가 자기 run.sh deploy로 skills/lifetract/에 바이너리와 SKILL.md를
직접 쓰고 있었다. 나쁜 의도가 아니라 옳은 직관이었다 — "바이너리와 문서는 한 세트다". 그런데
소유가 둘이 되니 GLG가 헷갈렸고, ~/.claude/skills가 skills/로 걸린 심링크라는 걸 모르면
그쪽 "세 자리 SHA256 검사"가 실은 한 자리를 두 번 세는 것도 안 보였다. 그 세트 보장은
없애지 않고 go_build로 올렸다(아래). 형제 repo에서 두 번 하지 않는다.
게이트 — go_build가 install을 막는다
setup은 바이너리 하나를 직접 설치 하네스 전부에 동시에 펼친다. 그래서 보증은 여기 산다:
- 스위트.
go test ./... 실패 → 설치 안 함, 직전 바이너리 유지, 나머지 CLI는 계속 빌드,
끝에서 non-zero. (테스트 없는 빌드가 gitcli day-summary 버그를 전 하네스로 내보냈다.)
- provenance. 소스가 미커밋이면 거부한다. 배포된 숫자엔 가리킬 커밋이 있어야 한다.
provenance는 저장소 전체가 아니라 소스 디렉토리로 잰다 (git rev-parse HEAD:<src>).
Go의 vcs.modified는 repo-wide인데 bibcli는 zotero-config 안에 살고 그 repo는 서지를
export할 때마다 dirty다. 그걸로 막으면 코드와 무관한 이유로 bibcli를 거부하게 되고 —
우회를 배우게 만드는 게이트는 없는 게이트보다 나쁘다.
skills/.provenance.json(gitignored)에 툴별 repo/revision/src_tree/sha256을 적는다.
timeline 축(~/repos/gh/junghan0611)은 이 스킬들을 링크만 하는 게 아니라 shell out해서 그
숫자를 events.jsonl에 역사로 쓴다 — 어느 바이너리가 그 행을 만들었는지 이제 읽을 수 있다.
./run.sh env가 각 바이너리의 revision을 찍고, 기록된 빌드와 다르면 경고한다.
dictcli — 배포 운영은 우리가 진다 (형제 중 유일한 예외 취급)
소유 경계: 로직·graph.edn·빌드 스크립트는 dictcli 리포(SSOT). 기기별 굽기와 배포
운영은 여기다. 어휘 그래프 고도화나 확장 알고리즘은 dictcli 담당자가, 회수 품질은 andenken
담당자가 각자 가져간다 — 우리는 그 산출물이 모든 기기·모든 하네스·봇 컨테이너에서 실제로
도는가 만 책임진다.
왜 형제 Go CLI와 다르게 취급하나:
-
GraalVM native-image라 go_build 게이트 밖이다. 크로스 컴파일이 없다 —
oracle(aarch64)과 thinkpad(x86_64)에서 각각 굽는다.
-
산출물이 2벌이고, 그 둘은 개발본과 배포본이다 (dictcli 4a3afd6, 2026-09-03):
| 개발본 (host) | 배포본 (portable) |
|---|
| 경로 | target/dictcli-<arch> | target/dictcli-<arch>-portable |
| loader | nix store 인터프리터 | 표준 (/lib/ld-linux-aarch64.so.1 · x86_64 /lib64/ld-linux-x86-64.so.2), RUNPATH 제거 |
| GC 보호 | 필요 — pin_libc_gcroot | 불필요 (store 의존 0) |
| 사는 곳 | 그 기기의 리포. 건너가지 않는다 | 스킬 디렉토리 + 봇 컨테이너 |
| 쓰임 | validate·테스트·다음 빌드 캐시 | 실제 호출 |
빌드·테스트가 기기마다 따로 도니 개발본은 기기에 남고, run.sh build --output 이
배포본만 graph.edn과 한 세트로 내보낸다.
배포는 언제나 cp 한 번 — 갈림길은 "어느 산출물을 복사했나"
정상 배포도 예외 처방도 하는 일은 같다. 다른 것은 원본뿐이고, readelf 로 사후에 구분된다.
| 정상 배포 | 예외 처방 |
|---|
| 원본 | target/<arch>-portable | target/dictcli-<arch> (개발본) |
| 실행 주체 | run.sh build --output (우리) | 손으로 |
| 결과 interp | 표준 loader | nix store |
그런데 그 판정이 기기마다 뒤집힌다. 그래서 doctor 는 이름을 하나가 아니라 둘로 쓴다
(dictcli 담당자와 합의, 2026-09-03 · run.sh has_std_loader):
- 표준 loader 있음 (nix-ld 또는 non-NixOS) — 배포본이 도는 기기다. 스킬 디렉토리의
store interp 는 개발본 오배포(
fragile: dictcli(misdeploy)), 처방은 setup:build.
gcroot 는 여기서 무관하다 — 배포본은 store 의존이 0이라 핀이 보호할 대상이 없다.
4a3afd6 이후 build --output 은 portable 만 내보내므로(dictcli run.sh:271 +
make_portable_binary 방어 셋), 이 상태는 4a3afd6 이전 잔재이거나 손으로 복사한 것뿐이다.
- 표준 loader 없음 (nix-ld 없는 NixOS) — 배포본이 못 돈다. 개발본 배포가 정상인 예외고,
이때만 gcroot 가 방어선이다. 단 불변식은 "인터프리터가 핀 목록에 있나"가 아니라
"배포된 개발본 == 지금 리포의 개발본"(
fragile: dictcli(stale-dev))이다 —
pin_libc_gcroot 는 매 빌드마다 root 집합을 리셋해 지금의 개발본만 가리키므로, 낡은
개발본이 깔려 있으면 핀이 있어도 그 본은 무방비다. 2026-09-03 thinkpad가 정확히 그
형태였다(08-21 배포본이 요구한 glibc-2.40-218 이 08-29 리빌드의 핀 교체로 보호 밖).
pin_libc_gcroot 의 rm -f "$root_dir"/* 는 결함이 아니다 — 기기당 개발본은 한 벌이고
핀은 그 상태의 반영이라, 누적하면 죽은 빌드의 glibc를 영원히 붙잡는다. dictcli 리포는
건드리지 않는다(담당자 확인: --output 이 portable 아닌 것을 내보내는 경로 없음).
이게 문서상의 정확성 문제가 아닌 이유: dictcli_stale_check 는 find -newer mtime 비교라
방금 손으로 cp 한 개발본은 오히려 제일 최신이라 안 걸린다. graph.edn 도 세트로 나르면
통과한다. 즉 interp 분기가 개발본 오배포의 유일한 탐지기다.
- 소비자가 호스트만이 아니다. 봇 컨테이너(openclaw-gateway, Debian)가 같은 파일 하나를
본다 —
~/.pi/agent/skills/pi-skills/dictcli/dictcli → skills/dictcli/dictcli 심링크.
그래서 "호스트에서 되니까 됐다"가 성립하지 않는다.
- 깨지는 방식이 조용하다. 파일은 멀쩡히 있고 스킬 목록에도 뜨는데 호출 순간에만 죽거나,
아예 안 죽고 낡은 어휘 그래프를 계속 쓴다.
지금까지 실제로 깨진 방식 셋
| 언제 | 증상 | 원인 | 처방 |
|---|
| 2026-08 (oracle) | cannot execute: required file not found | nix-collect-garbage가 host 본의 nix store 인터프리터를 수거 | pin_libc_gcroot(dictcli run.sh)가 gcroot로 고정 |
| 2026-09-03 (봇 컨테이너) | ./dictcli: not found, exit 127, Layer 3가 통째로 0 | 컨테이너에 그 nix store 경로가 없음 | portable 본 도입 (nixos-config#9 · andenken#11) |
| 상시 | 아무 증상 없음 | dictcli 리포가 앞서갔는데 이 기기에 안 날랐다 | doctor_bins의 dictcli_stale_check |
운영 루프
cd ~/repos/gh/agent-config && ./run.sh setup:build
./run.sh doctor:bins
doctor_bins가 dictcli에 대해 따로 보는 것:
- 표준 loader 부재 →
standard loader missing. 이건 재빌드로 안 고쳐진다. NixOS인데
nix-ld가 없는 기기다. nix-ld를 켜거나 target/dictcli-<arch>(개발본)로 교체한다 —
그 기기에서는 그게 오배포가 아니라 정상이다.
- 개발본 오배포 → 위 표 참조. 표준 loader 가 있는데 store interp 가 깔려 있는 경우.
✅ 양 기기 모두 nix-ld가 있어 이 분기는 미발동이다 (2026-09-03 실측): oracle
/lib/ld-linux-aarch64.so.1 · thinkpad /lib64/ld-linux-x86-64.so.2, 둘 다 → nix-ld-2.0.6.
portable 본이 NixOS 호스트에서도 돈다. 새 기기에서만 다시 확인하면 된다.
- 배포 신선도 — 리포의
src/·graph.edn·run.sh·deps.edn 중 배포본보다 새 것이 있으면 경고.
run.sh 를 보는 것은 과검출이 아니다 — 4a3afd6 이 배포 산출물의 종류를 host→portable로
바꿨고 두 기기 모두 실제로 재배포가 필요했다. 검사 대상에서 빼지 마라.
- graph.edn 세트 어긋남 — 바이너리와 그래프는 한 세트다. 따로 어긋나면 확장 결과가 조용히 달라진다.
아직 자동화하지 않은 것 (정직하게)
- 기기 간 전파는 수동이다. 오라클에서 굽는다고 thinkpad가 갱신되지 않는다. 각 기기에서
setup:build를 쳐야 한다. 신선도 검사가 그걸 알려줄 뿐 대신 해주지는 않는다.
.provenance.json에 dictcli revision을 적지 않는다(go_build 게이트 밖이라). 그래서 "지금
배포된 dictcli가 어느 커밋인가"는 mtime 비교로만 근사한다.
- aarch64 GraalVM에서 static 산출은 불가하다 (2026-09-03 실측). musl 툴체인은 nixos-26.05에
있지만(GCC 15.2.0 aarch64 static) GraalVM 25.0.2 자체에
lib/static/linux-aarch64/musl의
java/nio/net static library가 없다. 다음에 이 문제를 만나면 static을 먼저 시도하지 말고
patchelf 경로로 바로 가라.
repo-local 담당자 스킬 패턴 (← 이 파일이 바로 그 샘플)
두 가지 "스킬의 집"을 헷갈리지 마라:
| 위치 | 정체 | 누가 발견 |
|---|
agent-config/skills/<name>/ | 펼쳐지는 글로벌 스킬 (SSOT) | setup이 모든 하네스로 fan-out |
agent-config/.claude/skills/<repo>/ | 그 repo 담당자의 운영 스킬 (project-local) | Claude Code가 그 repo를 열었을 때만 |
이 파일은 후자다 — voscli/.claude/skills/voscli/, memex-kb/.claude/skills/scanbook/과
같은 종(種). 펼쳐지지 않는다. AGENTS.md(항상 로드되는 정신)와 짝을 이루는, 그 repo에서
일할 때만 on-demand로 뜨는 손. 작업 자체가 삽질이라 AGENTS.md에 넣기 뭐한 노하우 —
scanbook이 MinerU 원격 서버 삽질을 담듯, 이 스킬은 fan-out 삽질을 담는다.
새 repo에서 "에이전트 문서는 있는데 운영 노하우가 자꾸 휘발된다" 싶으면, 그 repo에
.claude/skills/<repo>/SKILL.md를 만들 때가 된 신호다. SSOT는 코드가 사는 그 repo,
펼침은 (필요하면) 거기 run.sh가.
새 기기 setup (재현 가능성)
cd ~/repos/gh/agent-config && ./run.sh setup
- server vs dev 기기 자동 분기:
~/.current-forge-profile가 oracle/work면 server
→ consumer pi install 경로 + pi/settings.server.json. 클라이언트(thinkpad/laptop/nuc)는
파일 없음 → dev 경로(entwurf를 ~/repos/gh/로 clone). 사설 기기명은
~/.config/agent-config/server-devices.txt.
- 끝나면
./run.sh env로 하네스 링크(pi/claude/codex/antigravity/copilot/kiro) + 바이너리 arch 한눈에 검증.
진단 — "내 스킬이 안 보여요"
./run.sh env
./run.sh doctor
env는 파일이 있는지만 본다. 있는데 실행하면 죽는 상태(아래 nix GC 함정)는 doctor만
잡는다. setup:build 끝에서도 자동으로 돈다.
체크 순서:
skills/<name>/SKILL.md 있나 — 없으면 fan-out 스캔([ -f SKILL.md ])에서 탈락.
- 개별-링크 하네스면
setup:links 다시 돌렸나 — pi/claude-plugin/codex는 새 스킬에
재링크 필요. 통링크 하네스는 자동.
- frontmatter description 트리거가 빈약하지 않나 — 스킬 발견은 description 매칭. scanbook
처럼 한/영 트리거를 넉넉히.
.bak.* 디렉토리가 스캔을 오염시키지 않나 (아래 함정).
🐛 삽질 (다시 당하지 말 것)
.bak.DATE 함정. ensure_link는 링크 자리에 일반 파일/디렉토리가 있으면
<link>.bak.YYYYMMDD로 백업한다. 개별-링크 하네스(pi-skills, claude-plugin, codex)는
백업 디렉토리도 스킬로 스캔해서 같은 스킬이 둘로 뜨거나 SDK가 충돌한다. setup이 끝에
.bak.*를 청소하지만, 수동으로 만졌으면 직접 rm -rf 할 것.
~/.claude/skills는 디렉토리 통링크 → skills/. 그래서 .claude/skills/(이 repo의
project-local 스킬 폴더)와 완전히 다른 경로다. 이걸 헷갈려 글로벌에 둘 걸 project에
두거나 반대로 하지 마라.
- server 기기에서 dev 설정 펼치면 깨진다. server는
settings.server.json(consumer
install 경로)을 쓴다. forge profile 감지가 틀어지면 잘못된 settings가 링크된다 →
cat ~/.current-forge-profile로 확인.