Redeo Docs
DocsLADR / Node Addressing: loopRef, nodeRef, pruned state

Foundations

Node Addressing: loopRef, nodeRef, pruned state

Node Addressing: loopRef, nodeRef, pruned state

Every output in a ladder is addressable by a triple: (stepId, loopRef, nodeRef). Understanding this addressing scheme is essential for wiring fields correctly. This article covers the three loopRef keywords, the three nodeRef keywords, and how pruning affects node numbers.

The addressing triple

Every LLM call output in a ladder is stored as a node belonging to a step, in a specific loop iteration. To read an output, you address it with three coordinates:

CoordinateValuesMeaning
stepIdany step IDWhich step produced the output
loopRefcurrent, previous, accumulate, or a numberWhich loop iteration
nodeRefcurrent, previous, accumulate, or omittedWhich node within the step
yaml
# Read node 1 of step "draft" from the current loop
- name: Draft
  type: ingest
  from: { stepId: draft, loopRef: current }

# Read all nodes of step "generate" from the previous loop
- name: PreviousAttempts
  type: multi_ingest
  from: [{ stepId: generate, loopRef: previous, nodeRef: accumulate }]

# Read all nodes across all prior loops
- name: AllHistory
  type: multi_ingest
  from: [{ stepId: generate, loopRef: accumulate, nodeRef: accumulate }]

loopRef values

loopRef selects which loop iteration(s) to read from. Four values:

current — the loop iteration currently executing. If the pipeline is on loop 3, current reads from loop 3. This is the most common value for same-loop data dependencies.

previous — the loop iteration before the current one. If the pipeline is on loop 3, previous reads from loop 2. Used for cross-loop refinement: "take the previous loop's output and improve it."

accumulate — all prior loop iterations (0 through current-1). Expands to multiple reads, one per loop. Only valid in multi_ingest fields. Used for aggregation: "read all previous attempts."

Numeric (0, 1, 2, ...) — a specific loop iteration by index. Rarely used; useful for debugging or fixed-depth patterns.

yaml
# Same-loop: read draft from the current loop
from: { stepId: draft, loopRef: current }

# Cross-loop: read draft from the previous loop  
from: { stepId: draft, loopRef: previous }

# Accumulate: read all prior loops' drafts
from: [{ stepId: draft, loopRef: accumulate, nodeRef: accumulate }]

Negative indices and indices beyond the current loop resolve to empty. The runtime does not error on out-of-range loop references — it simply returns no data.

nodeRef values

nodeRef selects which node(s) within a step to read. Three keyword values:

current — the node matching the current execution context. If the current step is executing node 3, and the field references { stepId: draft, nodeRef: current }, it reads node 3 of the draft step. This enables parallel alignment: "verifier node 3 evaluates draft node 3."

previous — the node before the current one. If current is node 3, reads node 2. If current is node 1, falls back to node 1 (there's no node 0). Used in sequential steps for chain-of-refinement: "improve on what the previous node produced."

accumulate — all nodes of the referenced step. Expands to multiple reads. Only valid in multi_ingest fields. Used for aggregation: "read all candidates that survived the gate."

Omitted — when nodeRef is not specified, the step's combined output is read (all nodes joined with double newlines). This is different from accumulate — omitted reads the step-level output, while accumulate reads each node individually.

yaml
# Parallel alignment: verifier N reads draft N
- name: Candidate
  type: ingest
  from: { stepId: draft, loopRef: current, nodeRef: current }

# Sequential chain: node N reads node N-1
- name: Previous
  type: ingest
  from: { stepId: refine, loopRef: current, nodeRef: previous }
  skipFirstNode: true

# Aggregate: read all surviving nodes individually
- name: Survivors
  type: multi_ingest
  from: [{ stepId: verify, loopRef: current, nodeRef: accumulate }]

How pruning affects node numbers

When a gate prunes nodes, the surviving nodes are remapped to a contiguous range. This is the pruned: true flag on NodesFrom.

Example: A step has 5 nodes. The gate prunes nodes 1, 3, 4. Survivors: nodes 2, 5.

After pruning, downstream consumers see the survivors as a contiguous sequence:

Physical nodeSurvivor indexVisible as
21node 1
52node 2

A downstream field with nodeRef: current reading node 1 gets the survivor node 1 (physical node 2), not the physical node 1 (which was pruned).

yaml
# Read the survivors count (not the original node count)
nodes: { from: { stepId: generate, loopRef: current, pruned: true } }

The pruned: true flag on a NodesFrom tells the runtime to count only surviving nodes, not all nodes. This is how a synthesize step knows how many candidates survived the gate.

Without pruning context: If a field references a step that had nodes pruned, but doesn't use pruned: true, the runtime applies the remap automatically for current and previous nodeRefs. The survivor mapping is tracked in the runtime's PrunedNodeMap.

Key insight: Authors rarely need to think about pruning explicitly. The runtime handles the remapping. But when counting nodes for nodes: declarations, use pruned: true to get the survivor count rather than the original count.

Common addressing patterns

Parallel evaluation (ToT). N generators produce candidates; N evaluators score them in parallel. Each evaluator reads its corresponding generator node.

yaml
- id: generate
  nodes: 5
  # ...

- id: evaluate
  nodes: 5
  fields:
    - name: Candidate
      type: ingest
      from: { stepId: generate, loopRef: current, nodeRef: current }

Evaluator node 3 reads generator node 3. One-to-one alignment.

Aggregation. A synthesis step reads all surviving candidates.

yaml
- id: synthesize
  fields:
    - name: Candidates
      type: multi_ingest
      from: [{ stepId: evaluate, loopRef: current, nodeRef: accumulate }]

All surviving evaluate nodes are joined into a numbered list.

Cross-loop refinement. Each loop improves on the previous loop's output.

yaml
- id: improve
  fields:
    - name: Previous
      type: ingest
      from: { stepId: improve, loopRef: previous }
      fallback: ""

On loop 1, previous reads from loop 0 (which doesn't exist → empty → fallback). On loop 2, reads loop 1's output. On loop 3, reads loop 2's output. Each loop builds on the last.

Full history accumulation. Read all outputs from all prior loops.

yaml
- name: History
  type: multi_ingest
  from: [{ stepId: improve, loopRef: accumulate, nodeRef: accumulate }]

On loop 3, this reads all nodes from loops 0, 1, and 2. The resulting list contains every attempt so far.