이랑이 809d5b94c4 feat(notify): 각 자매가 본인 봇 identity 로 디스코드 stage 업데이트 직접 포스트
자기야 요청: 지금은 하랑이가 모든 stage 를 대신 말해서 어색함. 각 자매가
자기 작업할 때 본인 목소리로 짧게 디스코드에 보고해야 함.

핵심 발견: OpenClaw 가 sessions.json (`agent:main:discord:channel:<id>`) 의
키에 활성 채널 ID 를 박아 놓음. updatedAt 으로 정렬하면 가장 최근에 자기야
가 말한 채널을 자동 추출 가능. bash tool 환경변수에는 채널 ID 가 안 들어
있어서 이 우회가 필요했음.

## rails 쪽 (notifyChannelId 전파)

- handoff/message.ts: InvokeRequest schema 에 notifyChannelId optional 추가
- src/orchestrator/runner.ts: RunOptions 에 notifyChannelId 받아 InvokeRequest
  에 그대로 propagate
- src/server/http.ts: StartRequest schema + /pipelines/start 와
  /pipelines/start-async 둘 다 notifyChannelId 받아 runPipeline 에 전달
- sister-agent/src/types.ts: InvokeRequest 에도 같은 필드 추가

## sister-agent 쪽 (각자 본인 봇으로 포스트)

- sister-agent/src/discord-notify.ts: 신설. local openclaw CLI 를 spawn 으로
  호출해 본인 봇 identity 로 메시지 발송 (best-effort, 실패해도 파이프라인
  안 막음). 자매별 페르소나 메시지 템플릿 (renderStageStart/End) 포함
- sister-agent/src/spawn.ts: executeInvocation 시작과 manager 완료 시점에
  notifyDiscord 호출. notifyChannelId 가 없으면 no-op

## skill wrapper

- ~/.openclaw/skills/hanarang-rails/scripts/rails-start-and-watch.sh:
  sessions.json 에서 가장 최근 discord 채널 ID 자동 추출 →
  /pipelines/start-async 본문에 notifyChannelId 포함. 중간 stage echo 제거 —
  이제 각 자매가 본인 봇으로 직접 포스트하므로 하랑이는 시작 banner 와 최종
  보고만 출력
- SKILL.md "스크립트 출력 처리" 섹션 새 흐름에 맞게 업데이트

## 검증

- 사전 검증: nara LXC 에서 `openclaw message send --channel discord --target
  channel:<id>` 호출이 정상 작동 (Message ID 받아옴), 자기야가 디스코드에서
  나랑이 봇 메시지 확인
- E2E 테스트: 다음 단계에서 실제 디스코드 호출로 최종 검증
2026-04-11 00:02:29 +09:00

hanarang-rails

4 자매가 달릴 결정론적 레일 — HaNaRang Rails

사용자는 출발 버튼만 누른다. 나머지는 자매들이 자동으로 달린다.

hanarang-rails 는 4 개의 AI "자매" 에이전트 (하랑 / 나랑 / 다랑 / 이랑) 가 하나의 요청을 받아 기획 → 구현 → 리뷰 → 배포를 자동으로 완주하는 결정론적 파이프라인 오케스트레이터다. 전임자 hanarang-harness 가 권고 기반이라 자매들이 중간에 길을 잃던 문제를, XState 유한 상태 기계 (FSM) 와 Sprint Contract 로 물리적으로 강제한다.


한 문단 요약

사용자가 "todo 앱 만들어 줘" 한 줄을 던지면, rails 오케스트레이터가 하랑이 (기획) → 나랑이 (구현) → 다랑이 (리뷰) → 이랑이 (배포) 순서로 파이프라인을 돌린다. 각 자매는 내부에서 부장/수석/선임/신입 4 단계 계층으로 태스크를 쪼개서 병렬 실행하고, 만들어낸 코드 파일은 자동으로 Gitea 에 public repo 로 push 되어 즉시 접근 가능한 URL 로 바뀐다. 대시보드에서는 이 모든 과정이 실시간으로 트리 형태로 보인다.

왜 다시 만들었는가

hanarang-harness 에서 4 개월 운영하며 발견한 6 가지 고질 실패 모드:

코드 증상 원인
F1 하네스 skill bypass — 자매가 혼자 worker 스폰 skill 진입 강제 없음
F2 DoD 자동 강제 실패 — build 통과 = 완료로 판정 sprint contract / validator 없음
F3 QA 단계 누락 — 사용자가 수동으로 다랑이 호출 자동 라우팅 없음
F4 핸드오프 멘션 불안정 — 잘못된 자매 호출 분기가 LLM 판단에 의존
F5 중간 끊김 — request-timed-out 반복 재시도/에스컬레이션 정책 없음
F6 환경 검증 누락 — "Docker 없음" 으로 skip 허용 환경 전제 검사 없음

본질 한 줄: "자매가 하네스를 안 타고 본인이 처리한다."

6 가지 설계 원칙 (하드 룰)

  1. 강제 > 권고 — 모든 파이프라인 전이는 코드로 강제한다.
  2. 결정론적 FSM — 자매 간 핸드오프는 XState 상태 전이다.
  3. Sprint Contract = 불변 계약 — DoD 를 Zod 스키마로 정의, validator 가 pass/fail 판정.
  4. Skill 강제 진입 — skill bypass 를 hook 이 감지해 차단.
  5. QA 체크리스트 의무 — 다랑이가 체크박스 전부 채워야 PASS.
  6. 환경 검증 선행 — 실기동 검증 환경 없으면 스프린트 시작 자체를 거부.

