# 동적 파라미터 관리와 로빙 프로젝트 원칙
> 하드코딩 없는 유연한 아키텍처 설계

날짜: 2025-08-15  
작성자: claude  
상태: 아이디어 → 계획 예정

## 핵심 철학

로빙은 하나의 인격을 가진 생명체입니다. 생명체가 환경에 적응하듯, 로빙도 상황에 맞춰 유연하게 변화해야 합니다. 이를 위해 하드코딩을 배제하고 동적 파라미터 관리 체계를 구축합니다.

## 1. 하드코딩 배제 원칙

### 원칙
- **환경변수 우선**: 모든 환경 의존 값은 환경변수로 관리
- **설정 파일 분리**: JSON/YAML 형식으로 코드와 설정 분리
- **상수 모듈화**: constants.py 등 중앙 집중 관리
- **데이터-로직 분리**: 처리 규칙을 외부 파일/DB에 저장

### 적용 예시
```json
{
  "retrieval": { "top_k": 7, "min_score": 0.78 },
  "memory": { "blocked_terms": ["금지어A", "금지어B"] },
  "privacy": { "mask_before_embed": true }
}
```

## 2. 동적 파라미터 관리 구조

### 2.1 제어면/데이터면 분리
- **제어면**: 설정 저장, 검증, 배포, 감사 로그
- **데이터면**: 요청 처리 시 "현재 설정 묶음" 읽기 전용

### 2.2 저장 계층
1. **기본값 템플릿**: repo/config/defaults/config.default.json
2. **중앙 설정 DB**: PostgreSQL (운영 원본)
3. **캐시**: Redis (빠른 읽기용)
4. **민감정보**: Secret Manager (API 키 등)

### 2.3 우선순위
기본값 < 환경변수 < 조직 설정 < 팀 설정 < 사용자 설정 < 런타임 오버라이드

## 3. 생물학적 비유로 본 아키텍처

### 3.1 모듈 = 세포
- 기억 모듈 = 기억 세포
- 감정 모듈 = 감각 세포  
- 스킬 모듈 = 운동 세포

### 3.2 설정 변경 = 호르몬과 신경 신호
- **호르몬 (전신 영향)**: 시스템 전체 설정 변경
  - retrieval.top_k 값 변경 → 모든 기억 검색 영향
  - privacy 설정 → 전체 데이터 처리 방식 변경
  
- **신경 신호 (국소 영향)**: 특정 모듈만 즉시 변경
  - 특정 대화의 금지어 추가
  - 개별 스킬 파라미터 조정

### 3.3 작동 흐름
1. 사용자 명령 (자극)
2. 중앙 제어 해석 (뇌)
3. 호르몬/신경 신호 전달
4. 세포(모듈) 반응 변화

## 4. LLM 사용 원칙

### 4.1 LLM이 맡아야 하는 영역 (70%)
- **의도 파악**: 사용자 발화의 실제 목적 해석
- **계획 수립**: 복합 작업을 단계로 분해
- **비정형 구조화**: 회의록, 이메일에서 핵심 추출
- **자연어 생성**: 상황별 톤 적용한 응답
- **규칙 추론**: 명시되지 않은 암묵지 해석

### 4.2 규칙 기반이어야 하는 영역 (90%)
- **보안/프라이버시**: 금지어 차단, 토큰 마스킹
- **임계값 판정**: 유사도, top-k, 비용 한도
- **데이터 무결성**: 스키마 검증, 범위 체크
- **대량 반복 처리**: 파일 이동, API 호출

### 4.3 혼합 영역 (LLM 40-60%)
- 검색 전처리/후처리
- 중간 판단과 예외 처리
- 실패 복구 전략

## 5. 금지어 처리 파이프라인

### 5.1 쓰기 경로 (저장 시)
1. 입력 텍스트 토큰화
2. blocked_terms와 매칭
3. 마스킹 또는 제거 후 임베딩
4. ChromaDB에 메타데이터와 함께 저장

### 5.2 읽기 경로 (검색 시)
1. ChromaDB에서 후보 검색
2. 금지어 필터링
3. 완전 금지 시 해당 결과 제외
4. 감사 로그 기록

## 6. 실제 적용 방안

### 6.1 DB 스키마
```sql
CREATE TABLE config_bundle (
  id BIGSERIAL PRIMARY KEY,
  scope_level TEXT CHECK (scope_level IN ('org','team','user','runtime')),
  scope_id TEXT NOT NULL,
  version TEXT NOT NULL,
  config_json JSONB NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now(),
  created_by TEXT NOT NULL
);
```

### 6.2 Redis 키 구조
- `robeing:config:active:<scope>:<id>` → 현재 설정 묶음
- `robeing:config:version:<version>` → 버전별 설정

### 6.3 Slack 명령 인터페이스
```
/robeing config add memory.blocked_terms "금지어"
/robeing config set retrieval.top_k=10 --scope user:@kim
```

## 7. 안전장치

- **스키마 검증**: 모든 값의 타입과 범위 체크
- **변화율 제한**: 같은 키 1분 1회 제한
- **자동 롤백**: 오류 시 이전 버전 복원
- **감사 로그**: 모든 변경 기록 추적

