아키텍처
10개 크레이트로 구성된 Cargo 워크스페이스 설계: 크레이트 역할, 격리 불변식, 그리고 CLI 부트스트랩부터 멀티 레이어 에이전트 오케스트레이션을 거쳐 TUI까지 이어지는 데이터 흐름.
stepper는 Rust로 작성된 레이어드 CLI/TUI AI 코딩 에이전트로, 10개 크레이트로 구성된 Cargo 워크스페이스 구조를 갖습니다. 이 페이지에서는 크레이트 구성, CLI 부트스트랩에서 에이전트 실행까지의 데이터 흐름, 그리고 깔끔한 아키텍처 경계를 보장하는 핵심 격리 불변식을 설명합니다.
크레이트 개요
워크스페이스에는 (원래 계획된 13개 중) 10개의 크레이트가 구현되어 있으며, 각각 시스템에서 고유한 책임을 맡습니다:
| 크레이트 | 역할 |
|---|---|
stepper-protocol | 채널 타입 및 DTO: Action(TUI→core), AppEvent(core→TUI), ApprovalRequest(oneshot). serde, uuid, tokio sync 프리미티브에만 의존합니다. |
stepper-tui | 인라인 뷰포트를 갖춘 Ratatui 기반 TUI. stepper-protocol에만 의존합니다(여기에 ratatui 스택 추가). |
stepper-cli | Clap CLI 바이너리. RealCore를 연결하고(기본값=TUI 에이전트, -p 플래그=헤드리스 자동 승인 모드), auth login, config, init 명령과 MCP 서버 생명주기를 처리합니다. |
stepper-provider | LLM 프로바이더를 위한 트레이트 및 정규화 타입: ChatRequest, Message, ContentBlock, ChatEvent, Usage, StopReason, ToolSpec. HTTP를 포함하지 않으며 reqwest나 tokio-rt도 사용하지 않습니다. |
stepper-providers | 구체적인 프로바이더 어댑터(Anthropic, OpenAI 호환, Responses), 인증(API 키, OAuth Codex), reqwest+SSE를 통한 스트리밍, 키링에 토큰 저장. |
stepper-config | 설정 로딩: .stepper/ 탐색, setting.json 딥 머지, frontmatter 파싱(YAML), 치환 엔진, 모델/프로바이더 해석, JSON 스키마 검증. |
stepper-permission | 순수 권한 평가 엔진: deny > ask > allow > mode 우선순위, bash 리다이렉션/명령 파싱, 심볼릭 링크 탈출 방지, 경로 정규화. |
stepper-tools | Tool 트레이트 및 레지스트리. 9개의 내장 도구(read/write/edit/bash/search/grep/todo/web_fetch), MCP 도구 브리징, 비밀 경로 탐지, ToolCx를 통한 권한 게이팅. |
stepper-mcp | MCP 1.7 클라이언트(stdio/HTTP 전송), 도구 네임스페이싱, 타임아웃, 로컬 도구 레지스트리와의 통합. context7로 검증되었습니다. |
stepper-core | 메인 오케스트레이터: 프로바이더 해석, AgentLoop(ReAct 패턴), 멀티 스텝 파이프라인, 병렬 레이어 실행, 세션/체크포인트 관리, 핸드오프 요약, 내장 슬래시 커맨드. |
격리 불변식
세 가지 엄격한 아키텍처 경계가 cargo metadata 분석을 사용하는 CI 테스트(crates/stepper-cli/tests/isolation.rs)를 통해 강제됩니다:
stepper-tui는stepper-protocol에만 의존합니다(여기에 ratatui 추가). core, config, providers나 HTTP 접근은 없습니다.reqwest와 HTTP는stepper-providers,stepper-tools(web_fetch),stepper-mcp(HTTP 전송)에 한정됩니다.stepper-protocol과stepper-provider트레이트는 HTTP를 포함하지 않으며 tokio-rt도 사용하지 않습니다.stepper-protocol은 clap을 사용하지 않습니다(채널 계약에 CLI 파싱이 없음).
상위 수준 데이터 흐름
text
stepper-cli main.rs (#[tokio::main])
├─ build_orchestrator(model, mode, cwd)
│ Config::load → build_steps(layer frontmatter+skills) → ensure_provider(convention fallback)
│ McpManager::connect(mcpServers) → register tools to base_tools
│ ConfigProviderResolver + RuleSet + HookHost
├─ channels: mpsc<Action>(TUI→core) + mpsc<AppEvent>(core→TUI) + CancellationToken
├─ stepper-core::spawn_core(orchestrator, session, action_rx, cancel) → event_rx [RealCore]
│ while action:
│ SubmitInput → checkpoint_turn → Orchestrator.run_turn → session append/save
│ SlashCommand → commands::expand(substitution) → run_turn
│ Rewind → restore+turns truncate
│ RunShell → bash tool single-turn execution
│ Orchestrator.run_turn: SessionStart → step layers (sequence or parallel):
│ resolver.resolve(model) → Box<dyn LlmProvider>
│ base_tools.filtered(allow/deny).filter_mcp
│ ToolCx{cwd, project_root, mode, rules, approver=ChannelApprover, cancel}
│ AgentLoop.drive(system, handoff): stream→token/usage emit→compact→tool exec
│ (gate→approver) → result injection → repeat
│ emit: LayerStarted/Finished/ModelChanged/UsageUpdated/ToolCall* → handoff
└─ stepper-tui::run_tui(event_rx, action_tx, init, cancel)
blocking input thread(event::poll/read) → mpsc → select!{input, 33ms tick, AppEvent rx, cancel}
input → (mode-dependent) Action → AppState.apply_action → Effect(Send/CommitToScrollback)
ApprovalRequested(oneshot) → overlay y/a/n → reply.send (resumes agent loop)spawn_fake_core(목)는 워킹 스켈레톤 테스트를 위해 stepper-tui에 남아 있으며, CLI는 오직 RealCore(stepper-core::spawn_core)만 사용합니다. 둘 다 동일한 채널 계약을 충족하므로 서로 교체 가능합니다.핵심 타입 및 계약
- **채널 계약**(고정):
mpsc<Action>+mpsc<AppEvent>+CancellationToken. RealCore와 목 모두 이를 충족합니다. - **
LlmProvider**:chat_stream(req, cancel) → BoxStream<ChatEvent>를 구현합니다. 정규화는WireDelta→StreamAccumulator→ChatEvent경로로 이루어집니다. - **
Tool**:spec()/call(args, cx) → ToolResult를 구현합니다.ToolCx.gate(PermissionRequest)가 권한 평가와 승인 게이팅을 강제합니다. 내장 도구와 MCP 도구가 동일한 트레이트를 사용합니다. - **
ProviderResolver**: 모델 참조 →Box<dyn LlmProvider>+ModelInfo.ConfigProviderResolver가 구현합니다. - **
Approver**(tools) → **ChannelApprover**(core): Ask 결정 시AppEvent::ApprovalRequested{oneshot}를 발생시키고 → TUI 오버레이 → 사용자 응답 → 에이전트 루프를 재개합니다. - **Orchestrator / AgentLoop**:
run_turn→ 스텝 레이어(각각 독립된 컨텍스트, 프로바이더, 도구를 가짐) →AgentLoop.drive(ReAct 패턴). 핸드오프 = 자유 텍스트 요약 체인. - **세션 및 체크포인트**:
SessionStore(.stepper/sessions/<id>.json),Snapshotter(.stepper/checkpoints/<turn>/파일 복사).--resume는resume_context를 시드하고,Rewind는 복원·정리·턴 절단을 수행합니다.
고급 기능
- **병렬 레이어**:
parallel: true(그리고 선택적인parallel-max워커 상한)가 설정된 스텝은 오케스트레이터가 이전 레이어의assign_tasks목록을 워커들에게 팬아웃하게 만듭니다. 각 워커는 서브에이전트입니다. 태스크 목록이 없으면 순차 실행으로 폴백합니다. - **내장 슬래시 커맨드**:
/help,/clear(세션 초기화),/model [provider/model-id](검증 + 첫 스텝 전환 +ModelChanged이벤트),/context(컨텍스트 윈도우 요약). - **스킬**: 레이어가 frontmatter에
skills를 선언하면,SkillTool을 통해 모델이skill { name }을 호출하여 레이어별 스킬을 사용할 수 있습니다. 점진적 공개: 스킬 이름과 설명은 시스템 프롬프트에 노출되고, 본문은 필요 시 제공됩니다. - **dispatch 도구**(C4): 모델은
dispatch(...)를 호출하여 현재 레이어 이후로 병렬 서브에이전트(워커)를 생성할 수 있습니다. 오케스트레이터가 이를 활성화해야 하며, 서브에이전트는 재귀할 수 없습니다. - **프롬프트 캐싱**:
ChatRequest.cache: bool옵션이 Anthropic 프리픽스 캐싱을 활성화하며, 시스템 메시지는cache_control: ephemeral로 래핑됩니다. - **모델 기반 컴팩션**:
compaction.provider가 설정되어 있으면, 오케스트레이터는 소프트 임계값(컨텍스트의 0.70)에서 해당 프로바이더의 모델을 사용해 메시지 기록을 요약하며, 최근 6개 메시지는 그대로 유지합니다. - **권한 게이팅**:
ToolCx::gate(request)는 모드(Auto/Plan/AcceptEdits)에 대해 규칙을 평가합니다. 리다이렉션/$()를 포함한 bash 원자는 Ask를 상향시켜 명시적 승인을 요구하게 합니다. - **MCP 타임아웃**: 연결 타임아웃(기본 10초,
STEPPER_MCP_CONNECT_TIMEOUT_MS)은 시작 시 멈춘 서버를 방지합니다. 도구 호출 타임아웃(기본 120초,STEPPER_MCP_TOOL_TIMEOUT_MS)은 턴 중간 멈춤을 방지합니다.
빌드 및 테스트
sh
cargo check --workspace # Type check
cargo clippy --workspace --all-targets # Lint (0 warnings enforced)
cargo test --workspace # full network-less suite (live mcp_live/e2e skip if env unset)
cargo run # Interactive TUI (real tty + API keys required)
cargo run -- -p "..." --model anthropic/claude-x # Headless one-shot (auto-approve)
cargo run -- --resume <session-id> # Resume session
cargo run -- auth login --codex # Codex OAuth login
cargo run -- config --schema | --validate # Settings schema/validation
cargo run -- init # Scaffold .stepper/ (stepper.md + setting.json)