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.
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: requiredRequired fields:
name: Human-readable label shown in Foundry and used in display.type: One ofnodes,loops,recursion,generic.input: Eithersliderornumerical.
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:
| Type | Where it's used | Effect |
|---|---|---|
nodes | nodes: field on a step | Number of parallel LLM calls |
loops | API maxLoops parameter | Maximum pipeline loop iterations |
recursion | recursion.maxDepth | Maximum recursion nesting depth |
generic | knobInfo fields only | Arbitrary 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:
# 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
defaultvalue if declared, clamped to[min, max]. If no default, usesmin.
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:andrecursion.maxDepthvia the template syntax. - In
knobInfofields via thefrom:declaration. - In the manifest snapshot (for dynamic step planning).
# 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 defaultInjecting knob values into prompts
Beyond controlling structural parameters (node counts, recursion depth), knob values can be injected directly into prompts via knobInfo fields.
- 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:
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:
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:
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:
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.