Operations
How to Debug a Ladder
How to Debug a Ladder
When a ladder doesn't behave as expected, the trace is your primary debugging tool. Every step, gate evaluation, and LLM call is logged as an event. This guide walks through common failure modes and how to diagnose them.
Reading the trace
Every ladder execution publishes a stream of events. In Foundry, the Trace View shows these events in real time. Via the API, events are available on the trace endpoint.
The event sequence for a normal execution:
pipeline_start → pipeline begins
loop_start → loop 0 begins
step_start → step "draft" begins
step_complete → step "draft" finishes
step_start → step "verify" begins
node_survivors → 3 of 5 nodes survived gate
step_complete → step "verify" finishes
step_start → step "answer" begins
step_complete → step "answer" finishes
loop_complete → loop 0 finishes
pipeline_complete → exit output returnedKey events for debugging:
node_survivors— shows which nodes passed the gate. If all nodes were pruned, check the gate condition.node_count_resolved— shows if the requested node count was clamped by the budget.node_failed— a specific node's LLM call failed. Check the message for the provider error.error— something went wrong. Checkceiling_exceededfor budget hits,nonFatalfor slate write failures.
Empty or wrong output
Symptom: The ladder returns an empty string or unexpected content.
Diagnosis:
-
Check which step is the
exit:step. The returned output is the exit step's output from the last completed loop. -
Check if the exit step ran. Look for
step_startandstep_completeevents for the exit step ID. If the exit step was skipped (by a forward jump that jumped past it), the output comes from the previous loop iteration. -
Check field resolution. If an
ingestfield references a step that was pruned to zero survivors, the field resolves to empty. Look fornode_survivorsevents showing zero survivors. -
Check for fallback. If a field has a
fallback:, it resolves to the fallback when the primary source misses. An unexpected fallback value suggests the primary source didn't resolve.
Fix: Ensure the exit step's fields reference steps that actually produce output. Use fallback: to handle missing sources gracefully.
Gate always fails (all nodes pruned)
Symptom: Every node is pruned by the gate; the else: action always fires.
Diagnosis:
-
Check the gate condition type. If using
integerRange: [4, 5]but the LLM outputs"Score: 4"(prose around the number), the parse fails. The integer predicates parse the entire string, not embedded numbers. -
Check the system prompt. Ensure it instructs the LLM to output the bare value (just the number, just the token, just the JSON).
-
Check
jsonMatchesschemas. If the schema requires fields the LLM isn't producing, validation fails. Use the trace to see what the LLM actually outputted. -
Use
containsinstead ofequalsfor prose-wrapped outputs.contains: "VERDICT: ACCEPT"handles surrounding text;equals: "ACCEPT"requires exact match.
Fix: Align the system prompt with the gate condition. If the prompt says "output JSON", use jsonMatches. If it says "output a number", use integerRange or integerEquals. If it says "output yes or no", use in: ["yes", "Y", "YES"].
Ceiling exhausted
Symptom: The ladder stops mid-execution with an error event containing ceiling_exceeded: true.
Diagnosis:
-
Check which ceiling was hit. The error message includes the ceiling name (
spend,hops,llmCalls, orrecursion) and the used vs. limit values. -
Spend ceiling: The ladder is making too many expensive LLM calls. Reduce node counts, reduce loop iterations, or use a cheaper model.
-
Hop ceiling: The ladder is looping too many times (jumps or loops). Check for backward jumps that create tight loops. Increase
executionBudget.maxHopsif the loop count is intentional. -
LLM call ceiling: Similar to spend but counts calls, not dollars. Reduce node counts or parallel branches.
-
Recursion ceiling: The ladder recurses too deeply. Reduce
recursion.maxDepthor restructure to avoid deep nesting.
Fix: Declare an executionBudget that matches your expected usage. The runtime returns best-so-far output on ceiling exhaustion, so the caller still gets a response.
executionBudget:
maxSpend: 15.0 # raise from $5 default
maxHops: 500 # raise from 200 defaultRemember: platform maximums are $50 spend, 10,000 hops, 5,000 LLM calls, 20 recursion depth. You cannot exceed these.
Slate writes not persisting
Symptom: The slate file is empty after a call that should have written to it.
Diagnosis:
-
Check for
errorevents withnonFatal: trueand "slateWrite failed" in the message. This means the write was attempted but failed. -
Check the
matchschema. If the LLM's output doesn't match the schema, the write is a silent no-op (not an error). Look at what the LLM actually produced vs. what the schema requires. -
Check JSON extraction. The
slateWriteextracts the first JSON block from the output. If the output has no JSON (prose only), extraction fails silently. -
Check the
field:extraction. Iffield: factis declared but the JSON doesn't have afactkey, extraction fails silently.
Fix: Temporarily remove the match schema to see if the write succeeds without validation. If it does, the schema is too strict. Adjust the schema to match what the LLM actually produces, or adjust the system prompt to produce schema-conforming output.
# Debug: log what the LLM produces before adding schema matching
slateWrite:
to: { slate: "Debug", folder: raw, file: output.md }
on: overwrite
match:
type: string # accept any string outputOnce you see what the LLM produces, tighten the schema to match.