State & Templating
Erdo provides powerful state management and templating capabilities that allow you to create dynamic, data-driven agent workflows. The state system enables clean Python syntax while supporting complex Go template expressions for advanced use cases.Why Templates? Templates provide a declarative way to express data flow in your agents. Because templates are declarative rather than imperative code, the CLI can perform static analysis to enumerate all possible execution paths and validate them automatically. Learn more about testing capabilities.
Overview
Erdo’s templating system supports two main approaches:State Syntax
Clean Python syntax like
state.query and state.dataset.idTemplateString
Complex Go templates with
TemplateString("{{complex expression}}")State Object
Thestate object provides clean, Pythonic access to workflow data with IDE autocomplete and type safety.
Basic Usage
Dynamic Content with TemplateString
For dynamic content that combines text with state variables, useTemplateString:
Step Output Access
Access outputs from previous steps with clean syntax:TemplateString for Complex Templates
For complex Go template expressions that can’t be expressed as simplestate.field access, use TemplateString:
When to Use TemplateString
UseTemplateString for:
- Conditional expressions:
{{if condition}}value{{else}}other{{end}} - Complex data access:
{{get "field" (find "array" "key" "value" .Data) .MissingKeys}} - Template functions:
{{sliceEndKeepFirstUserMessage system.messages 5}} - Data manipulation:
{{addkey "object" "key" "value"}} - Loops and iterations:
{{range $item := .Data.items}}{{$item.name}}{{end}}
Available State Fields
System Fields
Access system-level information:Step References
Reference data from previous steps:Parameters and Context
Access workflow parameters and context:Template Functions
Erdo provides built-in template functions for common operations:Understanding Template Functions
Erdo’s template system includes two categories of functions that work together to provide powerful data manipulation capabilities:Basic Functions (Pure Functions)
Basic Functions (Pure Functions)
Pure functions that work standalone without needing access to the state data structure:
- No automatic parameters: Called exactly as written
- Examples:
genUUID,now,toJSON,truncateString,eq,ne,gt,lt - Use cases: Utilities, comparisons, string operations, type conversions
Data Functions (Stateful Functions)
Data Functions (Stateful Functions)
Stateful functions that access the state data and require Key Benefit: You never need to manually add
.Data and .MissingKeys:- Automatic parameter injection: System automatically adds
.Dataand.MissingKeysparameters - Examples:
get,find,filter,addkey,slice,merge - Use cases: Data access, array manipulation, object operations, lookups
.Data or .MissingKeys parameters—the template engine handles this automatically, making templates cleaner and easier to write.How It Works
How It Works
The template system preprocesses your templates to inject required parameters:
- Parse: Identifies all function calls in your template
- Classify: Determines which functions need data access
- Inject: Automatically adds
.Dataand.MissingKeysto data functions - Execute: Runs the hydrated template with full state context
- ✅ Cleaner template syntax
- ✅ Less room for errors
- ✅ Consistent parameter handling
- ✅ Better performance through optimized data access
Function Reference by Use Case
The following sections organize template functions by common use cases with practical examples.Data Access & Lookup
Access nested data and perform lookups within your state:Array Operations
Manipulate arrays and lists in your state:Object Manipulation
Add, remove, and transform object fields:Conditionals & Comparison
Make decisions based on data values:String Operations
Manipulate and transform strings:Utilities & Helpers
Common utility functions:Error Handling & Debugging
Understanding how to debug template errors and handle missing data is crucial for building robust agents.Understanding InfoNeededError
When erdo encounters missing state data during template hydration, it raises anInfoNeededError with comprehensive debugging information:
- Missing Keys: Exact fields that are missing
- Full Path: Where in your workflow the missing key is referenced
- Available Keys: What data is actually available in state
- Context Level: Shows which parent objects exist to help narrow down the issue
Optional Parameters
Make template parameters optional using the? suffix to prevent errors when data might not be available:
- Optional metadata fields that may not always be present
- User-specific data that might not be set
- Conditional features that depend on optional config
- Graceful degradation scenarios
Debugging Missing Keys
When you encounter a missing key error:- Check the full path to understand where the key is being accessed
- Review available keys to see what data is actually in state
- Verify spelling and case - keys are case-sensitive
- Check data flow - ensure previous steps are setting the data you need
- Use optional syntax if the data might not always be present
Common Error Patterns
Missing Nested Field
Missing Nested Field
Error:
dataset.metadata.id is missingAvailable: dataset.name, dataset.filenameDiagnosis: The dataset object exists, but doesn’t have a metadata field or metadata.idSolution:- Check if the field should be
dataset.idinstead - Ensure the previous step that sets
datasetincludes themetadatafield - Use optional syntax if metadata might not always be present:
{{dataset.metadata.id?}}
Array Index Out of Bounds
Array Index Out of Bounds
Error: Template function failed on
{{getAtIndex "items" 5}}Diagnosis: The items array has fewer than 6 elementsSolution:- Use
{{if gt (len items) 5}}{{getAtIndex "items" 5}}{{end}}to check length first - Use
{{sliceEnd "items" 1}}to get last item safely - Ensure previous steps populate the array with expected items
Function Parameter Type Mismatch
Function Parameter Type Mismatch
Error: Template function expected string, got numberDiagnosis: Function received wrong type (e.g., passing number where string expected)Solution:
- Use
{{toString value}}to convert to string - Check function signature in documentation
- Ensure upstream data has correct types
Nil Pointer in Comparison
Nil Pointer in Comparison
Error: Cannot compare nil valueDiagnosis: Trying to compare a field that doesn’t exist or is nullSolution:
- Use
eq/nefunctions which handle nil as empty string:{{ne field ""}} - Check existence first:
{{if truthy "field"}}{{field}}{{end}} - Use coalesce for defaults:
{{coalesce "field" "default"}}
Validation During Development
Erdo’s CLI performs static analysis on templates to catch errors early:- ✅ Validates template syntax
- ✅ Checks for common function mistakes
- ✅ Identifies potential missing key issues
- ✅ Enumerates all execution paths
- ✅ Warns about unreachable code
Go Interoperability
Erdo’s backend is written in Go, and the templating system is designed to work seamlessly with Go’s type system, including pointer handling.Pointer-Aware Comparisons
Theeq and ne functions automatically handle Go pointers and nil values, making it easy to work with data from the Go backend:
- Automatic Dereferencing: Pointer values are automatically dereferenced
- Nil as Empty String: Nil pointers are treated as empty strings (
"") - Type Safety: The system handles the Go type conversion transparently
- No Manual Checking: You don’t need to check for nil before comparing
Working with Optional Go Fields
Many Go structs use pointers for optional fields. The templating system handles this elegantly:Nil Handling Best Practices
Checking for Non-Empty Values
Checking for Non-Empty Values
Use This works because:
ne to check if a field has a value (works for both pointers and regular fields):- Nil pointers are treated as
"" - Empty strings remain
"" - Non-empty values compare correctly
Providing Defaults for Optional Fields
Providing Defaults for Optional Fields
Use
coalesce or conditional logic for optional fields:Working with Pointer Arrays
Working with Pointer Arrays
Go pointer arrays are handled transparently:
Comparing Pointer Fields
Comparing Pointer Fields
When comparing fields that might be pointers:Why it works: The
eq/ne functions dereference both sides of the comparison and treat nil as empty string, so you never get nil pointer errors.Type Conversions
The template system handles common type conversions between Go and template types:Common Go Patterns
When working with data from erdo’s Go backend:Best Practices
When to Use Each Approach
When to Use Each Approach
Use State Syntax For:
- Simple field access (
state.query,state.dataset.id) - Step outputs (
step.output.result) - Direct value references without string interpolation
- IDE autocomplete and type safety
- Dynamic text with variable interpolation (
TemplateString("Analyzing {{dataset.name}}")) - Conditional logic (
{{if condition}}value{{else}}other{{end}}) - Complex data manipulation (
{{addkey "obj" "key" "value"}}) - Template functions (
{{sliceEndKeepFirstUserMessage messages 5}}) - Advanced Go template expressions
- Any string that needs runtime state resolution
Performance Considerations
Performance Considerations
Optimize Template Usage:
- Use simple state access when possible (faster)
- Cache complex template results
- Avoid deeply nested template expressions
- Use appropriate data types for state fields
- Clear unused state data
- Use appropriate data structures
- Monitor template compilation performance
Debugging Templates
Debugging Templates
Debug State Access:
- Use
state.get_accessed_fields()to see what’s being used - Enable template debugging in development
- Validate state field availability
- Test complex templates in isolation
- Use template validation tools
- Add logging for template execution
Different Syntax Approaches
Erdo supports multiple ways to reference state data, depending on your use case:Choosing the Right Approach
- Use
state.fieldwhen you need direct value access without string interpolation - Use
TemplateStringwhen you need dynamic content, conditionals, or template functions - Avoid raw strings like
"{{field}}"- use eitherstate.fieldorTemplateString("{{field}}") - Mix approaches freely - use whichever syntax fits each specific parameter
Troubleshooting
State Field Not Found
State Field Not Found
Error:
AttributeError: 'StateMagic' object has no attribute 'field'Cause: Trying to access a field on the state object that doesn’t existSolutions:- Check if the field exists in your workflow context
- Use
state.get_accessed_fields()to debug what fields are available - Verify field names match exactly (case-sensitive)
- For dynamic fields or fields with special characters, use
TemplateStringinstead
InfoNeededError - Missing Keys
InfoNeededError - Missing Keys
Error:
InfoNeededError: Missing required keys for template hydrationCause: Template references state fields that aren’t available yetSolutions:- Check the “Missing Keys” list in the error to see what’s missing
- Review the “Full Path” to understand where the key is referenced
- Compare against “Available Keys” to see what you can use
- Ensure previous steps are setting the required data with
depends_on - Use optional syntax
{{field?}}if the field might not always be present - Add
coalescefor fallback values:{{coalesce "field" "default"}}
Template Compilation Errors
Template Compilation Errors
Error:
Template compilation failed or template: invalid syntaxCause: Invalid Go template syntax in TemplateStringSolutions:- Validate TemplateString syntax - check for balanced
{{and}} - Check for unescaped quotes in templates - use
\"for literal quotes - Ensure proper Go template syntax -
{{if condition}}...{{end}}must be balanced - Check for typos in function names (e.g.,
getAtindexshould begetAtIndex) - Use template validation with
./erdo testduring development - Verify all
{{if}}statements have matching{{end}}
Template Function Errors
Template Function Errors
Error:
error calling function: wrong number of argumentsCause: Calling a template function with incorrect parametersSolutions:- Check the function signature in the documentation above
- Remember: data functions automatically get
.Dataand.MissingKeys- don’t add them manually - Example: Use
{{get "field"}}not{{get "field" .Data .MissingKeys}} - For basic functions, provide all required parameters explicitly
- Example:
{{truncateString text 100}}requires both text and length
Array/Slice Index Out of Bounds
Array/Slice Index Out of Bounds
Error:
template function failed on array operationsCause: Trying to access array index that doesn’t existSolutions:- Check array length first:
{{if gt (len items) 5}}{{getAtIndex "items" 5}}{{end}} - Use
sliceEndfor safe last-N access:{{sliceEnd "items" 5}} - Use negative indices for end access:
{{getAtIndex "items" -1}}for last item - Verify the array is actually populated before slicing
- Use
{{len array}}to debug array size
Type Conversion Issues
Type Conversion Issues
Error:
Type mismatch in template evaluation or cannot convertCause: Template function received unexpected typeSolutions:- Use
{{toString value}}to convert to string explicitly - For numeric comparisons, ensure both sides are numbers
- JSON numbers are often float64 - use type conversion if needed
- Check function documentation for expected parameter types
- Use
{{toJSON value}}to debug what type a value actually is - Validate data types before template usage in previous steps
Nil Pointer / Null Value Errors
Nil Pointer / Null Value Errors
Error:
nil pointer dereference or invalid memory addressCause: Accessing a field on a nil/null objectSolutions:- Use
eq/nefunctions which handle nil automatically:{{ne field ""}} - Check existence before accessing:
{{if truthy "parent.child"}}{{parent.child}}{{end}} - Use optional syntax:
{{field?}}won’t error if field is nil - Use
coalescefor safe defaults:{{coalesce "optional.field" "default"}} - The Go interoperability section above explains pointer handling in detail
Find/Filter Returns Nothing
Find/Filter Returns Nothing
Error: No error, but
find or filter returns unexpected resultsCause: Query doesn’t match any items in array, or wrong field nameSolutions:- Verify the array exists:
{{len array}}should be > 0 - Check field names are correct (case-sensitive)
- Ensure the value you’re searching for exists in the data
- Use
{{toJSON array}}to inspect the actual data structure - For
find: verify the field name and value exactly match - For
filter: check operator (eq, ne, gt, lt) and value type
Nested Function Calls Not Working
Nested Function Calls Not Working
Error:
unexpected function call or incorrect nestingCause: Incorrect syntax for nested function callsSolutions:- Use parentheses for nested calls:
{{get "field" (find "items" "id" "value")}} - Don’t manually add
.Dataparameters - system injects them automatically - Ensure inner function completes before outer function uses result
- Test complex nesting in isolation to identify the problematic level
- Break down complex nesting into multiple steps if needed
Reserved Word Conflicts
Reserved Word Conflicts
Error:
unexpected keyword or parsing errors with field namesCause: Using Go template reserved words as field namesSolutions:- Avoid these reserved words:
if,range,with,end,else,template,block,define - If your data has these field names, use
{{get "reserved_word"}}instead of{{reserved_word}} - Example: Use
{{get "end"}}instead of{{end}}if you have a field named “end” - Consider renaming fields in your data structures to avoid conflicts