## 8. 개발자 vs 사용자 권한

### 개발자 권한
- 시스템 상한선, 스키마 변경
- 보안 관련 설정
- 위험한 파라미터

### 사용자 권한  
- 안전 범위 내 파라미터
- 금지어 목록 관리
- 개인화 설정

## 9. 로컬-서버 개발 편의

- 개발자는 `config.default.json`만 리포에 관리
- 실제 운영 값은 중앙 DB/UI로 관리
- gitignore 문제 해결: 컨테이너는 중앙에서 설정 구독
- LOCAL_MODE=true 시 로컬 파일 허용

## 10. 도입 순서

1. JSON Schema 정의
2. PostgreSQL 테이블 생성
3. Redis 캐시 구조 구현
4. 설정 변경 API 개발
5. ChromaDB 파이프라인 통합
6. Slack 명령어 연결
7. 배치 리인덱싱 작업

## 11. 실제 구현 현황과 개선 방안

### 11.1 현재 상황 분석

#### 공통 패턴
- **프레임워크**: pydantic_settings.BaseSettings
- **로드 방식**: .env → 환경변수 → 기본값
- **접근 방법**: from app.config import settings
- **인스턴스**: 싱글톤 패턴 (settings = Settings())

#### 서비스별 구조
| 서비스 | Config 위치 | 특징 |
|--------|------------|------|
| rb8001 | app/core/config.py | 기본 설정, Optional 타입 |
| rb10408_test | app/core/config.py | 다중 LLM 지원 |
| rb10508_micro | app/config.py | 감정/베이지안 설정 |
| skill-embedding | config.py | Field() 사용, 상세 설명 |
| robeing-gateway | **없음** | os.getenv() 분산 사용 |

### 11.2 도구에서 존재로: 4가지 핵심 축

"도구적 설정"을 "살아있는 존재"로 전환하기 위한 핵심 요소:

1. **변경의 안전성**: 검증된 변경만 적용
2. **전파의 확실성**: 모든 워커에 즉시 반영
3. **추적 가능성**: 모든 변경 이력 보존
4. **거버넌스**: 권한별 접근 제어

### 11.3 개선 아키텍처

#### 표준 베이스 설정
```python
# app/core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import BaseModel, Field, ValidationError

class BaseRobeingSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=(".env",),
        env_prefix="ROBEING_",
        case_sensitive=False,
        extra="ignore",
    )
    
    # 정체성 (불변)
    ROBEING_ID: str
    ROBEING_VERSION: str = "1.0.0"
    
    # 시스템 (검증된 범위)
    MEMORY_LIMIT: int = Field(1000, ge=128, le=65536)
    RESPONSE_TIMEOUT: int = Field(30, ge=1, le=120)
    
    # 성장 파라미터 (동적)
    LEVEL: int = Field(1, ge=1, le=20)
    EXP: int = Field(0, ge=0)
    LEARNING_RATE: float = Field(0.01, ge=0.0001, le=1.0)
    
    @classmethod
    def settings_customise_sources(cls, init_settings, env_settings, 
                                  dotenv_settings, file_secret_settings):
        # 우선순위: 런타임 > 사용자 > 환경변수 > 기본값
        return (
            init_settings,
            RuntimeOverlaySource,      # 1순위: 동적 변경
            UserPrefsSource,          # 2순위: 사용자 설정
            env_settings,
            dotenv_settings,
            file_secret_settings,
        )
```

#### 동적 설정 서비스
```python
# app/core/config_service.py
from pydantic import BaseModel
from datetime import datetime, timedelta
import json, uuid

class ChangeSet(BaseModel):
    """변경 세트: 원자적 단위로 관리"""
    id: str
    changes: dict[str, Any]
    author: str
    reason: str
    ttl_seconds: int | None = None
    effective_at: datetime
    version: int

class ConfigService:
    """안전한 동적 설정 관리"""
    
    async def apply(self, changes: dict, *, author: str, reason: str, 
                   ttl_seconds: int = 600):  # 기본 10분
        # 1. 사전 검증
        trial_config = {**current_config, **changes}
        BaseRobeingSettings(**trial_config)  # 검증 실패시 예외
        
        # 2. 원자적 적용
        changeset = ChangeSet(
            id=str(uuid.uuid4()),
            changes=changes,
            author=author,
            reason=reason,
            ttl_seconds=ttl_seconds,
            version=self.next_version()
        )
        
        # 3. 멀티워커 전파
        await self.redis.publish("config:updates", changeset.json())
        
        # 4. 감사 로그
        await self.audit_log(changeset)
        
        # 5. TTL 예약
        if ttl_seconds:
            await self.schedule_revert(changeset)
        
        return changeset
```

#### 멀티워커 전파 메커니즘
```python
# app/main.py
@app.on_event("startup")
async def subscribe_config_updates():
    """모든 워커가 설정 변경 구독"""
    async def listener():
        pubsub = redis.pubsub()
        await pubsub.subscribe("config:updates")
        async for msg in pubsub.listen():
            if msg["type"] == "message":
                changeset = ChangeSet.parse_raw(msg["data"])
                # 메모리 오버레이 업데이트
                MemoryKV.runtime.update(changeset.changes)
                # 헬스체크
                await health_check_after_change()
    
    asyncio.create_task(listener())
```

