한눈에
Claude 기반 마스터 에이전트(harness)가 입력 하나를 받아 문헌 수집 → 초안 → 심사 →
문서 생성까지 지휘한다. 워커는 의견만 내고 문서를 쓰지 않는다.
| 항목 | 값 |
|---|---|
| 저장소 | contextBio/BioWrit (bare ContextBio/BioWrit.git) |
| 호스트 | c1.sysmed.kr — 백엔드·워커 CLI 모두 (2026-08-29 g2 에서 이전) |
| 운영 워크트리 | ContextBio/BioWrit — main, 127.0.0.1:8765, systemd user biowrit-backend |
| 개발 워크트리 | dev/BioWrit — dev, 127.0.0.1:8766, systemd user biowrit-backend-dev |
| 백엔드 주소 | Apache c1.sysmed.kr/biowrit/ → 8765 · /biowrit-dev/ → 8766 |
| 프론트 | 개발 미러에만 있다 — dev-contextbio.web.app/biowrit (아래 참조) |
| 백엔드 코드 | dashboard/server.py — 표준 라이브러리만 쓴다(의존성 없음) |
| 로그인 | contextBio 통합계정 ID 토큰 (contextbio_auth.py) |
| 릴리즈 | deploy/release.sh (scripts/ 가 아니다) |
| 사용자 자료 | CONTEXTBIO_USERDATA — 운영 /mnt/S1/sdata/contextbio/users, dev 는 contextbio-dev/users |
| 산출물 | <CONTEXTBIO_USERDATA>/<사용자>/biowrit/artifact/<타입>/<폴더명>/ (저장소 밖) |
| 실행 기록 | 저장소 안 runs/<user_slug>/<rid>/{meta.json,output.log} (.gitignore) |
| 로그 | journalctl --user -u biowrit-backend(-dev) |
화면은 지금 개발 미러에서만 나온다
2026-09-12 에 공개 사이트를 AURORA·PepDesigner 로 좁혔다(content.py 의
RELEASE_APPS). 그래서 운영 빌드(deploy.yml)는 BioWrit 을 굽지 않고,
contextbio.ai/biowrit 과 biowrit.contextbio.ai 는 /home 으로 302 된다(2026-09-29
확인).
화면은 사이트 저장소의 deploy-dev.yml 이 BioWrit dev 브랜치의 dashboard/ 를
build_subapp.py 로 구워 dev-contextbio.web.app/biowrit 에 싣는다. 이때 백엔드 주소는
SUBAPP_BACKEND_BIOWRIT=https://c1.sysmed.kr/biowrit-dev 로 dev 백엔드를 가리킨다.
화면을 고쳤으면 BioWrit 저장소에 푸시한 뒤 사이트 워크플로가 돌아야 반영된다.
운영 백엔드(:8765)는 계속 떠 있고 main 릴리즈도 나간다(최근 release-20260927-0344).
운영 빌드의 레시피(RECIPES["biowrit"])는 https://c1.sysmed.kr/biowrit 를 가리킨 채로
남아 있어, 공개 목록에 다시 넣으면 그쪽에 붙는다.
에이전트 구조
입력(URL · PMID · 키워드 · 원고 · 리뷰어 코멘트 · 계획서 요청)
│
▼ harness (Claude 마스터) — .claude/agents/harness.md
├ Stage 0~1 hermes 수집 라우터
├ Stage 2 출력 에이전트 8종 research · review · critic · rebuttal ·
│ editorial · editorial_comment · wiki · proposal
├ Stage 2.5 codex-bridge 코드 생성 (gpt-5.3-codex) → 패치 → 승인 후 적용
├ Stage 2-C+ codex/gemini-bridge 동료심사 2nd 의견 → critic 재호출
└ Stage 3~4 품질 검사 · 리포트
- 통신 규약: 서브에이전트 간 직접 호출 금지. 모든 데이터는 harness 를 지난다.
필요할 때는
critic_required·cross_validation_required같은 신호만 올리고 분기는 harness 가 한다. 예외는 보조 워커 둘 —pubmed-notebook(PubMed→NotebookLM)과hwp2docs(HWP→docx)는 hermes 가 직접 부를 수 있다. - 워커 선택은
.agents/rules/triangle.md§2 — 코드는 Codex 고정, 동료심사 2nd 의견은 사용자 지정 > ChatGPT > 실패 시 Gemini 승계. 둘을 동시에 부르지 않는다. - 워커가 없어도 파이프라인은 끝까지 간다 — cross-validation 과 Stage 2.5 만 꺼진다.
그래서 워커 설치는 기능 추가이지 복구 절차가 아니다(
deploy/workers.md). - 워커 CLI 는 c1 의
~/.npm-global/bin/{codex,gemini}에 있다.claude -p가 c1 에서 돌므로 g2 에 남은 설치분은 쓰이지 않는다.start-backend.sh가 이 경로를 PATH 에 넣는데, 빠지면 브릿지가not_installed로만 보고하고 조용히 Claude 단독으로 돈다. - Gemini 브릿지는 항상
--approval-mode plan이라 파일을 쓸 수 없다 — 코드 패치 경로가 없다는 뜻이다.
규칙 파일이 정본이다
에이전트는 매 호출마다 자기 스펙을 다시 읽는다. 그래서 동작을 바꾸는 자리는 코드가 아니라 마크다운이다.
| 파일 | 정하는 것 |
|---|---|
.agents/rules/setPath.md |
출력 경로 단일 출처 — 사용자 자료 뿌리·운영자 지정 루트·타입별 경로 패턴 |
.agents/rules/source_policy.md |
논문 소스 단일 출처 — PubMed 는 pubmed_fetch.py(E-utilities)만, 최근 5년·IF≥5 하한·IF순→최신순 상위 10, 출처 원장 필수 |
.agents/rules/triangle.md |
워커 거버넌스 — 모델 매트릭스·폴백·안전 가드·승격 기준 |
.agents/rules/llmwiki.md |
문서 구조·파일명·프론트매터·내비 동기화 |
.agents/rules/tool_mapping.md |
스킬의 추상 도구명 → 실행 환경 도구명 |
.agents/rules/karpathy-guidelines.md |
코딩 태도(단순함·수술적 변경·검증 가능한 목표) |
.agents/skill/*.md |
실행 워크플로 20개 (수집·출력·공통·검사·교정) |
출력 경로를 바꾸려면 setPath.md 만 고친다 — 대시보드 서버도 그 규칙을 따라간다.
소스 규칙은 source_policy.md 가 스킬·에이전트 문서보다 우선한다.
산출물 경로 — 저장소 밖이다
<CONTEXTBIO_USERDATA>/<사용자>/biowrit/artifact/<타입>/<폴더명>/
운영 /mnt/S1/sdata/contextbio/users/… 개발 /mnt/S1/sdata/contextbio-dev/users/…
<폴더명> = NotebookLM 노트북 이름(snake_case)
타입 = wiki · editorial_comment · editorial · review · research ·
critic · rebuttal · proposal
절대경로 하나로 통일돼 있다 — 그 폴더 자체가 이미 그 사람 것이므로 경로에 <사용자>
세그먼트를 다시 두지 않는다. 대시보드가 프롬프트에 <output_root> 로 이 값을 그대로
넘기고, 결과보기·다운로드도 같은 자리를 본다.
쓰는 자리의 우선순위는 setPath.md §1 — ① 운영자 지정 루트 → ② (로컬 모드) 사용자
커스텀 루트 → ③ 사용자 자료 뿌리 → ④ 사용자 미지정이면 레포 안 artifact/ 다.
운영자 지정 루트(BIOWRIT_PINNED_ARTIFACT_ROOTS) 는 특정 사용자의 쓰는 자리를 서버
환경변수로 고정한다("<slug>=<절대경로>,…"). 배포별 정책이라 레포가 아니라 systemd
유닛의 Environment= 에 둔다. 지금은 운영 hyun_goo_woo, dev hyungoowoo 가 c1 의
구글 드라이브 rclone 마운트 ~/GoogleDrive/03_manuscript/artifact/ 에 쓴다(2026-09-23).
c1 에만 있는 마운트이므로 다른 호스트에서는 무효다. 경로가 없으면 경고를 남기고 ③ 으로
돌아가고, 결과보기는 지정 경로·원래 자리·레거시 트리를 모두 읽는다.
2026-09-07 에 저장소 밖으로 옮겼다. 그전까지 신규 출력이 레포 안 artifact/ 로
떨어졌는데, 읽는 자리는 이미 사용자 자료 뿌리로 옮겨져 있어 읽기와 쓰기가 어긋난
상태였다. 그 사이 산출물 73개가 저장소에 추적돼 두 워크트리가 늘 더러웠고, 관계없는
커밋에 산출물이 실려 나갔다. 지금 레포 안 artifact/ 는 .gitignore 에 있고 이관 전
산출물을 결과보기에 계속 노출하는 레거시 읽기전용 트리로만 남는다.
끊는 순서가 중요하다.
git rm --cached로 추적만 끊으면 그 커밋을 다른 워크트리가 체크아웃할 때 디스크의 연구 산출물이 지워진다. 먼저 사용자 자료 뿌리로 복사해 해시로 확인하고, 그다음에 추적을 끊는다.
하나의 노트북에 속한 산출물이 모두 같은 <폴더명> 아래로 모인다. 그림은 .docx 에
박히지 않고 같은 폴더에 별도 TIFF(600 dpi 이상) 로 떨어지고 본문에는 legend 만
들어간다 — 저널 제출 형식에 맞춘 것이다.
더 오래된 루트(03_manuscript/artifact)도 읽기전용 스캔 대상으로 남아 있다.
BIOWRIT_LEGACY_ARTIFACT_ROOT → 레포 상대 → ~/GoogleDrive/… → ~/gdrive/… 순으로
실존하는 곳을 쓴다. 상대경로 계산이 실제 트리를 못 찾은 적이 있고(2026-08-08), 드라이브
마운트 이름이 ~/gdrive 에서 ~/GoogleDrive 로 바뀌어 다시 안 보인 적이 있다(2026-09-09).
사용자 폴더명은 이메일 로컬파트를 그대로 쓴다(hyun.goo.woo, 점 유지). API 의 user
slug(hyun_goo_woo, 밑줄)와 다르다 — 둘을 헷갈리면 남의 폴더를 찾는다.
대시보드 백엔드
dashboard/server.py 는 표준 라이브러리만 쓴다. 화면이 문서를 만들지는 않는다 —
harness 에 건넬 지침을 짜고(dashboard/tasks/pending/<timestamp>.json) 실행은
claude -p --dangerously-skip-permissions 를 백그라운드로 띄워 결과가 나왔는지 지켜본다.
deploy/start-backend.sh 가 dashboard/.env(시크릿)를 읽고 원격 모드
(BIOWRIT_DEPLOY_MODE=remote)·127.0.0.1 바인드로 띄운다. 외부 노출·HTTPS 종단은 Apache
몫이다 — 백엔드를 직접 열면 프록시를 우회한 평문 접근이 생긴다.
| 환경변수 | 기본값 | 무엇 |
|---|---|---|
BIOWRIT_DASH_PORT |
8765 | 포트 — dev 유닛은 8766 |
BIOWRIT_ALLOWED_ORIGINS |
(start-backend.sh 가 채운다) |
CORS 허용 오리진. dev 유닛은 dev-contextbio.web.app·localhost 만 — 실서비스 오리진은 일부러 뺐다 |
BIOWRIT_ALLOWED_USERS |
관리자만 | 실행(/api/run) 허용 계정. 운영·dev 유닛 모두 * = 이메일 인증된 모든 사용자 |
BIOWRIT_RUN_MAX_CONCURRENT |
1 | 사용자당 동시 실행 |
BIOWRIT_RUN_LIMIT_PER_HOUR |
20 | 사용자당 시간당 실행 |
BIOWRIT_DEFAULT_TOKEN_QUOTA |
50M | 새 계정 기본 토큰 한도(생성 약 2회 분량). 기존 레코드에 소급하지 않는다 |
BIOWRIT_DEFAULT_STORAGE_BYTES |
50 MB | 계정 기본 저장 한도(0 이하 = 무제한) |
BIOWRIT_PINNED_ARTIFACT_ROOTS |
(없음) | 운영자 지정 산출물 루트 (위 참조) |
BIOWRIT_PRIMARY_USER |
(start-backend.sh 가 채운다) |
레거시 평면 산출물 트리의 소유자 |
CONTEXTBIO_USERDATA |
/mnt/S1/sdata/contextbio/users |
사용자 자료 루트 — dev 유닛은 contextbio-dev/users |
원격 모드는 기동 전 검사에서 fail closed 다(_preflight_remote). Firebase 웹 apiKey·
BIOWRIT_ALLOWED_ORIGINS·BIOWRIT_PRIMARY_USER·실행 허용 계정 중 하나라도 비면 기동을
거부한다 — 인증이 꺼진 채 뜨는 사고를 막는다.
실행 허용 판정(can_run)은 이메일 인증 → 관리자는 항상 허용 → 관리 화면에서 정한 계정별
run_allowed → BIOWRIT_ALLOWED_USERS 순이다. 토큰 한도와 저장 한도는 따로 잰다 —
자원이 달라 합치면 엉뚱한 사람이 막힌다.
인증과 관리자
- 원격 모드에서는 모든
/api/*와/download가Authorization: Bearer <ID 토큰>을 요구한다. 예외는/api/config·/api/info·/api/version셋뿐이다. 이메일 미인증 계정은 403 이다. 클라이언트가 보낸user값은 무시하고 토큰에서 신원을 정한다. - 토큰 검증은 공용 모듈
contextbio_auth.py가 한다(폐기·클레임 포함, 5분 캐시). 레포의 파일은 사본이고 정본은 사이트 저장소다 — 갱신은 사이트 저장소에서python tools/sync_auth.py --write. - 전체관리자의 정본은 커스텀 클레임
admin이다.BIOWRIT_ADMIN_EMAILS(없으면js/firebase-config.js의ADMIN_EMAILS)는 아무에게도 클레임이 없는 최초 상태를 넘기는 부트스트랩일 뿐이다. - 클레임
apps가 목록이면서biowrit이 없으면 앱을 쓸 수 없다(management.py). - 앱 역할은
user·admin둘이다. 앱관리자는 사용자 목록·토큰 한도·잡 관리를 쓸 수 있지만, 역할 변경과 앱관리자의 권한 변경은 전체관리자만 한다(app_management.py).
실행 중인 작업 보기·중지
| 누가 | API | 비고 |
|---|---|---|
| 본인 | GET /api/runs · POST /api/run/stop {id} |
대시보드 "Running jobs" 구획(8초 폴링) |
| 관리자 | GET /api/management/jobs · POST /api/management/kill {id, owner} |
사이트 공용 관리 패널 jobsPanel(). 끝난 잡은 409 |
관리자 목록은 사용자 자료 뿌리 전체를 훑고 PID 가 살아 있는지 확인한다. 킬은 PID 가 사는 호스트에서만 유효하다.
유닛은 KillMode=process 다 — 백엔드를 재기동해도 claude -p 자식은 계속 돈다.
기본값(control-group)이면 재기동이 수십 분짜리 생성을 죽인다(2026-08-08 실측).
NotebookLM
NotebookLM 은 MCP 로 붙는다. 주요 변경·릴리즈·재기동 뒤에는 /api/nlm/health 로
authenticated 를 확인한다 — 서버가 헤드리스라 브라우저 로그인을 화면에서 끝낼 수 없고,
끊긴 상태로도 다른 기능은 멀쩡해 보이기 때문이다.
- 주소는 운영
https://c1.sysmed.kr/biowrit/api/nlm/health, dev 는/biowrit-dev/…. 인증 게이트 뒤라 토큰 없이 부르면 401 이 정상이다 — NLM 장애가 아니다. - 서버가 실제로 믿는 신호는 쿠키 DB 다.
~/.local/share/notebooklm-mcp/chrome_profile/**/Cookies를 읽기 전용으로 열어.google.com의SID·__Secure-1PSID·__Secure-3PSID·SSID·HSID중 하나라도 미만료면 로그인 상태다. MCPget_health는 호출마다 새 프로세스라 늘false로 나오므로 그것만 보고 판단하지 않는다. - 쿠키가 만료됐으면 관리자가
setup_auth를 한 번 수행한다.
문서 생성
python .scripts/generate_docx.py <content> <out.docx> "<title>" "<citation>" "" <doc_type>
python .scripts/generate_docx.py <content> <out.docx> "<title>" "" <prev_content> research
# └ 5번째 인자를 주면 변경내용 추적
doc_type(6번째 인자)은 반드시 명시한다. editorial_comment 만 고정 byline 과 citation
라벨을 쓰고, 나머지 타입에서는 citation 을 무시한다. proposal 은 review/research 와 같은
학술 렌더링을 물려받되 본문이 한국어다. 변경내용 추적은 5번째 인자를 준 개정 모드에서만
켜진다.
wiki 타입만 .md 로 나가고 MkDocs(Material)가 굽는다. 내비게이션 정본은 mkdocs.yml
이라 위키 파일과 어긋나지 않게 함께 고친다.
배포
./deploy/release.sh # main 에 dev 머지 → 태그 → 푸시 → 재기동 → /api/version 검증
./deploy/release.sh --dry-run # 무엇이 나가는지만
운영 워크트리(main)에서 dev 를 --no-ff 머지하고 release-YYYYMMDD-HHMM 태그를 남긴 뒤
systemctl --user restart biowrit-backend 한다. 배포 확인은 /api/version 이다 —
응답의 commit·branch·started_at 이 새 것이어야 새 코드가 뜬 것이다. 릴리즈 뒤에는
위 NotebookLM 확인까지 마친다.
dev 는 릴리즈가 없다. dev 워크트리에 커밋하고 systemctl --user restart biowrit-backend-dev
로 반영한다.
실제로 도는 유닛은 ~/.config/systemd/user/biowrit-backend(-dev).service 다. 저장소
deploy/ 의 사본은 경로가 맞지만(ExecStart 가 각 워크트리의 deploy/start-backend.sh),
운영 설치본에만 BIOWRIT_ALLOWED_USERS=* 가 더 있다(2026-09-29 확인). 사본으로
다시 설치하면 실행이 관리자에게만 열린다.
Node 는 /opt/node-v24.19.0-linux-x64/bin 을 쓴다 — 셸 기본 /usr/bin/node 는 v12 라
워커 CLI 설치·실행이 실패한다(Cannot find module 'node:path').
지켜야 할 것
- 원고 전문은 NotebookLM 에 올리지 않는다. 기밀·저작권·미발표 데이터 때문에 일부러 막은 동작이다. 근거로 쓸 문헌만 노트북에 넣는다.
- 인용 형식은 Vancouver/NLM 하나뿐이고 Word 에는 EndNote 필드로 들어간다.
- 실행은 서버에서 도구를 자동 승인한 채 돈다 — 파일을 쓰고 외부 도구를 부르며 토큰을 쓴다.
runs/로그에는 프롬프트와 원고 본문이 그대로 남는다 — 커밋하지 않는다.- 시크릿(Claude·Gemini·NCBI 키 등)은
dashboard/.env에만 둔다 — 유닛이나 레포에 적지 않는다.