From 68a19a924c36b67f4079fbba59b76b5800629a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= <66245186+kys0213@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:39:11 +0900 Subject: [PATCH 1/2] docs(spec): add v6 spec draft incorporating issues #717-#723 Complete spec/draft/ with 18 documents forming a self-contained v6 spec set. Key additions: - Stagnation Detection with Composite Similarity (SimilarityJudge trait) - Lateral Thinking with 5 built-in personas (include_str! embedded) - Daemon internal decomposition (Advancer, Executor, HitlService) - Phase transition encapsulation (QueueItem::transit) - ItemContext source_data extension (OCP) - hitl_terminal_action type safety (EscalationAction enum) - Dependency gate DB-based (restart-safety) - Evaluator per-item judgment Co-Authored-By: Claude Opus 4.6 (1M context) --- spec/draft/DESIGN-v6.md | 392 ++++++++++++++ spec/draft/README.md | 61 +++ spec/draft/concerns/agent-runtime.md | 138 +++++ spec/draft/concerns/agent-workspace.md | 250 +++++++++ spec/draft/concerns/cli-reference.md | 204 +++++++ spec/draft/concerns/cron-engine.md | 144 +++++ spec/draft/concerns/cross-platform.md | 196 +++++++ spec/draft/concerns/daemon.md | 329 ++++++++++++ spec/draft/concerns/data-model.md | 569 ++++++++++++++++++++ spec/draft/concerns/datasource.md | 352 ++++++++++++ spec/draft/concerns/distribution.md | 127 +++++ spec/draft/concerns/queue-state-machine.md | 286 ++++++++++ spec/draft/concerns/stagnation.md | 597 +++++++++++++++++++++ spec/draft/flows/01-setup.md | 119 ++++ spec/draft/flows/02-spec-lifecycle.md | 119 ++++ spec/draft/flows/03-issue-pipeline.md | 110 ++++ spec/draft/flows/04-failure-and-hitl.md | 212 ++++++++ spec/draft/flows/05-monitoring.md | 252 +++++++++ 18 files changed, 4457 insertions(+) create mode 100644 spec/draft/DESIGN-v6.md create mode 100644 spec/draft/README.md create mode 100644 spec/draft/concerns/agent-runtime.md create mode 100644 spec/draft/concerns/agent-workspace.md create mode 100644 spec/draft/concerns/cli-reference.md create mode 100644 spec/draft/concerns/cron-engine.md create mode 100644 spec/draft/concerns/cross-platform.md create mode 100644 spec/draft/concerns/daemon.md create mode 100644 spec/draft/concerns/data-model.md create mode 100644 spec/draft/concerns/datasource.md create mode 100644 spec/draft/concerns/distribution.md create mode 100644 spec/draft/concerns/queue-state-machine.md create mode 100644 spec/draft/concerns/stagnation.md create mode 100644 spec/draft/flows/01-setup.md create mode 100644 spec/draft/flows/02-spec-lifecycle.md create mode 100644 spec/draft/flows/03-issue-pipeline.md create mode 100644 spec/draft/flows/04-failure-and-hitl.md create mode 100644 spec/draft/flows/05-monitoring.md diff --git a/spec/draft/DESIGN-v6.md b/spec/draft/DESIGN-v6.md new file mode 100644 index 0000000..e0ca279 --- /dev/null +++ b/spec/draft/DESIGN-v6.md @@ -0,0 +1,392 @@ +# DESIGN v6 + +> **Date**: 2026-04-05 +> **Status**: Draft +> **기준**: v5 운영 피드백 + Multi-LLM 분석 + 7개 이슈(#717~#723) + [Ouroboros](https://github.com/kys0213/ouroboros) resilience 적용 + +--- + +## 목표 + +Daemon을 **상태 머신 + 정의된 시점에 prompt/script를 호출하는 실행기**로 단순화한다. +DataSource가 자기 시스템의 언어로 워크플로우를 정의하되, 코어는 큐만 돌린다. +실패 시 **패턴을 감지하고 사고를 전환**하여 같은 실수를 반복하지 않는다. + +``` +Daemon이 아는 것 = 큐 상태 머신 + yaml에 정의된 prompt/script 실행 +DataSource가 정의 = 수집 조건(trigger), 처리(handlers), 결과 반영(on_done/on_fail script) +evaluate가 판단 = handler 결과가 충분한지, 사람이 봐야 하는지 (Done or HITL) +stagnation이 감지 = 실패 패턴을 분석하고, 사고를 전환하여 다르게 재시도 +``` + +--- + +## 설계 철학 + +### 1. 컨베이어 벨트 + +아이템은 한 방향으로 흐른다. 되돌아가지 않는다. 부족하면 Cron이 새 아이템을 만들어서 다시 벨트에 태운다. + +### 2. Workspace = 1 Repo + +workspace는 하나의 외부 레포와 1:1로 대응한다. v4의 `repo` 개념을 리네이밍. GitHub 외 Jira, Slack 등도 지원하기 위한 추상화 (v5~v6는 GitHub에 집중). + +### 3. DataSource가 워크플로우를 소유 + +각 DataSource는 자기 시스템의 상태 표현으로 워크플로우를 정의한다. 코어는 DataSource 내부를 모른다. 상세: [DataSource](./concerns/datasource.md) + +### 4. Daemon = Orchestrator + +수집 → 전이 → 실행 → 분류 → 반영 → 스케줄. GitHub 라벨이 뭔지 모르고 yaml대로 실행할 뿐. 내부는 Advancer·Executor·HitlService·StagnationDetector·Evaluator 모듈로 분리. 상세: [Daemon](./concerns/daemon.md) + +### 5. 코드 작업은 항상 worktree + +handler prompt는 항상 git worktree 안에서 실행. worktree 생성/정리는 인프라 레이어 담당. on_done script가 외부 시스템 반영 (gh CLI 등). + +### 6. 코어가 출구에서 분류 + +evaluate가 Completed 아이템을 per-work_id 단위로 Done or HITL로 분류. LLM이 `belt queue done/hitl` CLI를 직접 호출하여 상태 전이. + +### 7. 아이템 계보 (Lineage) + +같은 외부 엔티티에서 파생된 아이템은 `source_id`로 연결. 모든 이벤트는 append-only history로 축적. + +### 8. 환경변수 최소화 + +`WORK_ID` + `WORKTREE` 2개만 주입. 나머지는 `belt context $WORK_ID --json`으로 조회. 상세: [DataSource](./concerns/datasource.md) + +### 9. Concurrency 제어 + +workspace.concurrency (workspace yaml 루트) + daemon.max_concurrent 2단계. evaluate LLM 호출도 slot 소비. 상세: [Daemon](./concerns/daemon.md) + +### 10. Cron은 품질 루프 + +파이프라인은 1회성, 품질은 Cron이 지속 감시. gap-detection이 새 이슈 생성 → 파이프라인 재진입. 상세: [Cron 엔진](./concerns/cron-engine.md) + +### 11. Phase 전이 캡슐화 + +`QueueItem.phase` 필드를 직접 대입하지 못하도록 `pub(crate)` + `transit()` 메서드로 캡슐화한다. 모든 전이는 `can_transition_to()` 검증을 경유한다. 상세: [QueuePhase 상태 머신](./concerns/queue-state-machine.md) + +### 12. Stagnation Detection + Lateral Thinking — 실패하면 다르게 시도 + +실패 횟수만으로는 "같은 실수 반복"과 "다른 시도 실패"를 구분할 수 없다. Composite Pattern 기반 유사도 판단(SimilarityJudge)으로 SPINNING·OSCILLATION 패턴을 감지하고, 내장 페르소나(Lateral Thinking)가 접근법을 전환하여 재시도한다. 모든 retry에 lateral plan이 자동 주입되는 것이 기본 동작. 상세: [Stagnation Detection](./concerns/stagnation.md) + +### Agent는 대화형 에이전트 + +`belt agent` / `/agent` 세션. 자연어로 큐 조회, HITL 응답, cron 관리. 상세: [Agent](./concerns/agent-workspace.md) + +--- + +## 전체 상태 흐름 + +``` + DataSource.collect() + │ + ▼ +┌───────────────────────────────────────────────────────────────────┐ +│ Pending │ +│ 큐 대기. spec dependency gate 확인. │ +└───────────────────────────┬───────────────────────────────────────┘ + │ Advancer: spec dep gate (DB) + ▼ +┌───────────────────────────────────────────────────────────────────┐ +│ Ready │ +│ 실행 준비 완료. concurrency slot 대기. │ +└───────────────────────────┬───────────────────────────────────────┘ + │ Advancer: queue dep gate (DB) + │ + conflict gate + │ + concurrency (ws + global) + ▼ +┌───────────────────────────────────────────────────────────────────┐ +│ Running │ +│ │ +│ ① worktree 생성 (or retry 시 기존 재사용) │ +│ ② on_enter script │ +│ ③ handlers 순차 실행 │ +│ lateral_plan이 있으면 handler prompt에 추가 컨텍스트 주입 │ +└────────┬──────────────────────────────────────────┬───────────────┘ + │ │ + 전부 성공 handler/on_enter 실패 + │ │ + ▼ ▼ +┌─────────────────┐ ┌──────────────────────────────────────┐ +│ Completed │ │ Stagnation Analyzer (항상 실행) │ +│ │ │ │ +│ evaluate 대기 │ │ ① ExecutionHistory 구성 │ +│ (per-item) │ │ outputs = DB history.summary │ +│ │ │ errors = DB history.error │ +│ │ │ │ +│ │ │ ② StagnationDetector.detect │ +│ │ │ CompositeSimilarity: │ +│ │ │ ExactHash (w:0.5) │ +│ │ │ TokenFingerprint (w:0.3) │ +│ │ │ NCD (w:0.2) │ +│ │ │ │ +│ │ │ outputs → SPINNING? OSCILLATION? │ +│ │ │ errors → SPINNING? (별도) │ +│ │ │ drifts → NO_DRIFT? DIMINISHING?│ +│ │ │ │ +│ │ │ ③ LateralAnalyzer (패턴 감지 시) │ +│ │ │ 패턴 → 페르소나 선택 (기시도 제외)│ +│ │ │ belt agent -p → lateral_plan │ +│ │ │ │ +│ │ │ ④ Escalation (failure_count 기반) │ +│ │ │ retry → lateral_plan 주입 │ +│ │ │ retry_w_com → lateral + on_fail │ +│ │ │ hitl → report 첨부 ─────────┐ +│ │ │ terminal → skip/replan ─────────┤ +│ │ └──────────────────────────────────────┘ │ +│ │ │ +│ evaluate cron: │ retry → 새 아이템 │ +│ belt agent -p │ │ │ +│ per-item 판정 │ ┌─────────────┘ │ +│ │ │ │ +└───┬─────────┬────┘ │ │ + │ │ ▼ │ + 완료 판정 사람 필요 Pending (lateral_plan 보존) │ + │ │ │ + ▼ │ │ +┌────────┐ │ │ +│on_done │ │ │ +│ script │ │ │ +└─┬───┬──┘ │ │ + │ │ │ │ +성공 실패 │ │ + │ │ │ │ + ▼ ▼ ▼ │ +┌──────┐ ┌───────┐ ┌──────────────────────────────────────────────┐ │ +│ Done │ │Failed │ │ HITL │◄───┘ +│ │ │ │ │ │ +│ wt │ │ wt │ │ hitl_notes에 lateral_report 포함: │ +│ 정리 │ │ 보존 │ │ 패턴, 시도한 페르소나들, 각 분석 결과 │ +│ │ │ │ │ │ +│TERM. │ │ │ │ 응답: done / retry / skip / replan │ +└──────┘ └───────┘ │ timeout → terminal (skip / replan) │ + └────────────────────────┬─────────────────────┘ + │ skip + ▼ + ┌──────────────────────────────────────────────┐ + │ Skipped │ + │ terminal (worktree 정리) │ + └──────────────────────────────────────────────┘ +``` + +### 상태별 소유 모듈 + +| Phase | 소유 모듈 | 핵심 동작 | +|-------|----------|----------| +| Pending | Advancer | spec dependency gate (DB 조회) | +| Ready | Advancer | queue dep gate (DB) + concurrency check | +| Running | Executor | worktree + on_enter + handlers (lateral_plan 주입) | +| Running → 실패 | StagnationDetector + LateralAnalyzer + Executor | 유사도 분석 → 페르소나 사고 전환 → escalation | +| Completed | Evaluator | per-item LLM 판정 (belt agent -p) | +| Done | Executor | on_done script → worktree 정리 | +| HITL | HitlService | 응답 대기 / timeout / terminal action | +| Failed | — | on_done 실패, 인프라 오류 | +| Skipped | — | terminal | + +--- + +## Daemon 내부 구조 + +``` +┌─ Daemon (Orchestrator) ───────────────────────────────────────────────┐ +│ │ +│ loop { collector → advancer → executor → cron_engine.tick() } │ +│ │ +│ ┌──────────┐ ┌───────────────────────────────────────────────────┐ │ +│ │ Advancer │ │ Executor │ │ +│ │ │ │ │ │ +│ │ 전이 │ │ handler/lifecycle 실행 │ │ +│ │ dep gate │ │ │ │ │ +│ │ (DB) │ │ ▼ 실패 시 │ │ +│ │ conflict │ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ concurr. │ │ │ StagnationDetector │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ judge: Box │ │ │ +│ │ │ │ │ ┌── CompositeSimilarity ────────────────┐ │ │ │ +│ │ │ │ │ │ ExactHash (w: 0.5) │ │ │ │ +│ │ │ │ │ │ TokenFingerprint (w: 0.3) │ │ │ │ +│ │ │ │ │ │ NCD (w: 0.2) │ │ │ │ +│ │ │ │ │ └───────────────────────────────────────┘ │ │ │ +│ │ │ │ └────────────────┬────────────────────────────┘ │ │ +│ │ │ │ │ 패턴 감지 시 │ │ +│ │ │ │ ▼ │ │ +│ │ │ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ │ │ LateralAnalyzer │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ 패턴 → 페르소나 (내장, include_str!) │ │ │ +│ │ │ │ │ SPINNING → HACKER │ │ │ +│ │ │ │ │ OSCILLATION → ARCHITECT │ │ │ +│ │ │ │ │ NO_DRIFT → RESEARCHER │ │ │ +│ │ │ │ │ DIMINISHING → SIMPLIFIER │ │ │ +│ │ │ │ │ 복합 → CONTRARIAN │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ belt agent -p → lateral_plan 생성 │ │ │ +│ │ │ │ └─────────────────────────────────────────────┘ │ │ +│ │ │ │ │ │ +│ │ │ │ escalation 적용 (lateral_plan 주입) │ │ +│ └──────────┘ └───────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ +│ │ Evaluator │ │ HitlService │ │ CronEngine │ │ +│ │ │ │ │ │ │ │ +│ │ per-item │ │ 응답 처리 │ │ tick │ │ +│ │ Done/HITL │ │ timeout │ │ force_trigger│ │ +│ │ 분류 │ │ terminal │ │ 품질 루프 │ │ +│ └─────────────┘ └─────────────┘ └──────────────┘ │ +└───────────────────────────────────────────────────────────────────────┘ + │ + ┌────────────┼────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ DataSource │ │ AgentRuntime │ │ SQLite DB │ +│ │ │ │ │ │ +│ 수집 │ │ LLM 실행 │ │ queue_items │ +│ 컨텍스트 │ │ 추상화 │ │ history │ +│ source_data │ │ │ │ transition_ │ +│ │ │ │ │ events │ +└──────────────┘ └──────────────┘ └──────────────┘ +``` + +--- + +## Stagnation — Composite Similarity + +belt-core는 `SimilarityJudge` trait 하나만 의존. Composite도 Judge를 구현하므로 중첩 가능. + +``` +trait SimilarityJudge + fn score(a, b) → f64 + │ + ├── ExactHash SHA-256 동일=1.0, 다름=0.0 + ├── TokenFingerprint 정규화 후 해시 (숫자/경로/UUID 무시) + ├── NCD 압축 거리 0.0~1.0 + └── CompositeSimilarity 가중 합산 (자기도 Judge, 중첩 가능) + │ + ├── (ExactHash, 0.5) + ├── (TokenFingerprint, 0.3) + └── (NCD, 0.2) +``` + +상세: [Stagnation Detection](./concerns/stagnation.md) + +--- + +## 관심사 분리 + +| 레이어 | 책임 | 토큰 | +|--------|------|------| +| Daemon | Orchestrator — 모듈 조율 + cron 스케줄링 | 0 | +| Advancer | Pending→Ready→Running 전이, dependency gate (DB), conflict 검출 | 0 | +| Executor | handler/on_enter/on_done/on_fail 실행, escalation 적용 | handler별 | +| StagnationDetector | CompositeSimilarity로 유사도 판단, 4가지 패턴 탐지 | 0 | +| LateralAnalyzer | 내장 페르소나로 대안 접근법 분석, lateral_plan 생성 | 분석 시 | +| HitlService | HITL 응답 처리, timeout 만료, terminal action | 0 | +| Evaluator | Completed → Done/HITL 분류 (per-item, CLI 도구 호출) | 분류 시 | +| 인프라 | worktree 생성/정리, 플랫폼 추상화 (셸, IPC) | 0 | +| DataSource | 수집(collect) + 컨텍스트 조회(context, source_data) | 0 | +| AgentRuntime | LLM 실행 추상화 | handler별 | +| on_done/on_fail script | 외부 시스템에 결과 반영 | 0 | +| Agent | `belt agent` / `/agent` 대화형 에이전트 | 세션 시 | +| Cron | 주기 작업, 품질 루프 | job별 | + +--- + +## OCP 확장점 + +``` +새 외부 시스템 = DataSource impl 추가 → 코어 변경 0 +새 LLM = AgentRuntime impl 추가 → 코어 변경 0 +새 파이프라인 단계 = workspace yaml 수정 → 코어 변경 0 +새 품질 검사 = Cron 등록 → 코어 변경 0 +새 OS/플랫폼 = ShellExecutor impl 추가 → 코어 변경 0 +새 DataSource 컨텍스트 = source_data 자유 스키마 → 코어 변경 0 +새 유사도 알고리즘 = SimilarityJudge impl 추가 → 코어 변경 0 +``` + +--- + +## 상세 문서 + +| 문서 | 설명 | +|------|------| +| [QueuePhase 상태 머신](./concerns/queue-state-machine.md) | 상태 전이, 전이 캡슐화, worktree 생명주기, on_fail 조건 | +| [Daemon](./concerns/daemon.md) | 내부 모듈 구조, 실행 루프, dependency gate (DB), concurrency, graceful shutdown | +| [Stagnation Detection](./concerns/stagnation.md) | Composite Similarity, 4가지 패턴, Lateral Thinking (내장 페르소나) | +| [DataSource](./concerns/datasource.md) | trait, context 스키마 (source_data), 워크플로우 yaml, escalation | +| [AgentRuntime](./concerns/agent-runtime.md) | LLM 실행 추상화, RuntimeRegistry | +| [Agent](./concerns/agent-workspace.md) | 대화형 에이전트, per-item evaluate, slash command | +| [Cron 엔진](./concerns/cron-engine.md) | 품질 루프, per-item evaluate, force trigger | +| [CLI 레퍼런스](./concerns/cli-reference.md) | 3-layer SSOT, belt context, 전체 커맨드 | +| [Cross-Platform](./concerns/cross-platform.md) | OS 추상화 (ShellExecutor, DaemonNotifier) | +| [Data Model](./concerns/data-model.md) | SQLite 스키마, 도메인 enum, source_data, stagnation types | + +--- + +## v5 → v6 변경 요약 + +| 항목 | v5 | v6 | 이슈 | +|------|-----|-----|------| +| Daemon 내부 | 단일 daemon.rs | Orchestrator + Advancer·Executor·HitlService 모듈 분리 | #717 | +| Phase 전이 | `item.phase =` 직접 대입 | `QueueItem::transit()` 강제, phase `pub(crate)` | #718 | +| ItemContext | `issue`/`pr` 필드 직접 | `source_data: serde_json::Value` 추가 (OCP) | #719 | +| hitl_terminal_action | `Option` | `Option` (타입 안전) | #720 | +| Dependency gate | in-memory queue | DB 조회 기반 (restart-safety) | #721 | +| Evaluate | workspace 배치 | per-work_id 판정 | #722 | +| 실패 대응 | failure_count → 단순 retry | Composite Similarity 패턴 감지 + Lateral Thinking 사고 전환 | #723 | + +--- + +## v4 → v5 변경 요약 + +| 항목 | v4 | v5 | +|------|-----|-----| +| 레포 단위 | `repo` | `workspace` (1:1 매핑) | +| Daemon 역할 | 수집 + drain + Task 실행 + escalation | 상태 머신 + yaml 액션 실행기 | +| Task trait | 5개 구현체 | **제거**. prompt/script로 대체 | +| 파이프라인 단계 | `TaskKind` enum (하드코딩) | yaml states (동적 정의) | +| 부수효과 (PR, 라벨) | Task.after_invoke() | on_done script (gh CLI 등) | +| 인프라 (worktree) | Task.before_invoke() | 인프라 레이어, retry 시 보존 | +| 컨텍스트 조회 | Task 내부 | `belt context` CLI | +| 환경변수 | DataSource별 다수 | `WORK_ID` + `WORKTREE` 만 | +| QueuePhase | 5개 | 8개 (+Completed, HITL, Failed) | +| evaluate | Agent가 판단 | cron 기반 + force_trigger 하이브리드, CLI 도구 호출 | +| DataSource trait | 5개 메서드 | collect + get_context 만 | +| Concurrency | InFlightTracker | 2단계 (workspace + global) | + +--- + +## 구현 순서 + +``` +Phase 1: 코어 재구성 + → workspace 마이그레이션, DataSource trait, QueuePhase 확장 + → 상태 머신 단순화, belt context CLI + +Phase 2: handler 실행기 + → AgentRuntime trait, prompt/script 실행기, worktree 인프라 + → Task trait 제거 + +Phase 3: evaluate + escalation + → evaluate cron (CLI 도구 호출), force_trigger + → escalation 정책, on_done/on_fail, Failed 상태 + +Phase 4: Agent + slash command + → /agent, /auto, /spec 통합 + +Phase 5: TUI + 품질 루프 + → dashboard, gap-detection, spec completion + +Phase 6: 내부 품질 강화 (v6 신규) + → #720 hitl_terminal_action 타입 안전 + → #721 Dependency gate DB 기반 + → #718 Phase 전이 캡슐화 (QueueItem::transit) + → #717 Daemon 모듈 분리 (Advancer, Executor, HitlService) + → #722 Evaluator per-item 판정 + → #719 ItemContext source_data 확장 + → #723 Stagnation Detection + Lateral Thinking + SimilarityJudge trait (Composite Pattern) + CompositeSimilarity (ExactHash + TokenFingerprint + NCD) + LateralAnalyzer (내장 페르소나 5종) +``` diff --git a/spec/draft/README.md b/spec/draft/README.md new file mode 100644 index 0000000..3b43ee7 --- /dev/null +++ b/spec/draft/README.md @@ -0,0 +1,61 @@ +# Spec v6 Draft + +> **Date**: 2026-04-04 +> **Status**: Draft +> **구조**: 설계 개요 + 관심사별 상세 스펙 + 사용자 플로우 + +## 핵심 변경 (v5 → v6) + +- **Stagnation Detection**: 실패 횟수가 아니라 실패 패턴(SPINNING, OSCILLATION)을 감지하여 escalation 가속 +- **Daemon 모듈 분리**: 단일 daemon.rs → Advancer + Executor + HitlService + StagnationDetector 모듈 +- **Phase 전이 캡슐화**: `item.phase` 직접 대입 금지, `QueueItem::transit()` 메서드 강제 +- **ItemContext 확장**: `source_data: serde_json::Value` 추가 — 새 DataSource 추가 시 코어 변경 0 +- **hitl_terminal_action 타입 안전**: `Option` → `Option` +- **Dependency Gate DB 기반**: in-memory → DB 조회, 재시작 시 순서 보장 +- **Evaluator per-item 판정**: workspace 배치 → per-work_id 개별 LLM 판정 + +## 설계 문서 + +- **[DESIGN-v6.md](./DESIGN-v6.md)** — 설계 철학 + 전체 구조 개요 (간결) + +## 관심사별 상세 스펙 (concerns/) + +"이 시스템은 내부적으로 어떻게 동작하지?" — 구현자 대상 + +| 문서 | 설명 | +|------|------| +| [QueuePhase 상태 머신](./concerns/queue-state-machine.md) | 8개 phase 전이, **전이 캡슐화**, worktree 생명주기, on_fail 조건 | +| [Daemon](./concerns/daemon.md) | **내부 모듈 구조**, 실행 루프, **DB dependency gate**, concurrency, graceful shutdown | +| [Stagnation Detection](./concerns/stagnation.md) | **v6 신규** — 4가지 정체 패턴, 해시 기반 탐지, escalation 가속 | +| [DataSource](./concerns/datasource.md) | 외부 시스템 추상화 trait + **source_data** + 워크플로우 yaml | +| [AgentRuntime](./concerns/agent-runtime.md) | LLM 실행 추상화 trait + Registry | +| [Agent 워크스페이스](./concerns/agent-workspace.md) | 대화형 에이전트 + **per-item evaluate** + slash command | +| [Cron 엔진](./concerns/cron-engine.md) | 주기 실행 + **per-item evaluate** + 품질 루프 | +| [CLI 레퍼런스](./concerns/cli-reference.md) | 3-layer SSOT + `belt context` + 전체 커맨드 트리 | +| [Cross-Platform](./concerns/cross-platform.md) | OS 추상화 (ShellExecutor, DaemonNotifier) | +| [Distribution](./concerns/distribution.md) | 배포 전략 | +| [Data Model](./concerns/data-model.md) | SQLite 스키마, **StagnationPattern enum**, **EscalationAction FromStr**, **source_data** | + +## 사용자 플로우 (flows/) + +"사용자가 X를 하면 어떻게 되지?" — 시나리오 기반, 기획자/사용자 대상 + +| # | Flow | 설명 | +|---|------|------| +| 01 | [온보딩](./flows/01-setup.md) | workspace 등록 → 컨벤션 부트스트랩 | +| 02 | [스펙 생명주기](./flows/02-spec-lifecycle.md) | 스펙 등록 → 이슈 분해 → 완료 판정 | +| 03 | [이슈 파이프라인](./flows/03-issue-pipeline.md) | handlers 실행 → **stagnation detection** → evaluate → on_done | +| 04 | [실패 복구와 HITL](./flows/04-failure-and-hitl.md) | **stagnation 가속** → escalation → on_fail → 사람 개입 | +| 05 | [모니터링](./flows/05-monitoring.md) | TUI + CLI + /agent 시각화 + **stagnation 표시** | + +## 이슈 매핑 + +| 이슈 | 주요 반영 문서 | +|------|-------------| +| #723 Stagnation/Oscillation 탐지 | stagnation.md, daemon.md, data-model.md, flow-04, DESIGN | +| #717 Daemon 내부 모듈 분리 | daemon.md, DESIGN | +| #718 Phase 전이 캡슐화 | queue-state-machine.md, data-model.md, DESIGN | +| #719 ItemContext source_data | data-model.md, datasource.md | +| #720 hitl_terminal_action 타입 | data-model.md, flow-04 | +| #721 Dependency Gate DB 기반 | daemon.md | +| #722 Evaluator per-item 판정 | cron-engine.md, agent-workspace.md, daemon.md | diff --git a/spec/draft/concerns/agent-runtime.md b/spec/draft/concerns/agent-runtime.md new file mode 100644 index 0000000..424ce76 --- /dev/null +++ b/spec/draft/concerns/agent-runtime.md @@ -0,0 +1,138 @@ +# AgentRuntime — LLM 실행 추상화 + +> LLM 실행 시스템(Claude, Gemini, Codex, ...)을 추상화한다. +> 새 LLM 추가 = 새 AgentRuntime impl, 코어 변경 0 (OCP). + +--- + +## trait 정의 + +```rust +#[async_trait] +pub trait AgentRuntime: Send + Sync { + fn name(&self) -> &str; + async fn invoke(&self, request: RuntimeRequest) -> RuntimeResponse; + fn capabilities(&self) -> RuntimeCapabilities; +} + +pub struct RuntimeRequest { + pub working_dir: PathBuf, + pub prompt: String, + pub model: Option, + pub system_prompt: Option, + pub structured_output: Option, + pub session_id: Option, +} + +pub struct RuntimeResponse { + pub exit_code: i32, + pub stdout: String, + pub stderr: String, + pub duration: Duration, + pub token_usage: Option, + pub session_id: Option, +} +``` + +handler의 prompt 타입 액션이 실행될 때 AgentRuntime.invoke()를 경유한다. `working_dir`에는 worktree 경로가 설정된다. + +### Token usage 기록 + +RuntimeResponse의 `token_usage`는 Daemon이 `token_usage` 테이블에 자동 저장한다 (work_id, workspace, runtime, model, input/output tokens, duration). `belt status`와 TUI Dashboard의 Runtime 패널에서 집계하여 표시한다. + +--- + +## 의존성 방향 + +``` +core/runtime.rs (trait + DTO) + ↑ impl +infra/runtimes/ + ├── claude.rs + ├── gemini.rs + ├── codex.rs + └── custom.rs + +core → infra 방향 의존 없음. +``` + +--- + +## 모델 결정 우선순위 + +``` +1. RuntimeRequest.model ← 호출 시 명시 (최우선) +2. handler의 runtime/model ← DataSource state config +3. workspace yaml의 runtime 기본값 +4. 런타임 내장 기본 모델 +``` + +--- + +## core 옵션 → CLI 매핑 + +| core 옵션 | Claude | Gemini | Codex | +|-----------|--------|--------|-------| +| `model` | `--model ` | `-m ` | `-m ` | +| `system_prompt` | `--append-system-prompt` | prompt prepend | prompt prepend | +| `structured_output` | `--output-format json` + `--json-schema` | `--output-format json` | `--output-schema ` + `--json` | +| `working_dir` | `current_dir()` | `current_dir()` | `--cd ` | +| `session_id` | `--resume ` | `--resume ` | `codex exec resume ` | + +--- + +## RuntimeRegistry + +```rust +pub struct RuntimeRegistry { + runtimes: HashMap>, + default_name: String, +} + +impl RuntimeRegistry { + pub fn resolve(&self, name: &str) -> Arc { + self.runtimes.get(name) + .unwrap_or(&self.runtimes[&self.default_name]) + .clone() + } +} +``` + +어떤 런타임을 사용할지는 workspace yaml의 handler에서 지정한다. + +--- + +## 설정 + +```yaml +runtime: + default: claude + claude: + model: sonnet + gemini: + model: pro +``` + +--- + +## Handler에서의 사용 + +workspace yaml의 handler가 prompt 타입이면 AgentRuntime.invoke()를 경유: + +```yaml +states: + analyze: + handlers: + - prompt: "이슈를 분석해줘" + runtime: claude # 이 handler는 Claude 사용 + model: haiku # haiku 모델로 + - prompt: "PR을 리뷰해줘" + runtime: gemini # 이 handler는 Gemini 사용 +``` + +--- + +### 관련 문서 + +- [DESIGN-v5](../DESIGN-v5.md) — 전체 아키텍처 +- [DataSource](./datasource.md) — handler에서 AgentRuntime 사용 diff --git a/spec/draft/concerns/agent-workspace.md b/spec/draft/concerns/agent-workspace.md new file mode 100644 index 0000000..1382104 --- /dev/null +++ b/spec/draft/concerns/agent-workspace.md @@ -0,0 +1,250 @@ +# Agent — 대화형 에이전트 + +> `belt agent` = 대화형 + 비대화형 LLM 세션. 자연어로 시스템을 조회하고 조작하는 통합 인터페이스. +> +> 분류(Done or HITL)는 **코어 evaluate cron**이 담당한다. Agent는 분류기가 아니다. + +--- + +## 코어 evaluate (참고) + +분류 로직은 코어에 속한다. Agent와 무관. + +**v6 (#722)**: evaluate는 **per-work_id 단위**로 LLM 판정을 실행한다. + +``` +handler 전부 성공 → Completed + │ + ▼ +evaluate cron (force_trigger로 즉시 실행 가능): + for item in queue.get(Completed): + belt agent -p "아이템 {work_id}의 완료 여부를 판단해줘. + belt context {work_id} --json 으로 컨텍스트를 확인하고, + belt queue done {work_id} 또는 belt queue hitl {work_id} 를 실행해줘" + │ + │ LLM이 해당 아이템의 belt context로 컨텍스트 조회 후 CLI 도구로 결정: + │ + ├── belt queue done $WORK_ID + │ → Daemon이 on_done script 실행 + │ ├── script 성공 → Done (worktree 정리) + │ └── script 실패 → Failed (worktree 보존, 로그 기록) + │ + └── belt queue hitl $WORK_ID --reason "..." + → HITL 이벤트 생성 → 사람 대기 (worktree 보존) +``` + +- evaluate cron: `interval 60s + force_trigger on Completed 전이` +- LLM이 JSON을 파싱하는 게 아니라, 직접 `belt queue done/hitl` CLI를 호출하여 상태를 전이한다 +- 개별 판정 실패 시 해당 아이템만 Completed에 머물고, 다른 아이템 판정에 영향 없다 +- `batch_size`로 한 tick에서 처리할 최대 아이템 수를 제한한다 + +--- + +## CLI 통합 설계 + +`belt agent`는 서브커맨드 유무에 따라 동작이 결정된다. + +### 사용법 + +```bash +# 대화형 세션 (서브커맨드 없이 실행) +belt agent # 글로벌 rules 로드, 대화형 세션 시작 +belt agent --workspace workspace.yaml # workspace 지정 대화형 세션 + +# 비대화형 실행 (evaluate cron이 호출) +belt agent -p "프롬프트" # 글로벌 rules로 비대화형 실행 +belt agent --workspace workspace.yaml -p "프롬프트" # workspace 지정 비대화형 실행 + +# 실행 계획 +belt agent --plan # 실행 계획만 출력 +belt agent --workspace workspace.yaml --plan # workspace 지정 실행 계획 + +# 워크스페이스 관리 +belt agent init [--force] # agent 워크스페이스 초기화 +belt agent rules # 규칙 조회 +belt agent edit [rule] # 규칙 편집 + +# 플러그인 +belt agent plugin [--install-dir] # /agent 슬래시 커맨드 설치 +belt agent context # 시스템 컨텍스트 수집 +``` + +### --workspace 옵션 동작 + +| --workspace | -p | 동작 | +|---|---|---| +| 없음 | 없음 | 글로벌 rules → 대화형 세션 | +| 없음 | 있음 | 글로벌 rules → 비대화형 실행 | +| 있음 | 없음 | workspace rules → 대화형 세션 | +| 있음 | 있음 | workspace rules → 비대화형 실행 | + +> `--workspace`가 없으면 글로벌 agent 워크스페이스(`~/.belt/agent-workspace/`)의 rules를 로드한다. + +--- + +## 대화형 세션 (/agent) + +어디서든 실행 가능한 대화형 인터페이스. + +### 진입 경험 + +``` +belt agent 실행 → + +Step 1: 상태 수집 + belt status --json + belt hitl list --json + belt queue list --phase failed --json + +Step 2: 요약 표시 + + ● daemon running (uptime 2h 15m) + + Workspaces: + auth-project — queue: 1R 1C 2D | specs: auth-v2 60% + + ⚠ HITL 대기: 1건 + → #44 Session adapter — 3회 실패 + + ⚠ Failed: 1건 + → #39 Auth refactor — on_done script 실패 + + 무엇을 도와드릴까요? + +Step 3: 자연어 대화 + → Bash tool로 belt CLI 호출 +``` + +### 자연어 → CLI 매핑 예시 + +``` +"지금 상황 어때?" → belt status --format rich +"큐 막힌 거 있어?" → belt queue list --json → 분석 +"HITL 대기 목록" → belt hitl list --json +"실패한 거 있어?" → belt queue list --phase failed --json +"cron 일시정지" → belt cron pause gap-detection +"뭐 하면 좋을까?" → status + hitl + queue(failed) 종합 → 추천 +``` + +--- + +## 워크스페이스 구조 + +``` +~/.belt/agent-workspace/ +├── CLAUDE.md # 판단 원칙 +├── .claude/rules/ +│ ├── classify-policy.md # Done vs HITL 분류 기준 +│ ├── hitl-policy.md # HITL 판단 기준 +│ └── auto-approve-policy.md # 자동 승인 기준 +├── commands/ +└── skills/ + ├── gap-detect/ + └── prioritize/ +``` + +Per-workspace 오버라이드: `~/.belt/workspaces//agent/system/` + +--- + +## Plugin slash command 통합 + +``` +v4 (15개) → v5 (3개): + /auto — 데몬 제어 (start/stop/setup/config/dashboard/update) + /spec — 스펙 CRUD (add/update/list/status/remove/pause/resume) + /agent — 대화 세션 (조회/조작/모니터링을 자연어로, 읽기 전용 CLI 흡수) +``` + +### 실행 컨텍스트 + +| Command | 실행 위치 | 설명 | +|---------|----------|------| +| `/auto` | 어디서든 | Daemon 제어, workspace 등록 | +| `/spec` | 레포의 Claude 세션 | 해당 레포의 스펙 CRUD | +| `/agent` | 어디서든 | 대화형 에이전트 (전체 workspace 조회/조작) | + +--- + +## 실행 흐름 + +``` +1. --workspace 옵션에 따라 workspace 결정 (위 "--workspace 옵션 동작" 테이블 참조) +2. RuntimeRegistry 구성 (workspace yaml의 runtime 설정 기반, 없으면 기본 ClaudeRuntime) +3. Rules 로딩 (아래 우선순위) +4. System prompt = built-in agent rules + workspace rules +5. -p 있으면 ActionExecutor로 비대화형 실행, 없으면 대화형 세션 시작 +``` + +### Rules 로딩 우선순위 + +1. `agent_config.rules_path` — workspace yaml에서 명시적 지정 +2. `~/.belt/workspaces//agent/system/` — per-workspace 오버라이드 +3. `~/.belt/agent-workspace/.claude/rules/` — 글로벌 기본값 + +디렉토리 내 모든 `.md` 파일을 concat하여 system prompt에 주입한다. + +### classify-policy.md 로딩 경로 및 해석 (R-CW-007) + +`classify-policy.md`는 LLM 에이전트가 큐 아이템을 Done / HITL로 분류할 때 +참조하는 정책 문서다. 두 가지 형태로 존재한다: + +| 파일 | 위치 | 소비자 | 용도 | +|------|------|--------|------| +| `classify-policy.md` | `.claude/rules/` 하위 | LLM agent (system prompt) | 자연어 분류 기준 | +| `classify-policy.yaml` | workspace root | daemon evaluator | machine-readable 라우팅 규칙 | + +#### 로딩 경로 (classify-policy.md) + +`agent::resolve_rules_dir` 함수가 아래 우선순위로 **디렉토리**를 탐색한다. +첫 번째로 존재하는 디렉토리 안의 **모든 `.md` 파일**이 로드된다. + +``` +Priority 1: claw_config.rules_path (workspace YAML 명시) +Priority 2: $BELT_HOME/workspaces//claw/system/ (per-workspace) +Priority 3: $BELT_HOME/claw-workspace/.claude/rules/ (global, belt claw init) +``` + +`$BELT_HOME`은 환경변수 `BELT_HOME`이 설정되지 않으면 `~/.belt`로 기본값. + +#### 파일 미존재 시 fallback + +- 디렉토리 자체가 없는 경우: agent는 built-in Claw rules(대화 턴 제한, 응답 포맷, 에러 핸들링)만으로 실행. 에러 없음. +- 디렉토리는 있지만 `.md` 파일이 없는 경우: 동일하게 built-in rules만 사용. +- `classify-policy.md`만 없고 다른 `.md`가 있는 경우: 다른 정책 파일은 정상 로드, 분류 정책 가이던스만 빠진 채 실행. + +#### 구현 위치 + +- `crates/belt-cli/src/agent.rs` — `resolve_rules_dir`, `load_rules_from_dir` +- `crates/belt-cli/src/claw/mod.rs` — `ClawWorkspace::init`, `default_classify_policy()` + +### LLM이 사용 가능한 도구 + +`belt agent`로 실행된 LLM은 bash tool을 통해 다음 belt CLI를 호출할 수 있다: + +| CLI | 용도 | 사용 시점 | +|-----|------|----------| +| `belt context $WORK_ID --json` | 아이템 정보 조회 | evaluate 판단 입력 | +| `belt queue done $WORK_ID` | Done 판정 | evaluate 결과 | +| `belt queue hitl $WORK_ID --reason "..."` | HITL 판정 | evaluate 결과 | +| `belt status --json` | 시스템 상태 조회 | 대화형 세션 | +| `belt hitl list --json` | HITL 목록 조회 | 대화형 세션 | +| `belt queue list --json` | 큐 목록 조회 | 대화형 세션 | + +### evaluate cron과의 관계 + +evaluate cron은 내부적으로 각 Completed 아이템에 대해 `belt agent --workspace -p ""`를 호출한다. 이때: +- **per-item**: 각 아이템에 대해 개별 프롬프트 발행 (v6 #722) +- LLM이 `belt context $WORK_ID`로 해당 아이템 정보를 조회 +- 판단 후 `belt queue done/hitl` CLI를 직접 호출하여 상태 전이 +- classify-policy.md의 state별 Done 조건이 판단 기준 +- 개별 실패는 해당 아이템만 영향, `batch_size`로 tick당 처리 수 제한 + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — QueuePhase 상태 머신 + evaluate 위치 +- [CLI 레퍼런스](./cli-reference.md) — CLI 전체 커맨드 트리 +- [Cron 엔진](./cron-engine.md) — evaluate cron +- [Data Model](./data-model.md) — 컨텍스트 모델 (belt context 출력) diff --git a/spec/draft/concerns/cli-reference.md b/spec/draft/concerns/cli-reference.md new file mode 100644 index 0000000..b51eea0 --- /dev/null +++ b/spec/draft/concerns/cli-reference.md @@ -0,0 +1,204 @@ +# CLI 레퍼런스 — 3-layer 아키텍처 + 전체 커맨드 트리 + +> belt CLI는 모든 레이어의 SSOT(Single Source of Truth)이다. + +--- + +## 아키텍처 (3-layer) + +``` +Layer 1: Slash Command (3개, thin wrapper) + /auto, /spec, /agent + +Layer 2: DataSource + AgentRuntime (OCP 확장점) + → 외부 시스템 워크플로우 + LLM 실행 추상화 + +Layer 3: belt CLI (SSOT) + → DB 조작, 상태 전이, 코어 로직 + → 모든 레이어가 CLI를 호출 +``` + +--- + +## Slash Command 매핑 (v4 → v5) + +| v4 | v5 | +|----|-----| +| /auto, /auto-setup, /auto-config, /auto-dashboard, /update | /auto (서브커맨드) | +| /add-spec, /update-spec, /spec | /spec (서브커맨드) | +| /status, /board, /decisions, /hitl, /repo, /claw, /cron | /agent (자연어) | + +--- + +## belt CLI 전체 참조 + +### Phase 1: 코어 CLI (v5 초기 구현) + +상태 변경, 데몬 제어, CRUD — 직접 CLI로 노출. + +``` +belt +├── start / stop / restart +├── status [--format text|json|rich] +├── dashboard +├── workspace +│ ├── add / list / show / update / remove / config +├── spec +│ ├── add / list / show / update +│ ├── pause / resume / complete / remove +│ ├── link / unlink +│ ├── status / verify +├── queue +│ ├── list [--phase ] / show / skip +│ ├── done ← evaluate가 호출: Completed → Done (on_done 실행) +│ ├── hitl [--reason ] ← evaluate가 호출: Completed → HITL +│ ├── retry-script ← Failed 아이템의 on_done script 재실행 +│ └── dependency add / remove +├── context [--json] ← script용 정보 조회 +├── hitl +│ ├── list / show / respond / timeout +├── cron +│ ├── list / add / update +│ ├── pause / resume / remove / trigger +├── agent ← 서브커맨드 없이 실행 시 대화형 세션 시작 +│ ├── [default] # 대화형 세션 (글로벌 rules 로드) +│ ├── [-p ] # 비대화형 실행 (evaluate cron이 호출) +│ ├── [--workspace ] # 대상 workspace 지정 +│ ├── [--plan] # 실행 계획만 출력 +│ ├── [--json] # JSON 출력 +│ ├── init [--force] # agent 워크스페이스 초기화 +│ ├── rules # 규칙 조회 +│ ├── edit [rule] # 규칙 편집 +│ ├── plugin [--install-dir] # /agent 슬래시 커맨드 설치 +│ └── context # 시스템 컨텍스트 수집 (agent injection용) +├── bootstrap ← .claude/rules 컨벤션 파일 생성 +│ ├── [--workspace ] # 워크스페이스 루트 (기본: 현재 디렉토리) +│ ├── [--rules-dir ] # 커스텀 rules 디렉토리 경로 +│ ├── [--force] # 기존 파일 덮어쓰기 +│ ├── [--llm] # LLM으로 맞춤 컨벤션 생성 +│ ├── [--project-name ] # 프로젝트 이름 (--llm 전용) +│ ├── [--language ] # 주 언어 (--llm 전용, e.g., Rust, TypeScript) +│ ├── [--framework ] # 프레임워크 (--llm 전용, e.g., tokio, Next.js) +│ ├── [--description ] # 프로젝트 설명 (--llm 전용) +│ └── [--create-pr] # 생성된 컨벤션으로 PR 생성 (--llm 전용) +├── auto ← /auto 슬래시 커맨드 플러그인 관리 +│ └── plugin +│ ├── install [--project ] [--force] # /auto 슬래시 커맨드 설치 +│ ├── uninstall [--project ] # /auto 슬래시 커맨드 제거 +│ └── status [--project ] # 플러그인 설치 상태 확인 +``` + +> **v4 대비 변경**: `queue advance` 제거 (Pending→Ready 자동 전이), `context` 서브커맨드 추가, `repo` → `workspace` 리네이밍. `claw` + `agent` → `agent`로 통합. + +### Phase 2: /agent 위임 (읽기 전용) + +아래 커맨드는 `/agent` 세션에서 자연어로 접근. 별도 CLI 구현은 `/agent`가 안정화된 후 필요 시 추가. + +``` +# /agent가 내부적으로 호출하는 조회 커맨드 (구현 우선순위 낮음) +├── decisions list / show +├── board [--format text|json|rich] +├── convention +├── worktree list / clean +├── logs / usage / report +``` + +> `/agent`는 `belt status --json`, `belt queue list --json` 등 Phase 1 CLI의 JSON 출력을 파싱하여 자연어로 표시한다. Phase 2 커맨드도 동일한 패턴으로, `/agent`가 먼저 커버하고 독립 CLI는 수요가 확인되면 추가. + +모든 서브커맨드는 `--json` 또는 `--format json` 출력 지원. + +--- + +## `belt context` 상세 + +script가 아이템 정보를 조회하는 유일한 방법. + +```bash +# 기본 사용 (on_done/on_fail script 내에서) +CTX=$(belt context $WORK_ID --json) +ISSUE=$(echo $CTX | jq -r '.issue.number') +REPO=$(echo $CTX | jq -r '.source.url') + +# 특정 필드만 조회 (jq 없이) +belt context $WORK_ID --field issue.number # → 42 +belt context $WORK_ID --field source.url # → https://github.com/org/repo +``` + +context 스키마는 DataSource별로 다르다. 상세는 [DataSource](./datasource.md) 참조. + +--- + +## `belt bootstrap` 상세 + +워크스페이스에 `.claude/rules` 컨벤션 파일을 생성한다. 정적 템플릿 또는 LLM 기반 맞춤 생성을 지원. + +```bash +# 기본 사용 (정적 템플릿) +belt bootstrap + +# 특정 디렉토리에 생성 +belt bootstrap --workspace /path/to/project + +# 기존 파일 덮어쓰기 +belt bootstrap --force + +# LLM으로 맞춤 컨벤션 생성 +belt bootstrap --llm \ + --project-name my-app \ + --language Rust \ + --framework tokio \ + --description "비동기 웹 서버" + +# LLM 생성 후 PR까지 자동 생성 +belt bootstrap --llm --create-pr +``` + +| 플래그 | 기본값 | 설명 | +|--------|--------|------| +| `--workspace` | 현재 디렉토리 | 워크스페이스 루트 경로 | +| `--rules-dir` | `/.claude/rules` | 커스텀 rules 디렉토리 | +| `--force` | false | 기존 파일 덮어쓰기 | +| `--llm` | false | LLM 기반 맞춤 생성 | +| `--project-name` | — | 프로젝트 이름 (`--llm` 필요) | +| `--language` | — | 주 프로그래밍 언어 (`--llm` 필요) | +| `--framework` | — | 프레임워크/런타임 (`--llm` 필요) | +| `--description` | — | 프로젝트 설명 (`--llm` 필요) | +| `--create-pr` | false | 컨벤션 PR 생성 (`--llm` 필요) | + +--- + +## `belt auto` 상세 + +`/auto` 슬래시 커맨드 플러그인을 프로젝트의 `.claude/commands/`에 설치, 제거, 상태 확인한다. + +```bash +# 플러그인 설치 +belt auto plugin install +belt auto plugin install --project /path/to/project +belt auto plugin install --force # 기존 파일 덮어쓰기 + +# 플러그인 제거 +belt auto plugin uninstall + +# 설치 상태 확인 +belt auto plugin status +``` + +| 서브커맨드 | 설명 | +|-----------|------| +| `plugin install` | `/auto` 슬래시 커맨드 파일을 `.claude/commands/`에 설치 | +| `plugin uninstall` | 설치된 `/auto` 슬래시 커맨드 파일 제거 | +| `plugin status` | 플러그인 설치 여부 확인 | + +| 플래그 | 적용 대상 | 기본값 | 설명 | +|--------|----------|--------|------| +| `--project` | install, uninstall, status | 현재 디렉토리 | 프로젝트 루트 경로 | +| `--force` | install | false | 기존 파일 덮어쓰기 | + +--- + +### 관련 문서 + +- [DESIGN-v5](../DESIGN-v5.md) — 전체 아키텍처 +- [DataSource](./datasource.md) — context 스키마 +- [Agent](./agent-workspace.md) — /agent 세션 diff --git a/spec/draft/concerns/cron-engine.md b/spec/draft/concerns/cron-engine.md new file mode 100644 index 0000000..281ba14 --- /dev/null +++ b/spec/draft/concerns/cron-engine.md @@ -0,0 +1,144 @@ +# Cron 엔진 — 주기 실행 + 품질 루프 + +> 주기적으로 실행되는 작업을 관리한다. +> 파이프라인은 1회성, 품질은 Cron이 지속 감시하여 새 아이템을 생성. + +--- + +## 두 가지 역할 + +``` +1. 인프라 유지 — hitl-timeout, log-cleanup, daily-report (결정적) +2. 품질 루프 — evaluate, gap-detection, knowledge-extract (LLM 사용) +``` + +--- + +## 품질 루프 + +파이프라인이 아이템을 처리한 후, Cron이 지속적으로 결과물을 검증한다. + +``` +Pipeline: issue → analyze → implement → review → Done + │ +Cron: gap-detection ─── 스펙 vs 코드 비교 ──────────┘ + │ + ▼ + gap 발견 → 중복 검사 → 새 이슈 생성 → DataSource.collect() → 파이프라인 재진입 +``` + +부족하면 되돌아가는 게 아니라 **새 아이템이 생긴다**. + +### Dedupe 가드 + +gap-detection이 이슈를 생성하기 전, DataSource의 현재 open 아이템 목록을 조회하여 **동일 gap에 대한 아이템이 이미 존재하면 skip**한다. + +``` +gap 발견 → DataSource에서 open 아이템 조회 (Pending/Ready/Running) + → 동일 gap에 해당하는 아이템 존재 → skip (이미 처리 중) + → 해당 아이템 없음 → 새 이슈 생성 +``` + +이를 통해 동일 문제에 대한 이슈 무한 증식을 방지한다. + +--- + +## 기본 Cron Jobs + +### 인프라 (Global, 결정적, 토큰 0) + +| Job | 주기 | 동작 | +|-----|------|------| +| hitl-timeout | 5분 | 미응답 HITL 만료 처리 | +| daily-report | 매일 06시 | 일간 리포트 | +| log-cleanup | 매일 00시 | 오래된 로그/worktree 삭제 | + +### 품질 루프 (Per-workspace, LLM 사용) + +| Job | 주기 | 동작 | +|-----|------|------| +| evaluate | 60초 | 완료 아이템 분류 (**per-item**, Done or HITL) — `belt agent -p` | +| gap-detection | 1시간 | 스펙-코드 대조, gap 발견 시 이슈 생성 | +| knowledge-extract | 1시간 | merged PR 지식 추출 | + +### 사용자 정의 (예시) + +| Job | 주기 | 동작 | +|-----|------|------| +| qa-test | 30분 | 테스트 실행, 실패 시 이슈 생성 | +| security-scan | 2시간 | 보안 취약점 스캔 | + +--- + +## Force Trigger (하이브리드 실행 모델) + +evaluate는 **cron 주기 폴링 + force_trigger 즉시 실행**의 하이브리드 모델로 동작한다: + +``` +1. 주기 폴링: evaluate cron이 60초마다 Completed 아이템을 스캔 +2. 즉시 실행: handler 성공 → Completed 전이 → force_trigger("evaluate") + → last_run_at = NULL → 다음 tick(10초 이내)에서 즉시 실행 +``` + +- **force_trigger**는 동기적으로 evaluate를 실행하지 않는다. cron의 `last_run_at`을 리셋하여 다음 tick에서 우선 실행되도록 스케줄링한다. +- evaluate LLM 호출도 concurrency slot을 소비한다 (`daemon.max_concurrent`에 `active_evaluate_count`가 포함됨). +- 주기 폴링은 force_trigger가 누락된 경우(예: 프로세스 재시작)의 안전망 역할을 한다. + +> handler 실패 시에는 force_trigger 없이 즉시 escalation 정책이 적용된다 (evaluate 불필요). + +--- + +## 스크립트 구조 (v6 per-item evaluate, #722) + +evaluate cron은 **per-work_id 단위**로 각 Completed 아이템에 대해 개별 LLM 판정을 실행한다. + +```bash +#!/bin/bash +# Guard: Completed 상태 아이템 있을 때만 +ITEMS=$(belt queue list --workspace "$WORKSPACE" --phase completed --json) +COUNT=$(echo "$ITEMS" | jq 'length') +if [ "$COUNT" = "0" ]; then exit 0; fi + +# Per-item evaluate: 각 아이템에 대해 개별 판정 +echo "$ITEMS" | jq -r '.[].work_id' | while read WORK_ID; do + belt agent --workspace "$WORKSPACE" -p \ + "아이템 $WORK_ID 의 완료 여부를 판단해줘. + belt context $WORK_ID --json 으로 컨텍스트를 확인하고, + belt queue done $WORK_ID 또는 belt queue hitl $WORK_ID 를 실행해줘" +done +``` + +> **v5 대비 변경**: 이전에는 workspace 단위로 단일 프롬프트를 발행했다. v6에서는 각 아이템에 대해 context를 포함한 개별 프롬프트를 발행하여 세밀한 판정이 가능하다. `batch_size`로 한 tick에서 처리할 최대 아이템 수를 제한한다. + +--- + +## Daemon 주입 환경변수 (Cron 전용) + +Cron 스크립트에는 workspace 정보가 필요하므로 추가 변수를 주입한다. + +| 변수 | 예시 | +|------|------| +| `WORKSPACE` | `auth-project` | +| `BELT_HOME` | `~/.belt` | +| `BELT_DB` | `~/.belt/belt.db` | + +> **참고**: handler/on_done/on_fail script에는 `WORK_ID` + `WORKTREE`만 주입된다. Cron은 아이템 단위가 아니라 workspace 단위로 실행되므로 다른 환경변수 세트를 사용한다. + +--- + +## Built-in vs Custom + +| | Built-in | Custom | +|---|---|---| +| 생성 | workspace 등록 시 자동 | `belt cron add` | +| 제거 | 불가 (pause/resume) | 자유 | +| Guard | 내장 | 사용자 정의 | + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — evaluate 아키텍처 +- [DataSource](./datasource.md) — belt context 스키마 +- [Agent](./agent-workspace.md) — evaluate와 Agent의 관계 +- [Stagnation Detection](./stagnation.md) — handler 실패 시 패턴 감지 (cron이 아닌 동기 실행) diff --git a/spec/draft/concerns/cross-platform.md b/spec/draft/concerns/cross-platform.md new file mode 100644 index 0000000..9962b9b --- /dev/null +++ b/spec/draft/concerns/cross-platform.md @@ -0,0 +1,196 @@ +# Cross-Platform Compatibility + +Belt는 Linux, macOS, Windows에서 동작해야 한다. 릴리스 바이너리는 5개 타겟으로 빌드된다. + +## 빌드 타겟 + +| Target | OS | Arch | Archive | +|--------|-----|------|---------| +| x86_64-unknown-linux-gnu | Linux | x64 | tar.gz | +| aarch64-unknown-linux-gnu | Linux | ARM64 | tar.gz (cross) | +| x86_64-apple-darwin | macOS | x64 | tar.gz | +| aarch64-apple-darwin | macOS | ARM64 | tar.gz | +| x86_64-pc-windows-msvc | Windows | x64 | zip | + +## 현재 플랫폼 의존성 목록 + +### 1. Unix Signal (SIGUSR1) + +**영향 범위**: daemon 실행 루프, cron trigger CLI + +| 파일 | 용도 | Windows 대안 | +|------|------|-------------| +| `daemon.rs` `run_select_loop` | SIGUSR1로 cron 동기화 트리거 | 없음 (tick 폴링으로 fallback) | +| `daemon.rs` `handle_cron_trigger_signal` | SIGUSR1 수신 핸들러 | `#[cfg(unix)]`로 게이트 | +| `main.rs` `signal_daemon` | PID 파일 → `kill -USR1` 전송 | 미구현 (`anyhow::bail!`) | + +**대안 설계**: Windows에서는 named pipe, TCP localhost, 또는 파일 기반 polling으로 daemon ↔ CLI 통신 구현. + +### 2. Shell 실행 (`sh -c`, `bash -c`) + +**영향 범위**: handler script 실행, cron script, test runner, on_done/on_fail + +| 파일 | 셸 | Windows 호환 | +|------|-----|-------------| +| `executor.rs` | `bash -c` | ❌ bash 미설치 시 실패 | +| `cron.rs` (ScriptJob) | `sh -c` | ❌ sh 미존재 | +| `test_runner.rs` | `sh -c` | ❌ sh 미존재 | +| `main.rs` (cron run) | `sh -c` | ❌ sh 미존재 | + +**대안 설계**: Windows에서는 `cmd.exe /C` 또는 `powershell -Command`로 분기. 또는 workspace yaml에 `shell: bash|cmd|pwsh` 설정 추가. + +```rust +#[cfg(unix)] +fn shell_command(script: &str) -> Command { + let mut cmd = Command::new("sh"); + cmd.arg("-c").arg(script); + cmd +} + +#[cfg(windows)] +fn shell_command(script: &str) -> Command { + let mut cmd = Command::new("cmd.exe"); + cmd.arg("/C").arg(script); + cmd +} +``` + +### 3. 외부 CLI 의존성 + +| CLI | 용도 | 설치 요구 | Windows 호환 | +|-----|------|----------|-------------| +| `gh` | GitHub 이슈/PR 조회, 생성 | 필수 | ✅ (winget으로 설치 가능) | +| `claude` | LLM agent 호출 | handler prompt 실행 시 | ✅ (npm으로 설치 가능) | +| `git` | worktree 관리 | 필수 | ✅ | + +외부 CLI는 모두 Windows 지원됨. 문제 없음. + +### 4. 파일 경로 + +| 패턴 | 현재 | Windows 이슈 | +|------|------|-------------| +| `~/.belt/` | `dirs::home_dir().join(".belt")` | ✅ (`C:\Users\\.belt`) | +| PID 파일 | `belt_home.join("belt.pid")` | ✅ | +| Worktree 경로 | `PathBuf` 사용 | ✅ (path separator 자동 변환) | +| `/` 하드코딩 | 일부 `format!` 내 | ⚠️ 점검 필요 | + +**규칙**: 경로 조합은 반드시 `PathBuf::join()` 또는 `Path::join()`을 사용. 문자열 결합(`format!("{}/{}",...)`)으로 경로를 만들지 않는다. + +### 5. Evaluator 테스트 + +| 파일 | 이슈 | +|------|------| +| `evaluator.rs` 테스트 | `#[cfg(unix)]`로 대부분 게이트됨 — Windows 테스트 커버리지 0% | + +**대안**: 핵심 로직(prompt 생성, 결과 파싱)을 subprocess 호출과 분리하여 플랫폼 무관 테스트 가능하게 리팩터링. + +## 추상화 설계 (OCP) + +플랫폼 의존 코드를 trait으로 추상화하여, 새 OS 지원 시 기존 코드를 수정하지 않고 구현체만 추가한다. + +### trait 정의 (`belt-core`) + +```rust +// core/src/platform.rs + +/// 플랫폼별 셸 스크립트 실행을 추상화한다. +#[async_trait] +pub trait ShellExecutor: Send + Sync { + /// 셸 스크립트를 실행하고 결과를 반환한다. + async fn execute( + &self, + script: &str, + working_dir: &Path, + env_vars: &HashMap, + ) -> Result; +} + +pub struct ShellOutput { + pub exit_code: i32, + pub stdout: String, + pub stderr: String, +} + +/// 플랫폼별 프로세스 간 통신을 추상화한다. +/// Daemon ↔ CLI 간 cron trigger 등에 사용. +#[async_trait] +pub trait DaemonNotifier: Send + Sync { + /// Daemon에게 cron 동기화를 요청한다. + async fn notify(&self, pid: u32) -> Result<()>; +} +``` + +### 구현체 (`belt-infra`) + +``` +infra/src/platform/ + mod.rs // pub use + platform_default() 팩토리 + unix_shell.rs // sh -c 기반 ShellExecutor + unix_signal.rs // SIGUSR1 기반 DaemonNotifier + windows_shell.rs // cmd.exe /C 기반 ShellExecutor (향후) + windows_ipc.rs // named pipe 기반 DaemonNotifier (향후) +``` + +```rust +// infra/src/platform/mod.rs + +mod unix_shell; +mod unix_signal; + +/// 현재 플랫폼에 맞는 ShellExecutor를 반환한다. +pub fn default_shell() -> Box { + #[cfg(unix)] + { Box::new(unix_shell::UnixShell) } + #[cfg(windows)] + { Box::new(windows_shell::WindowsShell) } +} + +/// 현재 플랫폼에 맞는 DaemonNotifier를 반환한다. +pub fn default_notifier() -> Box { + #[cfg(unix)] + { Box::new(unix_signal::UnixSignalNotifier) } + #[cfg(windows)] + { Box::new(windows_ipc::WindowsIpcNotifier) } +} +``` + +### 소비자 변경 + +| 현재 | 변경 후 | +|------|---------| +| `executor.rs`: `Command::new("bash").arg("-c")` | `shell.execute(script, dir, vars)` | +| `cron.rs` ScriptJob: `Command::new("sh").arg("-c")` | `shell.execute(script, dir, vars)` | +| `test_runner.rs`: `Command::new("sh").arg("-c")` | `shell.execute(cmd, dir, vars)` | +| `main.rs` cron run: `Command::new("sh").arg("-c")` | `shell.execute(script, dir, vars)` | +| `main.rs` signal_daemon: `kill(pid, SIGUSR1)` | `notifier.notify(pid)` | +| `daemon.rs` run_select_loop: `signal::unix::signal(USR1)` | 플랫폼별 `#[cfg]` 유지 (이벤트 루프는 trait 추상화 어려움) | + +### 주입 경로 + +``` +Daemon::new(config, sources, registry, worktree_mgr, shell, max_concurrent) + ^^^^^ +ActionExecutor::new(registry, shell) + ^^^^^ +``` + +`Daemon`과 `ActionExecutor`가 생성 시점에 `Box`를 받는다. +테스트에서는 `MockShell`을 주입하여 subprocess 없이 검증 가능. + +## 수용 기준 + +1. `cargo build --target ` 가 5개 타겟 모두에서 경고 없이 성공 +2. `cargo test --workspace`가 Linux, macOS, Windows CI에서 모두 통과 +3. Unix 전용 코드는 `#[cfg(unix)]`로 명시적 게이트 +4. Windows에서 미구현 기능은 `#[cfg(not(unix))]` 블록에 명확한 에러 메시지 또는 fallback +5. Shell script 실행 시 플랫폼별 셸 자동 선택 (`sh`/`cmd.exe`) +6. 파일 경로에 `/` 하드코딩 없음 — `Path::join()` 사용 강제 + +## 우선순위 + +| 순위 | 항목 | 근거 | +|------|------|------| +| P0 | CI 통과 (warnings as errors) | 모든 플랫폼에서 빌드/테스트 통과 보장 | +| P1 | Shell 실행 분기 | handler script가 핵심 기능 | +| P2 | Daemon IPC (signal 대안) | Windows에서 cron trigger 지원 | +| P3 | Evaluator 테스트 크로스 플랫폼 | 테스트 커버리지 확보 | diff --git a/spec/draft/concerns/daemon.md b/spec/draft/concerns/daemon.md new file mode 100644 index 0000000..2fff0b7 --- /dev/null +++ b/spec/draft/concerns/daemon.md @@ -0,0 +1,329 @@ +# Daemon — Orchestrator + +> Daemon은 yaml에 정의된 prompt/script를 호출하는 단순 실행기. +> GitHub 라벨, PR 생성 같은 도메인 로직을 모른다. +> 내부는 Advancer·Executor·HitlService 모듈로 분리. 실패 시 StagnationDetector + LateralAnalyzer가 사고를 전환하여 재시도한다. + +--- + +## 역할 + +``` +1. 수집: DataSource.collect() → Pending에 넣기 +2. 전이: Pending → Ready → Running (자동, concurrency 제한) +3. 실행: yaml에 정의된 prompt/script 호출 +4. 완료: handler 성공 → Completed 전이 +5. 분류: evaluate cron이 Completed → Done or HITL 판정 (per-item, CLI 도구 호출) +6. 반영: on_done/on_fail script 실행 +7. 스케줄: Cron engine으로 주기 작업 실행 +``` + +--- + +## 내부 모듈 구조 (#717) + +Daemon은 실행 루프와 모듈 조율만 담당하는 Orchestrator이다. + +``` +Daemon (Orchestrator) + loop { + collector.collect() + advancer.advance() + executor.execute() + cron_engine.tick() + } +``` + +| 모듈 | 책임 | 소유하는 상태 | +|------|------|-------------| +| **Advancer** | Pending→Ready→Running 전이, dependency gate (DB), conflict 검출 | queue, ConcurrencyTracker | +| **Executor** | handler/lifecycle 실행, 실패 시 stagnation 분석 + lateral plan + escalation | ActionExecutor, StagnationDetector, LateralAnalyzer | +| **Evaluator** | Completed → Done/HITL 분류 (per-item, 이미 분리됨) | eval_failure_counts | +| **HitlService** | HITL 응답 처리, timeout 만료, terminal action 적용 | — (DB 직접 조회) | +| **CronEngine** | cron tick, force_trigger (이미 분리됨) | CronJob 목록 | + +### Executor 내부 구조 + +``` +Executor + │ + ├── ActionExecutor handler/script 실행 + │ + ├── StagnationDetector 실패 시 패턴 탐지 + │ └── judge: Box + │ └── CompositeSimilarity + │ ├── ExactHash (w: 0.5) + │ ├── TokenFingerprint (w: 0.3) + │ └── NCD (w: 0.2) + │ + └── LateralAnalyzer 패턴 감지 시 사고 전환 + └── personas/ (include_str! 내장) + hacker.md, architect.md, researcher.md, + simplifier.md, contrarian.md +``` + +### 모듈 간 의존 + +``` +Daemon + ├── Advancer (queue, db, dependency_guard) + ├── Executor (action_executor, stagnation_detector, lateral_analyzer) + ├── Evaluator (workspace_config) + ├── HitlService (db) + └── CronEngine (db) +``` + +- 모듈 간 의존은 trait 또는 함수 파라미터로만 전달 (순환 참조 금지) +- 각 모듈은 독립적으로 단위 테스트 가능 +- StagnationDetector는 `Box` 하나만 의존 (Composite 또는 단일) + +--- + +## Concurrency 제어 + +두 레벨로 동시 실행을 제어한다: + +```yaml +# workspace.yaml — workspace 루트 레벨에 정의 +concurrency: 2 # 이 workspace에서 동시 Running 아이템 수 + +# daemon 글로벌 설정 (별도 config) — 전체 workspace 합산 상한 +max_concurrent: 4 +``` + +- **workspace.concurrency**: workspace yaml 루트에 정의. "이 프로젝트에 동시에 몇 개까지 돌릴까". 모든 source의 아이템 합산 기준. +- **daemon.max_concurrent**: "머신 리소스 한계" (evaluate cron의 LLM 호출도 slot을 소비) + +Advancer는 `Ready → Running` 전이 시 두 제한을 모두 확인한다. + +--- + +## 실행 루프 (의사코드) + +``` +loop { + // 1. 수집 + for source in workspace.sources: + items = source.collect() + queue.push(Pending, items) + + // 2. 자동 전이 (Advancer) + advancer.advance_pending_to_ready() // spec dep gate (DB) + advancer.advance_ready_to_running(limit) // queue dep gate (DB) + concurrency + + // 3. 실행 (Executor) + for item in queue.get_new(Running): + state = lookup_state(item) + worktree = create_or_reuse_worktree(item) + + // on_enter (실패 시 handler 건너뛰고 실패 경로) + result = executor.run_actions(state.on_enter, WORK_ID=item.id, WORKTREE=worktree) + if result.failed: + executor.handle_failure(item, state) + continue + + // handlers 순차 실행 (lateral_plan 있으면 prompt에 주입) + for action in state.handlers: + result = executor.execute(action, WORK_ID=item.id, WORKTREE=worktree, + lateral_plan=item.lateral_plan) + if result.failed: + executor.handle_failure(item, state) + break + else: + item.transit(Completed) + force_trigger("evaluate") + + // 4. cron tick + cron_engine.tick() +} +``` + +### Executor.handle_failure — Stagnation + Lateral + Escalation + +``` +fn handle_failure(item, state): + // ① ExecutionHistory 구성 (outputs/errors 별도) + execution_history = ExecutionHistory { + outputs: db.recent_summaries(item.source_id, item.state, limit=N), + errors: db.recent_errors(item.source_id, item.state, limit=N), + drifts: db.recent_drift_scores(item.source_id, item.state), + } + + // ② Stagnation Detection (항상 실행) + detections = stagnation_detector.detect(execution_history) + active = detections.filter(|d| d.detected && d.confidence >= threshold) + + // ③ Lateral Plan 생성 (패턴 감지 시) + lateral_plan = None + if active.is_not_empty() && lateral_config.enabled: + tried = db.get_tried_personas(item.source_id, item.state) + persona = select_persona(active[0].pattern, tried) + if persona.is_some(): + lateral_plan = lateral_analyzer.analyze( + detection=active[0], + history=execution_history, + persona=persona, + workspace=item.workspace_id, + ) + + // ④ transition_events에 stagnation 기록 + record_stagnation_event(item, detections, lateral_plan) + + // ⑤ Escalation 적용 (failure_count 기반, 기존과 동일) + failure_count = count_failures(item.source_id, item.state) + escalation = lookup_escalation(failure_count) + + match escalation: + retry: + // lateral_plan을 새 아이템에 전달 + new_item = create_retry_item(item, lateral_plan) + // on_fail 실행 안 함, worktree 보존 + + retry_with_comment: + run_actions(state.on_fail, WORK_ID=item.id, WORKTREE=worktree) + new_item = create_retry_item(item, lateral_plan) + // worktree 보존 + + hitl: + run_actions(state.on_fail, WORK_ID=item.id, WORKTREE=worktree) + // lateral report (모든 시도 이력)를 hitl_notes에 첨부 + lateral_report = build_lateral_report(item.source_id, item.state) + create_hitl_event(item, reason, hitl_notes=lateral_report) + // worktree 보존 +``` + +### lateral_plan이 handler에 주입되는 방식 + +retry로 생성된 새 아이템이 다시 Running에 진입하면, `lateral_plan`이 handler prompt에 추가 컨텍스트로 주입된다: + +``` +원래 prompt: "이슈를 구현해줘" + +주입 후: + "이슈를 구현해줘 + + ⚠ Stagnation Analysis (attempt 2/3) + Pattern: SPINNING | Persona: HACKER + + 실패 원인: 이전 2회 시도에서 동일한 컴파일 에러 반복 + 대안 접근법: tower-sessions crate 활용 + 실행 계획: 1. Cargo.toml 수정 2. 타입 교체 3. middleware 등록 + 주의: 이전과 동일한 접근은 같은 실패를 반복합니다" +``` + +--- + +## Dependency Gate (#721) + +### Spec Dependency Gate + +`check_dependency_gate()` — Pending→Ready 전이 시 확인. 스펙 간 의존 관계 확인. + +### Queue Dependency Gate + +`check_queue_dependency_gate()` — Ready→Running 전이 시 확인. + +dependency phase 확인은 **DB 조회 기반**: + +``` +1. DB에서 dependency work_id 목록 조회 +2. 각 dependency의 phase를 DB에서 조회 +3. 판정: + - Done → gate open + - DB에 없음 → gate open (orphan 허용) + - 그 외 → gate blocked +``` + +### Conflict Gate + +`check_conflict_gate()` — entry_point 겹침 감지. DB 기반. + +--- + +## 통합 액션 타입 + +```yaml +- prompt: "..." # → AgentRuntime.invoke() (LLM, worktree 안에서) +- script: "..." # → bash 실행 (결정적, WORK_ID + WORKTREE 주입) +``` + +| 위치 | prompt | script | 설명 | +|------|:------:|:------:|------| +| `handlers` | O | O | 워크플로우 핵심 작업 | +| `on_enter` | X | O | Running 진입 시 사전 작업 | +| `on_done` | X | O | 완료 후 외부 시스템 반영 | +| `on_fail` | X | O | 실패 시 외부 시스템 알림 | + +lifecycle hook은 **script만 허용**. LLM 호출은 handler에서만. + +--- + +## 환경변수 + +| 변수 | 설명 | +|------|------| +| `WORK_ID` | 큐 아이템 식별자 | +| `WORKTREE` | worktree 경로 | + +나머지는 `belt context $WORK_ID --json`으로 조회. + +--- + +## Graceful Shutdown + +``` +SIGINT → on_shutdown: + 1. Running 아이템 완료 대기 (timeout: 30초) + → timeout 초과: Pending으로 롤백, worktree 보존 + 2. Cron engine 정지 +``` + +--- + +## 수용 기준 + +### 내부 모듈 구조 (#717) + +- [ ] Daemon 구조체는 실행 루프와 모듈 조율만 담당한다 +- [ ] phase 전이는 Advancer, handler 실행+stagnation+lateral+escalation은 Executor, HITL은 HitlService +- [ ] 각 모듈은 독립적으로 단위 테스트 가능하다 +- [ ] 모듈 간 의존은 trait 또는 함수 파라미터로만 전달 (순환 참조 금지) + +### Stagnation + Lateral 통합 (#723) + +- [ ] handler/on_enter 실패 시 StagnationDetector가 항상 실행된다 +- [ ] CompositeSimilarity로 outputs/errors를 별도 검사한다 +- [ ] 패턴 감지 시 LateralAnalyzer가 내장 페르소나로 lateral_plan을 생성한다 +- [ ] lateral_plan이 retry 시 handler prompt에 추가 컨텍스트로 주입된다 +- [ ] hitl 도달 시 모든 lateral 시도 이력이 hitl_notes에 첨부된다 +- [ ] stagnation 이벤트가 transition_events에 기록된다 + +### Dependency Gate (#721) + +- [ ] queue dependency의 phase 확인은 DB 조회 기준이다 +- [ ] 재시작 후에도 dependency gate가 정확히 동작한다 +- [ ] dependency가 Failed/Hitl이면 blocked, DB에 없으면 open + +### Concurrency + +- [ ] workspace.concurrency + daemon.max_concurrent 2단계 제한 +- [ ] evaluate LLM 호출도 concurrency slot 소비 + +### Graceful Shutdown + +- [ ] SIGINT → 30초 대기 → Pending 롤백 + worktree 보존 + +### 환경변수 + +- [ ] handler/script에 WORK_ID, WORKTREE 2개만 주입 + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — 전체 상태 흐름 + 설계 철학 +- [QueuePhase 상태 머신](./queue-state-machine.md) — 상태 전이 상세 +- [Stagnation Detection](./stagnation.md) — Composite Similarity + Lateral Thinking +- [DataSource](./datasource.md) — 워크플로우 정의 + context 스키마 +- [AgentRuntime](./agent-runtime.md) — LLM 실행 추상화 +- [Cron 엔진](./cron-engine.md) — evaluate cron + 품질 루프 diff --git a/spec/draft/concerns/data-model.md b/spec/draft/concerns/data-model.md new file mode 100644 index 0000000..9ff3ada --- /dev/null +++ b/spec/draft/concerns/data-model.md @@ -0,0 +1,569 @@ +# Data Model + +> 관련 문서: [DESIGN-v6](../DESIGN-v6.md), [QueuePhase 상태 머신](./queue-state-machine.md), [DataSource](./datasource.md), [Cron 엔진](./cron-engine.md), [Stagnation](./stagnation.md) + +Belt의 모든 상태는 SQLite 단일 파일(`~/.belt/belt.db`)에 저장된다. 이 문서는 테이블 스키마, 도메인 모델, 직렬화 규칙을 한 곳에 정의한다. + +--- + +## 테이블 개요 + +| 테이블 | 역할 | PK | +|--------|------|----| +| `queue_items` | 컨베이어 벨트 위의 작업 단위 | `work_id` | +| `history` | 작업 시도 기록 (append-only) | `id` (auto) | +| `transition_events` | phase 전이 이벤트 로그 | `id` | +| `queue_dependencies` | 아이템 간 실행 순서 제약 | `(work_id, depends_on)` | +| `specs` | 스펙 정의 및 라이프사이클 | `id` | +| `spec_links` | 스펙 ↔ 외부 리소스 연결 | `id` | +| `workspaces` | 등록된 워크스페이스 메타 | `name` | +| `cron_jobs` | 예약 작업 정의 | `name` | +| `token_usage` | LLM 토큰 사용량 추적 | `id` (auto) | +| `knowledge_base` | PR에서 추출한 지식 | `id` (auto) | + +--- + +## 테이블 스키마 + +### queue_items + +컨베이어 벨트의 작업 단위. 하나의 아이템은 하나의 워크플로우 상태(analyze, implement 등)에 대응한다. + +```sql +CREATE TABLE queue_items ( + work_id TEXT PRIMARY KEY, -- '{source_id}:{state}' + source_id TEXT NOT NULL, -- 'github:org/repo#42' + workspace_id TEXT NOT NULL, -- workspace 이름 + state TEXT NOT NULL, -- 워크플로우 상태 ('analyze', 'implement' 등) + phase TEXT NOT NULL, -- QueuePhase enum (lowercase) + title TEXT, -- 표시용 제목 + created_at TEXT NOT NULL, -- RFC3339 + updated_at TEXT NOT NULL, -- RFC3339 + + -- HITL 메타데이터 (phase = 'hitl' 일 때 유효) + hitl_created_at TEXT, -- HITL 진입 시각 + hitl_respondent TEXT, -- 응답한 사용자 + hitl_notes TEXT, -- 사용자 메모 + hitl_reason TEXT, -- HitlReason enum (snake_case) + hitl_timeout_at TEXT, -- 만료 시각 (RFC3339) + hitl_terminal_action TEXT, -- EscalationAction enum (snake_case) ← v6: Option → Option + + -- 추적 필드 + replan_count INTEGER NOT NULL DEFAULT 0, -- 재계획 횟수 (max 3) + worktree_preserved INTEGER NOT NULL DEFAULT 0 -- 1 = worktree 보존됨 +); +``` + +**v6 변경 (#720)**: `hitl_terminal_action`은 `EscalationAction` enum 값만 허용한다. DB에는 snake_case 문자열로 저장, 로드 시 `FromStr`로 파싱한다. 유효하지 않은 값은 파싱 에러. + +**인메모리 전용 필드** (DB에 저장하지 않음): +- `previous_worktree_path: Option` — retry 시 이전 아이템의 worktree 경로를 전달하기 위한 transient 필드 + +### history + +작업 시도 기록. append-only로만 쓰고, 읽기 전용으로 조회한다. `failure_count`는 이 테이블에서 계산한다. **Stagnation detection도 이 테이블의 summary/error를 입력으로 사용한다.** + +```sql +CREATE TABLE history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + work_id TEXT NOT NULL, + source_id TEXT NOT NULL, + state TEXT NOT NULL, -- 워크플로우 상태 + status TEXT NOT NULL, -- 'running' | 'done' | 'failed' | 'skipped' | 'hitl' + attempt INTEGER NOT NULL, -- 시도 번호 (1-based) + summary TEXT, -- 결과 요약 + error TEXT, -- 에러 메시지 (실패 시) + created_at TEXT NOT NULL -- RFC3339 +); +``` + +**파생 쿼리**: +- `failure_count`: `SELECT COUNT(*) FROM history WHERE source_id = ? AND state = ? AND status = 'failed'` +- `max_attempt`: `SELECT MAX(attempt) FROM history WHERE source_id = ? AND state = ?` +- **stagnation 입력**: `SELECT summary, error FROM history WHERE source_id = ? AND state = ? ORDER BY attempt DESC LIMIT ?` + +### transition_events + +phase 전이 이벤트. history와 달리 phase 변경에 초점을 맞춘 상세 로그. + +```sql +CREATE TABLE transition_events ( + id TEXT PRIMARY KEY, -- UUID + work_id TEXT NOT NULL, + source_id TEXT NOT NULL, + event_type TEXT NOT NULL, -- 'phase_enter' | 'handler' | 'evaluate' | 'on_done' | 'on_fail' | 'stagnation' + phase TEXT, -- 진입한 phase + from_phase TEXT, -- 이전 phase + detail TEXT, -- 사람이 읽을 수 있는 설명 + created_at TEXT NOT NULL -- RFC3339 +); +``` + +**v6 변경 (#723)**: `event_type`에 `'stagnation'` 추가. `detail`에 탐지 패턴, confidence, evidence, 가속 결과를 JSON으로 기록한다. + +### queue_dependencies + +아이템 간 실행 순서 제약. `depends_on` 아이템이 Done이 아니면 `work_id` 아이템은 Ready→Running 전이가 블로킹된다. + +**v6 변경 (#721)**: dependency phase 확인은 **DB 조회 기반**이다 (in-memory queue가 아님). 재시작 후에도 정확히 동작한다. + +```sql +CREATE TABLE queue_dependencies ( + work_id TEXT NOT NULL, + depends_on TEXT NOT NULL, + created_at TEXT NOT NULL, -- RFC3339 + PRIMARY KEY (work_id, depends_on) +); +``` + +### specs + +스펙 정의. 6-status 라이프사이클을 따른다. + +```sql +CREATE TABLE specs ( + id TEXT PRIMARY KEY, -- UUID + workspace_id TEXT NOT NULL, + name TEXT NOT NULL, + status TEXT NOT NULL, -- SpecStatus enum (lowercase) + content TEXT NOT NULL, -- 마크다운 본문 + priority INTEGER, -- 낮을수록 높은 우선순위 + labels TEXT, -- 쉼표 구분 레이블 + depends_on TEXT, -- 쉼표 구분 의존 spec ID + entry_point TEXT, -- 쉼표 구분 파일/모듈 경로 + decomposed_issues TEXT, -- 쉼표 구분 GitHub 이슈 번호 + test_commands TEXT, -- 쉼표 구분 검증 명령어 + created_at TEXT NOT NULL, -- RFC3339 + updated_at TEXT NOT NULL -- RFC3339 +); +``` + +### spec_links + +스펙과 외부 리소스(이슈 URL, PR 등) 간 연결. + +```sql +CREATE TABLE spec_links ( + id TEXT PRIMARY KEY, + spec_id TEXT NOT NULL, + target TEXT NOT NULL, -- URL 또는 'owner/repo#123' + created_at TEXT NOT NULL, + UNIQUE(spec_id, target) +); +``` + +### workspaces + +등록된 워크스페이스. yaml 파일 경로를 참조한다. + +```sql +CREATE TABLE workspaces ( + name TEXT PRIMARY KEY, + config_path TEXT NOT NULL, -- workspace.yaml 절대 경로 + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +``` + +### cron_jobs + +예약 작업. built-in(evaluate, hitl-timeout 등)과 사용자 정의 모두 저장. + +```sql +CREATE TABLE cron_jobs ( + name TEXT PRIMARY KEY, + schedule TEXT NOT NULL, -- cron 표현식 ('*/5 * * * *') + script TEXT NOT NULL DEFAULT '', + workspace TEXT, -- NULL = 글로벌 + enabled INTEGER NOT NULL DEFAULT 1, + last_run_at TEXT, -- RFC3339, force_trigger 시 NULL로 리셋 + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL DEFAULT '' +); +``` + +### token_usage + +LLM 호출 토큰 사용량. `belt status`와 TUI Dashboard에서 집계 표시. + +```sql +CREATE TABLE token_usage ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + work_id TEXT NOT NULL, + workspace TEXT NOT NULL, + runtime TEXT NOT NULL, -- 'claude' | 'gemini' | 'codex' + model TEXT NOT NULL, -- 'opus' | 'sonnet' | 'haiku' 등 + input_tokens INTEGER NOT NULL, + output_tokens INTEGER NOT NULL, + cache_read_tokens INTEGER, + cache_write_tokens INTEGER, + duration_ms INTEGER, + created_at TEXT NOT NULL -- RFC3339 +); +``` + +### knowledge_base + +merged PR에서 추출한 지식. knowledge-extract cron이 저장. + +```sql +CREATE TABLE knowledge_base ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + workspace TEXT NOT NULL, + source_ref TEXT NOT NULL, -- 'PR #42' + category TEXT NOT NULL, -- 'decision' | 'pattern' | 'domain' | 'review_feedback' + content TEXT NOT NULL, + created_at TEXT NOT NULL +); +``` + +--- + +## 도메인 Enum + +### QueuePhase + +8개 상태. 직렬화 시 lowercase. + +| Variant | 직렬화 | Terminal | 설명 | +|---------|--------|----------|------| +| `Pending` | `"pending"` | No | DataSource가 감지, 큐 대기 | +| `Ready` | `"ready"` | No | 실행 준비 완료 (자동 전이) | +| `Running` | `"running"` | No | worktree 생성 + handler 실행 중 | +| `Completed` | `"completed"` | No | handler 성공, evaluate 대기 | +| `Done` | `"done"` | **Yes** | evaluate 완료 + on_done 성공 | +| `Hitl` | `"hitl"` | No | 사람 판단 필요 | +| `Failed` | `"failed"` | No | on_done 실패 또는 인프라 오류 | +| `Skipped` | `"skipped"` | **Yes** | escalation skip 또는 preflight 실패 | + +**v6 (#718)**: `phase` 필드는 `pub(crate)` 가시성. 외부에서 직접 대입 불가, 반드시 `QueueItem::transit()` 경유. 상세: [QueuePhase 상태 머신](./queue-state-machine.md) + +### SpecStatus + +6개 상태. 직렬화 시 lowercase. + +| Variant | 직렬화 | 설명 | +|---------|--------|------| +| `Draft` | `"draft"` | 초기 상태 | +| `Active` | `"active"` | 활성 (이슈 생성/처리 진행) | +| `Paused` | `"paused"` | 일시 중단 | +| `Completing` | `"completing"` | 모든 이슈 Done + gap 없음, HITL 대기 | +| `Completed` | `"completed"` | 최종 완료 | +| `Archived` | `"archived"` | 소프트 삭제 | + +### HitlReason + +HITL 생성 경로. 직렬화 시 snake_case. + +| Variant | 직렬화 | 설명 | +|---------|--------|------| +| `EvaluateFailure` | `"evaluate_failure"` | evaluate 반복 실패 | +| `RetryMaxExceeded` | `"retry_max_exceeded"` | 재시도 횟수 초과 | +| `Timeout` | `"timeout"` | 실행 타임아웃 | +| `ManualEscalation` | `"manual_escalation"` | 사용자 수동 요청 | +| `SpecConflict` | `"spec_conflict"` | 스펙 파일 겹침 | +| `SpecCompletionReview` | `"spec_completion_review"` | 스펙 완료 최종 확인 | +| `SpecModificationProposed` | `"spec_modification_proposed"` | Agent 수정 제안 | +| `StagnationDetected` | `"stagnation_detected"` | **v6** 반복 패턴 감지 + lateral thinking 사고 전환 | + +### EscalationAction + +failure_count별 대응. 직렬화 시 snake_case. + +| Variant | 직렬화 | on_fail 실행 | 설명 | +|---------|--------|:------------:|------| +| `Retry` | `"retry"` | **No** | 조용한 재시도 | +| `RetryWithComment` | `"retry_with_comment"` | Yes | on_fail + 재시도 | +| `Hitl` | `"hitl"` | Yes | on_fail + HITL 생성 | +| `Skip` | `"skip"` | Yes | on_fail + Skipped | +| `Replan` | `"replan"` | Yes | on_fail + HITL(replan) | + +**v6 (#720)**: `EscalationAction`은 `FromStr` + `Display` impl을 가진다. `hitl_terminal_action` 필드 타입으로도 사용된다. + +```rust +impl FromStr for EscalationAction { + type Err = BeltError; + fn from_str(s: &str) -> Result { /* snake_case 파싱 */ } +} +``` + +### StagnationPattern (v6 신규) + +정체 패턴 유형. 직렬화 시 snake_case. + +| Variant | 직렬화 | 설명 | +|---------|--------|------| +| `Spinning` | `"spinning"` | 동일/유사 출력 반복 (A→A→A) | +| `Oscillation` | `"oscillation"` | 교대 반복 (A→B→A→B) | +| `NoDrift` | `"no_drift"` | 진행 점수 정체 | +| `DiminishingReturns` | `"diminishing_returns"` | 개선폭 감소 | + +SPINNING/OSCILLATION은 `CompositeSimilarity`로 유사도 판단, NO_DRIFT/DIMINISHING은 drift score 수치 비교. + +### Persona (v6 신규) + +Lateral Thinking 사고 전환 페르소나. belt-core에 `include_str!`로 내장. 직렬화 시 snake_case. + +| Variant | 직렬화 | 패턴 친화도 | 전략 | +|---------|--------|-----------|------| +| `Hacker` | `"hacker"` | SPINNING | 제약 우회, 워크어라운드 | +| `Architect` | `"architect"` | OSCILLATION | 구조 재설계, 관점 전환 | +| `Researcher` | `"researcher"` | NO_DRIFT | 정보 수집, 체계적 디버깅 | +| `Simplifier` | `"simplifier"` | DIMINISHING | 복잡도 축소, 가정 제거 | +| `Contrarian` | `"contrarian"` | 복합/기타 | 가정 뒤집기, 문제 역전 | + +상세: [Stagnation Detection](./stagnation.md) + +### HistoryStatus + +history 테이블의 status 컬럼. 직렬화 시 lowercase. + +| Variant | 직렬화 | +|---------|--------| +| `Running` | `"running"` | +| `Done` | `"done"` | +| `Failed` | `"failed"` | +| `Skipped` | `"skipped"` | +| `Hitl` | `"hitl"` | + +--- + +## 액션 타입 + +handler와 lifecycle hook은 서로 다른 타입을 사용한다. + +### HandlerConfig (yaml 설정) + +handler 배열에서 사용. prompt + script 모두 가능. + +```yaml +handlers: + - prompt: "이슈를 분석하세요" + runtime: claude # optional + model: sonnet # optional + - script: "cargo test" +``` + +### ScriptAction (yaml 설정) + +lifecycle hook(`on_enter`, `on_done`, `on_fail`)에서 사용. **script만 허용**. + +```yaml +on_done: + - script: "gh pr create ..." +on_fail: + - script: "gh issue comment ..." +``` + +### Action (런타임 추상화) + +코어의 실행 단위. `HandlerConfig`와 `ScriptAction` 모두 `Action`으로 변환되어 실행된다. + +``` +HandlerConfig::Prompt → Action::Prompt { text, runtime, model } +HandlerConfig::Script → Action::Script { command } +ScriptAction → Action::Script { command } +``` + +--- + +## Workspace yaml 설정 모델 + +### WorkspaceConfig + +```yaml +name: my-project +concurrency: 2 # workspace 동시 Running 수 (default: 1) + +sources: + github: + url: "https://github.com/org/repo" + scan_interval_secs: 300 # default: 300 + states: + analyze: + trigger: { label: "belt:analyze" } + handlers: + - prompt: "이슈를 분석하세요" + on_done: + - script: "gh issue edit ... --add-label belt:implement" + # ... 추가 states + escalation: + 1: retry + 2: retry_with_comment + 3: hitl + terminal: skip # HITL 만료 시 ('skip' | 'replan') + +# v6 신규: stagnation 탐지 + lateral thinking 설정 +stagnation: + enabled: true # default: true + spinning_threshold: 3 # default: 3 + oscillation_cycles: 2 # default: 2 + similarity_threshold: 0.8 # composite score 유사 판정 (default: 0.8) + no_drift_epsilon: 0.01 # default: 0.01 + no_drift_iterations: 3 # default: 3 + diminishing_threshold: 0.01 # default: 0.01 + confidence_threshold: 0.5 # default: 0.5 + + similarity: # CompositeSimilarity (Composite Pattern) + - judge: exact_hash # 기본 프리셋 + weight: 0.5 + - judge: token_fingerprint + weight: 0.3 + - judge: ncd + weight: 0.2 + + lateral: + enabled: true # default: true + max_attempts: 3 # 페르소나 최대 시도 (default: 3) + +runtime: + default: claude + claude: + model: sonnet + gemini: + model: pro +``` + +### TriggerConfig + +| 필드 | 타입 | 설명 | +|------|------|------| +| `label` | `Option` | 라벨 매칭 트리거 | +| `changes_requested` | `bool` | PR CHANGES_REQUESTED 트리거 (default: false) | + +--- + +## 컨텍스트 모델 (belt context 출력) + +`belt context $WORK_ID --json`이 반환하는 구조. script가 정보를 조회하는 유일한 방법. + +**v6 변경 (#719)**: `source_data` 필드 추가. DataSource별 자유 스키마. 기존 `issue`/`pr` 필드는 호환성을 위해 유지. + +```json +{ + "work_id": "github:org/repo#42:implement", + "workspace": "my-project", + "queue": { + "phase": "running", + "state": "implement", + "source_id": "github:org/repo#42" + }, + "source": { + "type": "github", + "url": "https://github.com/org/repo", + "default_branch": "main" + }, + "source_data": { + "issue": { + "number": 42, + "title": "...", + "body": "...", + "labels": ["belt:implement"], + "author": "user", + "state": "open" + }, + "pr": { + "number": 43, + "title": "...", + "state": "open", + "draft": false, + "head_branch": "belt/42-implement", + "base_branch": "main", + "reviews": [ + { "reviewer": "user", "state": "APPROVED" } + ] + } + }, + "issue": { + "number": 42, + "title": "...", + "body": "...", + "labels": ["belt:implement"], + "author": "user", + "state": "open" + }, + "pr": { + "number": 43, + "title": "...", + "state": "open", + "draft": false, + "head_branch": "belt/42-implement", + "base_branch": "main", + "reviews": [ + { "reviewer": "user", "state": "APPROVED" } + ] + }, + "history": [ + { + "work_id": "github:org/repo#42:analyze", + "state": "analyze", + "status": "done", + "attempt": 1, + "created_at": "2026-03-25T10:00:00Z" + } + ], + "worktree": "/tmp/belt/worktrees/42-implement" +} +``` + +### source_data 마이그레이션 전략 (#719) + +| Phase | 상태 | 설명 | +|-------|------|------| +| **1 (v6)** | `source_data` + `issue`/`pr` 양쪽 채움 | 하위 호환. 기존 script 수정 불필요 | +| **2 (v7+)** | `issue`/`pr` deprecated | script가 `source_data` 경로로 전환 | +| **3 (v8+)** | `issue`/`pr` 제거 | `source_data`만 사용 | + +script에서의 접근: +```bash +# v6 (양쪽 모두 가능) +belt context $WORK_ID --json | jq '.issue.number' +belt context $WORK_ID --json | jq '.source_data.issue.number' + +# v7+ (source_data 권장) +belt context $WORK_ID --json | jq '.source_data.issue.number' + +# Jira DataSource (v7+) +belt context $WORK_ID --json | jq '.source_data.ticket.key' +``` + +--- + +## 타임스탬프 규칙 + +- 모든 `created_at`, `updated_at`: **RFC3339 문자열** (`"2026-03-27T12:30:45Z"`) +- 생성 시: `Utc::now().to_rfc3339()` +- 파싱 시: `DateTime::parse_from_rfc3339()` → `DateTime` +- SQLite에 TEXT로 저장 (네이티브 datetime 미사용) + +## 직렬화 규칙 + +| 대상 | serde 설정 | +|------|-----------| +| enum variant | `#[serde(rename_all = "lowercase")]` 또는 `"snake_case"` | +| Optional 필드 | `#[serde(skip_serializing_if = "Option::is_none")]` | +| bool default false | `#[serde(default, skip_serializing_if = "std::ops::Not::not")]` | +| Vec default empty | `#[serde(default, skip_serializing_if = "Vec::is_empty")]` | +| u32 default 0 | `#[serde(default, skip_serializing_if = "is_zero")]` | + +--- + +## 테이블 관계 + +``` +queue_items.work_id ──< history.work_id +queue_items.work_id ──< transition_events.work_id +queue_items.work_id ──< token_usage.work_id +queue_items.work_id ──< queue_dependencies.work_id +queue_items.source_id ─── (같은 외부 엔티티를 공유하는 아이템들을 연결) + +specs.id ──< spec_links.spec_id + +workspaces.name ──< queue_items.workspace_id +workspaces.name ──< specs.workspace_id +workspaces.name ──< cron_jobs.workspace +workspaces.name ──< token_usage.workspace +workspaces.name ──< knowledge_base.workspace +``` + +> 참고: FK 제약은 SQLite에서 명시적으로 선언하지 않는다. 애플리케이션 레이어에서 정합성을 보장한다. diff --git a/spec/draft/concerns/datasource.md b/spec/draft/concerns/datasource.md new file mode 100644 index 0000000..04092f1 --- /dev/null +++ b/spec/draft/concerns/datasource.md @@ -0,0 +1,352 @@ +# DataSource — 외부 시스템 추상화 + 워크플로우 정의 + +> 외부 시스템(GitHub, Slack, Jira, ...)을 추상화하고, 각 시스템의 언어로 자동화 워크플로우를 정의한다. +> 새 외부 시스템 추가 = 새 DataSource impl + yaml 설정, 코어 변경 0 (OCP). + +--- + +## 역할 + +``` +DataSource가 소유하는 것: + 1. 수집 — 어떤 조건에서 아이템을 감지하는가 (trigger) + 2. 컨텍스트 — 해당 아이템의 외부 시스템 정보를 어떻게 조회하는가 (context) + +코어/yaml이 소유하는 것: + 3. 처리 — 감지된 아이템을 어떻게 처리하는가 (handlers: prompt/script) + 4. 전이 — 처리 완료 후 다음에 뭘 트리거하는가 (on_done script) + 5. 실패 반영 — 실패 시 외부 시스템에 어떻게 알리는가 (on_fail script) + 6. 실패 정책 — 실패 시 어떻게 escalation하는가 (escalation) + +코어는 DataSource 내부를 모른다. collect() 결과를 큐에 넣고, 상태 전이만 관리. +``` + +--- + +## trait 정의 + +```rust +pub trait DataSource: Send + Sync { + /// DataSource 이름 (예: "github", "jira") + fn name(&self) -> &str; + + /// 외부 시스템에서 trigger 조건에 매칭되는 새 아이템 감지 + async fn collect(&mut self, workspace: &WorkspaceConfig) -> Result>; + + /// 해당 아이템의 외부 시스템 컨텍스트를 조회 + /// belt context CLI가 내부적으로 호출 + async fn get_context(&self, item: &QueueItem) -> Result; +} +``` + +v4 대비 대폭 축소. `on_phase_enter`, `on_failed`, `on_done`, `before_task`, `after_task` 모두 제거. +- on_done/on_fail → yaml에 정의된 script가 처리 (gh CLI 등 직접 호출) +- worktree 셋업 → 인프라 레이어가 항상 처리 +- escalation → yaml의 escalation 정책을 코어가 실행 + +**v6 (#719)**: `get_context()`가 반환하는 `ItemContext`에 `source_data: serde_json::Value` 필드가 추가된다. DataSource는 자신의 고유 데이터를 `source_data`에 자유 스키마로 채운다. + +--- + +## `belt context` — 스크립트용 조회 CLI + +script가 아이템 정보를 조회하는 유일한 방법. DataSource.get_context()를 내부적으로 호출한다. + +```bash +belt context $WORK_ID --json +``` + +### 왜 환경변수 대신 CLI인가 + +Daemon이 `$ISSUE_NUMBER`, `$REPO_URL` 같은 환경변수를 주입하면 DataSource마다 변수가 끝없이 늘어난다 (GitHub: `$ISSUE_NUMBER`, Jira: `$TICKET_KEY`, Slack: `$THREAD_TS`, ...). 대신 `belt context`로 통일하고, DataSource별 context 스키마를 정의한다. + +Daemon이 주입하는 환경변수는 **2개만**: + +| 변수 | 설명 | +|------|------| +| `WORK_ID` | 큐 아이템 식별자 | +| `WORKTREE` | worktree 경로 | + +### GitHub context 스키마 + +**v6 (#719)**: `source_data` 필드에 DataSource 고유 데이터를 담는다. 기존 `issue`/`pr` 필드는 Phase 1에서 호환성을 위해 유지. + +```json +{ + "work_id": "github:org/repo#42:implement", + "workspace": "auth-project", + "queue": { + "phase": "running", + "state": "implement", + "source_id": "github:org/repo#42" + }, + "source": { + "type": "github", + "url": "https://github.com/org/repo", + "default_branch": "main" + }, + "source_data": { + "issue": { + "number": 42, + "title": "JWT middleware 구현", + "body": "...", + "labels": ["belt:implement"], + "author": "irene" + }, + "pr": { + "number": 87, + "head_branch": "feat/jwt-middleware", + "review_comments": [] + } + }, + "issue": { + "number": 42, + "title": "JWT middleware 구현", + "body": "...", + "labels": ["belt:implement"], + "author": "irene" + }, + "pr": { + "number": 87, + "head_branch": "feat/jwt-middleware", + "review_comments": [] + }, + "history": [ + { "state": "analyze", "status": "done", "attempt": 1, "summary": "구현 가능" }, + { "state": "implement", "status": "failed", "attempt": 1, "error": "compile error" }, + { "state": "implement", "status": "running", "attempt": 2 } + ], + "worktree": "/tmp/belt/auth-project-42" +} +``` + +> **source_data 마이그레이션**: Phase 1(v6)에서는 `issue`/`pr`과 `source_data` 양쪽 모두 채운다. Phase 2(v7+)에서 기존 필드 deprecated, Phase 3(v8+)에서 제거. 상세: [Data Model](./data-model.md#source_data-마이그레이션-전략-719) + +### history는 append-only + +같은 `source_id`의 모든 이벤트가 시간순으로 축적된다. 실패 횟수는 history에서 계산: + +```bash +# on_fail script에서 실패 횟수 조회 +FAILURES=$(echo $CTX | jq '[.history[] | select(.status=="failed" and .state=="implement")] | length') +``` + +별도 `failure_count` 컬럼 없이 history 조회만으로 충분. + +### Jira context 스키마 (v7+) + +```json +{ + "work_id": "jira:BE-123:analyze", + "workspace": "backend-tasks", + "queue": { + "phase": "running", + "state": "analyze", + "source_id": "jira:BE-123" + }, + "source": { + "type": "jira", + "url": "https://jira.company.com/project/BE" + }, + "source_data": { + "ticket": { + "key": "BE-123", + "summary": "...", + "status": "In Progress", + "assignee": "irene" + } + }, + "history": [] +} +``` + +> Jira DataSource는 `source_data.ticket`에 데이터를 채운다. `issue`/`pr` 필드는 없음 — `source_data`만으로 OCP 달성. + +--- + +## 상태 기반 워크플로우 + +각 DataSource는 자기 시스템의 상태 표현으로 워크플로우를 정의한다. v6는 GitHub에 집중한다. + +### GitHub (라벨 기반) + +```yaml +sources: + github: + url: https://github.com/org/repo + scan_interval_secs: 300 + + states: + analyze: + trigger: { label: "belt:analyze" } + handlers: + - prompt: "이슈를 분석하고 구현 가능 여부를 판단해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + gh issue edit $ISSUE --remove-label "belt:analyze" -R $REPO + gh issue edit $ISSUE --add-label "belt:implement" -R $REPO + + implement: + trigger: { label: "belt:implement" } + handlers: + - prompt: "이슈를 구현해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + TITLE=$(echo $CTX | jq -r '.source_data.issue.title // .issue.title') + gh pr create --title "$TITLE" --body "Closes #$ISSUE" -R $REPO + gh issue edit $ISSUE --remove-label "belt:implement" -R $REPO + gh issue edit $ISSUE --add-label "belt:review" -R $REPO + + review: + trigger: { label: "belt:review" } + handlers: + - prompt: "PR을 리뷰하고 품질을 평가해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + gh issue edit $ISSUE --remove-label "belt:review" -R $REPO + gh issue edit $ISSUE --add-label "belt:done" -R $REPO + + escalation: + 1: retry + 2: retry_with_comment + 3: hitl + terminal: skip # hitl timeout 시 적용 (skip 또는 replan) +``` + +### 향후 확장 (v7+) + +DataSource trait을 구현하면 코어 변경 없이 새 외부 시스템을 추가할 수 있다. `source_data`를 통해 코어 타입 변경도 불필요. + +| 시스템 | 상태 표현 | trigger 예시 | source_data | +|--------|----------|-------------|-------------| +| Jira | 티켓 status | `{ status: "To Analyze" }` | `source_data.ticket` | +| Slack | 리액션 | `{ reaction: "robot_face" }` | `source_data.message` | +| Linear | 라벨/status | `{ label: "belt" }` | `source_data.issue` | + +--- + +## Handler + +handler는 **prompt** 또는 **script** 두 가지 타입. 동일한 통합 액션 타입을 사용한다. + +```yaml +handlers: + - prompt: "이슈를 분석해줘" # LLM (AgentRuntime.invoke(), worktree 안에서) + - script: "scripts/lint-check.sh" # 결정적 (bash, WORK_ID + WORKTREE 주입) +``` + +- **prompt**: 순수 작업 지시만 담당. 린트/컨벤션은 hooks와 rules가 단계 진입 시 자동 보장 +- **script**: `belt context $WORK_ID --json`으로 필요한 정보를 조회하여 사용 + +handler 배열은 Running 상태에서 순차 실행. 하나라도 실패 시 on_fail → escalation. + +--- + +## on_done / on_fail / on_enter + +모든 lifecycle hook은 **script 배열**로 정의. handler와 동일한 통합 액션 타입. + +```yaml +states: + implement: + trigger: { label: "belt:implement" } + on_enter: # Running 진입 시 (선택) + - script: | + CTX=$(belt context $WORK_ID --json) + echo "시작: $(echo $CTX | jq -r '.source_data.issue.title // .issue.title')" + handlers: + - prompt: "이슈를 구현해줘" + on_done: # 성공 시 + - script: | + CTX=$(belt context $WORK_ID --json) + # PR 생성, 라벨 전환 등 + on_fail: # 실패 시 (escalation 전) + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + gh issue comment $ISSUE --body "구현 실패" -R $REPO +``` + +실행 주체: Daemon의 Executor 모듈이 상태 전이 시점에 직접 실행. +- `on_enter`: Running 진입 후, handler 실행 전. **실패 시 handler를 실행하지 않고 즉시 escalation 정책을 적용**한다. on_enter 실패도 history에 failed 이벤트로 기록되며 failure_count에 포함된다. +- `on_done`: evaluate가 Done 판정 후 (script 실패 시 → Failed 상태) +- `on_fail`: handler 또는 on_enter 실패 시, escalation level에 따라 조건부 실행 (`retry`에서는 실행 안 함) + +--- + +## Escalation 정책 + +workspace yaml에서 실패 정책을 정의하고, 코어가 실행한다. + +Escalation level은 **순차 실행 구간**과 **대안 선택 구간**으로 나뉜다: + +- Level 1~3: 순차적으로 적용 (1회 실패 → retry, 2회 → retry_with_comment, 3회 → hitl) +- Level 4: **terminal 분기** — hitl 응답에서 사람이 선택하거나, `terminal` 설정으로 자동 적용 + +```yaml +escalation: + 1: retry # 같은 state에서 재시도 (on_fail 실행 안 함) + 2: retry_with_comment # on_fail script 실행 + 재시도 + 3: hitl # on_fail script 실행 + HITL 이벤트 생성 + terminal: skip # hitl에서 사람이 결정하지 않으면 (timeout) 적용되는 최종 액션 + # 선택지: skip (종료) 또는 replan (스펙 수정 제안) +``` + +> **v6 (#723) Stagnation 가속**: failure_count 기반 escalation에 stagnation detection 결과가 가속 요소로 적용된다. 예: 1회 실패인데 SPINNING이 감지되면 retry → retry_with_comment로 승격. OSCILLATION이면 → hitl로 즉시 승격. 상세: [Stagnation Detection](./stagnation.md) + +### on_fail 실행 조건 + +`retry`만 on_fail script를 실행하지 않는다. 나머지(`retry_with_comment`, `hitl`)는 on_fail script 실행 후 해당 액션을 수행한다. + +``` +1회 실패 → retry → 조용히 재시도 (worktree 보존) +2회 실패 → retry_with_comment → 외부 시스템에 실패 알림 + 재시도 +3회 실패 → hitl → 외부 시스템에 알림 + 사람 대기 + └── 사람 응답: done / retry / skip / replan + └── timeout → terminal 액션 적용 (skip 또는 replan) +``` + +failure_count는 history의 append-only 이벤트에서 계산. 코어는 `history | filter(state, failed) | count` → escalation 매핑만 알면 된다. + +### Retry와 worktree + +retry 시 worktree를 보존하여 이전 작업 위에서 재시도한다. 새 아이템이 같은 source_id로 생성되며, worktree 경로가 이전 아이템에서 인계된다. + +--- + +## 아이템 계보 (Lineage) + +같은 외부 엔티티에서 파생된 아이템들은 `source_id`로 연결된다. + +``` +source_id = "github:org/repo#42" + +queue_items 테이블: + work_id | source_id | state | phase + github:org/repo#42:a | github:org/repo#42 | analyze | Done + github:org/repo#42:i | github:org/repo#42 | implement | Running + github:org/repo#42:r | github:org/repo#42 | review | Pending +``` + +`belt context $WORK_ID`는 source_id 기반으로 같은 엔티티의 이전 단계 이력(`history`)을 포함한다. + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — 전체 아키텍처 +- [AgentRuntime](./agent-runtime.md) — handler prompt 실행 +- [Stagnation Detection](./stagnation.md) — escalation 가속 +- [Cron 엔진](./cron-engine.md) — 품질 루프 +- [CLI 레퍼런스](./cli-reference.md) — belt context CLI +- [Data Model](./data-model.md) — source_data 마이그레이션 전략 diff --git a/spec/draft/concerns/distribution.md b/spec/draft/concerns/distribution.md new file mode 100644 index 0000000..7a9f86d --- /dev/null +++ b/spec/draft/concerns/distribution.md @@ -0,0 +1,127 @@ +# Distribution + +Belt 바이너리를 사용자에게 쉽게 설치할 수 있는 경로를 제공한다. + +## 설치 채널 + +| 채널 | 대상 | 명령 | 우선순위 | +|------|------|------|---------| +| Shell installer | macOS/Linux | `curl -sSf https://.../install.sh \| sh` | P0 | +| PowerShell installer | Windows | `irm https://.../install.ps1 \| iex` | P0 | + +--- + +## 1. Shell Installer (`install.sh`) + +macOS와 Linux 사용자를 위한 단일 스크립트. + +### 동작 + +``` +1. OS 감지 (Linux / Darwin) +2. 아키텍처 감지 (x86_64 / aarch64) +3. OS + Arch → GitHub Release 타겟 매핑 +4. 최신 릴리즈 태그 조회 (GitHub API) +5. 바이너리 다운로드 + 검증 +6. $INSTALL_DIR (기본 ~/.belt/bin) 에 설치 +7. PATH 설정 안내 (shell profile 감지) +``` + +### 타겟 매핑 + +| OS | Arch | GitHub Release Asset | +|----|------|---------------------| +| Linux | x86_64 | `belt-x86_64-unknown-linux-gnu.tar.gz` | +| Linux | aarch64 | `belt-aarch64-unknown-linux-gnu.tar.gz` | +| Darwin | x86_64 | `belt-x86_64-apple-darwin.tar.gz` | +| Darwin | arm64 | `belt-aarch64-apple-darwin.tar.gz` | + +### 환경 변수 + +| 변수 | 기본값 | 설명 | +|------|--------|------| +| `BELT_INSTALL_DIR` | `~/.belt/bin` | 설치 경로 | +| `BELT_VERSION` | latest | 특정 버전 지정 (e.g. `v0.1.1`) | + +### PATH 설정 + +스크립트는 설치 후 현재 셸 프로파일을 감지하여 PATH 추가 안내를 출력한다. + +``` +~/.zshrc → export PATH="$HOME/.belt/bin:$PATH" +~/.bashrc → export PATH="$HOME/.belt/bin:$PATH" +~/.profile → export PATH="$HOME/.belt/bin:$PATH" +``` + +`--yes` 플래그가 주어지면 자동으로 프로파일에 추가한다. + +### 의존성 + +- `curl` 또는 `wget` (다운로드) +- `tar` (아카이브 해제) + +--- + +## 2. PowerShell Installer (`install.ps1`) + +Windows 사용자를 위한 PowerShell 스크립트. + +### 동작 + +``` +1. 아키텍처 확인 (x86_64 고정 — ARM Windows 미지원) +2. GitHub Release에서 belt-x86_64-pc-windows-msvc.zip 다운로드 +3. $INSTALL_DIR (기본 $HOME\.belt\bin) 에 압축 해제 +4. User PATH에 설치 경로 추가 (레지스트리) +5. 현재 세션 PATH 갱신 +``` + +### 환경 변수 + +| 변수 | 기본값 | 설명 | +|------|--------|------| +| `BELT_INSTALL_DIR` | `$HOME\.belt\bin` | 설치 경로 | +| `BELT_VERSION` | latest | 특정 버전 지정 | + +### PATH 설정 + +User 환경변수 레지스트리(`HKCU:\Environment\Path`)에 설치 경로를 추가한다. 관리자 권한 불필요. + +### 의존성 + +- PowerShell 5.1+ (Windows 10 기본 탑재) +- `Invoke-WebRequest` (기본 cmdlet) +- `Expand-Archive` (기본 cmdlet) + +--- + +## Release Workflow 연동 + +`release.yml`이 `v*` 태그 푸시 시 자동 실행: + +``` +1. 5개 타겟 병렬 빌드 (기존 구현) +2. GitHub Release 생성 + 바이너리 첨부 (기존 구현) +3. install.sh / install.ps1 은 릴리즈 바이너리를 참조 (신규) +``` + +installer 스크립트는 프로젝트 루트에 위치하며, `main` 브랜치의 raw URL로 접근한다: +- `https://raw.githubusercontent.com/kys0213/belt/main/install.sh` +- `https://raw.githubusercontent.com/kys0213/belt/main/install.ps1` + +--- + +## 수용 기준 + +1. `curl -sSf https://.../install.sh | sh` 로 macOS/Linux에서 belt 설치 가능 +2. `irm https://.../install.ps1 | iex` 로 Windows에서 belt 설치 가능 +3. 설치된 belt 바이너리가 `belt --version` 으로 버전 출력 +4. `BELT_VERSION=v0.1.0` 으로 특정 버전 설치 가능 +5. `BELT_INSTALL_DIR` 으로 설치 경로 커스터마이징 가능 +6. 네트워크 에러, 미지원 플랫폼 등에 명확한 에러 메시지 + +## 우선순위 + +| 순위 | 항목 | 근거 | +|------|------|------| +| P0 | install.sh + install.ps1 | 모든 사용자의 첫 진입점 | diff --git a/spec/draft/concerns/queue-state-machine.md b/spec/draft/concerns/queue-state-machine.md new file mode 100644 index 0000000..f32a809 --- /dev/null +++ b/spec/draft/concerns/queue-state-machine.md @@ -0,0 +1,286 @@ +# QueuePhase 상태 머신 + +> 큐 아이템의 전체 생명주기를 정의한다. +> 상위 설계는 [DESIGN-v6](../DESIGN-v6.md) 참조. + +--- + +## Phase 정의 + +| Phase | 설명 | +|-------|------| +| **Pending** | DataSource.collect()가 감지, 큐 대기 | +| **Ready** | 실행 준비 완료 (자동 전이) | +| **Running** | worktree 생성 + handler 실행 중 | +| **Completed** | handler 전부 성공, evaluate 대기 | +| **Done** | evaluate 완료 판정 + on_done script 성공 | +| **HITL** | evaluate가 사람 판단 필요로 분류 | +| **Skipped** | escalation skip 또는 preflight 실패 | +| **Failed** | on_done script 실패, 인프라 오류 등 | + +--- + +## Phase 전이 캡슐화 (v6 #718) + +`QueueItem.phase` 필드를 직접 대입하면 `can_transition_to()` 검증을 우회할 수 있다. +v6에서는 모든 전이를 `QueueItem::transit()` 메서드로 강제한다. + +```rust +impl QueueItem { + /// phase 필드는 pub(crate) — belt-core 외부에서 직접 대입 불가 + /// 읽기는 pub getter: fn phase(&self) -> QueuePhase + + pub fn transit(&mut self, to: QueuePhase) -> Result { + let from = self.phase; + if !from.can_transition_to(&to) { + return Err(BeltError::InvalidTransition { from, to }); + } + self.phase = to; + self.updated_at = Utc::now().to_rfc3339(); + Ok(from) + } +} +``` + +### 테스트 지원 + +테스트에서 특정 phase의 아이템을 생성하려면 빌더를 사용한다: + +```rust +// 테스트 전용 빌더 (cfg(test) 또는 #[doc(hidden)]) +QueueItem::builder() + .work_id("test:1:analyze") + .with_phase(QueuePhase::Running) // 검증 없이 직접 설정 + .build() +``` + +### DB 로드 + +`belt-infra/db.rs`의 `from_row()`는 `pub(crate)` 접근 가능하므로 DB에서 로드 시 phase 직접 설정이 가능하다. + +--- + +## 전체 상태 전이 + +``` + DataSource.collect() + │ + ▼ + ┌───────────────────────────────┐ + │ Pending │ + │ (큐 대기, 수집됨) │ + └───────────────┬───────────────┘ + │ 자동 전이 + ▼ + ┌───────────────────────────────┐ + │ Ready │ + │ (실행 준비 완료) │ + └───────────────┬───────────────┘ + │ 자동 전이 (concurrency 제한) + ▼ + ┌───────────────────────────────┐ + │ Running │ + │ │ + │ ① worktree 생성 (or 재사용) │ + │ ② on_enter script │ + │ ③ handlers 순차 실행 │ + │ prompt → LLM (worktree) │ + │ script → bash │ + └──────┬────────────┬───────────┘ + │ │ + 전부 성공 handler/on_enter 실패 + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────────────────┐ + │ Completed │ │ Stagnation Analyzer (항상 실행)│ + │ │ │ │ + │ handler 완료 │ │ ① CompositeSimilarity로 │ + │ evaluate 대기 │ │ outputs/errors 유사도 분석 │ + │ │ │ ② 패턴 감지 시 │ + │ force_trigger │ │ LateralAnalyzer가 │ + │ ("evaluate") │ │ 내장 페르소나로 대안 분석 │ + └────────┬────────┘ │ → lateral_plan 생성 │ + │ │ │ + │ │ Escalation (failure_count): │ + │ │ 1: retry │ + │ │ → lateral_plan 주입 │ + │ │ → 새 아이템 → Pending │ + │ │ → worktree 보존 │ + │ │ → on_fail 실행 안 함 │ + │ │ │ + │ │ 2: retry_with_comment │ + │ │ → lateral_plan 주입 │ + │ │ → on_fail script 실행 │ + │ │ → 새 아이템 → Pending │ + │ │ → worktree 보존 │ + │ │ │ + │ │ 3: hitl │ + │ │ → lateral_report 첨부 │ + │ │ → on_fail script 실행 │ + │ │ → HITL 이벤트 생성 ───────┐│ + │ │ → worktree 보존 ││ + │ │ ││ + │ │ terminal: skip 또는 replan ││ + │ │ (hitl timeout 시 적용) ││ + │ │ skip → Skipped ─────────┼┼──┐ + │ │ replan → HITL(replan) ────┤│ │ + │ └───────────────────────────────┘│ │ + │ │ │ + │ evaluate cron (per-item) │ │ + │ (LLM이 belt queue done/hitl CLI 호출) │ │ + │ │ │ + ┌────┴────┐ │ │ + │ │ │ │ + 완료 판정 사람 필요 │ │ + │ │ │ │ + ▼ ▼ │ │ + ┌──────────┐ ┌──────────────────────────────────────┐ │ │ + │ on_done │ │ HITL │◄──────┘ │ + │ script │ │ │ │ + │ 실행 │ │ 사람 대기 (worktree 보존) │ │ + └──┬───┬──┘ │ │ │ + │ │ │ 응답 경로: │ │ + 성공 실패 │ "done" → on_done → Done │ │ + │ │ │ "retry" → 새 아이템 → Pending │ │ + ▼ ▼ │ "skip" → Skipped │ │ + ┌──────┐┌─────┐ │ "replan"→ 스펙 수정 제안 │ │ + │ Done ││ Fail│ └──────────────────────────────────────┘ │ + │ ││ ed │ │ + │ wt ││ │ ┌──────────────────────────────────────┐ │ + │ 정리 ││ wt │ │ Skipped │◄─────────┘ + │ ││ 보존 │ │ │ + └──────┘│ 로그 │ │ terminal (worktree 정리) │ + │ 기록 │ └──────────────────────────────────────┘ + └─────┘ +``` + +--- + +## Worktree 생명주기 + +| Phase / 이벤트 | Worktree | +|----------------|----------| +| Running | 생성 (또는 retry 시 기존 보존분 재사용) | +| Completed | 유지 (evaluate 대기) | +| Done | **정리** | +| HITL | 보존 (사람 확인 후 결정) | +| Failed | 보존 (디버깅용) | +| Skipped | 정리 | +| Retry | 보존 (이전 작업 위에서 재시도) | +| Graceful shutdown 롤백 (Running→Pending) | **보존** (재시작 후 재사용) | +| hitl-timeout (HITL 만료) | **정리** | +| log-cleanup cron | 보존된 worktree 중 TTL 초과분 정리 | + +**정리 원칙**: worktree는 **Done 또는 Skipped**가 되어야만 정리한다. HITL 만료 시에도 정리하여 좀비 worktree를 방지한다. Shutdown 롤백 시에는 재시작 후 재사용을 위해 보존한다. 나머지 보존분(Failed 등)은 `log-cleanup` cron이 TTL(기본 7일) 기준으로 주기 정리한다. + +--- + +## on_fail 실행 조건 + +| Escalation | on_fail 실행 | 동작 | +|------------|-------------|------| +| retry | 안 함 | 조용한 재시도 | +| retry_with_comment | 실행 | 외부 알림 + 재시도 | +| hitl | 실행 | 외부 알림 + 사람 대기 | + +`retry`만 on_fail을 실행하지 않는다. "조용한 재시도"로 외부 시스템에 노이즈를 주지 않는다. + +> `skip`과 `replan`은 hitl의 응답 경로 또는 hitl timeout 시 `terminal` 설정에 의해 적용된다. 독립적인 escalation level이 아니다. 상세는 [DataSource](./datasource.md)의 Escalation 정책 참조. + +failure_count는 append-only history에서 계산한다: `history | filter(state, failed) | count`. on_enter 실패도 handler 실패와 동일하게 failure_count에 포함된다. + +> **v6 (#723)**: 모든 실패에서 StagnationDetector가 CompositeSimilarity로 유사도 분석을 수행한다. 패턴이 감지되면 LateralAnalyzer가 내장 페르소나(HACKER, ARCHITECT 등)로 대안 접근법을 분석하고, lateral_plan을 생성하여 retry 시 handler prompt에 주입한다. escalation 자체는 기존 failure_count 기반 그대로이되, **모든 retry가 lateral plan으로 강화**된다. 상세: [Stagnation Detection](./stagnation.md) + +--- + +## Evaluate 원칙 + +### 판단 원칙 + +1. **의심스러우면 HITL** (safe default) — evaluate가 확신할 수 없으면 Done이 아니라 HITL로 분류한다. 잘못된 Done보다 불필요한 HITL이 낫다. + +2. **"충분한가?"만 판단** — "이 handler의 결과물이 다음 단계로 넘어가기에 충분한가?"만 본다. 품질 판단(좋은 코드인가?)은 Cron 품질 루프가 담당한다. + +3. **state별 구체 기준은 agent-workspace rules에 위임** — `~/.belt/agent-workspace/.claude/rules/classify-policy.md`에 state별 Done 조건을 정의한다. 코어는 rules를 모르고, `belt agent`가 rules를 참조하여 판단한다. + +### Per-Item 판정 (v6 #722) + +evaluate는 **per-work_id 단위**로 LLM 판정을 실행한다. 각 Completed 아이템에 대해 개별 프롬프트를 발행하고, 해당 아이템의 context를 포함한다. + +``` +for item in queue.get(Completed): + belt_agent_p(workspace, + "아이템 {work_id}의 완료 여부를 판단해줘. + belt context {work_id} --json 으로 컨텍스트를 확인하고, + belt queue done {work_id} 또는 belt queue hitl {work_id} 를 실행해줘") +``` + +- 개별 판정 실패 시 해당 아이템만 Completed에 머물고, 다른 아이템 판정에 영향 없다 +- `batch_size`로 한 tick에서 처리할 최대 아이템 수를 제한한다 +- 기존 `eval_failure_counts`는 이미 per-work_id로 관리됨 (설계 의도 일치) + +### 실패 원칙 + +Completed는 **안전한 대기 상태**. evaluate가 실패하든 CLI가 실패하든 Completed에서 멈추고, 다음 기회에 재시도한다. + +| 실패 유형 | 동작 | 상태 | +|-----------|------|------| +| evaluate LLM 오류/timeout | Completed 유지, 다음 cron tick에서 재시도 | Completed | +| evaluate 반복 실패 (N회) | HITL로 에스컬레이션 | → HITL | +| CLI 호출 실패 (`belt queue done/hitl`) | Completed 유지 + 에러 로그, 다음 tick 재시도 | Completed | +| on_done script 실패 | Failed 상태 (on_fail은 실행하지 않음 — handler 실패가 아니므로) | → Failed | + +--- + +## 수용 기준 + +### Phase 전이 캡슐화 (#718) + +- [ ] `QueueItem.phase` 필드는 `pub(crate)` 가시성으로, belt-core 외부에서 직접 대입 불가 +- [ ] 모든 phase 변경은 `QueueItem::transit(to)` 메서드를 경유한다 +- [ ] `transit()` 메서드는 내부에서 `can_transition_to()` 검증 + `updated_at` 갱신을 수행한다 +- [ ] 테스트 코드에서도 phase 직접 대입 대신 `transit()` 또는 테스트 헬퍼를 사용한다 + +### 상태 전이 규칙 + +- [ ] Pending→Ready 전이는 Daemon tick마다 자동 수행된다 +- [ ] Ready→Running 전이는 workspace.concurrency와 daemon.max_concurrent 모두 만족할 때만 수행된다 +- [ ] queue_dependencies에 미완료(Done이 아닌) 의존이 있으면 Ready→Running 전이가 블로킹된다 +- [ ] `can_transition_to()`가 허용하지 않는 전이를 시도하면 `InvalidTransition` 에러가 반환된다 +- [ ] Done, Skipped는 terminal — 이후 전이 불가 + +### Escalation 정책 + +- [ ] failure_count=1일 때 `retry`가 적용되면 on_fail을 실행하지 않고 새 아이템으로 재시도한다 +- [ ] failure_count=2일 때 `retry_with_comment`가 적용되면 on_fail 실행 후 새 아이템으로 재시도한다 +- [ ] failure_count=3일 때 `hitl`이 적용되면 on_fail 실행 후 HITL 이벤트가 생성된다 +- [ ] on_enter 실패도 failure_count에 포함된다 +- [ ] 모든 실패에서 stagnation 분석이 실행되고, 패턴 감지 시 lateral_plan이 retry에 주입된다 + +### Evaluate (per-item, #722) + +- [ ] evaluate는 per-work_id 단위로 LLM 판정을 실행한다 +- [ ] 각 판정에 해당 아이템의 context가 포함된다 +- [ ] 개별 판정 실패 시 해당 아이템만 Completed에 머물고, 다른 아이템에 영향 없다 +- [ ] evaluate LLM 오류 시 아이템은 Completed에 머무르고, 다음 cron tick에서 재시도된다 +- [ ] evaluate 반복 실패(N회)로 HITL 에스컬레이션 시 HitlReason::EvaluateFailure가 기록된다 +- [ ] on_done script 실패 시 Failed 전이되고, on_fail은 실행하지 않는다 + +### Worktree 생명주기 + +- [ ] Running 진입 시 worktree가 생성된다 (retry 시 기존 worktree 재사용) +- [ ] Done, Skipped 전이 시 worktree가 정리된다 +- [ ] HITL, Failed 전이 시 worktree가 보존된다 +- [ ] log-cleanup cron이 TTL(7일) 초과 보존 worktree를 정리한다 + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — 설계 철학 +- [Daemon](./daemon.md) — 내부 모듈 구조 + 실행 루프 +- [Stagnation Detection](./stagnation.md) — 반복 패턴 감지 + escalation 가속 +- [DataSource](./datasource.md) — escalation 정책 + on_fail script +- [Cron 엔진](./cron-engine.md) — evaluate cron + force_trigger +- [실패 복구와 HITL](../flows/04-failure-and-hitl.md) — 실패/HITL 시나리오 +- [Data Model](./data-model.md) — 테이블 스키마, 도메인 enum diff --git a/spec/draft/concerns/stagnation.md b/spec/draft/concerns/stagnation.md new file mode 100644 index 0000000..ce2eaf5 --- /dev/null +++ b/spec/draft/concerns/stagnation.md @@ -0,0 +1,597 @@ +# Stagnation Detection — 반복 실행 패턴 감지 + 사고 전환 + +> LLM이 같은 실수를 반복하거나 A↔B 왕복하는 패턴을 감지하고, 접근법을 전환하여 재시도한다. +> "몇 번 실패했는가"가 아니라 "어떻게 실패했는가"를 보고, "다르게 시도"한다. +> +> 참고: [Ouroboros](https://github.com/kys0213/ouroboros) 프로젝트의 이중 계층 탐지 + lateral thinking을 Belt에 적용. + +--- + +## 설계 요약 + +``` +handler 실패 + │ + ▼ +Stagnation Analyzer (항상 실행) + │ + ├── ① 유사도 판단 (CompositeSimilarity) + │ outputs/errors 별도 검사 + │ → 4가지 패턴 탐지 + │ + ├── ② Lateral Plan 생성 (패턴 감지 시) + │ 내장 페르소나가 대안 접근법 분석 + │ → lateral_plan 출력 + │ + └── ③ Escalation 적용 (기존 failure_count 기반) + retry → lateral_plan 주입하여 재시도 + retry_with_comment → lateral_plan 주입 + on_fail + hitl → lateral_report를 hitl_notes에 첨부 +``` + +--- + +## 탐지 대상: 4가지 정체 패턴 + +| 패턴 | 정의 | Belt에서의 예시 | +|------|------|----------------| +| **SPINNING** | A→A→A (동일/유사 반복) | 같은 코드 생성 → 같은 컴파일 에러 반복 | +| **OSCILLATION** | A→B→A→B (교대 반복) | 리팩토링 → 원복 → 리팩토링, 설정 A↔B 왕복 | +| **NO_DRIFT** | 진행 점수 정체 | 테스트 통과율이 변하지 않음 | +| **DIMINISHING_RETURNS** | 개선폭 감소 | 매 시도마다 개선은 있으나 점점 미미 | + +--- + +## Core Types + +### StagnationPattern + +```rust +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum StagnationPattern { + Spinning, + Oscillation, + NoDrift, + DiminishingReturns, +} +``` + +### StagnationDetection + +```rust +pub struct StagnationDetection { + pub pattern: StagnationPattern, + pub detected: bool, + pub confidence: f64, // 0.0 ~ 1.0 + pub evidence: serde_json::Value, +} +``` + +### ExecutionHistory + +outputs와 errors를 **별도 필드**로 분리한다. Ouroboros와 동일하게 각 데이터 소스를 독립적으로 검사. + +```rust +pub struct ExecutionHistory { + pub outputs: Vec, // handler 출력 (history.summary) + pub errors: Vec, // 에러 메시지 (history.error) — 별도 검사 + pub drift_scores: Vec, // 진행 점수 (optional) +} +``` + +- `outputs`: history 테이블의 `summary` 필드. 최근 N개 sliding window. +- `errors`: history 테이블의 `error` 필드. 별도로 sliding window. +- `drift_scores`: 정량적 진행 지표. Phase 2에서 도입. + +--- + +## Similarity — Composite Pattern + +유사도 판단을 단일 trait으로 추상화하고, Composite Pattern으로 여러 알고리즘을 가중 합산한다. belt-core는 `SimilarityJudge` trait 하나만 의존한다. + +### trait 정의 + +```rust +/// 두 텍스트의 유사도를 판단하는 단일 알고리즘 +pub trait SimilarityJudge: Send + Sync { + fn name(&self) -> &str; + fn score(&self, a: &str, b: &str) -> f64; // 0.0 (다름) ~ 1.0 (동일) +} +``` + +### CompositeSimilarity + +Composite 자체도 `SimilarityJudge`를 구현한다. leaf와 composite가 같은 인터페이스이므로 중첩 가능. + +```rust +pub struct CompositeSimilarity { + judges: Vec<(Box, f64)>, // (judge, weight) +} + +impl SimilarityJudge for CompositeSimilarity { + fn name(&self) -> &str { "composite" } + + fn score(&self, a: &str, b: &str) -> f64 { + let (sum, w_sum) = self.judges.iter() + .map(|(j, w)| (j.score(a, b) * w, w)) + .fold((0.0, 0.0), |(s, ws), (v, w)| (s + v, ws + w)); + sum / w_sum + } +} +``` + +### 내장 Judge 구현체 + +| Judge | 원리 | 출력 | 용도 | +|-------|------|------|------| +| **ExactHash** | SHA-256 해시 비교 | 동일=1.0, 다름=0.0 | 완전 동일 감지 (빠름) | +| **TokenFingerprint** | 숫자/경로/해시값 정규화 후 해시 | 구조동일=1.0, 다름=0.0 | "line 42" vs "line 58" 같은 차이 무시 | +| **NCD** | Normalized Compression Distance | 0.0~1.0 연속값 | 구조적 유사도 측정 | + +#### ExactHash + +```rust +impl SimilarityJudge for ExactHash { + fn score(&self, a: &str, b: &str) -> f64 { + if sha256(a) == sha256(b) { 1.0 } else { 0.0 } + } +} +``` + +#### TokenFingerprint + +변하는 부분을 정규화한 뒤 해시 비교: + +``` +정규화 규칙: + 숫자 → "line 42" → "line " + 파일경로 → "/tmp/abc123/foo.rs" → "" + 해시값 → "0x7f3a2b" → "" + UUID → "550e8400-..." → "" + +예시: + "error[E0433]: not found in auth::middleware (line 42)" + "error[E0433]: not found in auth::middleware (line 58)" + → 정규화: "error[E]: not found in auth::middleware (line )" + → 해시 동일 → score = 1.0 +``` + +#### NCD (Normalized Compression Distance) + +```rust +impl SimilarityJudge for Ncd { + fn score(&self, a: &str, b: &str) -> f64 { + let ca = compress(a).len() as f64; + let cb = compress(b).len() as f64; + let cab = compress(&format!("{a}{b}")).len() as f64; + let ncd = (cab - ca.min(cb)) / ca.max(cb); + 1.0 - ncd // NCD를 유사도로 변환 + } +} +``` + +- 외부 의존성 없음 (flate2 crate의 deflate 사용) +- 언어 무관, 구현 단순 + +### 구성 예시 + +``` +기본 프리셋: +┌─ CompositeSimilarity ─────────────────────┐ +│ ├── ExactHash (weight: 0.5) │ +│ ├── TokenFingerprint (weight: 0.3) │ +│ └── NCD (weight: 0.2) │ +└───────────────────────────────────────────┘ + +동작 예시: + "error line 42" vs "error line 58" + ExactHash: 0.0 × 0.5 = 0.0 + TokenFingerprint: 1.0 × 0.3 = 0.3 + NCD: 0.92 × 0.2 = 0.184 + composite score = 0.484 / 1.0 = 0.484 → threshold 0.8 미달 + + "error line 42" vs "error line 42" + ExactHash: 1.0 × 0.5 = 0.5 + TokenFingerprint: 1.0 × 0.3 = 0.3 + NCD: 1.0 × 0.2 = 0.2 + composite score = 1.0 → threshold 초과 → 유사 + +중첩 Composite: +┌─ CompositeSimilarity (root) ──────────────┐ +│ ├── ExactHash (weight: 0.4) │ +│ └── CompositeSimilarity (weight: 0.6) │ +│ ├── TokenFingerprint (weight: 0.5)│ +│ ├── NCD (weight: 0.3)│ +│ └── LineJaccard (weight: 0.2)│ +└───────────────────────────────────────────┘ +``` + +--- + +## StagnationDetector + +```rust +pub struct StagnationDetector { + pub judge: Box, // Composite 또는 단일 Judge + pub config: StagnationConfig, +} + +impl StagnationDetector { + pub fn detect(&self, history: &ExecutionHistory) -> Vec; +} +``` + +Detector는 `SimilarityJudge` trait 하나만 의존. Composite인지 단일 Judge인지 모른다. + +### 탐지 알고리즘 + +#### SPINNING — 유사 출력 반복 + +outputs와 errors를 **별도로** 검사한다. 어느 쪽이든 감지되면 SPINNING. + +``` +detect_spinning(history): + // 1. outputs 검사 + recent_outputs = history.outputs[-threshold..] + if all_pairs_similar(recent_outputs, judge, similarity_threshold): + return SPINNING(source: "outputs") + + // 2. errors 검사 + recent_errors = history.errors[-threshold..] + if all_pairs_similar(recent_errors, judge, similarity_threshold): + return SPINNING(source: "errors") + +all_pairs_similar(items, judge, threshold): + for i in 1..items.len(): + if judge.score(items[0], items[i]) < threshold: + return false + return true +``` + +- threshold 개수: `spinning_threshold` (기본 3) +- 유사도 기준: `similarity_threshold` (기본 0.8) +- confidence: 유사도 점수의 평균값 + +#### OSCILLATION — 교대 반복 + +``` +detect_oscillation(history): + recent = history.outputs[-(cycles * 2)..] + + // 짝수 그룹 내 유사 + even_similar = all_pairs_similar(recent[::2], judge, similarity_threshold) + // 홀수 그룹 내 유사 + odd_similar = all_pairs_similar(recent[1::2], judge, similarity_threshold) + // 짝수↔홀수 비유사 + cross_different = judge.score(recent[0], recent[1]) < 0.3 + + if even_similar && odd_similar && cross_different: + return OSCILLATION +``` + +#### NO_DRIFT / DIMINISHING_RETURNS — 수치 기반 + +drift score 기반. SimilarityJudge 불필요, 기존 알고리즘 유지. + +``` +detect_no_drift(history): + scores = history.drift_scores[-iterations..] + deltas = consecutive_abs_deltas(scores) + if all(d < epsilon for d in deltas): + confidence = 1.0 - (avg(deltas) / epsilon) + return NO_DRIFT + +detect_diminishing(history): + scores = history.drift_scores[-(iterations+1)..] + improvements = consecutive_diffs(scores) // lower = better + if all(imp < threshold for imp in improvements): + is_decreasing = monotonically_decreasing(improvements) + confidence = if is_decreasing { 0.8 } else { 0.6 } + return DIMINISHING_RETURNS +``` + +--- + +## Lateral Thinking — 내장 페르소나에 의한 사고 전환 + +Stagnation이 감지되면 **모든 retry에 lateral plan이 자동 주입**된다. 이것이 retry의 기본 동작이다. + +### 페르소나 + +5가지 사고 페르소나가 belt-core에 내장된다. 각 페르소나는 `include_str!`로 바이너리에 임베딩된 prompt template이다. + +``` +crates/belt-core/src/stagnation/ + personas/ + hacker.md # include_str!로 바이너리에 포함 + architect.md + researcher.md + simplifier.md + contrarian.md +``` + +| 페르소나 | 패턴 친화도 | 전략 | +|----------|-----------|------| +| **HACKER** | SPINNING | 제약 우회, 워크어라운드, 다른 도구/라이브러리 시도 | +| **ARCHITECT** | OSCILLATION | 구조 재설계, 관점 전환, 근본 원인 분석 | +| **RESEARCHER** | NO_DRIFT | 정보 수집, 문서/테스트 조사, 체계적 디버깅 | +| **SIMPLIFIER** | DIMINISHING | 복잡도 축소, 가정 제거, 최소 구현 | +| **CONTRARIAN** | 복합/기타 | 가정 뒤집기, 문제 역전, 완전히 다른 접근 | + +### 패턴 → 페르소나 선택 + +```rust +fn select_persona( + pattern: StagnationPattern, + tried: &[Persona], // 이전에 시도한 페르소나 제외 +) -> Option { + let affinity = match pattern { + Spinning => [Hacker, Contrarian, Simplifier, Architect, Researcher], + Oscillation => [Architect, Contrarian, Simplifier, Hacker, Researcher], + NoDrift => [Researcher, Contrarian, Architect, Hacker, Simplifier], + DiminishingReturns => [Simplifier, Contrarian, Researcher, Architect, Hacker], + }; + affinity.iter().find(|p| !tried.contains(p)).copied() +} +``` + +### LateralAnalyzer + +```rust +pub struct LateralAnalyzer; + +impl LateralAnalyzer { + /// 감지된 패턴에 대해 lateral plan을 생성 + /// 내부적으로 belt agent -p를 호출하여 LLM이 분석 + pub async fn analyze( + &self, + detection: &StagnationDetection, + history: &ExecutionHistory, + persona: Persona, + workspace: &str, + ) -> Result; +} + +pub struct LateralPlan { + pub persona: Persona, + pub failure_analysis: String, // 이전 실패 원인 분석 + pub alternative_approach: String, // 대안 접근법 + pub execution_plan: String, // 구체적 실행 계획 + pub warnings: String, // 주의사항 +} +``` + +실행: `belt agent --workspace -p "{persona.prompt}\n\n{failure_context}"` + +### Retry에 lateral_plan 주입 + +``` +원래 handler prompt: + "이슈를 구현해줘" + +lateral retry 시 합성: + "이슈를 구현해줘" + + + "⚠ Stagnation Analysis (attempt 2/3) + Pattern: SPINNING | Persona: HACKER + + 실패 원인: 이전 2회 시도에서 동일한 컴파일 에러 반복 + 대안 접근법: 기존 Session 직접 구현 대신 tower-sessions crate 활용 + 실행 계획: 1. Cargo.toml 수정 2. 타입 교체 3. middleware 등록 + 주의: 이전과 동일한 접근은 같은 실패를 반복합니다" +``` + +### HITL에 lateral report 첨부 + +모든 페르소나가 소진되거나 failure_count가 hitl에 도달하면, 지금까지의 lateral 시도 이력이 `hitl_notes`에 첨부된다. + +``` +HITL Event: + reason: retry_max_exceeded + hitl_notes: + "Stagnation Report: + pattern: SPINNING (3회 유사 에러) + + attempt 1: compile error (Session not found) + attempt 2: HACKER 제안 → tower-sessions 시도 → 다른 에러 + attempt 3: CONTRARIAN 제안 → trait object 시도 → 컴파일 성공, 테스트 실패 + + 2회 접근 전환 후에도 미해결. 구조적 문제일 가능성." +``` + +--- + +## Integration Points + +### Daemon 실행 루프 + +``` +handler/on_enter 실행 실패 + │ + ▼ +① ExecutionHistory 구성 + outputs = DB에서 최근 N개 history.summary + errors = DB에서 최근 N개 history.error (별도) + drifts = (Phase 2) drift scores + │ + ▼ +② StagnationDetector.detect(history) + 내부: CompositeSimilarity로 유사도 판단 + → Vec + │ + ▼ +③ Lateral Plan 생성 (패턴 감지 시) + 패턴 → 페르소나 선택 (이전 시도 제외) + belt agent -p로 LLM 분석 → lateral_plan + │ + ▼ +④ Escalation 적용 (failure_count 기반, 기존과 동일) + retry → lateral_plan 주입하여 재시도 + retry_with_comment → lateral_plan 주입 + on_fail + hitl → lateral_report를 hitl_notes에 첨부 + │ + ▼ +⑤ transition_events에 기록 + event_type: 'stagnation' + detail: { pattern, confidence, evidence, + persona, lateral_plan, judge_scores } +``` + +### 데이터 소스 + +| 데이터 | 출처 | 용도 | +|--------|------|------| +| handler 출력 | `history.summary` | SPINNING, OSCILLATION (outputs) | +| 에러 메시지 | `history.error` | SPINNING (errors, 별도 검사) | +| 시도 번호 | `history.attempt` | sliding window 범위 결정 | +| drift score | (Phase 2) `belt context` 확장 | NO_DRIFT, DIMINISHING_RETURNS | +| 이전 lateral | `transition_events` (stagnation) | 페르소나 중복 제외 | + +### 이벤트 기록 + +``` +event_type = 'stagnation' +detail = JSON { + "pattern": "spinning", + "confidence": 0.95, + "evidence": { + "source": "errors", + "judge_scores": { "exact_hash": 0.0, "token_fp": 1.0, "ncd": 0.92 }, + "composite_score": 0.484 + }, + "persona": "hacker", + "lateral_plan": "tower-sessions crate로 전환...", + "escalation": "retry" +} +``` + +--- + +## Configuration + +```yaml +# workspace.yaml +stagnation: + enabled: true # 기본 true + spinning_threshold: 3 # 최소 연속 유사 출력 수 (기본 3) + oscillation_cycles: 2 # 최소 교대 사이클 수 (기본 2, → 4회 출력) + similarity_threshold: 0.8 # composite score 유사 판정 기준 (기본 0.8) + no_drift_epsilon: 0.01 # drift score 변화 임계값 (기본 0.01) + no_drift_iterations: 3 # drift 정체 판정 반복 수 (기본 3) + diminishing_threshold: 0.01 # 개선폭 임계값 (기본 0.01) + confidence_threshold: 0.5 # 탐지 유효 최소 confidence (기본 0.5) + + similarity: # CompositeSimilarity 구성 (기본 프리셋 제공) + - judge: exact_hash + weight: 0.5 + - judge: token_fingerprint + weight: 0.3 + - judge: ncd + weight: 0.2 + + lateral: + enabled: true # 기본 true + max_attempts: 3 # 페르소나 최대 시도 횟수 (기본 3) +``` + +### StagnationConfig + +```rust +pub struct StagnationConfig { + pub enabled: bool, + pub spinning_threshold: u32, + pub oscillation_cycles: u32, + pub similarity_threshold: f64, + pub no_drift_epsilon: f64, + pub no_drift_iterations: u32, + pub diminishing_threshold: f64, + pub confidence_threshold: f64, + pub similarity: Vec, // judge name + weight + pub lateral: LateralConfig, +} + +pub struct LateralConfig { + pub enabled: bool, + pub max_attempts: u32, +} + +pub struct JudgeConfig { + pub judge: String, // "exact_hash" | "token_fingerprint" | "ncd" + pub weight: f64, +} +``` + +--- + +## 모듈 구조 + +``` +crates/belt-core/src/stagnation/ + mod.rs # pub exports + pattern.rs # StagnationPattern, StagnationDetection + history.rs # ExecutionHistory + detector.rs # StagnationDetector + similarity/ + mod.rs # SimilarityJudge trait + composite.rs # CompositeSimilarity (impl SimilarityJudge) + exact_hash.rs # ExactHash judge + token_fingerprint.rs # TokenFingerprint judge + ncd.rs # NCD judge + lateral/ + mod.rs # LateralAnalyzer, LateralPlan + persona.rs # Persona enum, select_persona() + personas/ + hacker.md # include_str! 내장 + architect.md + researcher.md + simplifier.md + contrarian.md +``` + +--- + +## 수용 기준 + +### Similarity (Composite Pattern) + +- [ ] `SimilarityJudge` trait이 단일 인터페이스로 유사도를 제공한다 +- [ ] `CompositeSimilarity`가 `SimilarityJudge`를 구현하여 중첩 가능하다 +- [ ] `StagnationDetector`는 `Box` 하나만 의존한다 +- [ ] yaml의 `similarity` 설정으로 judge 구성을 변경할 수 있다 +- [ ] 기본 프리셋(exact_hash + token_fp + ncd)이 설정 생략 시 적용된다 + +### Detection (4 Patterns) + +- [ ] outputs에서 최근 N개가 유사(composite score ≥ threshold)하면 SPINNING이 감지된다 +- [ ] errors에서 최근 N개가 유사하면 SPINNING이 감지된다 (별도 검사) +- [ ] 최근 2N개 outputs이 짝수/홀수 교대 패턴이면 OSCILLATION이 감지된다 +- [ ] drift score 변화량이 epsilon 미만이면 NO_DRIFT가 감지된다 +- [ ] 개선폭이 threshold 미만이면 DIMINISHING_RETURNS가 감지된다 +- [ ] stagnation.enabled=false이면 탐지를 수행하지 않는다 + +### Lateral Thinking + +- [ ] 패턴 감지 시 패턴 친화도 순으로 페르소나가 선택된다 +- [ ] 이전에 시도한 페르소나는 제외된다 +- [ ] 선택된 페르소나의 내장 prompt로 `belt agent -p`를 호출하여 lateral_plan을 생성한다 +- [ ] lateral_plan이 retry 시 handler prompt에 추가 컨텍스트로 주입된다 +- [ ] hitl 도달 시 모든 lateral 시도 이력이 hitl_notes에 첨부된다 +- [ ] lateral.enabled=false이면 lateral plan 없이 기존 escalation만 적용된다 +- [ ] lateral.max_attempts를 초과하면 더 이상 페르소나를 시도하지 않는다 + +### 이벤트 + +- [ ] 탐지 이벤트가 transition_events에 event_type='stagnation'으로 기록된다 +- [ ] evidence에 각 judge별 score가 포함된다 +- [ ] lateral plan과 페르소나 정보가 event detail에 포함된다 + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — 설계 철학 #11 +- [Daemon](./daemon.md) — 실행 루프 통합 지점 +- [QueuePhase 상태 머신](./queue-state-machine.md) — escalation 정책 +- [Data Model](./data-model.md) — StagnationPattern enum, HitlReason 확장 +- [실패 복구와 HITL](../flows/04-failure-and-hitl.md) — 실패 경로 통합 diff --git a/spec/draft/flows/01-setup.md b/spec/draft/flows/01-setup.md new file mode 100644 index 0000000..2257e2f --- /dev/null +++ b/spec/draft/flows/01-setup.md @@ -0,0 +1,119 @@ +# Flow 1: 온보딩 — workspace 등록 → DataSource 설정 → Agent 초기화 + +> 사용자가 workspace를 등록하고, DataSource별 워크플로우를 설정하면 자동화가 시작된다. + +--- + +## 1. Workspace 등록 + +```bash +belt workspace add --config workspace.yaml +``` + +### workspace.yaml + +```yaml +name: "auth-project" +concurrency: 2 # workspace 레벨: 동시 Running 아이템 수 + +sources: + github: + url: https://github.com/org/repo + scan_interval_secs: 300 + + states: + analyze: + trigger: { label: "belt:analyze" } + handlers: + - prompt: "이슈를 분석하고 구현 가능 여부를 판단해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + gh issue edit $ISSUE --remove-label "belt:analyze" -R $REPO + gh issue edit $ISSUE --add-label "belt:implement" -R $REPO + + implement: + trigger: { label: "belt:implement" } + handlers: + - prompt: "이슈를 구현해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + TITLE=$(echo $CTX | jq -r '.issue.title') + gh pr create --title "$TITLE" --body "Closes #$ISSUE" -R $REPO + gh issue edit $ISSUE --remove-label "belt:implement" -R $REPO + gh issue edit $ISSUE --add-label "belt:review" -R $REPO + + review: + trigger: { label: "belt:review" } + handlers: + - prompt: "PR을 리뷰하고 품질을 평가해줘" + on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + gh issue edit $ISSUE --remove-label "belt:review" -R $REPO + gh issue edit $ISSUE --add-label "belt:done" -R $REPO + + escalation: + 1: retry + 2: retry_with_comment + 3: hitl + terminal: skip # hitl timeout 시 적용 (skip 또는 replan) + +runtime: + default: claude + claude: + model: sonnet +``` + +### workspace = 1 repo + +workspace는 하나의 외부 레포와 1:1로 대응한다. GitHub 기준으로는 1 workspace = 1 GitHub repo. 다른 DataSource 타입도 해당 시스템에서 "레포"에 해당하는 단위와 1:1 매핑. + +### 기대 동작 + +``` +1. DB에 workspace 등록 +2. workspace 디렉토리 생성 (~/.belt/workspaces/auth-project/) +3. DataSource 인스턴스 생성 + Daemon에 등록 +4. AgentRuntime 바인딩 (RuntimeRegistry 구성) +5. per-workspace cron seed (evaluate, gap-detection, knowledge-extract) +6. Agent 워크스페이스 초기화 확인 +``` + +--- + +## 2. 컨벤션 부트스트랩 + +레포의 `.claude/rules/`가 비어있다면 기술 스택 기반 컨벤션 자동 생성. + +``` +1. .claude/rules/ 존재 확인 → 있으면 skip +2. 기술 스택 추출 → 카테고리별 컨벤션 제안 (대화형) +3. 사용자 승인 → PR로 커밋 +``` + +--- + +## 3. Workspace 관리 + +```bash +belt workspace update --config '' +belt workspace config # 유효 설정 조회 +belt workspace remove # cascade 삭제 (외부 시스템 데이터는 유지) +``` + +--- + +### 관련 문서 + +- [DataSource](../concerns/datasource.md) — 상태 기반 워크플로우 정의 +- [AgentRuntime](../concerns/agent-runtime.md) — RuntimeRegistry 구성 +- [Agent](../concerns/agent-workspace.md) — Agent 워크스페이스 초기화 +- [Cron 엔진](../concerns/cron-engine.md) — per-workspace cron seed diff --git a/spec/draft/flows/02-spec-lifecycle.md b/spec/draft/flows/02-spec-lifecycle.md new file mode 100644 index 0000000..60025f2 --- /dev/null +++ b/spec/draft/flows/02-spec-lifecycle.md @@ -0,0 +1,119 @@ +# Flow 2: 스펙 생명주기 — 등록 → 이슈 분해 → 완료 + +> 사용자가 디자인 스펙을 등록하면, 이슈가 자동 생성되어 파이프라인에 진입하고, Cron 품질 루프가 스펙 완료까지 감시한다. + +--- + +## Spec Lifecycle + +``` +Draft ──→ Active ←──→ Paused + │ + ▼ + Completing + │ + ▼ + Completed (terminal) + +Any ──→ Archived (soft delete) +Archived ──resume──→ Active (복구) +``` + +| 상태 | 가능한 전이 | CLI | +|------|------------|-----| +| Draft | → Active | `spec add` | +| Active | → Paused, → Completing(자동), → Archived | `spec pause`, `spec remove` | +| Paused | → Active, → Archived | `spec resume`, `spec remove` | +| Completing | → Active(gap 발견), → Completed(HITL 승인) | 자동 | +| Completed | → Archived | `spec remove` | +| Archived | → Active | `spec resume` | + +--- + +## 등록 → 이슈 분해 + +``` +/spec add [file] + → 필수 섹션 검증 (개요, 요구사항, 아키텍처, 테스트, 수용 기준) + → DB에 저장 (status: Active) + → 스펙 분해 → 이슈 자동 생성 + → 각 이슈에 trigger 라벨 (예: belt:analyze) 부착 + → DataSource.collect()가 감지 → 파이프라인 진입 +``` + +### 스펙 분해 전략 + +스펙 분해는 **built-in skill**로 제공되며, 커스텀 오버라이드가 가능하다. + +``` +기본 분해: acceptance criteria 기반 + → 각 수용 기준을 독립 이슈로 분해 + → 이슈 간 의존성 자동 추론 (공유 파일/모듈 기반) + +커스텀 분해: per-workspace 또는 per-user skill 오버라이드 + → ~/.belt/workspaces//skills/decompose/ 에 커스텀 skill 배치 + → 기본 skill 대신 커스텀 skill 실행 +``` + +분해 결과는 사용자에게 제시 → 확인/수정 후 이슈 생성 (대화형). +잘못 분해된 경우 `/spec update`로 스펙 수정 → gap-detection이 재평가. + +--- + +## 스펙 완료 판정 + +스펙 완료는 Cron 품질 루프가 담당: + +``` +gap-detection cron (1시간): + → 스펙 vs 현재 코드 비교 + → gap 발견 → 새 이슈 생성 → 파이프라인 재진입 + → gap 없음 + 모든 linked issues Done → Completing 상태로 전이 + +on_spec_completing: + → TestRunner: spec.test_commands 실행 + → 실패 → 새 이슈 생성 → Active로 복귀 + → 성공 → HITL: 최종 확인 (approve / request-changes) +``` + +> **Completing 전이 조건**: gap-detection이 gap을 발견하지 못하고, 해당 스펙에 linked된 모든 이슈가 Done 상태일 때 자동으로 Completing 상태로 전이한다. + +--- + +## 다중 스펙 우선순위 + +스펙 등록 시점에 Agent가 기존 Active 스펙과의 관계를 판단한다: + +``` +/spec add [file] + → Agent: 기존 Active 스펙과 충돌/의존성 분석 + → 독립: 병렬 실행 가능 (concurrency 제한 내) + → 의존: DependencyGuard 등록 → 선행 스펙 완료 후 실행 + → 충돌: 같은 파일/모듈 영향 → HITL 요청 (사용자 판단) +``` + +판단 기준: 스펙의 대상 모듈/파일 경로, acceptance criteria의 겹침 여부. + +> **DependencyGuard**: 스펙 간 의존 관계를 추적하는 메커니즘. 의존 스펙의 이슈가 선행 스펙 완료 전에 실행되지 않도록 큐 스케줄링에서 제어한다. 충돌이 감지되면 (같은 파일/모듈에 영향) HITL 이벤트를 생성하여 사용자에게 판단을 요청한다. 내부적으로는 `belt queue dependency add/remove` CLI로 관리한다. + +--- + +## /spec 통합 커맨드 + +``` +/spec → 목록 +/spec add [file] → 등록 +/spec update → 수정 +/spec status → 진행도 상세 +/spec remove → Archived +/spec pause → 일시정지 +/spec resume → 재개 +``` + +--- + +### 관련 문서 + +- [이슈 파이프라인](./03-issue-pipeline.md) — 분해된 이슈의 처리 흐름 +- [실패 복구와 HITL](./04-failure-and-hitl.md) — 완료 확인 HITL +- [Cron 엔진](../concerns/cron-engine.md) — gap-detection 품질 루프 diff --git a/spec/draft/flows/03-issue-pipeline.md b/spec/draft/flows/03-issue-pipeline.md new file mode 100644 index 0000000..bd413ef --- /dev/null +++ b/spec/draft/flows/03-issue-pipeline.md @@ -0,0 +1,110 @@ +# Flow 3: 이슈 파이프라인 — 컨베이어 벨트 + +> 이슈가 DataSource의 상태 정의에 따라 자동으로 처리되고, Done이 다음 단계를 트리거한다. + +--- + +## 컨베이어 벨트 흐름 + +``` +belt:analyze 감지 → [analyze handlers] → evaluate → on_done script → belt:implement 부착 + │ +belt:implement 감지 → [implement handlers] → evaluate → on_done script → belt:review 부착 + │ +belt:review 감지 → [review handlers] → evaluate → on_done script → belt:done 부착 +``` + +각 구간은 독립적인 QueueItem. 되돌아가지 않고, 항상 새 아이템으로 다음 구간에 진입. + +--- + +## 단일 구간 상세 + +``` +DataSource.collect(): trigger 조건 매칭 (예: belt:analyze 라벨) + │ + ▼ + Pending → Ready → Running (자동 전이, concurrency 제한) + │ + │ ① worktree 생성 (인프라, 또는 retry 시 기존 보존분 재사용) + │ ② on_enter script 실행 (정의된 경우) + │ ③ handlers 순차 실행: + │ prompt → AgentRuntime.invoke() (worktree 안에서) + │ script → bash (WORK_ID + WORKTREE 주입) + │ + ├── 전부 성공 → Completed + │ │ + │ ▼ + │ evaluate cron (per-item, force_trigger로 즉시 실행 + 주기 폴링 하이브리드): + │ "이 아이템의 결과가 충분한가?" + │ ├── Done → on_done script 실행 → worktree 정리 + │ │ └── script 실패 → Failed (로그 기록, 재시도 가능) + │ └── HITL → HITL 이벤트 생성 → 사람 대기 (worktree 보존) + │ + └── 실패 (handler 또는 on_enter) + │ + ▼ + Stagnation Analyzer (항상 실행): + CompositeSimilarity로 outputs/errors 유사도 분석 + 패턴 감지 시 → LateralAnalyzer가 페르소나로 대안 분석 + → lateral_plan 생성 + │ + ▼ + Escalation (failure_count 기반, lateral_plan 포함): + ├── retry → lateral_plan 주입, 재시도, worktree 보존 + ├── retry_with_comment → lateral_plan 주입, on_fail + 재시도 + └── hitl → lateral_report 첨부, on_fail + HITL 생성 + └── 사람: done/retry/skip/replan + └── timeout → terminal (skip 또는 replan) +``` + +--- + +## on_done script 예시 + +on_done script는 `belt context`로 필요한 정보를 조회하여 외부 시스템에 결과를 반영한다. + +```yaml +on_done: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + TITLE=$(echo $CTX | jq -r '.source_data.issue.title // .issue.title') + gh pr create --title "$TITLE" --body "Closes #$ISSUE" -R $REPO + gh issue edit $ISSUE --remove-label "belt:implement" -R $REPO + gh issue edit $ISSUE --add-label "belt:review" -R $REPO +``` + +Daemon이 주입하는 환경변수는 `WORK_ID`와 `WORKTREE`뿐. 이슈 번호, 레포 URL 등은 `belt context`로 직접 조회한다. + +--- + +## 피드백 루프 + +### PR review comment (changes-requested) + +``` +DataSource.collect()가 changes-requested 감지 + → 새 아이템 생성 → handlers 실행 → 수정 반영 +``` + +### /spec update + +``` +스펙 변경 → on_spec_active → Cron(gap-detection) 재평가 + → gap 발견 시 새 이슈 생성 → 파이프라인 재진입 +``` + +### 핵심 원칙 + +**스펙 = 계약**. 계약이 바뀌어야 하면 `/spec update`. 계약 범위 내 작업이면 이슈 등록. + +--- + +### 관련 문서 + +- [DataSource](../concerns/datasource.md) — 상태 기반 워크플로우 + context 스키마 +- [실패 복구와 HITL](./04-failure-and-hitl.md) — escalation 정책 +- [Stagnation Detection](../concerns/stagnation.md) — 실패 패턴 감지 +- [Cron 엔진](../concerns/cron-engine.md) — evaluate cron + 품질 루프 diff --git a/spec/draft/flows/04-failure-and-hitl.md b/spec/draft/flows/04-failure-and-hitl.md new file mode 100644 index 0000000..e73849f --- /dev/null +++ b/spec/draft/flows/04-failure-and-hitl.md @@ -0,0 +1,212 @@ +# Flow 4: 실패 복구와 HITL + +> handler 실패 시 stagnation 분석 + lateral thinking으로 사고를 전환하여 재시도하고, evaluate가 HITL로 분류하면 사람의 판단을 요청한다. + +--- + +## 실패 경로 + +``` +handler 또는 on_enter 실행 실패 + │ + ▼ +Stagnation Analyzer (항상 실행): + ① ExecutionHistory 구성 + outputs = DB history.summary (별도) + errors = DB history.error (별도) + │ + ▼ + ② StagnationDetector.detect + CompositeSimilarity (ExactHash + TokenFingerprint + NCD) + outputs → SPINNING? OSCILLATION? + errors → SPINNING? (별도 검사) + drifts → NO_DRIFT? DIMINISHING? + │ + ├── 패턴 없음 ────────────────────── escalation 적용 (lateral 없이) + │ + └── 패턴 감지 ─┐ + ▼ + ③ LateralAnalyzer + 패턴 → 페르소나 선택 (이전 시도 제외) + SPINNING → HACKER + OSCILLATION → ARCHITECT + NO_DRIFT → RESEARCHER + DIMINISHING → SIMPLIFIER + 복합 → CONTRARIAN + belt agent -p → lateral_plan 생성 + │ + ▼ + ④ Escalation (failure_count 기반, lateral_plan 포함): + │ + ├── retry → lateral_plan 주입, 재시도 (on_fail 안 함, worktree 보존) + ├── retry_with_comment → lateral_plan 주입, on_fail script 실행, 재시도 (worktree 보존) + └── hitl → lateral_report 첨부, on_fail script 실행, HITL 이벤트 (worktree 보존) + └── 사람 응답: done / retry / skip / replan + └── timeout → terminal 액션 (skip 또는 replan) +``` + +`retry`만 on_fail을 실행하지 않는다. "조용한 재시도"로 외부 시스템에 노이즈를 주지 않는다. + +--- + +## Lateral Plan 주입 예시 + +retry로 생성된 새 아이템이 다시 Running에 진입하면, lateral_plan이 handler prompt에 추가 컨텍스트로 주입된다: + +``` +원래 handler prompt: + "이슈를 구현해줘" + +lateral retry 시 합성: + "이슈를 구현해줘 + + ⚠ Stagnation Analysis (attempt 2/3) + Pattern: SPINNING | Persona: HACKER + + 실패 원인: 이전 2회 시도에서 동일한 컴파일 에러 반복 + error[E0433]: cannot find type Session in auth::middleware + 대안 접근법: 기존 Session 직접 구현 대신 tower-sessions crate 활용 + 실행 계획: + 1. Cargo.toml에 tower-sessions 추가 + 2. Session 타입 참조를 교체 + 3. middleware에 SessionManagerLayer 등록 + 주의: 이전과 동일한 접근은 같은 실패를 반복합니다" +``` + +--- + +## Escalation 정책 (workspace yaml 소유) + +```yaml +sources: + github: + escalation: + 1: retry + 2: retry_with_comment + 3: hitl + terminal: skip # hitl timeout 시 (skip 또는 replan) + +stagnation: + enabled: true + similarity: + - judge: exact_hash + weight: 0.5 + - judge: token_fingerprint + weight: 0.3 + - judge: ncd + weight: 0.2 + lateral: + enabled: true + max_attempts: 3 +``` + +- escalation 레벨은 기존과 동일 (failure_count 기반) +- stagnation + lateral은 **모든 retry의 품질을 투명하게 높이는 내장 레이어** +- `stagnation.enabled: false`이면 lateral 없이 기존 v5 동작 + +### on_fail script 예시 + +```yaml +on_fail: + - script: | + CTX=$(belt context $WORK_ID --json) + ISSUE=$(echo $CTX | jq -r '.source_data.issue.number // .issue.number') + REPO=$(echo $CTX | jq -r '.source.url') + FAILURES=$(echo $CTX | jq '[.history[] | select(.status=="failed")] | length') + gh issue comment $ISSUE --body "실패 (시도 횟수: $FAILURES)" -R $REPO +``` + +--- + +## HITL (Human-in-the-Loop) + +### 생성 경로 + +| 경로 | 트리거 | +|------|--------| +| Escalation | handler/on_enter 실패 → failure_count=3 → hitl | +| evaluate | handler 성공 → evaluate가 "사람이 봐야 한다" 판단 | +| 스펙 완료 | 모든 linked issues Done → 최종 확인 요청 | +| 충돌 | DependencyGuard가 스펙 충돌 감지 | + +### HITL에 lateral report 첨부 + +hitl에 도달하면 지금까지의 모든 lateral 시도 이력이 `hitl_notes`에 첨부된다: + +``` +HITL Event: + work_id: github:org/repo#42:implement + reason: retry_max_exceeded + hitl_notes: + "Stagnation Report: + pattern: SPINNING (3회 유사 에러 — CompositeSimilarity score: 0.92) + + attempt 1: compile error (Session not found) + attempt 2: HACKER 제안 → tower-sessions 시도 → 다른 에러 발생 + attempt 3: CONTRARIAN 제안 → trait object 시도 → 컴파일 성공, 테스트 실패 + + 2회 접근 전환 후에도 미해결. 구조적 문제일 가능성." +``` + +사람이 lateral report를 참고하여 더 정확한 판단을 내릴 수 있다. + +### 응답 경로 + +``` +사용자 응답 (TUI / CLI / /agent 세션) + → belt hitl respond --choice N + → 라우팅: + "done" → on_done script 실행 + ├── script 성공 → Done (worktree 정리) + └── script 실패 → Failed (worktree 보존, 로그 기록) + "retry" → 새 아이템 생성 → Pending (worktree 보존) + "skip" → Skipped (worktree 정리) + "replan" → replan 처리 (아래 참조) +``` + +### Replan + +``` +replan 요청 (사람 응답 또는 hitl timeout) + → replan_count 증가 (max 3) + → max 초과 시: Skipped (worktree 정리) + → max 이내: + 1. HitlReason::SpecModificationProposed 이벤트 생성 + 2. Failed 전이 (worktree 보존) + 3. Agent가 실패 컨텍스트 + lateral report를 분석하여 스펙 수정 제안 + 4. 사용자가 /spec update로 스펙 수정 → 새 이슈 생성 → 파이프라인 재진입 +``` + +### 타임아웃 + +``` +기본: 24시간 +초과 시: hitl-timeout cron (5분 주기)이 감지 + → escalation.terminal 설정에 따라: + skip → Skipped (worktree 정리) + replan → replan 처리 (위 참조) +``` + +`hitl_terminal_action`은 `EscalationAction` enum 타입 (#720). + +--- + +## Graceful Shutdown + +``` +SIGINT → on_shutdown: + 1. Running 아이템 완료 대기 (timeout: 30초) + → timeout 초과: Pending으로 롤백, worktree 보존 + 2. Cron engine 정지 +``` + +--- + +### 관련 문서 + +- [Stagnation Detection](../concerns/stagnation.md) — Composite Similarity + Lateral Thinking +- [Daemon](../concerns/daemon.md) — Executor.handle_failure 상세 +- [DataSource](../concerns/datasource.md) — escalation 정책 + on_fail script +- [Agent](../concerns/agent-workspace.md) — 대화형 에이전트 (HITL 응답 경로 포함) +- [이슈 파이프라인](./03-issue-pipeline.md) — 실패가 발생하는 실행 흐름 +- [Data Model](../concerns/data-model.md) — HitlReason, EscalationAction, Persona enum diff --git a/spec/draft/flows/05-monitoring.md b/spec/draft/flows/05-monitoring.md new file mode 100644 index 0000000..cbe509a --- /dev/null +++ b/spec/draft/flows/05-monitoring.md @@ -0,0 +1,252 @@ +# Flow 5: 모니터링 — 칸반 보드 + 시각화 + +> 사용자가 다수 workspace x 다수 스펙의 전체 진행 상황을 TUI, CLI, /agent 세션에서 일관되게 확인한다. + +--- + +## 1. 인터페이스 3개 레이어 + +| 레이어 | 진입 | 특징 | +|--------|------|------| +| TUI Dashboard | `belt dashboard` | ratatui, 실시간 갱신, 키보드 네비게이션 | +| CLI 출력 | `belt status --format rich` | 정적 스냅샷, 코어 CLI | +| Agent 세션 | `belt agent` → "보드 보여줘" | 자연어, 읽기 전용 조회의 주 인터페이스 | + +> **Phase 구분**: `status`, `spec status` 등 코어 CLI는 Phase 1로 직접 구현. `board`, `decisions`, `logs` 등 읽기 전용 조회는 `/agent`가 흡수 (Phase 2, 필요 시 독립 CLI 추가). + +--- + +## 2. TUI Dashboard + +### AllWorkspaces 뷰 (기본) + +``` +┌─ Workspaces ─────┐┌─ Active Items ─────────┐┌─ Runtime ─────────────┐ +│ ● auth-project ││ #42 Completed (eval) ││ claude/sonnet 12 OK │ +│ ○ backend-tasks ││ #44 Running ││ Tokens: 45.2K / 1h │ +│ ││ ││ Avg: 4m 32s │ +├───────────────────┤├────────────────────────┤├───────────────────────┤ +│ Logs ││ DataSource ││ Scripts (1h) │ +│ 14:30 done #42 ││ github ● connected ││ on_done 8 ok │ +│ 14:25 eval .. ││ scan: 30s ago ││ on_fail 1 ok │ +│ 14:20 skip ││ ││ evaluate 12 ok │ +│ 14:15 ⚠ fail #39 ││ ││ ⚠ failed 1 │ +└───────────────────┘└────────────────────────┘└───────────────────────┘ +``` + +### PerWorkspace 뷰 (Tab 전환) + +``` +┌─ Board ──────────────────────────────┐┌─ Active ──────────────┐ +│ auth-v2 ████████░░ 60% (3/5) ││ #44 Running │ +│ ✅ #42 JWT middleware ││ claude/sonnet │ +│ ✅ #43 Token API ││ 3m elapsed │ +│ 🔄 #44 Session adapter (running) ││ │ +│ ⏳ #45 Error handling (dep: #44) ││ #42 Completed (eval) │ +│ ⚠ #39 Auth refactor (failed) ││ evaluate 대기 │ +│ ⏳ #46 Missing tests ││ │ +├──────────────────────────────────────┤├────────────────────────┤ +│ Orphan: 0 | HITL: 1 | Stag: 0 ││ Logs │ +│ Kanban: 0P | 0Re | 1Ru | 1C | 2D ││ ... │ +└──────────────────────────────────────┘└────────────────────────┘ +``` + +> **v6**: Stagnation 카운터 추가 — 현재 stagnation이 감지된 아이템 수를 표시. +> **Kanban 약어**: P=Pending, Re=Ready, Ru=Running, C=Completed, D=Done, H=HITL, S=Skipped, F=Failed + +### 전이 타임라인 (ItemDetail 오버레이, Enter) + +``` +┌─ #42 JWT middleware ─────────────────────────┐ +│ Phase: Done | Runtime: claude/sonnet │ +│ │ +│ Timeline: │ +│ 14:00 ○ Pending ← github.collect() │ +│ 14:00 ○ Ready ← auto │ +│ 14:01 ○ Running │ +│ ├ worktree: /tmp/belt/auth-42 │ +│ └ handler: claude/sonnet (1.2K, 6m) │ +│ 14:07 ○ Completed ← handlers 성공 │ +│ 14:07 ○ evaluate → Done │ +│ 14:07 ○ on_done script (exit 0, 2s) │ +│ 14:07 ● Done │ +│ └ worktree 정리 │ +└───────────────────────────────────────────────┘ +``` + +### 실패 + Lateral Thinking 타임라인 (v6) + +``` +┌─ #39 Auth refactor ──────────────────────────┐ +│ Phase: HITL | Runtime: claude/sonnet │ +│ Stagnation: SPINNING (score: 0.95) │ +│ │ +│ Timeline: │ +│ 13:00 ○ Pending ← github.collect() │ +│ 13:00 ○ Running (attempt 1) │ +│ └ handler: compile error │ +│ 13:08 ⟳ SPINNING detected (score: 0.95) │ +│ └ lateral: HACKER 페르소나 │ +│ └ plan: tower-sessions crate 시도 │ +│ 13:08 ○ Running (attempt 2, lateral) │ +│ └ handler: 다른 에러 (progress!) │ +│ 13:18 ⟳ retry_with_comment (2/3) │ +│ └ lateral: CONTRARIAN 페르소나 │ +│ └ plan: trait object 접근 │ +│ 13:18 ○ Running (attempt 3, lateral) │ +│ └ handler: 컴파일 성공, 테스트 실패 │ +│ 13:28 ● HITL (3/3) │ +│ └ lateral report 첨부 │ +│ └ 2회 사고 전환 후에도 미해결 │ +│ │ +│ Actions: [d] done [r] retry [s] skip │ +└───────────────────────────────────────────────┘ +``` + +### 키보드 + +| 키 | 동작 | +|----|------| +| j/k, ↑/↓ | 아이템 이동 | +| ←/→ | workspace 전환 | +| Tab | AllWorkspaces ↔ PerWorkspace | +| Enter | 상세 / 전이 타임라인 | +| h | HITL 오버레이 | +| s | Spec 상세 | +| d | 판단 이력 | +| R | 새로고침 | + +--- + +## 3. CLI 출력 + +### `--format` 옵션 (CLI 공통) + +| 값 | 용도 | +|---|------| +| `text` | 기본 텍스트 (기존 호환) | +| `json` | 구조화된 JSON (Agent 파싱용) | +| `rich` | 색상 + 박스 + 진행률 바 (터미널용) | + +모든 CLI 서브커맨드(status, board, spec list, spec status, queue list 등)에 적용. + +### `belt status --format rich` + +``` +● belt daemon (uptime 2h 15m) + +Workspaces: + auth-project ● active queue: 1P 1R 1C 2D 1F specs: 2/3 stag: 0 + backend-tasks ● active queue: 0P 0R 0C 5D specs: 1/1 ✓ + +Runtime: claude/sonnet (45.2K tokens/1h) +HITL: 1 pending ⚠ +Failed: 1 ⚠ +Stagnation: 0 +Next evaluate: 25s +``` + +### `belt spec status --format rich` + +``` +auth-v2 Auth Module v2 +Status: Active | Runtime: claude/sonnet +Progress: ████████░░░░ 60% (3/5) + +Issues: + ✅ #42 JWT middleware Done 6m 1.2K tokens + ✅ #43 Token API Done 8m 1.8K tokens + 🔄 #44 Session adapter Running 3m ... + ⚠ #39 Auth refactor Failed — on_done script 실패 + ⏳ #45 Error handling Pending dep:#44 + ⏳ #46 Missing tests Pending + +Acceptance Criteria: + ✅ POST /auth/login → JWT 반환 (200) + ✅ 만료 토큰 → 401 반환 + ⬜ POST /auth/refresh → 새 토큰 반환 + ⬜ cargo test -p auth 전체 통과 + +Dependencies: + #45 depends on #44 (shared: src/auth/session.rs) +``` + +--- + +## 4. TUI 추가 패널 + +``` +┌─ Runtime ──────────────────┐ ┌─ DataSource ────────────────┐ +│ claude/sonnet 12 runs OK │ │ github ● connected │ +│ claude/opus 2 runs OK │ │ last scan: 30s ago │ +│ Tokens: 45.2K in / 12.1K │ │ │ +│ Avg duration: 4m 32s │ │ │ +└────────────────────────────┘ └─────────────────────────────┘ + +┌─ Scripts (1h) ───────────────────┐ +│ on_done 8 ok │ +│ on_fail 1 ok │ +│ on_enter 3 ok │ +│ evaluate 12 ok 1 hitl │ +│ ⚠ on_done 1 failed │ +│ stagnation 2 detected │ +│ lateral 2 plans │ +└──────────────────────────────────┘ +``` + +--- + +## 5. HITL 알림 + +HITL 이벤트가 생성되면 사용자에게 다음 경로로 알린다: + +| 경로 | 방법 | +|------|------| +| TUI Dashboard | HITL 카운터 실시간 갱신 (`HITL: 1 pending ⚠`) | +| CLI | `belt status`에 경고 표시, `belt hitl list`로 조회 | +| /agent 세션 | 진입 시 HITL 대기 목록 자동 표시 | +| on_fail script | escalation=hitl 시 실행 — GitHub 코멘트 등으로 외부 알림 가능 | + +별도 push 알림(Slack, email)은 on_fail/on_done script에서 직접 구현한다 (webhook 호출 등). + +> **v6**: Stagnation으로 HITL에 진입한 경우 `HitlReason::StagnationDetected`와 함께 lateral report(시도한 페르소나, 각 분석 결과)가 표시되어 사용자가 지금까지의 접근 전환 이력을 참고할 수 있다. + +--- + +## 6. 데이터 요구사항 + +```sql +-- 아이템별 전이 이벤트 (append-only) +transition_events ( + id TEXT PRIMARY KEY, + work_id TEXT NOT NULL, + source_id TEXT NOT NULL, -- 계보 추적 + event_type TEXT NOT NULL, -- phase_enter, handler, evaluate, on_done, on_fail, on_enter, stagnation + phase TEXT, -- Pending, Ready, Running, Completed, Done, HITL, Failed, Skipped + detail TEXT, -- script exit code, prompt result, error message, stagnation evidence + created_at TEXT NOT NULL +) + +-- token 사용량 (AgentRuntime 실행마다 기록) +token_usage ( + id TEXT PRIMARY KEY, + work_id TEXT NOT NULL, + workspace TEXT NOT NULL, + runtime TEXT NOT NULL, -- claude, gemini, codex + model TEXT, -- sonnet, opus, haiku + input_tokens INTEGER, + output_tokens INTEGER, + duration_ms INTEGER, + created_at TEXT NOT NULL +) +``` + +--- + +### 관련 문서 + +- [DESIGN-v6](../DESIGN-v6.md) — QueuePhase 상태 머신 +- [Stagnation Detection](../concerns/stagnation.md) — 반복 패턴 감지 시각화 +- [스펙 생명주기](./02-spec-lifecycle.md) — 스펙 진행률 +- [실패 복구와 HITL](./04-failure-and-hitl.md) — HITL 오버레이 +- [CLI 레퍼런스](../concerns/cli-reference.md) — 전체 커맨드 트리 From 823c507287917d9830d0a4fd0446c43b20131a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= <66245186+kys0213@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:51:27 +0900 Subject: [PATCH 2/2] docs: add spec hierarchy rule and refine DESIGN-v6 abstraction level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .claude/rules/spec-hierarchy.md with paths: spec/**/*.md Defines L1(Design)/L2(Concern)/L3(Flow) layering rules - DESIGN-v6: add Actor section (7 actors with roles) - DESIGN-v6: add external system integration table (trait boundaries only) - DESIGN-v6: remove concrete implementations (gh CLI, rusqlite etc.) from design level — delegated to concern documents Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/rules/spec-hierarchy.md | 60 +++++++++++++++++++++++++++++++++ spec/draft/DESIGN-v6.md | 30 +++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .claude/rules/spec-hierarchy.md diff --git a/.claude/rules/spec-hierarchy.md b/.claude/rules/spec-hierarchy.md new file mode 100644 index 0000000..67bb933 --- /dev/null +++ b/.claude/rules/spec-hierarchy.md @@ -0,0 +1,60 @@ +--- +paths: + - "spec/**/*.md" +--- + +# Spec 문서 계층 구조 + +Belt 스펙은 3계층으로 구성된다. 각 계층은 독자와 추상화 수준이 다르다. + +## 계층 정의 + +| 계층 | 파일 | 독자 | 질문 | 추상화 수준 | +|------|------|------|------|-----------| +| **L1 — Design** | `DESIGN-v*.md` | 의사결정자, 아키텍트 | "이 시스템은 왜 이렇게 생겼는가?" | 가장 높음 | +| **L2 — Concern** | `concerns/*.md` | 구현자 | "이 관심사는 내부적으로 어떻게 동작하는가?" | 중간 | +| **L3 — Flow** | `flows/*.md` | 기획자, 사용자 | "사용자가 X를 하면 어떻게 되는가?" | 시나리오 | + +## L1 — Design (설계 개요) + +**포함**: 목표, Actor, 설계 철학, 전체 상태 흐름 다이어그램, 관심사 분리 테이블, OCP 확장점, trait 경계, 버전 간 변경 요약, 구현 순서 + +**금지**: 구체 구현 (CLI 이름, 라이브러리, SQL DDL, Rust 코드), 특정 외부 시스템의 프로토콜/인증 방식 + +**원칙**: +- trait 이름까지는 OK, 메서드 시그니처는 concern으로 +- "무엇을 추상화하는가"는 OK, "어떻게 연동하는가"는 concern으로 +- 다이어그램에 모듈/컴포넌트 이름은 OK, 내부 구현 흐름은 concern으로 + +## L2 — Concern (관심사별 상세) + +**포함**: trait 시그니처, 의사코드, DB 스키마 (DDL), 도메인 enum 정의, 설정 yaml 상세, 알고리즘 상세, 수용 기준 체크리스트, 구체 구현 방식 (CLI subprocess, in-process 등) + +**금지**: 사용자 시나리오 (flow로), 설계 철학 반복 (design 참조) + +**원칙**: +- 구현자가 이 문서만 보고 구현할 수 있어야 한다 +- 각 concern은 독립적 — 다른 concern을 읽지 않아도 이해 가능 (참조 링크는 제공) +- 수용 기준은 이 계층에 위치 + +## L3 — Flow (사용자 플로우) + +**포함**: 시나리오별 단계, 사용자 행동 → 시스템 반응, TUI/CLI 출력 예시, 에러 시나리오 + +**금지**: 내부 구현 상세 (concern으로), trait/DB 스키마 + +**원칙**: +- "사용자가 X를 하면 Y가 보인다" 수준 +- 내부 모듈이 어떻게 동작하는지는 concern 참조로 위임 + +## 정보가 중복될 때 + +동일 개념이 여러 계층에 등장하면: +- L1: 개념의 존재와 역할만 (1-2문장) +- L2: 상세 정의와 동작 방식 +- L3: 사용자 관점에서의 경험 + +예시 — Stagnation Detection: +- L1 (DESIGN): "실패 패턴을 감지하고 사고를 전환하여 재시도한다" + 전체 흐름에서의 위치 +- L2 (stagnation.md): SimilarityJudge trait, CompositeSimilarity, 알고리즘 상세, 페르소나 정의, 설정 yaml +- L3 (flow-04): "handler가 3회 실패하면 → stagnation 분석 → lateral plan이 주입된 retry → 사람에게 lateral report 포함된 HITL" diff --git a/spec/draft/DESIGN-v6.md b/spec/draft/DESIGN-v6.md index e0ca279..b6f959a 100644 --- a/spec/draft/DESIGN-v6.md +++ b/spec/draft/DESIGN-v6.md @@ -21,6 +21,36 @@ stagnation이 감지 = 실패 패턴을 분석하고, 사고를 전환하여 --- +## Actor + +| Actor | 역할 | 상호작용 | +|-------|------|---------| +| **운영자** | Belt를 설치·설정·모니터링하는 사람 | workspace.yaml 작성, `belt start`, TUI dashboard, HITL 응답 | +| **이슈 작성자** | GitHub에 이슈를 등록하는 개발자/PM | 이슈 등록 + belt 라벨 부착 → Belt가 자동 수집 | +| **Belt Daemon** | 자율 실행 프로세스 | 수집 → 전이 → 실행 → 분류 → 반영 루프 | +| **LLM Agent** | handler prompt를 실행하는 AI (Claude, Gemini, Codex) | Daemon이 subprocess로 호출, worktree 안에서 실행 | +| **GitHub** | 이슈/PR 소스 시스템 | DataSource.collect()가 `gh` CLI로 이슈 조회, on_done script가 PR 생성 | +| **Cron Engine** | 주기 작업 스케줄러 | evaluate, gap-detection, hitl-timeout 등 내부 주기 실행 | +| **Reviewer** | PR을 리뷰하는 사람 또는 Bot | changes_requested → DataSource가 감지 → 파이프라인 재진입 | + +--- + +## 외부 시스템 연동 + +Belt는 외부 시스템을 trait으로 추상화한다. 코어는 구체적 시스템을 모른다. + +| 경계 | 추상화 | 사용 지점 | +|------|--------|----------| +| **이슈 소스** | `DataSource` trait | collect(), get_context() | +| **LLM 실행** | `AgentRuntime` trait | handler prompt, evaluate, lateral plan | +| **외부 반영** | on_done/on_fail script (yaml 정의) | 결과 반영 | +| **상태 저장** | belt-infra DB 레이어 | 전체 상태 영속화 | +| **코드 격리** | belt-infra worktree 레이어 | worktree 생성/정리 | + +> **연동 원칙**: 코어는 외부 시스템의 프로토콜/인증을 모른다. trait impl이 각자의 방식으로 연동한다. 새 외부 시스템 추가 = trait impl 추가, 코어 변경 0. 구체적인 연동 방식은 각 concern 문서([DataSource](./concerns/datasource.md), [AgentRuntime](./concerns/agent-runtime.md))에서 정의한다. + +--- + ## 설계 철학 ### 1. 컨베이어 벨트