자기야 발견: maxReplans=2, maxReviewRounds=3 budget 으로 forced REQUEST_CHANGES
검증을 돌렸는데, FSM 자체는 정상 작동했지만 (replanCount=1 정확히 카운트, plan→
review×3→implement loop 정확히 발동) 총 12 review attempts × LLM 호출 30-60s =
실 운영에서 escalation 까지 15-30분 걸림. 사용자가 "무한 루프" 라고 느낄 정도.
수정:
- src/orchestrator/context.ts:
maxReviewRounds: 3 → 2
maxReplans: 2 → 1
→ 총 budget = (1+1)*(1+2) = 6 review attempts (이전 12 의 절반)
→ 실 운영 wall-clock 약 3-6 분 안에 escalation 도달
- src/orchestrator/machine.ts: 동일하게 default context 갱신
- tests/machine.test.ts: 새 budget 에 맞춰 round 수 조정
- "after max review rounds" 테스트: 4 round → 3 round
- "escalates after both budgets exhausted" 테스트: 12 round → 6 round
- sister-agent/src/spawn.ts:
RAILS_FORCE_REVIEW_VERDICT 환경변수 가드 추가 (test-only).
APPROVE / REQUEST_CHANGES / ABORT 중 하나 설정하면 LLM 우회하고 즉시 verdict
반환. darang sister 에 박아서 retry FSM E2E 검증 가능.
운영 시점엔 env 미설정 → no-op, LLM 응답 정상 사용.
테스트: 113 통과 (변경 없음).
검증 흔적: pipeline 01KNWB8WYRR11PGY8DYMNQ1BZD 가 forced REQUEST_CHANGES 로
이전 budget (12 round) 의 60% 까지 진행 후 수동 abort. transitions 18 개에
replanCount=1, reviewRound=3 정확히 보존됨 — persistence/FSM 모두 정상.
자기야 요청: 안쪽 review-loop 다 써도 그냥 escalation 이 아니라, 한 단계
위에서 plan 부터 다시 짜야 함. 첫 plan 자체가 잘못된 접근일 수도 있으니까.
새 FSM:
reviewing → REQUEST_CHANGES
├─ canReviewAgain (reviewRound < maxReviewRounds) → implementing
├─ canReplan (replanCount < maxReplans) → planning
│ • incrementReplanCount, resetReviewRound
│ • lastError = "Re-planning after exhausted review rounds"
│ • 다음 plan 단계가 priorStages 로 review issues 를 보고 새 접근
└─ both exhausted → escalated
기본값: maxReplans=2 → 총 budget = (1+2)*(1+3) = 12 round (3 plans × 4 reviews
each). 그 이상 가면 사용자 개입.
추가:
- src/orchestrator/context.ts: replanCount, maxReplans 필드 (default 0, 2)
- src/orchestrator/machine.ts: canReplan guard, incrementReplanCount action,
reviewing.REQUEST_CHANGES 의 transition 분기
- tests/machine.test.ts: 기존 "escalates after max review rounds" 를 새
re-plan 동작에 맞게 수정 + 두 개 새 케이스 추가
1. maxReplans+maxReviewRounds 둘 다 소진 후 escalated
2. re-plan 후 새 plan 으로 APPROVE → done 흐름
113 테스트 모두 통과 (105 → 113).
이전 커밋 08ea92f 은 잘못된 접근이었음. OpenClaw 가 이미 Discord 게이트웨이를
띄우고 있고 자매들의 봇 identity 는 거기 하나로 통일되어야 함. rails 가
discord.js 로 별도 봇을 등록하면 두 봇이 같은 채널에 공존하는 기이한 구조가
된다.
올바른 경로는 OpenClaw skill 의 user-invocable frontmatter 로 슬래시 커맨드를
노출하는 것이고, 이건 별도 커밋으로 ~/.openclaw/skills/hanarang-rails/ 에
반영됨.
삭제:
- src/bridge/ 전체 (discord-client, discord-commands, discord-notifier, index)
- tests/discord-notifier.test.ts
- package.json 의 discord.js 의존성
- src/cli/serve.ts 의 bridge 부트스트랩
- .env.example 의 DISCORD_* 블록
보존:
- runner.ts 의 PipelineLifecycleEvent + onEvent 훅 — 유닛 테스트/대시보드
WebSocket 등에 재사용 가능
- POST /pipelines/start-async 엔드포인트 — skill 의 polling wrapper 가 이걸 씀
- runner opts.pipelineId 지원 — async start 가 의존함
- http.ts ServerOpts 의 onPipelineEvent/notifier 파라미터 — 추상화는 유지,
serve.ts 가 주입을 안 할 뿐
테스트: 118 → 111 (discord-notifier 7 개 삭제), 나머지 그대로 통과.
v0.1.4 — 옵션 3 (outbound + inbound). DISCORD_TOKEN 이 설정되지 않으면
bridge 는 no-op 이라 기존 배포는 영향 없음.
## Outbound (rails → Discord)
- runner.ts: PipelineLifecycleEvent emitter 추가
started / stage-done / stage-failed / completed / failed / escalated
- DiscordNotifier: 이벤트 → Discord 메시지 렌더링
thread mode (슬래시 커맨드 트리거) vs channel mode (CLI/HTTP 트리거)
- EscalationNotifier 인터페이스도 구현 — escalate.ts 에서 사용자에게 알림
- runPipeline opts 에 onEvent + notifier 주입
## Inbound (Discord → rails)
- /rails start project:<name> requirements:<text> — 파이프라인 기동
→ defer reply → POST /pipelines/start-async → thread 생성 → 실시간 업데이트
- /rails status <id> — 상태 조회 (ephemeral)
- /rails abort <id> — 강제 종료 (ephemeral)
## Async start 엔드포인트
- POST /pipelines/start-async: pipelineId 즉시 리턴 후 background 에서
runPipeline 실행. Discord 의 3초 ACK 타임아웃을 회피.
- runPipeline 에 opts.pipelineId 지원: async 엔드포인트가 미리 만든
row 위에 파이프라인을 그대로 얹을 수 있게.
## 부트스트랩
- rails serve 가 DISCORD_TOKEN/GUILD_ID/NOTIFY_CHANNEL_ID 세 개가 모두
있으면 DiscordBridge 를 자동 시작. 없으면 "skip" 로그 남기고 무시.
- discord.js ^14.26 의존성 추가.
## 문서/테스트
- .env.example: Discord 섹션 전면 재작성 (동작 설명 포함)
- tests/discord-notifier.test.ts (7 tests): fake DiscordClientWrapper 로
라우팅/렌더링/바인딩 해제 로직 검증
- 총 111 → 118 테스트 통과
## LLM 공급자 어댑터 (B)
- sister-agent/src/llm/ 에 LlmAdapter 인터페이스 신설. 어댑터 5종:
openclaw (기존), openai, anthropic, ollama, mock
- 선택은 LLM_PROVIDER 환경변수로. 기본값 mock.
- 모델명은 LLM_MODEL_{MANAGER,PRINCIPAL,LEAD,JUNIOR} 로 외부화.
OpenClaw 내부 네이밍이 기본값이지만 env 로 얼마든지 갈아끼움.
- openai 어댑터는 OPENAI_BASE_URL 로 OpenRouter / Azure / 로컬 llama.cpp
서버까지 커버.
## In-process 단일 프로세스 모드 (C)
- sister-agent/src/core.ts 로 executeInvocation 을 library-export
- rails 에 InProcessTransport 추가. 동적 import 로 sister-agent core 를
로드해 같은 Node 프로세스에서 함수 호출로 실행.
- DirectRailsClient 로 HTTP 루프백 없이 DB 에 직접 쓰기 — 단일
프로세스에서도 observability 동일.
- RailsConfig 에 transport: "in-process" 추가.
- buildTransports 가 RAILS_TRANSPORT 와 per-stage override 를 지원하도록
확장. 레거시 RAILS_TRANSPORT_MODE + RAILS_AGENT_*_HOST 도 그대로 호환.
## Git push 외부화 + allowlist env 화
- spawn.ts 의 ENABLE_GIT_PUSH 를 "GITEA_TOKEN 있으면 auto on" 으로 변경.
기존 Dev 토폴로지는 sister LXC 들에 이미 토큰이 있어서 행동 변화 없음.
- rails.service.ts 의 파일 프록시 allowlist 를 GIT_RAW_ALLOWED_HOSTS
env 로 외부화. 기본값은 기존 Gitea 호스트 유지.
## Docker / 배포
- Dockerfile 추가. 단일 이미지로 rails + sister-agent 둘 다 빌드.
- docker-compose.yml (기본): mariadb + rails 한 컨테이너 = in-process.
docker compose up 한 줄로 로컬 E2E 가능.
- docker-compose.full.yml: rails + 4 개 독립 sister 컨테이너 = 분산.
## 설정 샘플 + 문서
- .env.example 완전 재작성: 필수/LLM/토폴로지/Gitea/Discord 5 섹션
- rails.config.local.yaml: in-process 샘플
- rails.config.distributed.yaml: http 분산 샘플
- docs/LOCAL-SETUP.md: 30분 퀵스타트 (Docker + 네이티브 두 경로)
- README 에 "5분 퀵스타트" + 토폴로지 표 + LLM 공급자 목록 추가
## 테스트
- tests/transport-build.test.ts (6 테스트): 기본값 / in-process /
per-stage override / http 엔드포인트 누락 / env 기반 wiring / 레거시
env 호환
- 전체 테스트 105 → 111 통과
Sprint 007 전체 구현 — 마지막 스프린트. 프로젝트 완성:
CLI:
- rails doctor — 환경 헬스체크 (Node/pnpm/git/env/프로젝트 파일)
- rails scaffold [dir] — 신규 프로젝트 .plans/ 구조 생성
- rails migrate from-hanarang-harness <path> — 레거시 아카이브 스캐너
agents/scripts/workflows 분류 (portable vs deprecated)
xhigh 참조 경고 등 위험 패턴 감지
Docs (신규 3종):
- docs/migration-guide.md — 레거시 하네스 → rails 단계별 이전 가이드
- docs/operations.md — PM2, health check, 트러블슈팅, DB 유지보수
- docs/discord-setup.md — 봇 생성, DiscordPoster 구현 예시,
marker 프로토콜 완전 명세
README 대폭 업데이트:
- v0.1.0 상태 선언
- 빠른 시작 가이드
- CLI 13 서브커맨드 목록
- 문서 링크
Tests (4 신규, 105 total pass):
- 마이그레이션 스캐너 (agents/scripts/workflows 감지)
- node_modules/.git 제외
- 빈 아카이브 처리
- scaffold 디렉토리 구조 검증
검증: tsc --noEmit ✓ | vitest 105/105 ✓ | build ✓
rails doctor → 정상 출력 ✓
rails --help → 13 subcommands ✓
마감 상태:
- F1~F6 모든 실패 모드 코어에서 해결
- 7 스프린트 완료 (000: 계획, 001~006: 코어, 007: 릴리즈)
- 105 테스트, 19 문서 (.plans/) + 3 운영 문서 (docs/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>