# How Double Diamond Decomposition Works in Ouroboros: Discover → Define → Design → Deliver

> Learn how Ouroboros applies Double Diamond decomposition within its `DoubleDiamond` orchestrator. Discover Define Design Deliver with event-sourced traceability and recursive capabilities.

- Repository: [Q00/ouroboros](https://github.com/Q00/ouroboros)
- Tags: deep-dive
- Published: 2026-03-14

---

**Ouroboros implements the Double Diamond decomposition through the `DoubleDiamond` orchestrator class in [`src/ouroboros/execution/double_diamond.py`](https://github.com/Q00/ouroboros/blob/main/src/ouroboros/execution/double_diamond.py), which executes the four phases sequentially while supporting recursive decomposition of non-atomic Acceptance Criteria and full event-sourced traceability.**

The Q00/ouroboros repository applies the **Double Diamond decomposition** (Discover → Define → Design → Deliver) as its core execution pipeline for transforming immutable Seeds into concrete implementations. This architectural pattern drives Phase 2 of the system’s six-phase workflow, utilizing a dedicated orchestrator that handles everything from LLM prompt management to recursive task decomposition and stagnation detection.

## The Double Diamond Pipeline Architecture

Ouroboros treats the Double Diamond as the **execution layer** that sits between the initial Seed and final implementation. According to the architecture documentation, the orchestration layer calls the four-phase cycle for every Acceptance Criterion (AC), while the execution layer handles the actual implementation.

The pipeline operates across five integrated layers:

- **Orchestration Layer**: Invokes the four-phase cycle for each AC
- **Execution Layer**: Implements the `DoubleDiamond` class with retry, back-off, and event-sourcing capabilities
- **Atomicity & Decomposition**: Checks if an AC is atomic after the **Define** phase, decomposing non-atomic tasks into child ACs
- **Resilience Layer**: Runs stagnation detection after every full cycle to trigger persona-based lateral thinking
- **Event-Sourcing Layer**: Persists each phase transition as a `BaseEvent` in the SQLite event store for full workflow replay

## Phase Enumeration and Divergent/Convergent Nature

The four phases are defined as a `StrEnum` in [`src/ouroboros/execution/double_diamond.py`](https://github.com/Q00/ouroboros/blob/main/src/ouroboros/execution/double_diamond.py) (lines 38-58) with properties that distinguish divergent exploration from convergent decision-making:

```python
class Phase(StrEnum):
    DISCOVER = "discover"
    DEFINE   = "define"
    DESIGN   = "design"
    DELIVER  = "deliver"

    @property
    def is_divergent(self) -> bool:  # Discover & Design

        return self in (Phase.DISCOVER, Phase.DESIGN)

    @property
    def is_convergent(self) -> bool:  # Define & Deliver

        return self in (Phase.DEFINE, Phase.DELIVER)

    @property
    def next_phase(self) -> Phase | None:
        return {
            Phase.DISCOVER: Phase.DEFINE,
            Phase.DEFINE:   Phase.DESIGN,
            Phase.DESIGN:  Phase.DELIVER,
            Phase.DELIVER: None,
        }[self]

```

This structure enforces the alternating pattern of divergence (exploration) and convergence (decision) that defines the Double Diamond methodology.

## Phase-Specific Prompts and Context

All phase-specific prompts are externalized in `PHASE_PROMPTS` within [`src/ouroboros/execution/double_diamond.py`](https://github.com/Q00/ouroboros/blob/main/src/ouroboros/execution/double_diamond.py) (lines 50-70). Each entry contains a system prompt, user template, output key for the `PhaseResult`, and event-key for telemetry. For example, the **Discover** phase uses:

```yaml
discover:
  system: |
    You are an expert problem analyst in the Discover phase of the Double Diamond process.
    Your role is to DIVERGE - explore the problem space widely and gather insights.
  user_template: |
    Acceptance Criterion: {current_ac}
    Execution ID: {execution_id}
    Iteration: {iteration}
    Explore this problem space. What insights, questions, challenges, and considerations emerge?
  output_key: insights
  event_data_key: insights_generated

```

## Executing the Four Phases

The `run_cycle` method (lines 885-950 in [`double_diamond.py`](https://github.com/Q00/ouroboros/blob/main/double_diamond.py)) executes the full Double Diamond workflow:

1. Emits a **cycle-started** event
2. Loops through `(DISCOVER, DEFINE, DESIGN, DELIVER)` in order
3. For each phase:
   - Builds a `PhaseContext` containing previous phase outputs
   - Executes with retry/back-off via `_execute_phase_with_retry`
   - On success, stores the `PhaseResult` and events
   - On failure, records the error, runs stagnation detection, emits **cycle-failed**, and aborts
4. After all phases succeed, runs stagnation detection once more, emits **cycle-completed**, and returns the `CycleResult`

## Atomicity Check and Recursive Decomposition

After the **Define** phase completes, `run_cycle_with_decomposition` (lines 1029-1066) invokes `check_atomicity` from [`execution/atomicity.py`](https://github.com/Q00/ouroboros/blob/main/execution/atomicity.py). If the AC is deemed non-atomic, the system calls `decompose_ac` from [`execution/decomposition.py`](https://github.com/Q00/ouroboros/blob/main/execution/decomposition.py) to create 2-5 child ACs with explicit dependencies.

The decomposition workflow follows these steps:

1. **Dependency Mapping**: The decomposition result includes `child_acs` (textual criteria) and `dependencies` (index-based tuples like `((0,), (0,1))`)
2. **Topological Sorting**: Uses `_topological_sort_to_levels` (lines 60-84) implementing Kahn’s algorithm to group independent children into execution levels for parallel processing
3. **Parallel Execution**: Each level executes via `asyncio.gather`, with `validate_child_result` ensuring child failures don't crash the parent
4. **Recursive Depth**: Recursion continues until `MAX_DEPTH = 5` is reached or the AC becomes atomic

## Resilience and Stagnation Detection

After each full cycle (or on failure), the orchestrator builds an `ExecutionHistory` from latest outputs, error signatures, and drift scores. This feeds into `StagnationDetector` (lines 1067-1085) which identifies patterns like *spinning*, *oscillation*, *no-drift*, or *diminishing-returns*, triggering lateral-thinking personas (Hacker, Researcher, etc.) to break execution deadlocks.

## Event-Sourced Traceability

Every phase and cycle emits a `BaseEvent` (e.g., `execution.phase.completed`, `execution.cycle.started`) persisted in the SQLite event store ([`persistence/event_store.py`](https://github.com/Q00/ouroboros/blob/main/persistence/event_store.py)). Because the pipeline is fully event-sourced, the entire history—from initial **Discover** insights to final **Deliver** artifacts—can be replayed, audited, or visualized in the TUI dashboard.

## Practical Implementation Examples

### Running a Double Diamond Cycle Manually

```python
import asyncio
from ouroboros.execution.double_diamond import DoubleDiamond, PhaseContext, Phase
from ouroboros.providers.litellm_adapter import LiteLLMAdapter

async def main():
    # Initialise the LLM adapter (LiteLLMAdapter wraps any supported model)

    llm_adapter = LiteLLMAdapter()

    # Create the orchestrator

    dd = DoubleDiamond(llm_adapter=llm_adapter)

    # Example Acceptance Criterion

    ac = "Implement a secure password‑reset flow for the web app"

    # Run the full Double‑Diamond cycle (no decomposition)

    result = await dd.run_cycle(
        execution_id="exec-001",
        seed_id="seed-001",
        current_ac=ac,
        iteration=1,
    )

    if result.is_ok:
        cycle = result.value
        print("✅ Cycle completed")
        print("Final deliverable:", cycle.final_output["result"])
    else:
        print("❌ Cycle failed:", result.error)

if __name__ == "__main__":
    asyncio.run(main())

```

### Running with Hierarchical Decomposition

```python
import asyncio
from ouroboros.execution.double_diamond import DoubleDiamond
from ouroboros.providers.litellm_adapter import LiteLLMAdapter

async def main():
    adapter = LiteLLMAdapter()
    dd = DoubleDiamond(llm_adapter=adapter)

    complex_ac = "Create a multi‑tenant SaaS platform with billing, role‑based access, and API integration"

    # This call will automatically:

    #   1. Run Discover & Define.

    #   2. Detect that the AC is non‑atomic.

    #   3. Decompose it into child ACs.

    #   4. Recursively run Double‑Diamond on each child.

    #   5. Aggregate the results.

    result = await dd.run_cycle_with_decomposition(
        execution_id="exec-002",
        seed_id="seed-002",
        current_ac=complex_ac,
        iteration=1,
    )

    if result.is_ok:
        print("✅ Whole workflow finished")
        for child in result.value.child_results:
            print("• Child deliverable:", child.final_output["result"])
    else:
        print("❌ Workflow failed:", result.error)

if __name__ == "__main__":
    asyncio.run(main())

```

### Inspecting Events for Debugging

```python
events = result.value.events  # List[BaseEvent] from the whole run

for ev in events:
    print(ev.type, ev.data)

```

The printed events can be fed to the TUI dashboard or persisted for audit trails.

## Summary

- **Double Diamond decomposition** in Ouroboros is implemented by the `DoubleDiamond` class in [`src/ouroboros/execution/double_diamond.py`](https://github.com/Q00/ouroboros/blob/main/src/ouroboros/execution/double_diamond.py), executing Discover → Define → Design → Deliver sequentially.
- **Divergent phases** (Discover, Design) explore widely while **convergent phases** (Define, Deliver) focus and finalize decisions.
- **Recursive decomposition** occurs after Define when `check_atomicity` determines an AC is non-atomic, spawning 2-5 child ACs with topological sorting for parallel execution.
- **Resilience mechanisms** include retry logic with back-off, stagnation detection, and validation of child results to prevent cascade failures.
- **Event sourcing** provides full traceability through `BaseEvent` emissions stored in SQLite, enabling workflow replay and audit capabilities.

## Frequently Asked Questions

### What is the difference between Discover and Design phases in Ouroboros?

**Discover** is the initial divergent phase that explores the problem space widely to gather insights, questions, and challenges regarding the Acceptance Criterion. **Design** is the second divergent phase that explores solution spaces widely after the problem has been defined and narrowed during the **Define** phase. While both are marked as `is_divergent` in the `Phase` enum, Discover focuses on *what* the problem is, while Design focuses on *how* to solve it.

### How does Ouroboros determine if an Acceptance Criterion needs decomposition?

After the **Define** phase completes, the system calls `check_atomicity` from [`src/ouroboros/execution/atomicity.py`](https://github.com/Q00/ouroboros/blob/main/src/ouroboros/execution/atomicity.py) (referenced in lines 1161-1178). This uses an LLM-based evaluation to determine if the AC is atomic (indivisible) or complex. If non-atomic, `decompose_ac` from [`execution/decomposition.py`](https://github.com/Q00/ouroboros/blob/main/execution/decomposition.py) breaks it into 2-5 child ACs with explicit dependencies, then recursively applies the Double Diamond cycle to each child until reaching atomic leaf nodes or the maximum depth of 5 levels.

### Can the Double Diamond pipeline recover from failures during execution?

Yes. The `run_cycle` method implements resilience through `_execute_phase_with_retry`, which applies back-off strategies for transient failures. If a phase fails definitively, the system emits a `cycle-failed` event, runs stagnation detection to determine if lateral thinking can resolve the blockage, and preserves all prior successful phase results in the event store for debugging or partial replay without losing progress.

### How are child Acceptance Criteria executed after decomposition?

Once decomposition generates child ACs with dependencies, the system uses `_topological_sort_to_levels` (implementing Kahn’s algorithm) to group independent children into execution levels. These levels execute in parallel using `asyncio.gather`, while dependent levels wait for their prerequisites to complete. Each child runs its own full Double Diamond cycle (Discover → Define → Design → Deliver), and `validate_child_result` ensures that individual child failures are handled gracefully without crashing the parent workflow.