이 노트에 대하여

여기는 forge-config 담당자가 사는 자리다. 봇멘트가 정원 댓글이라는 공통면을 만들었다면 포지 레이어는 이슈·PR·라벨·CI를 공통면으로 만들고, 핵심은 공장식 병렬 코딩이 아니라 공유 컨텍스트의 지속성 — repo 경계가 작업 경계가 아니라는 것이다. 공방은 집에만 있지 않다: Forgejo(집)와 GitHub Copilot(회사)은 호스트만 다르고 정체성은 하나이며, 신뢰의 질문은 “누가 호스트인가”가 아니라 “정체성과 상호협력이 충분한가”이다.

동작의 SSOT는 ~/repos/gh/forge-config/bin/forge 다. 문서와 어긋나면 bin/forge 가 정답이다. 이 집이 값을 치르고 배운 것 하나를 여기 둔다 — thin pointer 라는 낱말이 파일 수준과 CLI 수준 두 뜻으로 굳어 3개월을 갈렸다. 실현된 것은 CLI 수준이었는데 AGENTS.md 의 그림은 (앞으로 추가) 딱지를 붙인 파일 수준 계획을 현황도 자리에 앉혀두고 있었고, 3개월 뒤엔 아무도 그 딱지를 읽지 않았다. 계획을 현황도 자리에 그리지 않는다.

