Skip to content

fix(engine): template dict key collision and workflow.input for-each source#21

Merged
jrob5756 merged 3 commits intomicrosoft:mainfrom
Shazwazza:fix/template-dict-key-collision
Mar 5, 2026
Merged

fix(engine): template dict key collision and workflow.input for-each source#21
jrob5756 merged 3 commits intomicrosoft:mainfrom
Shazwazza:fix/template-dict-key-collision

Conversation

@Shazwazza
Copy link
Copy Markdown
Contributor

@Shazwazza Shazwazza commented Mar 5, 2026

Summary

Two related fixes for the workflow engine: a template rendering bug when dictionary keys shadow Python built-in methods, and missing support for workflow.input.* references in for-each source fields.

Changes

Fix template rendering error when dict keys shadow built-in methods

Dictionary outputs with keys like "items", "values", or "keys" caused AttributeError during Jinja2 template rendering because TemplateRenderer._flatten_to_namespace resolved these to the dict's built-in methods instead of the actual values.

  • Added explicit dict.__getitem__ access in _flatten_to_namespace to bypass method shadowing
  • Added 5 tests covering items, values, keys, get, and update as dict keys

Support workflow.input.* as for-each source

The docs and examples referenced source: workflow.input.items for for-each groups, but _resolve_array_reference only resolved paths from agent_outputs, causing a runtime error.

  • Added _resolve_workflow_input_array helper to resolve workflow.input.* paths from context.workflow_inputs
  • Handles automatic JSON string parsing (CLI --input passes arrays as strings like '["a","b"]')
  • Added 9 tests covering array, JSON string, nested path, missing field, invalid JSON, and empty array cases

Update for-each-simple.yaml example

  • Added items as an optional input parameter
  • item_finder agent now conditionally passes through provided items or generates random topics
  • Both usage modes documented:
    conductor run examples/for-each-simple.yaml --input items='["apple", "banana", "cherry"]'
    conductor run examples/for-each-simple.yaml

Jinja2's default attribute resolution tries Python getattr() before dict
key lookup. When a dict has a key named 'items' (or 'keys', 'values',
etc.), getattr(d, 'items') returns the dict.items method instead of
d['items'] (the stored value). Filters like '| length' then call len()
on the method object, producing:

  Template rendering failed: object of type 'builtin_function_or_method'
  has no len()

Fix: Subclass Jinja2 Environment with _DictSafeEnvironment that prefers
dict key lookup over attribute access for dict objects. This resolves the
collision for any field name that matches a dict method.

Also rename the 'items' output field in for-each-simple.yaml to 'topics'
to avoid the confusing collision in the example.
@codecov-commenter
Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

Add _resolve_workflow_input_array to handle workflow.input.* references
in for-each source fields, with automatic JSON string parsing for CLI
--input values.

Update for-each-simple.yaml to accept optional --input items array with
fallback to agent-generated topics when not provided.

Add 9 tests covering workflow.input source resolution.
@Shazwazza Shazwazza changed the title Fix template rendering error when dict keys shadow built-in methods fix(engine): template dict key collision and workflow.input for-each source Mar 5, 2026
@jrob5756 jrob5756 merged commit ca5868e into microsoft:main Mar 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants