Redeo Docs
DocsLADR / Knobs Deep Dive

Foundations

Knobs Deep Dive

Knobs Deep Dive

Knobs are caller-tunable parameters that let the caller control a ladder's strategy at call time without editing the config. Four knob types (nodes, loops, recursion, generic) and two input types (slider, numerical) cover all use cases. This article covers everything from declaration to resolution to API usage.

Knob anatomy

A knob declaration has four parts: a name, a type, an input, and input-specific parameters.

yaml
knobs:
  branches:
    name: Branches              # display label
    type: nodes                 # knob category
    input: slider               # input type: slider or numerical
    steps:                      # slider-specific: 3-5 discrete options
      - { title: Fast, value: 3, default: true }
      - { title: Balanced, value: 5 }
      - { title: Wide, value: 8 }

  threshold:
    name: Threshold
    type: generic               # knob category
    input: numerical            # input type: continuous range
    default: 0.8                # numerical-specific: default value
    min: 0.0                    # numerical-specific: required
    max: 1.0                    # numerical-specific: required

Required fields:

  • name: Human-readable label shown in Foundry and used in display.
  • type: One of nodes, loops, recursion, generic.
  • input: Either slider or numerical.

Slider-specific: steps array with 3-5 entries. Each step has title, value, and optional default: true. Exactly one step should be the default.

Numerical-specific: min and max are required. default is optional (defaults to min if omitted). The value is clamped to [min, max].

Knob types and their semantics

The type field is informational — it controls how Foundry categorizes the knob. But it also signals where the knob value is expected to be used:

TypeWhere it's usedEffect
nodesnodes: field on a stepNumber of parallel LLM calls
loopsAPI maxLoops parameterMaximum pipeline loop iterations
recursionrecursion.maxDepthMaximum recursion nesting depth
genericknobInfo fields onlyArbitrary numeric value injected into prompts

Important: The type does not enforce where a knob can be referenced. A nodes-typed knob can be used in a knobInfo field; a generic knob can be used in a nodes: field. The type is a hint, not a constraint. But using the right type helps Foundry display knobs in the correct category.

The {{knobs.X}} template syntax: In step declarations, knob values are referenced via the template form:

yaml
# nodes: references a knob by name (shorthand)
nodes: branches

# Explicit template form (same result)
nodes: "{{knobs.branches}}"

# Recursion depth from a knob
recursion:
  maxDepth: "{{knobs.depth}}"

Both the shorthand (bare knob name) and the template form ({{knobs.X}}) are valid in nodes: and recursion.maxDepth. The parser regex ^{{knobs.(.+)}}$ matches the template form.

How knob values resolve

Knob values resolve in a three-step process:

1. Default resolution. At config load time, the runtime computes default values for every knob:

  • Slider knobs: the first step with default: true, or the first step if none is marked default.
  • Numerical knobs: the default value if declared, clamped to [min, max]. If no default, uses min.

2. Sanitization. When a caller passes knob values, they are sanitized:

  • Slider knobs: only exact matches against a step value are accepted. Non-matching values are silently ignored (the default is used).
  • Numerical knobs: the value is clamped to [min, max]. NaN and infinity are rejected.

3. Injection. Resolved knob values are available in the runtime context:

  • In nodes: and recursion.maxDepth via the template syntax.
  • In knobInfo fields via the from: declaration.
  • In the manifest snapshot (for dynamic step planning).
yaml
# Caller passes knobs at call time
curl ... -d '{
  "knobs": { "branches": 8, "threshold": 0.95 }
}'

# Runtime resolves:
# - branches: 8 matches a slider step → accepted
# - threshold: 0.95 is within [0.0, 1.0] → accepted (clamped if needed)
# - Any knob not passed by the caller uses its default

Injecting knob values into prompts

Beyond controlling structural parameters (node counts, recursion depth), knob values can be injected directly into prompts via knobInfo fields.

yaml
- id: verify
  fields:
    - { name: Candidate, type: ingest, from: { stepId: generate, loopRef: current } }
    - { name: Threshold, type: knobInfo, from: threshold }
  systemPrompt: |
    Score the candidate from 1 to 5.
    The acceptance threshold is in the Threshold field.
    Output JSON {"score": N, "accepted": true|false}.

The Threshold field resolves to the knob's numeric value (e.g., 0.8). The LLM sees:

text
Candidate: [the generated text]

Threshold: 0.8

[System Instruction]
Score the candidate from 1 to 5.
The acceptance threshold is in the Threshold field.
Output JSON {"score": N, "accepted": true|false}.

Why this matters: The caller can control the acceptance threshold at call time without editing the ladder. A high-stakes use case might pass threshold: 0.95; a casual use case might pass threshold: 0.7. Same ladder, different strictness.

Number formatting: Knob values are floats. When rendered in prompts, integers display without a trailing .0 (e.g., 5 not 5.0). Non-integer values display with full precision (e.g., 0.8, 3.14).

Complete examples

Adjustable fan-out ToT:

yaml
knobs:
  branches:
    name: Branches
    type: nodes
    input: slider
    steps:
      - { title: Fast, value: 3, default: true }
      - { title: Balanced, value: 5 }
      - { title: Wide, value: 8 }
      - { title: Exhaustive, value: 12 }

steps:
  - id: generate
    nodes: branches
    # ...
  - id: verify
    nodes: branches
    # ...

The caller controls how many candidates to generate and verify. Fast (3 nodes) is cheap; Exhaustive (12 nodes) is thorough.

Adjustable strictness:

yaml
knobs:
  threshold:
    name: Threshold
    type: generic
    input: numerical
    default: 0.8
    min: 0.5
    max: 0.95

steps:
  - id: verify
    fields:
      - { name: Candidate, type: ingest, from: { stepId: generate, loopRef: current } }
      - { name: Threshold, type: knobInfo, from: threshold }
    systemPrompt: "Score 1-5. Accept if score >= Threshold * 5."

The caller controls how strict the verifier is. A threshold of 0.5 accepts scores >= 2.5; a threshold of 0.95 accepts only scores >= 4.75.

Adjustable recursion depth:

yaml
knobs:
  depth:
    name: Recursion Depth
    type: recursion
    input: numerical
    default: 3
    min: 1
    max: 5

steps:
  - id: decompose
    recursion:
      maxDepth: "{{knobs.depth}}"
    # ...

The caller controls how deep the recursion goes. Simple problems might need depth 1; complex problems might need depth 5.