Redeo Docs
DocsLADR / Language Overview

Foundations

Language Overview

Language Overview

LADR is a YAML dialect for writing reasoning programs. A LADR file declares the structure of a multi-step reasoning: which calls to make, in what order, what data flows between them, what conditions branch, what jumps where, and what the ladder should remember next time. A runtime engine walks the declaration and executes it. This article is the 30-second tour; the rest of the Language section documents each primitive in depth.

What a ladder is

A ladder is the atomic unit a user authors, runs, and publishes. Internally it is a YAML document with a fixed shape: a name, a target policy, a set of caller-tunable knobs, an ordered list of steps, and an exit step whose output is returned to the caller. Optionally: persistent memory stores (slates), a resource budget, trace settings.

The smallest possible ladder is one step with one field:

yaml
name: Echo
allowedTargets: { strategy: universal }
exit: answer
knobs: {}
steps:
  - id: answer
    name: Answer
    fields:
      - { name: Question, type: text, from: input.context }
    systemPrompt: "Answer the question."

Run this through the gateway and you get back the model's response in an OpenAI-compatible shape. It is functionally identical to calling the model directly. The point of LADR is what becomes possible when the ladder stops being one step.

A two-step draft-and-refine ladder already does something a single call cannot:

yaml
name: Draft and Refine
allowedTargets: { strategy: universal }
exit: refine
knobs: {}
steps:
  - id: draft
    name: Draft
    fields:
      - { name: Question, type: text, from: input.context }
    systemPrompt: "Write a first draft answering the question."

  - id: refine
    name: Refine
    fields:
      - { name: Question, type: text, from: input.context }
      - { name: Draft, type: ingest, from: { stepId: draft, loopRef: current } }
    systemPrompt: "Improve the draft. Address weaknesses. Output the final answer."

The runtime executes draft, stores its output, then executes refine with the draft injected as a field. Two LLM calls. The second sees the first. That is the foundation everything else builds on.

Why a declarative language for reasoning

A reasoning strategy is easier to analyze, share, and bound when its structure is explicit data rather than embedded in application code. LADR makes the structure of a multi-step reasoning declarative: the author states the steps, their data dependencies, and the control flow; a runtime engine assembles prompts, dispatches calls, evaluates gates, and tracks resource use.

The closest analogy is SQL. SQL is declarative — you describe the query and the database decides how to execute it; you do not write the join algorithm. LADR applies the same shape to inference-time reasoning: you describe the strategy and the runtime executes it. Like SQL, LADR is purpose-built for its domain rather than a general-purpose language.

What this buys:

  • Inspectability. Every ladder is a YAML file. You can read it, diff it, version it, render it visually, lint it.
  • Analyzability. The runtime knows what fields a step will read, what steps a jump can target, what slates a write will hit. Validation catches mistakes before a single LLM call runs.
  • Publishability. A ladder is data, not code. It can be listed in a directory, forked, priced, embedded as JSON-LD on a crawlable page.
  • Composability. Cross-ladder jumps turn published ladders into building blocks. One ladder can hand off to another mid-execution.
  • Bounded cost. Every execution runs under a spend cap. A published ladder cannot exceed its declared budget.

The atomic pieces

LADR has a strict hierarchy. Every article in this section is about one piece in this list.

PieceWhat it isWhere you author it
LadderA complete reasoning program. The whole YAML file.root
StepOne execution unit. Resolves fields, makes LLM calls, evaluates a gate, chooses the next action.under steps:
FieldOne prompt input. Renders as Name: value in the assembled prompt.under a step's fields:
NodeOne LLM call within a step. nodes: 5 fans out into 5 calls.the step's nodes: count
LoopOne full pass over the steps array. Ladders can run multiple loops.runtime; bounded by maxLoops
RecursionA step that spawns a child instance of the same ladder; child's exit output replaces the step's output.a step's recursion: block
GateA typed predicate evaluated against step output. Decides which nodes survive and which action fires next.a step's if: block
ActionWhat the runtime does after a gate evaluates: continue, jump, write, or abort.under the gate's then: or else:
KnobA caller-tunable parameter declared at config time. Same ladder, many use cases.under knobs:
SlateA persistent memory store declared at config time. Composed of folders of files plus typed metatags. Survives across calls.under slates:
MetatagA named, typed value attached to a slate file. Plus five system metatags the engine auto-maintains.under a folder or file's metatags:
ManifestA runtime snapshot of everything readable at a given step. Used by dynamic-step planners.a type: manifest field

The hierarchy in one sentence: a ladder is a list of steps; each step makes some number of LLM calls (its nodes), each call assembled from declared fields; steps can branch via gates and jumps, recurse, persist to slates, and rewrite themselves via dynamic loading.

The five power axes

Five features separate LADR from a chained-prompt library. Each axis is what makes a specific class of reasoning strategy expressible.

1. Knobs. Caller-tunable parameters declared at config time. A single ladder can serve "fast draft" and "high-stakes final" by exposing knobs the caller sets at request time. The same YAML; different cost/quality trade-off per call. Without knobs, every ladder is one strategy frozen at author time.

2. Gates and jumps. A step can declare a typed condition (if: { integerRange: [4,5] }) and what to do on match vs miss (then: continue, else: { jump: { stepId: refine } }). Backward jumps enable reflexion. Forward jumps enable early exit. Cross-ladder jumps enable composition. Without gates and jumps, every ladder runs the same fixed sequence every time.

3. Slates and writes. A persistent memory store declared at config time. Folders of plain-text files plus typed metatags. Schema-matched writes extract structured data from LLM output and persist only what conforms. Gated writes persist verbatim on a condition. Without slates, every call starts from scratch; the ladder cannot learn across invocations.

4. Retrieve. A bounded on-demand fetch loop inside a step. The LLM sees a folder index, decides what is relevant, requests specific files, gets them injected, and continues. Termination is guaranteed by maxRounds. Without retrieve, the ladder must know at config time what to read; with it, the LLM picks at runtime.

5. Dynamic steps. A step can load its own configuration at runtime, usually from the previous step's output. Combined with the manifest field (a runtime snapshot of everything readable), a ladder can adapt its strategy based on observed state rather than executing a fixed plan.

Every ladder uses some of these. The reference ladder Crucible uses all five.

What happens when you call a ladder

A caller hits the OpenAI-compatible endpoint:

bash
POST /v1/{author}/{ladder}/chat/completions
{
  "model": "gpt-4o",
  "messages": [{"role": "user", "content": "Explain the halting problem."}],
  "knobs": { "branches": 5 }
}

The runtime takes that request and runs:

  1. Load and parse the ladder config. Reject unknown top-level keys. Reject missing required fields.
  2. Validate. Twenty-five rule checks across structure, references, ranges, slates, gates, jumps, dynamic config, and budget. All errors aggregate; nothing runs until the config is clean.
  3. Resolve ceilings. Merge the ladder's declared executionBudget with platform defaults. The four ceilings: maxSpend (dollars), maxHops, maxLlmCalls, maxRecursion.
  4. Load slates into the hot tier (in-memory or Redis, depending on deployment). File contents, metatags, system indexes all become readable in sub-millisecond.
  5. Resolve knobs. Start with declared defaults. Override with caller-supplied values. Clamp out-of-range values silently. Drop unrecognized keys silently.
  6. Enter the main loop. For each loop iteration (up to maxLoops):
    • Walk steps in declaration order.
    • For each step: hop +1 (ceiling check), resolve fields, dispatch LLM calls, store outputs as numbered nodes, optionally apply schema-matched writes, optionally run retrieve loops, evaluate the gate per node and prune non-matching, fire the then: or else: action.
    • Jumps reposition the step cursor (forward skips steps; backward creates inner loops). The hop ceiling bounds the total across loops, jumps, and recursion.
  7. Resolve the exit step. Read its output from the final loop iteration. Return as the response's content.

Every step publishes lifecycle events as it runs. Studio and Foundry render these as a live timeline so the caller can observe the execution.

How untrusted ladders stay safe

LADR is Turing-complete. Ladders can loop, recurse, jump, and rewrite themselves at runtime. Termination is not statically guaranteed; it is enforced at runtime by four hard ceilings.

CeilingDefaultPer-ladder configurable viaPlatform max
Spend (dollars)$5executionBudget.maxSpend$50
Hops (loops + jumps + recursion)200executionBudget.maxHops10,000
LLM calls500executionBudget.maxLlmCalls5,000
Recursion depth5executionBudget.maxRecursion20

A ladder author can lower any ceiling (always safe) or raise it up to the platform max. A first-party ladder that needs more headroom declares a higher budget; a directory ladder that should be cheap to run declares a tighter one.

The spend cap is the primary safety mechanism. A ladder can loop freely, but every LLM call costs money. The runtime kills execution the moment cumulative spend crosses the cap and returns the best-so-far output. This is what allows published ladders to be invoked safely — the runtime owns the loop, so the runtime can stop it.

When a ceiling is hit, the runtime publishes an error event with ceiling_exceeded: true and exits gracefully. The caller sees a partial result, not a crash.

The shape of every ladder

Every ladder is a YAML object with this skeleton:

yaml
# Required
name: <ladder name>
allowedTargets: { strategy: universal }   # or { strategy: constrained, providers: [...], models: [...] }
exit: <step id whose output is returned>
knobs: { ... }                             # may be empty
steps: [ ... ]                             # at least one

# Optional
id: <platform-assigned uuid>
description: <human-readable summary>
slates: [ ... ]                            # persistent memory stores
executionBudget:
  maxSpend: 5.0
  maxHops: 200
  maxLlmCalls: 500
  maxRecursion: 5
trace: { mode: reasoning, allSteps: false }

The parser is strict: unknown top-level keys are rejected, not silently ignored. Adding a new top-level field is a grammar change, not a silent extension. The five required fields (name, allowedTargets, exit, knobs, steps) must all be present, even if knobs is {}.

Each entry in the steps: array is one of three step types. Every step shares a common base shape; the type adds specific behavior.

yaml
steps:
  - id: draft                              # unique step id; used by jumps and ingest references
    name: Draft                            # human-readable label
    type: normal                           # normal | sequential | group (default: normal)
    nodes: 5                               # fan-out count (number | knob name | { from: knob name })
    fields:                                # prompt inputs
      - { name: Question, type: text, from: input.context }
    systemPrompt: "..."                    # system-role instruction
    if:                                    # typed gate
      integerRange: [4, 5]
      then: continue
      else: { jump: { stepId: refine } }
    timeline: circle                       # UI hint: circle | init
    recursion: { maxDepth: 3 }             # optional recursion
    slateWrite: { ... }                    # optional schema-matched write
    retrieve: { ... }                      # optional on-demand fetch loop
    dynamic: false                         # optional: load config at runtime

The rest of this section walks each field of this skeleton in detail.

The three step types

Every step has a type. The type controls how its nodes (LLM calls) are dispatched.

TypeHow nodes runWhen to use
normal (default)All N nodes dispatched in parallel. Each gets the same assembled prompt, except for nodeInfo which differs per node.Fan-out for Tree-of-Thoughts, multi-perspective debate, self-consistency voting. The default for any step that benefits from independent attempts.
sequentialNodes dispatched in order. Node K can read outputs of nodes 1 through K-1 via nodeRef: previous.Chain-of-thought within one step, iterative self-refinement, any flow where later calls should build on earlier calls in the same step.
groupHas no nodes:, fields:, or systemPrompt: of its own. Instead has a steps: array of child steps that run as parallel siblings.Independent analyses feeding a shared downstream synthesis. Three reviewers running side by side, then a synthesis step merging their outputs.

When in doubt, use normal. Use sequential only when later nodes in the same step must consume earlier nodes' outputs. Use group only when two or more independent sub-strategies must run side by side and feed a shared downstream step.

Hard rules enforced at validation:

  • A group must have at least 2 child steps.
  • Groups cannot be nested.
  • Group children cannot reference each other with loopRef: current (parallel steps cannot read from each other).

A tour through a non-trivial ladder

The reference ladder Crucible uses every power axis. Reading this tour is the fastest way to see how the pieces compose. (The full config is in Examples > Reference Ladder: Crucible.)

yaml
name: Crucible
allowedTargets: { strategy: universal }
exit: answer
knobs:
  branches:   { name: Branches,   type: nodes,     input: slider, steps: [...] }
  verifiers:  { name: Verifiers,  type: nodes,     input: slider, steps: [...] }
  iterations: { name: Iterations, type: recursion, input: numerical, default: 2, min: 1, max: 4 }
slates:
  - title: Crucible Memory
    folders:
      - { name: trace,   tokenLimit: 800, files: [{name: verified.md, init: blank}] }
      - { name: lessons, tokenLimit: 400, files: [{name: failures.md, init: blank}] }
      - { name: solution, tokenLimit: 1000, files: [{name: best.md, init: blank}] }
steps:
  - id: generate
    nodes: "{{knobs.branches}}"             # fan out 3/5/8 candidates
    fields:
      - { name: Context, type: text, from: input.context }
      - { name: PriorArt, type: slateRead, from: { slate: Crucible Memory, folder: trace, file: verified.md } }
      - { name: Lessons, type: slateRead, from: { slate: Crucible Memory, folder: lessons, file: failures.md } }
    systemPrompt: "Generate an independent solution. Build on PriorArt; heed Lessons."

  - id: verify
    nodes: "{{knobs.verifiers}}"            # fan out verifiers
    fields:
      - { name: Candidate, type: ingest, from: { stepId: generate, loopRef: current } }
    systemPrompt: 'Emit JSON {"score": 1-5, "verdict": "accept|reject"}.'
    if:
      integerRange: [4, 5]            # accept on 4 or 5
      then: continue                  # survivors continue
      else:
        write:                        # rejects persist as lessons on the way out
          to: { slate: Crucible Memory, folder: lessons, file: failures.md }
          from: output
          on: append

  - id: reflect
    fields:
      - { name: Failures, type: multi_ingest, from: [{ stepId: verify, loopRef: current, nodeRef: accumulate }] }
    systemPrompt: 'Emit JSON {"root_cause": "...", "lesson": "..."}.'
    slateWrite:
      to: { slate: Crucible Memory, folder: lessons, file: failures.md }
      on: append
      match: { type: object, required: [root_cause, lesson] }
    if:
      jsonMatches: { type: object }   # lesson emitted → jump back
      then: { jump: { stepId: generate } }  # backward jump = reflexion

  - id: refine
    type: sequential
    nodes: "{{knobs.iterations}}"
    recursion: { maxDepth: "{{knobs.iterations}}" }
    # ...

  - id: answer
    fields:
      - { name: Solution, type: slateRead, from: { slate: Crucible Memory, folder: solution, file: best.md } }
    systemPrompt: "Return the solution as-is."

What this ladder does, in plain English:

  1. generate fans out N parallel candidates, each reading the user's question plus everything the ladder has learned so far (PriorArt, Lessons).
  2. verify runs M independent verifiers against each candidate. Scores 4-5 survive; scores 1-3 are pruned and their analysis is written to the lessons slate on the way out.
  3. If any survivors remain, control advances. If all were pruned, control jumps to reflect.
  4. reflect reads all failures, extracts a structured lesson, persists it via schema-matched write, and jumps backward to generate. The next generate pass reads the new lesson; the ladder has learned within a single call.
  5. refine runs a sequential chain that recursively deepens on the best candidate.
  6. answer reads the verified solution from the slate and returns it verbatim.

Crucible on call 1 starts with empty slates. On call 2, generate reads the verified trace and accumulated lessons from call 1. The ladder has accumulated state across invocations. Slate persistence makes this possible — the ladder does not need to relearn what it already determined.

Where to go next

Read the Language section in order for the systematic treatment of each primitive.

Start here: The LADR Mental Model — the six ideas that make every other concept intuitive.

  1. Top-Level Config. Every root-level field, every required/optional distinction, every validation rule.
  2. Knobs. Caller-tunable parameters. The first power axis.
  3. Steps. The execution unit. Anatomy, types, lifecycle.
  4. Fields. The seven field types. How prompt assembly works.
  5. Control Flow. The typed gate and the four actions.
  6. Jumps. Cursor movement within and across ladders.
  7. Recursion. Spawning child instances of the same ladder.
  8. Slates. Persistent memory: folders, files, metatags.
  9. Reads and Writes. slateRead, slateWrite, and gated writes.
  10. Retrieve. Bounded on-demand fetch inside a step.
  11. Dynamic Steps. The self-improvement primitive.
  12. Execution Budget. The four runtime ceilings.
  13. Validation. The 25 rule checks and what each catches.
  14. Observability. Lifecycle events, trace modes, timeline annotations.

If you learn better by example, start with Tutorials > Hello World and work through the Core Patterns. For a large composed example, see Examples > Reference Ladder: Crucible.