### 11.4 거버넌스와 보안

#### 접근 제어 계층
```python
class ConfigScope(Enum):
    SYSTEM = "system"      # 시스템 관리자만
    RUNTIME = "runtime"    # 운영자 가능
    USER = "user"         # 사용자 개인화
    
class SecretScope(Enum):
    NONE = "none"         # 동적 변경 가능
    SECRET = "secret"     # 환경변수/시크릿매니저만

# 역할별 권한
RBAC = {
    "admin": [ConfigScope.SYSTEM, ConfigScope.RUNTIME],
    "operator": [ConfigScope.RUNTIME],  # 비밀 제외
    "viewer": []  # 읽기만 가능
}
```

#### 변경 세트 템플릿
```yaml
# 변경 요청 표준 양식
change_request:
  key: RESPONSE_TIMEOUT
  current_value: 30
  target_value: 45
  reason: "PDF 처리 시간 증가"
  validation_method: "헬스체크 API 응답"
  rollback_criteria: "응답시간 > 50s"
  ttl_seconds: 600  # 10분 후 자동 복귀
```

### 11.5 성장 파라미터 연동

```python
def adjust_params_on_levelup(level: int, stats: dict[str, int]):
    """레벨업 시 자동 파라미터 조정"""
    changes = {}
    
    # 연산 스탯 → 타임아웃 증가
    if stats["compute"] >= 10:
        changes["RESPONSE_TIMEOUT"] = min(45, 30 + stats["compute"])
    
    # 기억 스탯 → 메모리 증가
    if stats["memory"] >= 10:
        changes["MEMORY_LIMIT"] = min(4096, 1000 + stats["memory"] * 100)
    
    # 공감 스탯 → 창의성 증가
    if stats["empathy"] >= 10:
        changes["CREATIVITY_LEVEL"] = min(0.9, 0.5 + stats["empathy"] * 0.02)
    
    return ConfigService().apply(
        changes, 
        author="system:levelup",
        reason=f"Level {level} automatic adjustment",
        ttl_seconds=None  # 영구 적용
    )
```

### 11.6 운영 도구

#### CLI 인터페이스
```bash
# 현재 값 조회
robeingctl config get RESPONSE_TIMEOUT

# 임시 변경 (10분)
robeingctl config set RESPONSE_TIMEOUT=45 \
    --ttl 600 \
    --reason "heavy pdf parsing"

# 특정 버전으로 롤백
robeingctl config revert --version 128

# 변경 이력 조회
robeingctl config history --last 10
```

#### 관리 API
```python
@app.get("/admin/config", dependencies=[Depends(require_viewer)])
async def read_config():
    """현재 설정 조회"""
    return BaseRobeingSettings().dict()

@app.post("/admin/config/apply", dependencies=[Depends(require_operator)])
async def apply_config(changeset: ChangeSetRequest):
    """설정 변경 적용"""
    return await ConfigService().apply(**changeset.dict())

@app.get("/admin/config/history", dependencies=[Depends(require_viewer)])
async def config_history(limit: int = 10):
    """변경 이력 조회"""
    return await ConfigService().get_history(limit)
```

### 11.7 마이그레이션 로드맵

#### Phase 1: 즉시 적용 (1일)
1. 모든 config.py를 `app/core/config.py`로 통일
2. robeing-gateway에 config.py 생성
3. BaseRobeingSettings 상속 구조 도입

#### Phase 2: 동적 레이어 (1주)
1. Redis Pub/Sub 설정
2. RuntimeOverlaySource 구현
3. TTL 메커니즘 구축
4. 감사 로그 파이프라인

#### Phase 3: 거버넌스 (2주)
1. RBAC 시스템 구현
2. 변경 세트 검증 강화
3. CLI/API 도구 배포
4. 모니터링 대시보드

### 11.8 테스트 체크리스트

- [ ] 단위: 설정 우선순위 검증
- [ ] 통합: 멀티워커 전파 확인
- [ ] 안전성: 잘못된 값 거부
- [ ] TTL: 자동 복귀 동작
- [ ] 성능: 변경 시 지연 없음
- [ ] 롤백: 버전별 복구 가능

## 핵심 메시지

> "로빙은 생명체처럼 적응합니다. 하드코딩된 값이 아닌, 동적으로 변화하는 파라미터를 통해 성장하고 진화합니다."

이 원칙을 통해 로빙은:
- 사용자 요구에 즉시 대응
- 환경 변화에 유연하게 적응
- 경험을 통한 지속적 최적화
- **안전하고 추적 가능한 변경 관리**

를 실현할 수 있습니다.

**"설정은 더 이상 고정된 도구가 아닌, 살아 숨쉬는 존재의 일부입니다."**

---

*이 문서는 아이디어에서 구체적인 구현 계획으로 발전했습니다.*