Stage 1 + 2 통합 구현 — 사용자 피드백 반영:
manager / principal / lead / junior 계층 구조 추가.
부장이 복잡도를 판단해서 팀을 동적으로 꾸린다.
Design:
- .plans/design/hierarchy.md — 전체 설계 문서 (점수/tier/plan/escalation)
Prisma schema:
- SubTask (계층 트리 + 역할/모델/state/complexity)
- SubTaskEvent (JSONL 스타일 이벤트 로그)
rails (core):
- src/hierarchy/roles.ts — 4역할 기본 config + 모델 매핑
manager/principal: gpt-5.4 / glm-5.1
lead: gpt-codex-5.3 / glm-5
junior: glm-5-turbo / gpt-5
- src/hierarchy/complexity.ts — 규칙 기반 스코어러 (7 factors, 0-100)
- src/hierarchy/planner.ts — tier → DecompositionPlan (trivial/simple/moderate/complex/massive)
+ concurrency budget 강제
- src/hierarchy/store.ts — Prisma CRUD + tree builder
- src/handoff/http-transport.ts — 실제 HTTP transport (MockTransport 대체)
- src/handoff/build.ts — config + env 기반 transport 빌더
(env RAILS_TRANSPORT_MODE + RAILS_AGENT_{STAGE}_HOST 오버라이드)
- src/server/http.ts — sub-task 엔드포인트 4개 추가
POST /api/sub-tasks
PATCH /api/sub-tasks/:id
POST /api/sub-tasks/:id/events
GET /api/pipelines/:id/sub-tasks (tree view)
sister-agent (new sub-project):
- sister-agent/ — 각 LXC 에 배포될 Node.js 데몬
- src/types.ts, roles.ts, complexity.ts, planner.ts
- src/spawn.ts — executeInvocation: 복잡도 점수 → plan → spawn 트리
simulation mode (현재는 work 를 delay 로 시뮬레이트, LLM 연동은 후속)
- src/rails-client.ts — sub-task / event push
- src/server.ts — POST /invoke 엔드포인트 (port 18801)
LXC 리소스 실측 기반 기본 concurrency:
104/106/107: 8 concurrent sub-agents
105 (narang, build 중): 6
검증: tsc --noEmit ✓ | vitest 105/105 ✓ | rails build ✓ | sister-agent build ✓
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
345 lines
11 KiB
Markdown
345 lines
11 KiB
Markdown
# Design — Hierarchical Sub-Agent Team (Manager / Principal / Lead / Junior)
|
|
|
|
> **목적**: 각 stage 의 agent(부장) 가 팀을 꾸려 작업을 분산시키도록 한다.
|
|
> 평면 구조 (stage 당 1명) → 조직 구조 (부장 + 수석 + 선임 + 신입).
|
|
|
|
## 왜 필요한가
|
|
|
|
현재 rails 는 stage 당 agent 1명이 전부 처리하는 구조. 이건:
|
|
- ❌ 병렬성 낭비 — 큰 태스크도 순차 처리
|
|
- ❌ 모델 비용 비효율 — 모든 작업을 고가 모델로
|
|
- ❌ 결과 품질 저하 — 한 모델이 전략+전술+실행 전부 담당
|
|
- ❌ 실제 팀 구조와 미스매치
|
|
|
|
## 조직 구조
|
|
|
|
```
|
|
manager (부장) — 전략, 최종 승인
|
|
└── principal (수석) — 태스크 분해, 기술 리뷰
|
|
└── lead (선임) — 실행 리드, 작은 팀 조율
|
|
└── junior (신입) — 개별 태스크 실행
|
|
```
|
|
|
|
**엄격한 한 계단씩 아님** — 복잡도에 따라 manager 가 직접 lead 또는 junior 를 바로 spawn 할 수도 있다. 결정은 manager 의 "판단 코드".
|
|
|
|
## 역할 정의 (`roles.yaml`)
|
|
|
|
```yaml
|
|
hierarchy:
|
|
manager:
|
|
korean: 부장
|
|
responsibilities: [strategy, team-composition, final-approval, escalation-relay]
|
|
models:
|
|
primary: gpt-5.4
|
|
fallback: glm-5.1
|
|
can_spawn: [principal, lead, junior] # 복잡도에 따라 직접 spawn 가능
|
|
max_spawn_per_call: 4 # 한 번에 최대 4개 팀원
|
|
|
|
principal:
|
|
korean: 수석
|
|
responsibilities: [task-decomposition, technical-review, risk-assessment]
|
|
models:
|
|
primary: gpt-5.4
|
|
fallback: glm-5.1
|
|
can_spawn: [lead, junior]
|
|
max_spawn_per_call: 3
|
|
|
|
lead:
|
|
korean: 선임
|
|
responsibilities: [execution-lead, sub-team-coordination, mid-validation]
|
|
models:
|
|
primary: gpt-codex-5.3
|
|
fallback: glm-5
|
|
can_spawn: [junior]
|
|
max_spawn_per_call: 4
|
|
|
|
junior:
|
|
korean: 신입
|
|
responsibilities: [single-task-execution, unit-output]
|
|
models:
|
|
primary: glm-5-turbo
|
|
fallback: gpt-5
|
|
can_spawn: []
|
|
max_spawn_per_call: 0
|
|
|
|
# LXC 별 동시 실행 상한 — narang 은 빌드 중이라 보수적
|
|
concurrency_limits:
|
|
default: 8
|
|
overrides:
|
|
narang: 6
|
|
```
|
|
|
|
## 복잡도 판단 (Complexity Scoring)
|
|
|
|
Manager 가 태스크를 받으면 먼저 복잡도 점수(0-100) 를 계산한다. 이 점수로
|
|
팀 구성 규모가 결정된다.
|
|
|
|
### 점수 요소 (Deterministic)
|
|
|
|
| 요소 | 조건 | 점수 |
|
|
|---|---|---|
|
|
| **Scope scale** (from description + keywords) | 단일 파일 / "한 줄" | +0~5 |
|
|
| | 컴포넌트 1개 / small feature | +5~15 |
|
|
| | 여러 파일 / 멀티 모듈 | +15~30 |
|
|
| | Sprint 단위 | +30~50 |
|
|
| | 전체 프로젝트 / architecture | +50~80 |
|
|
| | From scratch / scaffold | +70~100 |
|
|
| **Multi-domain** (+5 each, cap +20) | frontend / backend / db / infra / ci / security / test 언급 | max +20 |
|
|
| **Risk keywords** (+10 each, cap +30) | migration / breaking / security / auth / data-loss | max +30 |
|
|
| **Parallelism hints** (+5 each, cap +15) | "multiple" / "동시에" / "parallel" / "bulk" | max +15 |
|
|
| **Uncertainty** | "probably" / "maybe" / "아직 모르겠" | +10 |
|
|
| **Estimated LOC** | >500 추정 | +10 |
|
|
| **Cross-agent dependency** | 다른 stage 와 명시 연관 | +10 |
|
|
|
|
### Tier → Decomposition Plan
|
|
|
|
| Score | Tier | 전략 |
|
|
|---|---|---|
|
|
| 0-15 | **trivial** | Manager 직접 처리 (spawn X) |
|
|
| 16-30 | **simple** | 1 junior |
|
|
| 31-50 | **moderate** | 1 lead + 1-2 junior |
|
|
| 51-75 | **complex** | 1 principal + 2 lead + 4 junior |
|
|
| 76-100 | **massive** | 2 principal + 각자 팀 (병렬 fanout) |
|
|
|
|
### LLM 보강 (optional)
|
|
|
|
규칙 기반 점수 + 기본 plan 을 cheap LLM 에게 주고
|
|
"이 plan 이 맞는지 / 조정 필요한지" 판단받음. 규칙 + LLM 합의가 최종 plan.
|
|
|
|
## 상향 에스컬레이션 (Upward Escalation)
|
|
|
|
하위 역할이 실패하면 **즉시 상위** 로 에스컬레이션 (재시도 아님).
|
|
|
|
```
|
|
junior 실패 (confidence < 0.5 or 명시적 escalate)
|
|
→ lead 가 해당 태스크 재수행
|
|
→ 또 실패
|
|
→ principal
|
|
→ 또 실패
|
|
→ manager
|
|
→ 또 실패
|
|
→ rails orchestrator → 사용자
|
|
```
|
|
|
|
같은 역할로 재시도는 resilience retry 가 담당 (Sprint 005).
|
|
위 상향 에스컬레이션은 **서로 다른 역할** 로 넘기는 흐름.
|
|
|
|
## 데이터 모델 (Prisma)
|
|
|
|
### SubTask
|
|
|
|
```prisma
|
|
model SubTask {
|
|
id String @id @db.VarChar(26) // ULID
|
|
pipelineId String @db.VarChar(26)
|
|
parentId String? @db.VarChar(26) // null = manager 직속
|
|
role String @db.VarChar(30) // manager|principal|lead|junior
|
|
agentName String @db.VarChar(50) // harang|narang|darang|erang
|
|
title String @db.VarChar(500)
|
|
description String @db.Text
|
|
state String @db.VarChar(30) // queued|running|done|failed|escalated
|
|
complexityScore Int?
|
|
complexityTier String? @db.VarChar(20)
|
|
model String @db.VarChar(50) // 사용 모델
|
|
resultJson String? @db.LongText
|
|
errorReason String? @db.Text
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
parent SubTask? @relation("SubTaskHierarchy", fields: [parentId], references: [id])
|
|
children SubTask[] @relation("SubTaskHierarchy")
|
|
events SubTaskEvent[]
|
|
|
|
@@index([pipelineId, parentId])
|
|
@@index([state])
|
|
@@index([agentName, state])
|
|
}
|
|
```
|
|
|
|
### SubTaskEvent
|
|
|
|
```prisma
|
|
model SubTaskEvent {
|
|
id Int @id @default(autoincrement())
|
|
subTaskId String @db.VarChar(26)
|
|
eventType String @db.VarChar(50) // spawned|started|progress|output|completed|failed|escalated
|
|
payloadJson String @db.LongText
|
|
timestamp DateTime @default(now())
|
|
|
|
subTask SubTask @relation(fields: [subTaskId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([subTaskId, timestamp])
|
|
@@index([eventType])
|
|
}
|
|
```
|
|
|
|
## Sister-Agent 데몬 (LXC 에 배포)
|
|
|
|
각 sister LXC (104/105/106/107) 에 Node.js 데몬이 돈다. 포트 **18801** (openclaw-gateway 와 분리).
|
|
|
|
### 책임
|
|
|
|
1. rails 로부터 `POST /invoke` 수신
|
|
2. 복잡도 점수 계산
|
|
3. Decomposition plan 생성
|
|
4. sub-agent spawn (OpenClaw 를 경유하거나 LLM 직접 호출)
|
|
5. sub-task 이벤트를 rails 에 실시간 push
|
|
6. 결과 집계 후 rails 에 HandoffMessage 반환
|
|
|
|
### 디렉토리 (new sub-project under rails repo)
|
|
|
|
```
|
|
hanarang-rails/
|
|
└── sister-agent/
|
|
├── package.json
|
|
├── tsconfig.json
|
|
├── roles.yaml
|
|
└── src/
|
|
├── server.ts # HTTP /invoke 엔드포인트
|
|
├── complexity/
|
|
│ ├── scorer.ts # 규칙 기반 점수 계산
|
|
│ └── planner.ts # decomposition 전략
|
|
├── hierarchy/
|
|
│ ├── roles.ts # YAML 로더
|
|
│ ├── spawn.ts # openclaw agent spawn wrapper
|
|
│ └── escalate.ts # 상향 에스컬레이션
|
|
├── reporting/
|
|
│ └── rails-client.ts # rails API 로 이벤트 push
|
|
└── index.ts
|
|
```
|
|
|
|
### 통신 프로토콜
|
|
|
|
#### 1. rails → sister-agent: `POST /invoke`
|
|
|
|
```json
|
|
{
|
|
"pipelineId": "01HW...",
|
|
"contractId": "01HW...",
|
|
"stage": "implement",
|
|
"task": {
|
|
"title": "Sprint 001 — todo app MVP",
|
|
"description": "Next.js + Nest.js 로 기본 TODO CRUD",
|
|
"workdir": "/home/narang/projects/todo-app"
|
|
},
|
|
"timeoutMs": 600000,
|
|
"railsApiUrl": "http://10.10.10.169:18800"
|
|
}
|
|
```
|
|
|
|
#### 2. sister-agent → rails: `POST /api/sub-tasks`
|
|
|
|
```json
|
|
{
|
|
"id": "01HW...",
|
|
"pipelineId": "01HW...",
|
|
"parentId": null,
|
|
"role": "manager",
|
|
"agentName": "narang",
|
|
"title": "root task",
|
|
"description": "...",
|
|
"complexityScore": 42,
|
|
"complexityTier": "moderate",
|
|
"model": "gpt-5.4"
|
|
}
|
|
```
|
|
|
|
#### 3. sister-agent → rails: `POST /api/sub-tasks/:id/events`
|
|
|
|
```json
|
|
{
|
|
"eventType": "spawned",
|
|
"payload": { "childId": "01HW..." }
|
|
}
|
|
```
|
|
|
|
#### 4. sister-agent → rails: `POST /invoke` 응답 (HandoffMessage)
|
|
|
|
```json
|
|
{
|
|
"stage": "implement",
|
|
"verdict": "IMPL_DONE",
|
|
"payload": {
|
|
"branch": "feature/sprint-001",
|
|
"commits": ["abc1234"],
|
|
"workdir": "/home/narang/projects/todo-app",
|
|
"selfTestReport": { "typecheck": "pass" }
|
|
},
|
|
"errorReason": ""
|
|
}
|
|
```
|
|
|
|
## LLM 호출 전략 (현실적)
|
|
|
|
초기 구현은 **openclaw CLI wrapping** 으로 간다:
|
|
|
|
```bash
|
|
openclaw agent \
|
|
--prompt "$(cat prompt.txt)" \
|
|
--model gpt-5.4 \
|
|
--output json
|
|
```
|
|
|
|
sister-agent 가 sub-process 로 `openclaw agent` 를 호출하고 stdout 을
|
|
structured JSON 으로 파싱한다. 이게 안 되면 OpenAI/Z.ai SDK 직접 호출로
|
|
fallback.
|
|
|
|
## 관제 대시보드 연동
|
|
|
|
대시보드는 `sub_tasks` 테이블을 트리 구조로 렌더링한다:
|
|
|
|
```
|
|
Pipeline 01KNV4...
|
|
├── 🦊 하랑 (manager) — planning [45s] [gpt-5.4]
|
|
│ ├── principal: 요구사항 분해 [done, 12s] [gpt-5.4]
|
|
│ └── lead: Sprint 분해 [done, 18s] [gpt-codex-5.3]
|
|
│ ├── junior: SPRINT-001 문서 [done, 4s] [glm-5-turbo]
|
|
│ ├── junior: SPRINT-002 문서 [done, 5s] [glm-5-turbo]
|
|
│ └── junior: SPRINT-003 문서 [done, 4s] [glm-5-turbo]
|
|
│
|
|
├── ⚙️ 나랑 (manager) — implementing [current] [gpt-5.4]
|
|
│ ├── principal: 아키텍처 검토 [done, 8s] [glm-5.1]
|
|
│ └── lead: 코딩 리드 [running] [gpt-codex-5.3]
|
|
│ ├── junior: frontend scaffold [running] [glm-5-turbo]
|
|
│ └── junior: backend scaffold [queued] [glm-5-turbo]
|
|
```
|
|
|
|
각 노드 click → 상세 모달 (prompt / output / timing / 모델 / 비용).
|
|
|
|
## 관찰 가능성 (Observability)
|
|
|
|
모든 서브태스크 전이가 `SubTaskEvent` 로 기록되고 `POST /api/stream`
|
|
(Socket.IO) 을 통해 대시보드에 실시간 푸시된다. SIEM 관점에서:
|
|
|
|
- `spawned` — 부모 노드 등록
|
|
- `started` — 실제 LLM 호출 시작
|
|
- `progress` — 중간 출력 (streaming 지원 시)
|
|
- `output` — 부분 결과
|
|
- `completed` — 성공 종료
|
|
- `failed` — 실패 종료 (재시도 대상)
|
|
- `escalated` — 상위로 에스컬레이션
|
|
|
|
## 보안
|
|
|
|
- sister-agent 가 받는 task 는 rails 에서 HMAC 서명 포함 (nonce 재사용 방지)
|
|
- sister-agent ↔ rails 통신은 내부 네트워크 (vmbr1) 한정
|
|
- 모델 API 키는 각 sister LXC 의 openclaw 설정에 이미 있음 — sister-agent 는
|
|
openclaw CLI 만 wrapping 하면 키 노출 없음
|
|
- rails DB 의 `resultJson` 에 비밀이 들어가지 않도록 sister-agent 가 masking
|
|
|
|
## 참고
|
|
|
|
- `state-machine.md` — pipeline FSM (stage 단위)
|
|
- `handoff.md` — rails ↔ sister (stage 단위 HandoffMessage)
|
|
- `retry-policy.md` — 같은 역할 재시도 정책
|
|
- `transports.md` — SisterTransport 추상화 (HttpTransport 가 여기 들어감)
|
|
|
|
## Open questions (Stage 2 에서 결정)
|
|
|
|
- [ ] Sub-task streaming output 은 SSE 로 할지 Socket.IO 로 할지
|
|
- [ ] 모델 비용 트래킹을 SubTask 에 추가할지
|
|
- [ ] Token 수 트래킹
|
|
- [ ] Escalation 시 부모 sub-task 의 retry count 합산 로직
|