Redeo Docs
DocsLADR / How to Compose Patterns

Adding Features

How to Compose Patterns

How to Compose Patterns

Every LADR pattern (ToT, reflexion, self-consistency, memory) is a phase. Real ladders combine multiple phases. This guide shows how to connect patterns via field references, jumps, and cross-ladder handoffs to build complex strategies from simple building blocks.

Connecting via field references

Patterns compose naturally because every step communicates through declared fields. A step that declares an ingest field from { stepId: draft } works regardless of how many steps sit between the declaration and the reference.

yaml
# Phase 1: ToT (generate + verify + prune)
- id: generate
  nodes: 5
  fields: [{ name: Question, type: text, from: input.context }]
  systemPrompt: "Generate an independent solution."

- id: verify
  nodes: 5
  fields:
    - { name: Candidate, type: ingest, from: { stepId: generate, loopRef: current, nodeRef: current } }
  systemPrompt: 'Score 1-5. Output JSON {"score": N}.'
  if:
    jsonMatches: { properties: { score: { minimum: 4 } } }
    then: continue
    else: abort

# Phase 2: Reflexion (synthesize → critique → loop back)
- id: synthesize
  fields:
    - { name: Survivors, type: multi_ingest, from: [{ stepId: verify, loopRef: current, nodeRef: accumulate }] }
  systemPrompt: "Merge the surviving candidates into one answer."

- id: critique
  fields:
    - { name: Answer, type: ingest, from: { stepId: synthesize, loopRef: current } }
    - { name: Lessons, type: slateRead, from: { slate: Memory, folder: lessons, file: failures.md }, fallback: "" }
  systemPrompt: 'Critique the answer considering Lessons. Output JSON {"verdict": "good"|"bad"}.'
  if:
    jsonMatches: { properties: { verdict: { enum: [good] } } }
    then: continue
    else: { jump: { stepId: generate } }   # loop back with accumulated knowledge

The critique step's backward jump to generate creates the retry loop. On the next iteration, generate sees the same Question but the slate has accumulated lessons from the failed attempt.

Adding memory to any pattern

Any pattern can be made stateful by adding slate reads and writes. The pattern doesn't change; memory is layered on top.

yaml
# 1. Declare the slate at the top level
slates:
  - title: Memory
    folders:
      - name: lessons
        tokenLimit: 500
        files: [{ name: failures.md, init: blank }]
      - name: facts
        tokenLimit: 300
        files: [{ name: core.md, init: blank }]

# 2. Add slateRead to the first step (loads prior knowledge)
steps:
  - id: generate
    fields:
      - { name: Question, type: text, from: input.context }
      - name: PriorLessons
        type: slateRead
        from: { slate: Memory, folder: lessons, file: failures.md }
        fallback: "No prior lessons."
    systemPrompt: "Generate a solution. Learn from PriorLessons."

  # ... intermediate steps ...

  # 3. Add slateWrite to a step that discovers durable facts
  - id: extract
    systemPrompt: 'Emit JSON {"fact": "..."} if you discovered something durable.'
    slateWrite:
      to: { slate: Memory, folder: facts, file: core.md }
      field: fact
      on: append
      match:
        type: object
        required: [fact]
        properties:
          fact: { type: string, minLength: 5 }

The pattern structure (ToT, reflexion, etc.) is unchanged. Memory is a cross-cutting concern that any step can opt into via slateRead (input) and slateWrite (output).

Cross-ladder composition

When a ladder grows too complex, split it into multiple published ladders and connect them via cross-ladder jumps.

yaml
# Ladder 1: @you/research-router (general router)
steps:
  - id: classify
    systemPrompt: 'Output "deep-research", "quick-answer", or "code-review".'
    if:
      in: ["deep-research"]
      then: { jump: { ladderId: "@you/deep-research" } }
      else:
        in: ["code-review"]
        then: { jump: { ladderId: "@you/code-reviewer" } }
        else: continue                          # quick-answer: handle inline

  - id: quick_answer
    fields: [{ name: Question, type: text, from: input.context }]
    systemPrompt: "Answer concisely."
yaml
# Ladder 2: @you/deep-research (specialist)
steps:
  - id: search
    nodes: 5
    # ... fan-out search
  - id: synthesize
    # ... merge results
  - id: verify
    # ... gate

Each ladder is independently testable, versionable, and publishable. The router ladder is a thin dispatcher; the specialist ladders do the heavy lifting.

passFields for data wiring:

yaml
then:
  jump:
    ladderId: "@you/deep-research"
    passFields:
      - to: Query
        from: "input.context"
      - to: Context
        from: { stepId: classify }    # pass the classification output

The target ladder's first step sees Query and Context as passField overrides, ignoring its own from: declarations for those field names.

Composition principles

1. Patterns are phases, not ladders. A ToT phase is 2-3 steps (generate, verify, synthesize). A reflexion phase is 2 steps (critique, jump). Compose phases by chaining their steps.

2. Fields are the interface. Every step declares what it needs (fields:) and what it produces (its output). Composition is just matching field references: step B ingests step A's output.

3. Gates enable branching. A gate's then: and else: actions determine which phase runs next. continue goes to the next declared step; jump skips ahead or loops back.

4. Memory is cross-cutting. Any step can read from slates (slateRead) and write to slates (slateWrite). Memory doesn't change the step structure; it adds persistence.

5. Cross-ladder jumps enable modularity. When a ladder exceeds ~10 steps, consider splitting it into multiple ladders connected by cross-ladder jumps. Each ladder should handle one concern.

6. Budgets bound the composition. No matter how many phases or ladders you compose, the spend ceiling caps total cost. Composition is safe because the runtime owns the loop.