# ADR-004: Orchestrator Decomposition via Facade Pattern

## Status

Proposed

## Context

`DESOrchestrator` in `src/des/application/orchestrator.py` is 1086 lines with multiple responsibilities: prompt rendering (DES markers, timeout warnings, full prompt assembly), step execution (turn counting, timeout monitoring, step file persistence), validation orchestration (invocation limits, prompt validation), stale detection integration, and audit logging.

This violates the Single Responsibility Principle and creates several practical problems:
- Cognitive load: understanding any single behavior requires reading the entire 1086-line file
- Merge conflicts: unrelated changes to prompt rendering and step execution touch the same file
- Test complexity: test files must import and configure the full orchestrator to test any single responsibility

Business drivers prioritized for this decision:
- **Maintainability**: primary driver -- reducing time to understand and modify orchestrator behavior
- **Testability**: secondary driver -- enabling focused unit tests per responsibility
- **Modifiability**: tertiary driver -- reducing blast radius of changes

## Decision

Decompose `DESOrchestrator` into focused services while retaining a thin facade that preserves the existing public API:

1. **`prompt_rendering_service.py`**: Owns `render_prompt()`, `render_full_prompt()`, `prepare_ad_hoc_prompt()`, and DES marker generation
2. **`step_execution_service.py`**: Owns `execute_step()`, `execute_step_with_stale_check()`, turn counter lifecycle, timeout threshold checking, step file persistence
3. **`execution_results.py`**: DTOs (`ExecuteStepResult`, `ExecuteStepWithStaleCheckResult`, `HookResult`)
4. **`audit_bridge.py`**: `_log_audit_event()` convenience function
5. **`orchestrator.py`** (retained): Thin facade delegating to services, preserving constructor signature and public methods

## Alternatives Considered

### Alternative A: Full replacement (delete orchestrator, expose services directly)

All callers would import and use the new services directly instead of going through a facade.

**Rejected because**: 31 test files import `DESOrchestrator`. Changing all callers simultaneously is a big-bang rewrite that violates the incremental delivery constraint. The facade pattern allows gradual migration.

### Alternative B: Keep single file, use regions/comments

Mark responsibility boundaries with clear section headers (already partially done with `# ===` comments).

**Rejected because**: Comments do not enforce boundaries. The file has grown from ~400 to 1086 lines over 6 months despite existing section headers. Only module boundaries prevent cross-responsibility coupling.

### Alternative C: Mixin-based decomposition

Split responsibilities into mixin classes composed into the orchestrator via multiple inheritance.

**Rejected because**: Python mixins create implicit coupling through shared `self` state. They obscure the dependency graph and make testing harder (each mixin may access any attribute set by another). Composition via separate service instances is clearer.

## Consequences

### Positive

- Each service module is under 300 lines with a single responsibility
- Services are independently testable without orchestrator setup
- The facade preserves backward compatibility -- zero breaking changes for existing tests and callers
- New features can target the specific service without understanding the full orchestrator

### Negative

- One additional level of indirection for callers using the facade
- During transition period, both facade methods and direct service methods exist
- Slightly more files to navigate (4 new files vs 1 large file)

### Quality Attribute Impact

| Attribute | Impact | Direction |
|-----------|--------|-----------|
| Maintainability (modularity) | Responsibilities isolated in focused modules | Positive |
| Testability | Each service testable in isolation | Positive |
| Performance | Negligible -- one extra method call per facade delegation | Neutral |
| Analyzability | Smaller files, clear module names | Positive |