히스토리

  • [2026-09-04 Fri 14:36] @glg-bot@thinkpad [claude-opus-5 / Claude Code] — forge 스킬 실물 이관 완료(GLG 판정, agent-config 담당자와 자문). SKILL.md 361줄이 forge-config/.claude/skills/forge/ 로 오고 agent-config/skills/forge 는 상대 심링크 — voscli/incidentcli/sorge 에 이은 넷째. 두 커밋: forge-config dfe25c8(착지, 푸시됨) → agent-config 3b9f72e(git rm + ln -s 한 커밋, 로컬). 도달 6/6 직접 실측(claude/pi/claude-plugin/codex/copilot/kiro, md5 3bd833c3 동일). 1단계 푸시 후에도 AGENTS.md 관계 절은 형제 커밋과 도달 검증이 올 때까지 일부러 미뤘다 — 아직 안 선 상태를 현황도 자리에 적는 것이 오전에 값을 치른 그 고장이므로. 이 집이 새로 진 의무: bin/forge 를 고치는 커밋이 SKILL.md 도 같이 들고 간다. forge-config a0df6d9 푸시.
  • [2026-09-04 Fri 14:05] @glg-bot@thinkpad [claude-opus-5 / Claude Code] — GLG 지시(sorge 경유): 제목에 #담당자 표시 + 디스크립션 /초록 갱신. 제목을 §forge-config #담당자 … 로 개명(콜론 없는 형 — GLG가 자기 손으로 개명한 최근 둘 §apply/§prime-agent 를 읽고 따름; 옛 §repo: #담당자 콜론 형은 안 따름). :rename t 로 파일명·슬러그 동시 갱신 — #+identifier 만 안 움직였고, 이는 오늘 AGENTS.md 에 박은 「id 로만 찾아라」 규칙이 곧바로 시험대에 오른 자리다. description 을 설계 소개문에서 담당자 정체 카드로, 초록을 두 공방 + bin/forge SSOT + thin pointer 두 뜻 판정으로 교체.
  • [2026-09-04 Fri 13:42] @glg-bot@thinkpad [claude-opus-5 / Claude Code] — sorge 순회 호출로 담당자 사이클. sorge 가 넘긴 리드 「forge 는 thin pointer 라 적혀 있으나 실물 SKILL.md 가 agent-config 에 산다」 thinkpad 재측정 → 참. 다만 오류가 아니라 용어 분화였다: 실현된 것은 CLI 수준 thin pointer(agent-config 36fb001), AGENTS.md 그림만 파일 수준 계획을 현황도처럼 그려두고 있었다. AGENTS.md §agent-config 와의 관계를 실측 현황 + 두 뜻 표 + 이동 여부 열린 판단으로 교체. 이동 근거 실측: SKILL.md 와 bin/forge 가 05-28/05-29/06-01/06-02/06-04 전부 같은 날 짝지어 커밋됨 — 한 변경에 두 리포 두 커밋. agent-config 심링크 sibling 은 이미 선 패턴(voscli/incidentcli/sorge). 결정과 손은 GLG 자리 — agent-config 는 남의 집이라 건드리지 않음. 추가로 GLG 지시대로 AGENTS.md 최상단에 담당자 문서 denote id 20260527T073823 앵커를 박음(제목 아닌 id 로 기억, org 원본이 판정 자리, export md 는 한 주기 늦음, hugo_lastmod 는 손도장).
  • [2026-08-20 Thu 17:30] @junghan@힣: 분신의 공방 — 역할 기반 조율과 협업 운영규약 이 것을 참고하자. 아 담당자 불러야겠다.
  • [2026-05-27 Wed 11:55] @pi@oracle [claude-opus-4-7] — 세션 매듭: GitHub PAT 분리(GITHUB_PERSONAL_TOKEN / GITHUB_WORK_TOKEN, 의도된 GITHUB_TOKEN unset). agent-config thin pointer 박힘(별도 세션 결과 회수). 두 NEXT.md 정렬 — nixos-config / forge-config 정합. 오늘 8 commits, 봇로그 SSOT 9줄 — 다음 세션은 denotecli read 20260527T073823 한 줄로 시작.
  • [2026-05-27 Wed 11:37] @pi@oracle [claude-opus-4-7] — work forge(=alskdjf) 가동 검증. glg-bot 토큰 두 자리에 이중화: pass api/forge/work/glg-bot + ~/.env.local FORGE_WORK_*. forge path prefix 구조. v15.0.2 / glg-bot 응답 OK. bin/forge multi-host 지원(—host work)은 다음 spike.
  • [2026-05-27 Wed 11:12] @glg-bot@openclaw [claude-opus-4-7] — 공개 가동 확인. OpenClaw 컨테이너에서 forge.junghanacs.com 외부 가시(HTTP/2 200, Caddy via, Forgejo 15.0.2). glg-bot/sandbox + glg-bot/forge-config 두 repo 노출. forge-config README/AGENTS/NEXT 가 일차 리뷰의 세 권고를 그대로 흡수 — 라벨 v1 5개, 단일 glg-bot + footer 서명, Spike 0 봇멘트 fork. agent-config #13 일차 정리가 가동 단계에 깨끗하게 흡수됨. 댓글의 코드 버전이 실제로 섰다 — 봇멘트 부모 패턴의 자식 표면 분화 완료. 다음: bin/forge 동사 6개 완성 + 첫 검증 repo의 .forgejo/workflows/ci.yml.
  • [2026-05-27 Wed 10:52] @pi@oracle [claude-opus-4-7] — 매듭 갱신: OpenClaw verboseDefault on→full, glg-bot/forge-config (Forgejo 운영면) 박힘 — GitHub junghan0611/forge-config의 짝. 두 NEXT.md 진척 반영 commit (forge-config 382a17b, nixos-config 5ee3c03). alskdjf 인프라는 힣이 직접 구축 중.
  • [2026-05-27 Wed 10:34] @pi@oracle [claude-opus-4-7] — 매듭: gpt-5.5 담당자 두 사이클 commit/push 박음 (forge-config 1bd7adf). 자기 문서 수정 루프 검증 완료 — 작업면 이관 첫 단계.
  • [2026-05-27 Wed 10:32] @glg-bot@oracle [gpt-5.5] — Turn 2: AGENTS.md 운영 사실 반영(라벨 /forge cmd/footer/secret), bin/forge label-add 검증(sandbox#2 agent:running→done), 자기 문서 수정 루프 두 번째 사이클.
  • [2026-05-27 Wed 10:29] @glg-bot@oracle [gpt-5.5] — forge-config 담당자 첫 사이클 완료: NEXT.md 결정 6항목 업데이트, bin/forge minimal 4-command(sh+curl+jq) 박힘, sandbox#1 state/list/comment round-trip 검증.
  • [2026-05-27 Wed 10:20] @pi@oracle — 인프라 가동 + 첫 round-trip 검증. Forgejo 15.0.2 + postgres 16 on Oracle (forge.junghanacs.com), Caddy + Let’s Encrypt 30초 발급. forge-config 공개 repo 분리 (junghan0611/forge-config), nixos-config는 인프라만. glg-bot user + token (~/.env.local + pass), 라벨 5개 + 이슈 #1 + 봇 footer 코멘트 검증 완료. 함정 3개 박제: INSTALL_LOCK env 함정, write:user scope 누락, Caddy bind mount inode caching.
  • [2026-05-27 Wed 07:38] 생성 — 이슈 #13 일차 정리 후 시리즈 문서로 박제. 봇멘트의 다음 시리즈.

왜 포지 레이어인가

GitHub는 셀프호스트의 결여로 두 가지 비용이 생긴다.

  1. Latency 비용. webhook → runner spin-up → 첫 step 까지 분 단위. 에이전트 사용자 경험 측면에서 이 지연은 “흐름이 끊긴다”는 뜻이다.
  2. 주체성 비용. GitHub Copilot Workspace, Sweep, Devin은 “내가 모르는 모델이 내 코드에 코멘트한다.” 힣 하네스는 이 구조를 거부한다 — 내 에이전트가 리뷰하고 구현한다.

힣 하네스는 이미 셀프호스트 정체성을 갖는다 — Doomemacs / NixOS / Denote / 디지털 가든 / remark42(봇멘트). 코드면도 같은 자리에 와야 한다.

봇멘트와의 연속

봇멘트는 정원 페이지에 댓글을 남기는 패턴이다.

봇멘트:
  garden page comment
  -> 힣 agent reply
  -> durable marginal note on public knowledge surface
 
포지 레이어 (forge-config):
  issue / PR / label / CI / review comment
  -> 힣 agent interpretation and action
  -> durable work trace on code surface

두 시스템은 같은 부모 패턴 에서 자란다. 한 쪽은 정원, 한 쪽은 코드. 둘 다 공유 작업면이고, 둘 다 비동기 코멘트로 움직인다.

비-목표: 공장 모델 거부

흔히 빠지는 함정.

repository
  -> spawn 30 agents
  -> split worktrees
  -> implement in parallel
  -> merge queue
  -> industrialized coding factory

이건 힣의 의도가 아니다. 의도는:

shared harness memory + current work surface
  -> 힣 agent reads situation across repos and logs
  -> agent claims or assists a task
  -> agent implements/reviews/tests with context
  -> forge records the trace through issue/PR/CI/comment/label
  -> other 힣 agents can later recover the state and continue

핵심 가치는 에이전트 수가 아니라 연속성과 공유 컨텍스트. repo는 작업의 진입점이지 컨텍스트 경계가 아니다.

아키텍처 — 역할 분리

Forgejo
  - Git origin (셀프호스트)
  - issue / PR / label / comment surface
  - webhook/API source
  - GitHub mirror source
 
CI runner
  - deterministic verification
  - test / lint / build / artifact
  - isolated from agent identity
 
agent bus
  - receives webhook events
  - normalizes issue/PR/comment/label/CI state
  - routes tasks to appropriate 힣 agents
  - records job state
  - writes labels/comments back to forge
 
agent worker
  - reads issue + repo + AGENTS.md + wider harness memory
  - creates branch/worktree only when needed
  - calls pi-shell-acp / Claude Code / Codex / Gemini CLI / OpenClaw agents
  - pushes branch, opens PR, comments summary

규칙:

  • CI = verifier (재현 가능한 검증)
  • agent worker = implementer/reviewer (컨텍스트 있는 작업)
  • Forgejo = durable work surface (사라지지 않는 상태면)
  • memory = wider than any single repo (semantic-memory / botlog / notes)

라벨 프로토콜 — v1은 5개로

초기 이슈 #13 초안은 20개 라벨을 제안했지만, 운영 안 되면 라벨 묘지가 된다. v1 권장은 5개.

라벨의미
agent:ready에이전트가 잡아도 됨
agent:running잡힘 — 작업 중
agent:done완료
human:needs-review사람 판단 필요
ci:failedCI 깨짐

봇멘트도 처음엔 read/reply 두 동작뿐이었다. 운영하면서 부족하면 추가한다.

여러 에이전트가 forge에 코멘트할 때, 각자 Forgejo 사용자를 따로 만들면 토큰/권한 관리가 폭발한다.

v1 권장:

  • 단일 glg-bot Forgejo 사용자 (forge user)
  • 코멘트 본문 마지막에 footer 서명:
    • — glg-bot [claude-opus-4-7 / openclaw]
    • — glg-bot [claude-code / nuc]
    • — glg-bot [pi-codex / oracle]

봇멘트와 일관 (힣봇 단일 신원). 미래에 신원 분리가 필요하면 footer → user로 승격하면 된다.

OpenClaw 힣봇 접근성

forge-config 스킬은 HTTP curl 레시피집 형태로 가야 한다 — 명령어 모음이 아니라.

이유: OpenClaw 힣봇 컨테이너는 ~/org, ~/repos/gh read-only 접근만 있고 shell exec이 제한적이다. forge API를 직접 호출하려면 토큰 + curl + jq 가 가용 도구의 전부다.

봇멘트 스킬 패턴 그대로:

forge list-open                    # 열린 이슈 목록
forge comment <issue-id> <body>    # 코멘트 작성
forge label-add <issue-id> <label> # 라벨 부착
forge state <issue-id>             # 현재 상태 + 최근 코멘트

각 명령은 얇은 curl wrapper. 환경변수 FORGE_URL, FORGE_TOKEN 만 있으면 동작.

진행 — 이슈 #13 일차 정리

agent-config #13 에 일차 설계가 정리됐다. 7개 spike:

  1. Spike 1: Forgejo base — 외부 서버 배포, Caddy/HTTPS/PostgreSQL, latency 검증
  2. Spike 2: GitHub mirror — Forgejo origin, GitHub push mirror
  3. Spike 3: Actions runner — Forgejo Runner, ./scripts/ci convention
  4. Spike 4: Label/comment protocol — 라벨, 이슈 템플릿, /agent 명령
  5. Spike 5: Agent bus MVP — webhook 수신, 라벨 전이, dummy 코멘트
  6. Spike 6: Agent worker MVP — clone/worktree, 브랜치/PR/코멘트
  7. Spike 7: OpenClaw 힣봇 skill 통합skills/forge/SKILL.md

Spike 0 — 봇멘트 코드 fork

이슈 #13에 없지만 OpenClaw 측에서 제안한 추가 spike: 봇멘트 스킬을 fork → API endpoint만 Forgejo로 swap.

봇멘트의 list/reply/label 코어 로직은 거의 그대로 옮겨진다. 새 design부터 시작하지 말고 기존 코드 변형 으로 시작하면 첫 protocol round-trip이 며칠 안에 나온다. “botment는 forge-config의 부모 패턴”을 코드와 문서에 명시.

보안과 권한

  • 어떤 에이전트도 main 에 직접 push하지 않는다.
  • 모든 구현은 branch + PR을 거친다.
  • CI runner와 agent worker는 분리된다. CI는 신뢰 불가 코드를 실행, agent worker는 신뢰된 하네스 로직을 실행.
  • Fork/public PR에는 특권 시크릿을 주지 않는다.
  • 에이전트 토큰은 역할별 scope.
  • Merge는 첫 버전에선 사람 게이트. 자동 merge는 v1 non-goal.

요약

중요한 점은 Git을 셀프호스트한다는 것이 아니다.

중요한 점은 forge가 지속적이고, 낮은 지연을 갖고, 에이전트에 보이는 작업면 이 되어, 힣 에이전트들이 컨텍스트를 회수하고, 이슈 PR 코멘트/라벨로 조율하고, 검증을 실행하고, 미래 에이전트가 이해할 수 있는 자취를 남기는 자리가 되는 것이다.

이건 하네스 메모리/워크플로 레이어이지, 코딩 공장이 아니다.

관련 노트

[2026-05-27 Wed] agent-config 쪽 구상안 — skills/forge fork from botment

자리 정렬: 인프라/배치(docker caddy + Forgejo 컨테이너)는 nixos-config 담당자. agent-config 쪽 본질은 에이전트가 forge 위에서 일하는 손 — 즉 skills/forge/. GLG가 이 자리에 우리를 부른 이유.

botment 코드를 손으로 한 번 읽고 박는 mental model

skills/botment/scripts/botment.sh (277라인) 정독 결과: fork → endpoint swap이 진짜 거리.

botment → forge 동사 매핑:

botment (remark42)forge (Forgejo API)비고
unreadunreadmention/assigned 미응답
listlist-issues / list-prs
read <url>read <#>thread 평탄화로 더 단순
reply <bot> <cid> <url>comment <#>parent thread 없음
comment (독립)issue-create <repo>
(없음)label-add / label-removev1 5개
(없음)pr-createmerge는 human-gated
Dev auth 3-step OAuthAuthorization: token <T>인증 ~50줄 → 1줄

코드 추정: botment 277라인 → forge 200라인 이하. OAuth 댄스가 빠진다.

폴더 골격 — botment 동형

skills/forge/
├── SKILL.md      (LSP 패턴: description + API 테이블 + 노트, <100 라인)
└── scripts/
    └── forge.sh  (단일 스크립트, 동사 6개)

skills/botment 옆에 skills/forge. 디렉토리 동형이 부모/자식 명명을 영구화한다. 미래 에이전트가 폴더만 봐도 “댓글의 코드 버전” 즉시 이해.

동사 6개 (v1)

  1. unread — 라벨 agent:ready 또는 mention 있는 미응답
  2. list — 열린 이슈/PR 목록
  3. read <#> — 이슈/PR 본문 + 코멘트 + 라벨 + CI 상태
  4. comment <#> <text> — 코멘트 작성 (footer 서명 자동)
  5. label add <#> <label> / label remove
  6. issue create <repo> <title> <body>

PR 동사(pr create, pr merge)는 v2. merge는 첫 버전 사람 게이트.

  • Forgejo 사용자: glg-bot 하나
  • 토큰: ~/.env.localFORGE_TOKEN — 모든 백엔드(OpenClaw/pi/Claude Code) 공유
  • footer 서명: 본문 끝에 — glg-claude [claude-sonnet-4-6] / — glg-codex [gpt-5.5] / — glg-gemini [gemini-2.5-pro]
  • 분신이 형제이지 부속품이 아니다 (agent-config/AGENTS.md 첫 자리) — 신원은 하나, 모델은 footer로 식별

환경 변수와 closed-loop 정책 — botment에서 그대로 차용

  • FORGE_BASE_URL (e.g. https://forge.junghanacs.com) — caddy 뜨면 결정
  • FORGE_TOKEN~/.env.local
  • closed-loop write: botment처럼 외부 read OK / write는 oracle 내부에서만 또는 token이 있으면 어디서든 write — 결정 필요

Spike 0 — 오전 작업 단계

  1. skills/forge/scripts/forge.sh — botment.sh를 복사하여 endpoint/ 인증 부분만 Forgejo로 교체
  2. SKILL.md — botment SKILL.md 구조 그대로 차용, 동사 표만 갈아끼움
  3. caddy + Forgejo 떠 있으면: 첫 round-trip 실험 — glg-bot으로 “Hello forge” 이슈 생성 + 코멘트 + 라벨 부착
  4. 검증되면 첫 검증 대상 repo(denotecli 후보)에 ./scripts/ci + .forgejo/workflows/ci.yml 미리 깔기 (Spike 3 준비)

대기 결정 (caddy 뜨기 전)

  • FORGE_BASE_URL 도메인: forge.junghanacs.com / git.junghanacs.com
  • closed-loop write 여부 (oracle 내부 한정 vs 토큰 인증만)
  • 첫 검증 repo: denotecli / forge-config 자체

forge-config repo 정신 보호 — 다음 자리

이슈 #13 본문은 영문 spec체로 정리돼 있다. forge-config 새 repo 만들 때 AGENTS.md 첫 자리에 한글로 “이 집은 무엇인가” 박는 것 — 본문이 spec으로 읽히지 않게 하는 보호 장치. agent-config/AGENTS.md “담당자의 자리” 섹션 패턴 차용.

repo 후보 URL: https://github.com/junghan0611/forge-config (아직 생성 전 — 이름 박아둠)

[2026-05-27 Wed] 첫 round-trip — 인프라부터 봇 코멘트까지

[2026-05-27 Wed 10:20]

인프라 가동 (Oracle)

  • forge.junghanacs.com — Forgejo 15.0.2 LTS
  • DB: postgres:16-alpine 별도 인스턴스 (forge-internal bridge 격리)
  • Caddy 리버스 프록시 + Let’s Encrypt 자동 (ACME http-01 30초 발급)
  • SSH 비활성 (HTTPS git만), DISABLE_REGISTRATION=true 닫힌 계

설계 결정 두 가지:

  • DB는 PostgreSQL부터 — SQLite로 시작 추천했으나 멀티 에이전트 동시 write contention 회피 위해 처음부터 postgres. umami-db와 같은 패턴, 별도 인스턴스 격리. 리소스 ~150MB 추가는 작은 비용.
  • Forgejo 15 LTS — 13은 이미 EOL, 15가 현재 stable. codeberg.org/forgejo/forgejo:15.

책임 경계 — repo 셋

Layer위치책임
인프라nixos-config/docker/forge/Docker compose, Caddy, host-specific
운영 ownershipforge-config repo (신규 공개)라벨/footer/봇 행동 규약 + bin/forge + agent skill SSOT
agent surfaceagent-config/skills/forge/thin pointer (앞으로)

forge-config README/AGENTS/NEXT 박힘. 코드는 같이 논의 후 — 봇멘트가 부모 패턴이라 fork-then-swap이 첫 spike.

운영 함정 — 세 개 박제

INSTALL_LOCK=false env 함정

처음에 docker-compose.yml 에 FORGEJO__security__INSTALL_LOCK=false 박았다 — setup wizard 첫 진입을 위해. 그런데:

  1. wizard 통과 → app.ini 에 INSTALL_LOCK = true 박힘
  2. 다음 부팅 → env가 매번 false 로 덮어씀
  3. Forgejo: “이미 설치됐는데 lock=false 모순” → fatal → 무한 재시작 (crash loop)

해결: env에서 그 줄 제거. wizard가 자동 박는 값을 env로 덮지 말 것.

→ 일반 원칙: setup wizard가 자동으로 박는 값은 env에 박지 않는다. SECRET_KEY, INTERNAL_TOKEN, JWT_SECRET 도 같은 원칙.

Token scope — Forgejo가 GitHub와 다른 점

첫 토큰 발급 시 write:issue, write:repository, read:user, write:organization 만 체크. 검증 시도:

POST /user/repos
 403: token does not have at least one of required scope(s): [write:user]

GitHub은 user repo 생성에 repo scope만 필요. Forgejo는 write:user 별도 요구. 재발급 후 통과.

forge-config/AGENTS.md token 발급 절차에 필수 5개 scope 명시: write:user, write:repository, write:issue, write:organization, read:user.

단일 파일 bind mount inode caching (Caddy)

Caddyfile 수정 후 docker exec caddy caddy reload 했는데 새 forge 도메인 인식 안 됨. 원인: ./Caddyfile:/etc/caddy/Caddyfile 같은 파일 단위 bind mount는 컨테이너가 처음 mount된 inode를 캐싱. Edit=/=sed -i 가 atomic rename으로 새 inode 만들면 컨테이너는 옛 inode 계속 가리킴.

# host file: inode 2623087, size 934, mtime 2026-05-27
# container view: inode 2624603, size 709, mtime 2026-05-17  ← 옛 inode!

해결: docker compose restart caddy. reload만으로는 부족. 또는 디렉토리 단위 bind mount.

Round-trip 검증 — glg-bot/sandbox

검증용 repo 생성 후 5단계 protocol round-trip:

단계결과
1. POST /user/reposglg-bot/sandbox
2. POST /repos/.../labels × 5agent:ready/running/done, human:needs-review, ci:failed
3. GET /repos/.../labels5개 확인
4. POST /repos/.../issues (with labels)이슈 #1 — “Spike 0 — 봇멘트 fork → forge API endpoint swap”
5. POST /issues/1/comments (봇 footer)— glg-bot [claude-opus-4-7 / oracle]
6. GET /repos/.../issues/1open, agent:ready, 1 comment

라이브: https://forge.junghanacs.com/glg-bot/sandbox/issues/1

토큰 백업 이중화

자리용도
~/.env.local (600)쉘/ 에이전트 운영용 — source ~/.env.local$FORGE_TOKEN
pass api/forge/junghanacs/glg-bot백업 + 메타데이터 (scopes/created/note). pass git 자동 commit

봇멘트의 anonymous→Dev auth 진화 패턴과 같다 — 검증부터 시작해서 진화.

다음 — 담당자 호출 + 자기 문서 수정 루프

다음 단계는 forge-config 담당자 entwurf 호출. 봇멘트 패턴 그대로:

  1. 봇멘트 스킬을 fork → forge-config/bin/forge 로 변형
  2. list-open / comment / label-add / state 4개 명령 minimal
  3. 담당자가 자기 NEXT.md 를 자기 손으로 업데이트하는 루프 검증
  4. 정식 운영 표면 repo의 ownership 결정 (glg-bot/sandbox 는 검증용 임시)

중심은 봇로그가 세워져야 하고, 흔들리지 않게. 모든 진화는 이 노트에서 출발하고 결산한다. forge-config repo는 위성이고 코드 자리. 의미와 history의 SSOT는 이 봇로그.

[2026-05-27 Wed] ClawSweeper 차용 — 신박한 자리는 hooks.mappings 였다

힣이 OpenClaw/ClawHub 측과 직접 소통한 경험에서 신박하다고 짚은 자리 — openclaw/clawsweeper. 한 번 들여다보고 “그 정도까지는 우리에게 과하다, 그러나 한 자리는 진짜다” 로 결산.

과한 자리 — 우리에게 안 맞는 것

ClawSweeper는 본격 maintenance bot. 우리 규모를 한참 넘는다:

  • Codex review/fix/automerge 3-lane orchestration
  • shard 단위 worker 동시성 (review 39 shards, repair 별도)
  • openclaw/clawsweeper-state 별도 repo 에 durable markdown record 누적
  • maintainer commands (@clawsweeper review/fix/autofix/automerge)
  • guarded implementation PRs 자동 open
  • repair-cluster-intake / repair-comment-router / repair-publish-results 등 워크플로 14개

이건 오픈소스 maintainer 가 사람 손 없이 backlog 청소 하려는 봇. 우리는 힣 에이전트 군단이 forge 면에서 같은 상태를 보고 작업 하려는 거. 같은 결처럼 보이지만 한 단계 다르다. “공장 모델 거부” 의 다른 표현.

진짜 신박한 자리 — OpenClaw Gateway /hooks/* 네이티브 mapping

이게 발견의 핵심이다. ClawSweeper 가 GitHub Actions 로 무거운 일 다 하고 마지막 한 줄을 OpenClaw 로 보낼 때 쓰는 자리:

외부 이벤트 → POST /hooks/agent → OpenClaw hook session 분리 agent turn

그런데 우리 OpenClaw 2026.5.22 에 들어가서 schema 들여다보니 /hooks/agent 만 있는 게 아니다. hooks.mappings네이티브 다:

  • hooks.mappings[].match.path : forgejo 같은 subpath 매칭
  • hooks.mappings[].action : "wake" 또는 "agent"
  • hooks.mappings[].messageTemplate : payload/header/path 치환된 prompt
  • hooks.mappings[].sessionKey : hook:forgejo:payload.repository.full_name}}:{{payload.issue.number 같은 동적 키
  • hooks.transformsDir : safe relative path 의 transform module (filter/transform)
  • hooks.allowedAgentIds : agent 화이트리스트
  • hooks.defaultSessionKey : interactive session 과 자동 격리

검증 경로 (본 세션, 2026-05-27):

  • dist/runtime-schema-pPVn7_7T.js 라인 964-985
  • dist/zod-schema-Dsy5tXpj.js 라인 452 (messageTemplate)
  • dist/hooks-BrCZyF5b.js (allowedAgentIds 정책 + sessionKey 정책)
  • dist/codex-route-warnings-CppMIDKk.js 라인 554, 1404 (mappings 처리)

무엇이 폐기되는가 — Mattermost 우회 + forge-bus adapter

직전 세션에서 박은 C 안 (Mattermost incoming webhook Slack-compat + forgebot 새 계정 + chatmode: onmessage + #forge-events 채널 격리) 은 우회로 였다. ClawSweeper 가 보여준 정공법은 직진:

Forgejo repo webhook (Authorization: Bearer)
  → https://internal-host.example.com/openclaw/hooks/forgejo
  → OpenClaw hooks.mappings[forgejo-raw]
  → forge agent (hook-only, no channel)
  → forge skill / bin/forge

폐기된 자리:

  • Mattermost forgebot user 신설 ❌
  • #forge-events 채널 + onmessage chatmode ❌
  • Forgejo Slack webhook → Mattermost incoming webhook ❌
  • forge-bus adapter (sh/Node 50~100줄) ❌
  • OpenClaw webhooks plugin enable + allowlist ❌

전부 “OpenClaw 가 내장 hook 으로 이미 한다” 한 줄로 사라짐.

보존되는 자리

  • 라벨 v1 (5개) — agent 워크플로의 의미 단위. 변하지 않음
  • footer 서명 (— glg-bot [model / host]) — 1KB 정체성. 변하지 않음
  • bin/forge 5동사 — agent 의 손. 변하지 않음
  • thin pointer SKILL.md — repo 와 agent 표면 분리. 변하지 않음
  • 단일 glg-bot 정체성 — 공장 모델 거부의 다른 표현. 변하지 않음

손과 의미는 그대로. transport 만 한 단계 짧아짐.

ClawSweeper 에서 차용한 작은 자리 (3개)

  1. Hook-only agentforge agent 는 Mattermost channel 항목 없음. /hooks/forgejo 에서만 깨어남. interactive 봇 (mainbot/vocbot) 과 완전히 분리된 lane.
  2. Session key 자동 격리defaultSessionKey: "hook:forgejo" + allowedSessionKeyPrefixes: ["hook:"] 로 hook turn 이 같은 봇 토큰의 대화 session 과 절대 섞이지 않음.
  3. Idempotency key by headerX-Forgejo-Delivery 헤더를 Caddy 한 줄로 X-OpenClaw-Idempotency-Key 로 복사하면 OpenClaw replay cache 가 자동으로 중복 방어. transform / adapter 코드 없음.

NEXT.md 정렬 — 4 Phase 로 박음

본 자취 따라 ~/.openclaw/NEXT.md #0 트랙 전체 교체 (2026-05-27, commit 별도):

  • Phase 0/hooks/agent 라이브 동작 검증 (10분): mainbot 으로 curl POST 한 턴 검증. Caddy + Authelia 경로 정합 확인.
  • Phase 1forge agent + workspace-forge/ + forgejo-raw mapping 박기 (30~60분): hook-only agent. channel 항목 없음.
  • Phase 2 — Forgejo webhook 박고 라이브 round-trip (15분): voscli sandbox 라벨 토글 → forge skill 시퀀스 확인.
  • Phase 3transformsDir 에 필터 module 박기 (1시간, 필요 시): label=agent:ready 만 통과. 노이즈 제거.
  • Phase 4 — Caddy header copy 한 줄 (선택, 30분): idempotency 자동화.

코드 0줄. config 만. 진짜 정공법은 만들지 않는 길에 있었다.

메타 — 두 번 검토에서 배운 자리

오늘 forgebot 트랙 검토를 두 번 돌렸다:

  1. 첫 검토 — 본 세션 + 날것 챗지피티 합쳐서 C 안 (Mattermost+Slack-compat) 박음
  2. 두 번째 검토 — 챗지피티가 ClawSweeper 들여다본 다음 D 안 발견

두 번째에서 첫 번째가 우회로였음이 드러남. 부끄러운 자리가 아니라 검토를 한 번 더 돈 자리. 첫 검토에서 박은 “Trigger 문장 안정화” 자리는 D 안에서도 그대로 살아있다 — messageTemplate 안에 들어가는 자리가 그 문장. 검토 한 번 더 돈 가치는 비싸지 않다. 작은 도약은 자주 다시 박을 수 있다.

다음 — Phase 0 시작 시점

5/27 오후 voc 모델 전환 안정화 후. NEXT 1 끝난 뒤. 1 KST 시간 자리 따로 봐야.

[2026-09-04 Fri] 담당자의 현재 보고 — 말과 실물이 갈린 자리: “thin pointer” 두 뜻

sorge 순회(2026-09-04)가 이 집에 걸린 리드 하나를 넘겼다: forge 는 “thin pointer”라고 적혀 있으나 실물 SKILL.md 361줄이 agent-config 에 살고, forge-config 에는 .claude/skills/ 가 없다. 담당자가 thinkpad 에서 재측정했다. 참이다. 다만 그냥 “틀린 문서”가 아니라, 한 용어가 두 집에서 다른 뜻으로 굳은 자리 였다.

실측 (thinkpad, 2026-09-04)

  • ~/.claude/skills 자체가 ~/repos/gh/agent-config/skills 로의 심링크 (ls -ld).
  • agent-config/skills/forge/SKILL.md = 실물 361줄, agent-config 에 git-tracked (wc -l, git ls-files). ~/.claude/skills/forge/SKILL.md 와 inode 동일 (stat -c %i).
  • forge-config/.claude/ 는 존재하지 않음 (ls).
  • forge-config AGENTS.md 의 옛 그림은 “forge-config 가 SKILL.md 의 SSOT, agent-config 는 thin pointer” 를 (앞으로 추가) 딱지와 함께 그려두고 있었다. 계획을 현황도처럼 그린 것이다.

”thin pointer” 가 두 뜻으로 갈렸다

내용실현
파일 수준SKILL.md 본문 이 forge-config 에 살고 agent-config 는 stub아니오
CLI 수준본문은 agent-config 에 살되 *동작의 정답은 bin/forge * 라고 가리킴

실현된 것은 CLI 수준이다 — agent-config 36fb001 (2026-05-27, “replace stub fork with thin pointer to forge-config CLI”). SKILL.md 의 ## SSOT 단락이 ~/repos/gh/forge-config/bin/forge 를 정답으로 명시하고, “어긋나면 bin/forge 가 정답” 이라고까지 못박아 두었다. 즉 실물 배치는 일관되고 의도적이었는데, 이 집의 그림만 다른 뜻의 같은 단어로 남아 있었다.

이 노트의 히스토리에도 같은 자국이 있다 — [2026-05-27 Wed 11:55] 줄의 “agent-config thin pointer 박힘”. 그 문장은 참이었다. 다만 어느 뜻인지가 안 적혀 있었다.

열린 판단 — 실물을 forge-config 로 옮길 것인가

agent-config 에서 sibling 리포 심링크는 이미 선 패턴 이다. 45개 스킬 중 3개가 그렇고 (voscli, incidentcli, sorge), 셋 다 “스킬이 그 리포의 코드/바이너리에 의존한다”는 같은 모양이다. forge 도 정확히 그 모양이다 — bin/forgebin/git-credential-forge 가 여기 산다.

옮겨야 한다는 실측 근거: SKILL.md 와 bin/forge 는 늘 같은 날 함께 움직였다.

날짜forge-config bin/forgeagent-config skills/forge/SKILL.md
2026-05-282462c9a 06cc803 eadf442f0f02d8 957de68
2026-05-29a0e56bedb357c6 c323776 14e6ee4 29c4c7e
2026-06-0185c42a9 d6b2619 3988102767c67b
2026-06-02ffeacaa13f9d32
2026-06-04199375a 0329df5 e74b7fa8ccdecb ee42cd2

한 변경마다 두 리포에 두 커밋. 같은 축에 살면 한 커밋이다. 문서가 코드보다 늦게 따라가다 갈릴 표면이 구조적으로 닫힌다.

반례로 오독하기 쉬운 자리 하나를 미리 닫아둔다: agent-config 73c3be2 “merge co-owned settings without symlinks” 는 settings 조각 얘기이지 스킬 얘기가 아니다.

결정은 GLG 자리다. 옮기려면 agent-config 쪽 파일 삭제 + 심링크 생성이 한 손에서 일어나야 하는데 agent-config 는 남의 집이다. 담당자는 발견하고 이름 붙여 넘긴다. 그 전까지 SKILL.md 를 forge-config 로 복사하지 않는다 — 실물이 둘이 되는 게 갈린 말보다 나쁘다.

담당자 문서를 id 로 기억시켰다

GLG 지시(2026-09-04): 각 에이전트 문서가 담당자 문서의 denote id 를 기억할 것. AGENTS.md 최상단 (여는 blockquote 바로 다음, ## Wake-up Brief 앞)에 ## 담당자 문서 — denote id 20260527T073823 절을 세웠다. 담은 것:

  • id 20260527T073823 가 정본. 제목·슬러그·태그·파일명은 움직이고 #+identifier: 는 안 움직인다.
  • 읽는 자리는 ~/org/botlog/ org 원본. notes/content/ 의 내보내진 md 는 한 주기 늦으므로 판정 근거로 쓰지 않는다.
  • § 를 언급만 하는 주제 노트는 담당자 문서가 아니다.
  • :hugo_lastmod: 는 퍼블리시 필드가 아니라 “이 문서를 정말 고쳤다”는 손도장 (GLG 판정 2026-09-04). 히스토리 줄은 로그이지 수정이 아니므로 기준선을 올리지 않는다.

AGENTS.md## 관련 링크도 공개 export URL 단독에서 “id 정본 + 원본 자리 + export 지연 경고” 로 바꿨다.

빚 상태

마지막 도장(2026-06-01) 이후 5커밋 — 작다. 내용은 이미 AGENTS.md / NEXT.md / README.md 에 흡수되어 있었다 (close/reopen 동사, git-credential-forge, env self-source, 두 공방 문서화). 이 사이클은 갱신 빚 때문이 아니라 담당자 문서의 취지 때문에 돌았다.

자기수선 스킬은 없다. 반복되는 수선이 실제로 관측되지 않으므로 만들지 않는다.

[2026-09-04 Fri] 담당자의 현재 보고 — forge 스킬 실물 이관, 그리고 갈렸던 낱말이 닫힘

오늘 오전에 이름 붙인 자리가 오후에 닫혔다. GLG 판정(2026-09-04): “forge 스킬도 그러면 forge-config로 이관하도록하는데 이건 agent-config담당자 지금 살아있거든 거기 자문해서 처리하라고해.” 혼자 하지 말라는 것이 지시의 절반이었다.

무엇이 섰나

forge 스킬의 실물이 이 리포로 왔다. forge-config/.claude/skills/forge/SKILL.md 361줄이 실물이고, agent-config/skills/forge 가 상대 심링크로 가리키기만 한다. agent-config 에서 sibling 리포 심링크는 새 패턴이 아니라 넷째 다 — voscli, incidentcli, sorge 가 먼저 있었다.

도달 6/6 실측 (담당자 직접, readlink -f "$p/SKILL.md" + [ -f ]): ~/.claude, ~/.pi/agent/skills/pi-skills, ~/.pi/agent/claude-plugin/skills, ~/.codex/skills, ~/.copilot/skills, ~/.kiro/skills 여섯 전부 새 실물로 해석되고 md5 3bd833c3… 동일.

두 커밋이다. forge-config dfe25c8 (착지, 푸시됨) → agent-config 3b9f72e (git rm -r + ln -s 한 커밋, 로컬. 푸시는 GLG 자리). 순서를 이렇게 잡은 이유는 반대로 하면 agent-config 삭제부터 이쪽 착지까지 사람 시간만큼 ~/.claude/skills/forge 가 dangling 이기 때문이다.

이관보다 오래 갈 것 — 형제가 메워준 세 자리

내가 「확인 안 함」으로 표시해 넘긴 자리를 agent-config 담당자가 재서 채웠다. 전달받은 것과 내가 잰 것을 갈라 적는다.

  • 전달받음run.sh 배포 루프가 for skill_dir in "$SKILLS_DIR"/*/ + [ -f "$skill_dir/SKILL.md" ] 라 심링크-투-디렉터리를 잡는다. 그래서 파일이 아니라 디렉터리 를 링크해야 한다 — 파일 하나만 링크하면 skills/forge/ 가 실디렉터리로 남아 “실물이 어느 쪽인가”가 다시 흐려진다. 우리가 3개월 겪은 병이 정확히 그 모양이었다.
  • 전달받음 — 개별 링크 부류(pi / claude-plugin / codex)의 target 문자열이 절대경로라 실물 교체에 안 흔들린다. 몰랐으면 순서를 훨씬 복잡하게 짰을 것이다. 결과 6/6 은 내가 쟀다.
  • 전달받음run.shLINKED_SKILL_REPOS 가 리포 이름과 스킬 이름을 이중 사용해서, forge-configforge 를 소유하는 첫 사례 에 거짓 dangling 경고가 났다. 링크는 멀쩡한데 경고만 뜨는, 제일 나쁜 종류의 거짓말이다. 그 집이 LINKED_SKILL_NAMES 매핑으로 고쳤다.

그리고 그 집이 자기 헛디딤 하나를 먼저 넘겼다 — 아침에 「이 기기 sorge 세팅 끝」이라 보고했는데 Claude Code 기준으로만 참이었고 세 하네스엔 없었다는 것. 「링크를 건 것」과 「모든 하네스에 뜨는 것」은 다른 사건이다. 이 집 말로 옮기면 한 표면에서 참인 것을 전 표면의 완료로 보고한 것 이고, 오늘 이 집의 「선 적 없는 계획이 현황 칸에 3개월」과 같은 병의 짧은 판본이다. 서로 모르는 두 집에서 같은 모양이 났다.

(덧: forge기존 이름의 실물 교체 라 안 보인 순간이 없었고, sorge새 이름 이라 setup 전까지 세 하네스에서 아예 안 보였다 — 그 집 관측. 같은 규칙이 두 사례에서 다르게 발현된다.)

반례 하나가 두 사람 읽기로 닫혔다

agent-config 73c3be2 “merge co-owned settings without symlinks” 는 스킬 심링크 금지가 아니다. 나는 git show --stat 으로 “settings 조각이 본체”까지만 봤는데, 그 집이 run.sh:163-167 주석을 읽어 금했는지를 댔다 — “NEVER symlink such a file: a symlink = whole-file ownership”, 런타임이 lastChangelogVersion 을 쓰는 공동 소유 파일 만 금한 것이다. SKILL.md 는 아무도 런타임에서 쓰지 않는다. 독립적인 두 읽기가 같은 결론에 닿았으니 이 반례는 닫힌 것으로 본다.

이 집이 새로 진 의무

bin/forge 를 고치는 커밋이 SKILL.md 도 같이 들고 간다. 두 파일이 한 커밋에 없으면 그때부터 다시 갈리기 시작하는 것이다. 이관의 값어치는 파일 위치가 아니라 여기에 있다 — 다섯 날짜 전부 두 리포 두 커밋이던 것이 이제 한 커밋이 될 수 있다는 것.

미뤄둔 판단 하나

1단계를 푸시한 뒤에도 AGENTS.md 의 관계 절은 일부러 안 고쳤다. 그때 고치면 아직 서지 않은 상태를 현황도 자리에 적는 것이고, 그게 이 집이 오늘 오전에 값을 치른 바로 그 고장이다. 형제의 커밋과 도달 검증이 온 뒤에 실측 그대로 적었다. 오전에 이름 붙인 규칙을 오후에 자기 손으로 지킨 셈이다.