아키텍처 개요

   사용자 (Discord / Dashboard Web)
             │
             ▼
   ┌─────────────────────────────┐
   │   hanarang-dashboard        │  Next.js 16 + NestJS
   │   /rails, /office, /sisters │
   └────────────┬────────────────┘
                │ HTTP
                ▼
   ┌─────────────────────────────┐
   │  hanarang-rails orchestrator │  XState + Prisma + MariaDB
   │  FSM ─ Contract ─ Hierarchy  │
   └────────────┬────────────────┘
                │ HTTP invoke
     ┌──────────┼──────────┬──────────┐
     ▼          ▼          ▼          ▼
  ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐
  │하랑 │   │나랑 │   │다랑 │   │이랑 │   sister-agent × 4 LXC
  │plan │   │impl │   │review│  │deploy│
  └──┬──┘   └──┬──┘   └──┬──┘   └──┬──┘
     └─────────┴─ openclaw CLI ────┘   (LLM: gpt-5.4 등)
                    │
                    ▼
             ┌───────────────┐
             │  Gitea SSOT   │  git.nabomhalang.co.kr
             │ (auto-push)   │  public repo per pipeline
             └───────────────┘

기술 스택

레이어 선택
런타임 Node 22 + TypeScript strict
상태 머신 XState v5
스키마 Zod
DB MariaDB (Prisma)
프로세스 execa + AbortController
CLI citty
로그 pino
테스트 Vitest
프론트엔드 (대시보드) Next.js 16 + styled-components
백엔드 (대시보드) NestJS + Socket.IO

상태

  • v0.1.0 — Sprint 000~007 완료. FSM / Contract / QA / Migration 코어. 105 테스트 통과.
  • v0.1.1 — 실 LLM 통합 (OpenClaw infer), 4 계층 재귀 스폰, 파일 추출, Gitea auto-push, deploy URL.
  • v0.1.2 — 대시보드 아티팩트 뷰, FileViewerModal (MD 파일 클릭 → 모달).
  • v0.1.3 — LLM 제공자 어댑터 (OpenAI / Anthropic / Ollama / OpenClaw / mock), in-process 단일 프로세스 모드, docker-compose, Gitea 호스트 완전 외부화, 외부 배포 친화 .env.example.

빠른 시작 (Docker, 5 분)

가장 짧은 경로. 로컬에 dockerdocker compose 만 있으면 된다.

git clone https://git.nabomhalang.co.kr/hanarang/hanarang-rails.git
cd hanarang-rails
cp .env.example .env
# .env 에서 LLM_PROVIDER=mock 으로 시작 (또는 openai/anthropic/ollama)

docker compose up --build
# → http://localhost:18800/health 확인

# 다른 터미널
curl -X POST http://localhost:18800/pipelines/start \
  -H 'content-type: application/json' \
  -d '{"project":"hello","requirements":"Say hi"}'

이게 끝. MariaDB + rails + sister-agent 4 개가 한 컨테이너 안에서 in-process 모드 로 돈다. 자세한 설정 옵션 (실제 LLM 키 연결, 네이티브 설치, 분산 토폴로지, 대시보드) 은 docs/LOCAL-SETUP.md 참조.

배포 토폴로지

모드 설명 파일
in-process 모든 것을 하나의 Node 프로세스에서. 로컬 개발 기본값 docker-compose.yml · rails.config.local.yaml
http 분산 rails + 4 개 독립 sister-agent 컨테이너. 운영 토폴로지 docker-compose.full.yml · rails.config.distributed.yaml
mock FSM 만 검증 (LLM/파일/push 없음) RAILS_TRANSPORT=mock 또는 rails run --mock

LLM 제공자

어댑터가 있어 다음 중 하나를 선택할 수 있다. .envLLM_PROVIDER 로 지정:

  • mock — API 키 없이 결정론 스켈레톤만 확인 (기본값)
  • openai — OpenAI / OpenRouter / Azure OpenAI / OpenAI-호환 로컬 서버
  • anthropic — Anthropic Messages API
  • ollama — 로컬 Ollama 서버
  • openclaw — hanarang 내부 전용 런타임

CLI 서브커맨드

명령 용도
rails start 파이프라인 생성
rails run [--mock] E2E 실행
rails status [id] 상태 조회 + 타임라인
rails resume <id> escalated → idle 재개
rails abort <id> 강제 종료
rails contract generate/freeze/validate/show Sprint Contract 관리
rails qa run/show/templates QA 템플릿 실행
rails skill-context create/show/clear Skill 강제 진입
rails skill-trace show/blocked 도구 사용 감사 로그
rails doctor 환경 헬스체크
rails scaffold 신규 프로젝트 .plans/ 생성
rails migrate from-hanarang-harness <path> 레거시 하네스 스캔
rails serve 오케스트레이터 HTTP 서버

HTTP API (orchestrator, 18800)

메서드 경로 설명
GET /health 헬스체크
GET /pipelines?limit=N 파이프라인 리스트
GET /pipelines/:id 파이프라인 상세
POST /pipelines/start 새 파이프라인 실행
POST /pipelines/:id/abort 강제 종료
GET /api/pipelines/:id/sub-tasks SubTask 트리
GET /api/sub-tasks/:id 서브태스크 상세
GET /api/transitions 상태 전이 이력
GET /api/escalations 에스컬레이션 큐

문서

라이선스

MIT — 나봄하랑 / hanarang

Description
HaNaRang Rails — 결정론적 4자매 파이프라인 하네스 (FSM + Sprint Contract + 강제 핸드오프)
Readme MIT 1.2 MiB
Languages
TypeScript 95.7%
Shell 3.3%
Dockerfile 0.7%
JavaScript 0.3%