How Double Diamond Decomposition Works in Ouroboros: Discover → Define → Design → Deliver
Ouroboros implements the Double Diamond decomposition through the DoubleDiamond orchestrator class in 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
DoubleDiamondclass 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
BaseEventin 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 (lines 38-58) with properties that distinguish divergent exploration from convergent decision-making:
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 (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:
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) executes the full Double Diamond workflow:
- Emits a cycle-started event
- Loops through
(DISCOVER, DEFINE, DESIGN, DELIVER)in order - For each phase:
- Builds a
PhaseContextcontaining previous phase outputs - Executes with retry/back-off via
_execute_phase_with_retry - On success, stores the
PhaseResultand events - On failure, records the error, runs stagnation detection, emits cycle-failed, and aborts
- Builds a
- 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. If the AC is deemed non-atomic, the system calls decompose_ac from execution/decomposition.py to create 2-5 child ACs with explicit dependencies.
The decomposition workflow follows these steps:
- Dependency Mapping: The decomposition result includes
child_acs(textual criteria) anddependencies(index-based tuples like((0,), (0,1))) - Topological Sorting: Uses
_topological_sort_to_levels(lines 60-84) implementing Kahn’s algorithm to group independent children into execution levels for parallel processing - Parallel Execution: Each level executes via
asyncio.gather, withvalidate_child_resultensuring child failures don't crash the parent - Recursive Depth: Recursion continues until
MAX_DEPTH = 5is 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). 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
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
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
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
DoubleDiamondclass insrc/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_atomicitydetermines 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
BaseEventemissions 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 (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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →