Redeo Docs
DocsLADR / Cross-Ladder Composition

Jumps

Cross-Ladder Composition

Cross-Ladder Composition

Cross-ladder jumps let one ladder hand off to another mid-execution. The firing step's output becomes the target ladder's input. The target's exit-step output becomes the user-visible result. This is how published ladders become reusable building blocks — specialists that a general-purpose ladder can invoke.

Declaring a cross-ladder jump

A cross-ladder jump is declared under then: or else: with a ladderId instead of a stepId.

yaml
- id: route
  systemPrompt: 'Classify the query. Output "general", "coding", or "math".'
  if:
    in: ["coding"]
    then: { jump: { ladderId: "@alice/coding-specialist" } }
    else:
      in: ["math"]
      then: { jump: { ladderId: "@bob/math-solver" } }
      # else defaults to abort

Ladder ID format: @author/name[@version]. The version is optional; omitting it means "latest."

  • @alice/coding-specialist — latest version of alice's coding specialist.
  • @alice/coding-specialist@v3 — specifically version 3.
  • local/helper — a local (self-hosted) ladder named helper.

The runtime resolves the ladder ID to a config (from the database for @author/name, from local storage for local/name) and spawns a child execution.

The handoff mechanism

When a cross-ladder jump fires, the runtime performs a tail-call handoff:

  1. The current ladder terminates immediately.
  2. A child instance is spawned for the target ladder.
  3. The firing step's combined output becomes the child's input.context.
  4. The child runs the target ladder's full steps array.
  5. The child's exit-step output becomes the parent's final result.
text
Parent Ladder                          Child Ladder (@alice/helper)
┌─────────────────────┐                ┌─────────────────────────┐
│ step1: generate     │                │ step1: process          │
│ step2: verify       │                │ step2: format           │
│   if: good          │                │ exit: format            │
│     jump:           │──handoff──▶    │                         │
│       @alice/helper │                │ input.context =         │
│                     │                │   verify's output       │
│ exit: verify        │                │                         │
└─────────────────────┘                └─────────────────────────┘
                                        exit output →
                                        caller's response

The caller sees only the child's exit-step output. The parent's execution is not visible in the response (but it is visible in the trace, linked via childInstanceId).

Budget inheritance: The spend ceiling, hop ceiling, and LLM call ceiling carry over to the child. The child does not get a fresh budget — it shares the parent's remaining allocation. This prevents a cross-ladder chain from exceeding the original budget.

Wiring data with passFields

A cross-ladder jump can declare passFields to wire named values directly into the target ladder's first step fields. This overrides the target step's declared from: sources for one execution.

yaml
- id: delegate
  if:
    jsonMatches: { properties: { needs_help: { enum: [true] } } }
    then:
      jump:
        ladderId: "@alice/specialist"
        passFields:
          - to: Context              # target field name
            from: previous           # resolved in caller's context
          - to: Priority
            from: "high"             # literal string
          - to: SourceStep
            from: { stepId: analyze } # specific step output

Resolution priority: passField > from > fallback. If a passField overrides a field name, the target step's declared from: and fallback: for that field are ignored. The passField value wins outright.

Where passFields resolve: Each passField's from: is resolved in the caller's runtime context (the parent ladder). The resolved values are shipped to the child as an override map. The child's target step consults this map before falling through to its own field declarations.

Target validation: The runtime validates that every passField to: name matches a declared field on the target step. If a passField targets a field that doesn't exist, the handoff fails with handoff_field_mismatch.

Mid-ladder entry: A cross-ladder jump can also specify a stepId to enter the target ladder at a specific step (not just the first):

yaml
then:
  jump:
    ladderId: "@alice/specialist"
    stepId: "step3"            # enter at step3, skip steps 1-2

This is useful when the target ladder has a setup phase that the caller wants to skip.

Composition patterns

Router pattern. A general-purpose ladder classifies the input and routes to a specialist.

yaml
- id: classify
  systemPrompt: 'Output "code", "math", "writing", or "general".'
  if:
    in: ["code"]
    then: { jump: { ladderId: "@alice/code-helper" } }
    else:
      in: ["math"]
      then: { jump: { ladderId: "@bob/math-solver" } }
      else:
        in: ["writing"]
        then: { jump: { ladderId: "@carol/writing-coach" } }
        else: continue

Fallback pattern. Try a specialist; if it fails, fall back to a generalist.

yaml
- id: attempt_specialist
  if:
    jsonMatches: { properties: { solvable: { enum: [true] } } }
    then: { jump: { ladderId: "@alice/expert" } }
    else: continue                              # fall through to general approach

Chain pattern. One ladder's output feeds into the next. Use passFields to wire data across the boundary.

yaml
- id: handoff_to_formatter
  if:
    jsonMatches: { required: [result] }
    then:
      jump:
        ladderId: "@alice/formatter"
        passFields:
          - to: RawResult
            from: output

Safety. Every cross-ladder jump costs one hop. The hop ceiling bounds the total chain length. A budget of 200 hops supports a chain of up to ~50 ladder handoffs (assuming each ladder uses ~4 hops internally). The spend ceiling caps the total cost regardless of chain length.