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.
# 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 knowledgeThe 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.
# 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.
# 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."# Ladder 2: @you/deep-research (specialist)
steps:
- id: search
nodes: 5
# ... fan-out search
- id: synthesize
# ... merge results
- id: verify
# ... gateEach 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:
then:
jump:
ladderId: "@you/deep-research"
passFields:
- to: Query
from: "input.context"
- to: Context
from: { stepId: classify } # pass the classification outputThe 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.