Bug: {{steps.X.output}} renders as empty string silently — worker receives blank prompt
Summary
In the state-based template engine used for prompt_template rendering, any unresolvable expression — including {{steps.X.output}} — silently renders as an empty string. The prompt is dispatched to the worker empty, and the worker generates output from nothing (or hallucinates from residual session context). There is no error, no failed step, no log line pointing at the missing variable.
Root cause
src/templates.zig:401 resolveNewExpression handles state.*, input.*, item, item.*, config.*, store.* — but has no steps. prefix handler, and ends with:
// Unknown expression — return empty
return alloc.dupe(u8, "") catch return error.OutOfMemory;
(src/templates.zig:508)
The old graph-engine path (resolveStepRef, src/templates.zig:238) does handle steps. via ctx.step_outputs, but the runtime path used by executeTaskNode goes through renderWorkflowTemplate → the new engine (src/engine.zig:1148), so {{steps.X.output}} falls into the unknown-expression empty-string branch.
Every other miss in the same function (state.X missing key, input.X missing, item with no item_json) also returns "" silently.
Why this is dangerous
A typo'd or not-yet-populated variable produces a blank prompt instead of a failure. The step "succeeds", the run continues, and the garbage output propagates downstream. This is a correctness black hole: the failure mode is invisible at the orchestration layer.
Reproduction
- Create a 2-step sequential workflow. Step
b has prompt_template: "Summarize: {{steps.a.output}}".
- Run it. Step
b dispatches with prompt Summarize: — the {{steps.a.output}} expression vanishes.
- No error anywhere; step
b completes against a malformed prompt.
Expected behavior
{{steps.X.Y}} should resolve against a per-step output registry (populated as steps complete), so {{steps.a.output}} yields the actual output of step a.
- For prompt rendering (as opposed to
{% if %} conditionals where empty-as-falsy is legitimate), an unresolvable reference should fail the node visibly (UnresolvedReference / StepNotFound) instead of rendering empty.
Bug:
{{steps.X.output}}renders as empty string silently — worker receives blank promptSummary
In the state-based template engine used for
prompt_templaterendering, any unresolvable expression — including{{steps.X.output}}— silently renders as an empty string. The prompt is dispatched to the worker empty, and the worker generates output from nothing (or hallucinates from residual session context). There is no error, no failed step, no log line pointing at the missing variable.Root cause
src/templates.zig:401resolveNewExpressionhandlesstate.*,input.*,item,item.*,config.*,store.*— but has nosteps.prefix handler, and ends with:(
src/templates.zig:508)The old graph-engine path (
resolveStepRef,src/templates.zig:238) does handlesteps.viactx.step_outputs, but the runtime path used byexecuteTaskNodegoes throughrenderWorkflowTemplate→ the new engine (src/engine.zig:1148), so{{steps.X.output}}falls into the unknown-expression empty-string branch.Every other miss in the same function (
state.Xmissing key,input.Xmissing,itemwith no item_json) also returns""silently.Why this is dangerous
A typo'd or not-yet-populated variable produces a blank prompt instead of a failure. The step "succeeds", the run continues, and the garbage output propagates downstream. This is a correctness black hole: the failure mode is invisible at the orchestration layer.
Reproduction
bhasprompt_template: "Summarize: {{steps.a.output}}".bdispatches with promptSummarize:— the{{steps.a.output}}expression vanishes.bcompletes against a malformed prompt.Expected behavior
{{steps.X.Y}}should resolve against a per-step output registry (populated as steps complete), so{{steps.a.output}}yields the actual output of stepa.{% if %}conditionals where empty-as-falsy is legitimate), an unresolvable reference should fail the node visibly (UnresolvedReference/StepNotFound) instead of rendering empty.