Skip to content

Patterns Overview

ExecutionKit ships six composable pattern utilities. Each is a single async function that takes a provider and a prompt and returns a PatternResult carrying the answer, a score, accumulated cost, and per-pattern metadata. Lightweight orchestration helpers such as Router, Workflow, Plan, ApprovalGate, and evals live alongside these patterns in the public API.

Pattern Use when… Cost shape
Consensus You need agreement scoring across multiple independent factual or classification samples. O(num_samples) parallel calls.
Iterative Refinement Quality of the answer matters more than latency, and you can score it. Up to O(2 × (1 + max_iterations)) calls with the default evaluator; fewer with a non-LLM evaluator.
ReAct Tool Loop The model needs to call tools to gather information before answering. O(rounds) sequential calls; bounded by max_rounds.
Structured Output You need a JSON object or array with optional validation and repair. 1 + max_retries sequential calls in the worst case.
Pipe You want to chain patterns end-to-end with a shared budget. Sum of the individual pattern costs.
Map-Reduce You need to fan out over a collection of inputs, process each independently, then reduce to a single answer. O(len(inputs)) parallel calls for map; one call for reduce.

Choosing a pattern

flowchart TD
    A[Need an LLM answer?] --> B{Need tools
e.g. search, math, API?} B -- yes --> C[ReAct Tool Loop] B -- no --> D{Can you score
answer quality?} D -- yes --> E[Iterative Refinement] D -- no --> F{Need agreement
scoring?} F -- yes --> G[Consensus] F -- no --> K{Need JSON
with validation?} K -- yes --> L[Structured Output] K -- no --> M{Have a collection
of inputs to process?} M -- yes --> N[Map-Reduce] M -- no --> H[Single completion
via Provider directly] C --> I{Need multi-step?} E --> I G --> I L --> I N --> I I -- yes --> J[Pipe to chain them]

Common contract

Every pattern function returns a PatternResult[T]:

@dataclass(frozen=True, slots=True)
class PatternResult(Generic[T]):
    value: T                                      # the answer
    score: float | None = None                    # quality score (pattern-specific)
    cost: TokenUsage = TokenUsage()               # tokens + LLM calls used
    metadata: MappingProxyType[str, Any] = ...    # immutable, pattern-specific keys

metadata is a read-only MappingProxyType — frozen at construction. Each pattern's docstring lists its metadata keys; do not rely on undocumented ones.

Common kwargs

The reasoning patterns accept these (all optional). pipe() forwards compatible shared kwargs to each step rather than declaring them directly:

Kwarg Default Purpose
temperature pattern-specific Sampling temperature override per call.
max_tokens 4096 Per-completion token cap.
max_cost None TokenUsage budget. Raises BudgetExhaustedError when exceeded.
retry DEFAULT_RETRY RetryConfig for transient errors (429, 5xx).
trace None Optional TraceCallback receiving structured events for calls, tools, workflow steps, plan steps, approvals, cost, and latency.

max_cost enforcement uses two-phase accounting (reserve_call before the await, record_without_call after). This makes the llm_calls guard TOCTOU-safe under consensus's parallel calls and counts every dispatched wire attempt, including failed retries.

Sync wrappers

Every pattern has a _sync twin in the package root for use outside an async context:

from executionkit import (
    consensus_sync,
    refine_loop_sync,
    react_loop_sync,
    structured_sync,
    pipe_sync,
)

The wrappers raise RuntimeError if called inside a running event loop — use await directly there (e.g. Jupyter, FastAPI handlers).

Errors

All exceptions inherit from ExecutionKitError and carry .cost (the TokenUsage accumulated up to the failure) and .metadata. See API → Core for the full hierarchy.