Troubleshooting
Storage backend: SQLite as of v0.2.6. File paths under
agent_io/target/.../data.jsonin older snippets are stale; this page uses the current SQLite layout (agent_io/store/<workflow>.db, tabletarget_data).
What happens when something goes wrong in your agentic workflow? This guide helps you debug and fix common errors. Let's explore the error types, their causes, and how to resolve them.
Error Types
SchemaValidationError
The most common error. Occurs when data fails JSON Schema validation.
SchemaValidationError: Input schema validation failed for tool 'add_answer_text'
at target_word_counts -> correct_answer_words: 18 is not of type 'string'
[Context: function=add_answer_text, validation_type=input,
error_path=target_word_counts -> correct_answer_words,
failed_value=18, schema_constraint={'type': 'string'}]
Context Fields:
| Field | Description | Example |
|---|---|---|
error_path | Dot-path to failing field | target_word_counts -> correct_answer_words |
failed_value | The actual value received | 18, 'some text', ['a', 'b'] |
schema_constraint | Expected schema rule | {'type': 'string'} |
function | Tool that failed | add_answer_text |
validation_type | Input or output validation | input, output |
ProcessingError
Wraps lower-level errors with record context. Think of this as a breadcrumb trail—it tells you which specific record caused the problem.
ProcessingError: Failed to process item
[Context: source_guid=37812c37-80a2-596b-8747-8f93e7a34e7f,
agent_name=add_answer_text]
Context Fields:
| Field | Description |
|---|---|
source_guid | UUID of the record being processed |
agent_name | Action that failed |
RecordContextError (Record Namespace)
Occurs when a downstream action tries to read fields from an upstream action's output, but the expected fields are missing. This typically means the upstream action stored corrupted or empty data.
[RECORD NAMESPACE] 'generate_optimal_code': declared fields ['optimal_code'] not found.
Available: ['title', 'type', 'properties', 'required', 'additionalProperties']
Common causes:
| Cause | What happened | Fix |
|---|---|---|
| Schema-echo | LLM returned the JSON Schema definition instead of data | Enable on_schema_mismatch: reprompt — the validator now detects schema-echo payloads and triggers reprompt |
| Empty object | LLM returned {} | Same fix — empty objects are now rejected when the schema declares fields |
| Existing poison | Bad data already in target_data from a previous run | Re-run the upstream action with reprompt enabled, or manually clean the SQLite row |
Behavior: When a single record has corrupted namespace data, that record is skipped and processing continues for the remaining records in the file. The skipped record is logged at WARNING level. This prevents one bad record from failing an entire file of valid records.
AgentActionsError
Top-level agentic workflow failure. This wraps other errors to provide the full context of what went wrong.
AgentActionsError: Error generating target: Failed to process content
[Context: file_path=/path/to/data.json,
agent_name=my_workflow]
Common Errors & Fixes
Let's walk through the most common errors and how to fix them.
Template Variable Error
Error:
Template rendering failed for agent 'write_question'
Reference: classify.question_type
Namespace 'classify' exists: YES
Field 'question_type' in namespace: NO
Available in 'classify': question_category, difficulty_level, tags
Did you mean 'classify.question_category'?
Fix: Check that 'classify' produces the referenced field.
Cause: Template references a field that doesn't exist in the namespace. The error message shows:
- Whether the namespace (e.g.,
classify) exists - Whether the field exists within that namespace
- Available fields you can use instead
- A suggestion if there's a similar field name (typo detection)
Fix options:
- Check the field name - Use the suggested alternative if it's a typo
- Verify the dependency - Ensure the action producing this field is in your
dependencieslist - Check the upstream action's output - Verify the action produces the expected field
Source Data Structure Mismatch
Error:
Template rendering failed for agent 'my_action'
Reference: source.content
Namespace 'source' exists: NO
Available namespaces: items, metadata
Cause: Source data is a wrapper object, not a flat array of records. This happens when your JSON file has metadata alongside the actual records.
Wrong format:
{
"exam_name": "My Exam",
"items": [
{"id": "1", "content": "..."},
{"id": "2", "content": "..."}
]
}
Prompt expects {{ source.content }} but source is the wrapper, not individual items.
Correct format:
[
{"id": "1", "content": "..."},
{"id": "2", "content": "..."}
]
Fix options:
-
Restructure input - Extract array to staging file:
pythonPYdata = json.load(open("wrapper.json"))records = data["items"]json.dump(records, open("staging/data.json", "w")) -
Add preprocessing action - First action extracts items:
yamlYAML- name: extract_itemskind: toolimpl: extract_items_from_wrappergranularity: file -
Update prompts - If wrapper is intentional:
jinja2JINJA2{% for item in source.items %}{{ item.content }}{% endfor %}
Type Mismatch
Error:
18 is not of type 'string'
Cause: Integer value where string expected.
Fix: Convert in your tool:
data['field'] = str(data['field'])
Or fix the TypedDict:
# If the field can be int or string
field: Union[int, str]
Array vs String Mismatch
Error:
'Audit client code...' is not of type 'array'
Cause: String value where array expected.
Fix: Normalize in your tool:
answer_text = data["upstream_action"]["answer_text"]
if isinstance(answer_text, str):
answer_text = [answer_text]
Unexpected Field
Error:
'new_field' was unexpected
Cause: Field exists in data but not declared in TypedDict.
Fix: Add field to TypedDict:
class MyInput(TypedDict, total=False):
existing_field: str
new_field: str # Add the missing field
Or use total=False to allow any fields.
Mixed-Type Dict Values
Error:
'greater_than' is not of type 'integer'
Cause: Using Dict[str, int] but values include strings.
Fix: Use plain dict for mixed-type dictionaries:
# BAD - Fails if values include strings
target_word_counts: Dict[str, int]
# GOOD - Allows any structure
target_word_counts: dict
Missing Required Field
Error:
'question' is a required property
Cause: Schema requires field but tool didn't return it.
Fix: Ensure tool returns all required fields:
@udf_tool()
def my_function(data: dict) -> dict:
return {
'question': data["upstream_action"]["question"],
'processed': True
}
Or enable reprompting for LLM actions:
reprompt:
max_attempts: 4
on_exhausted: return_last
Debugging with Prompt Traces
When an LLM action produces unexpected output, the fastest path to understanding "why" is inspecting the compiled prompt and raw response. Agent Actions captures both automatically.
Prompt traces only exist for LLM actions. Tool (UDF) actions do not generate prompt traces. If a tool action produces empty or wrong output, debug the tool function directly — check the input data it receives, not prompt_trace.
Using the Data Explorer
- Run
agac docsto generate the documentation catalog - Open the Data Explorer in your browser
- Navigate to the action's output in the Data tab
- Find the record with unexpected output
- Click the Prompt Trace accordion below the record — it shows:
- Compiled Prompt: The exact text the LLM received (with all template variables resolved)
- LLM Response: The raw text the LLM returned (before parsing)
What to Look For
- Missing context: Template variables resolved to empty strings or wrong values — check your
context_scopeand dependency chain - Ambiguous instructions: The prompt doesn't clearly constrain the output format — tighten the prompt template
- Schema mismatch: The LLM response doesn't match the expected JSON structure — consider enabling reprompting
- Model badge: Check if the model name matches what you expected — a misconfigured provider can route to the wrong model
Querying Traces Directly
For bulk analysis across many records, you can query the storage backend directly. With the default SQLite backend:
sqlite3 my_workflow/agent_io/store/my_workflow.db \
"SELECT record_id, response_text FROM prompt_trace WHERE action_name = 'classify_issue' LIMIT 10"
See Prompt Traces reference for the full table schema and query examples.
Inspecting Failed Record Input Snapshots
When records fail or exhaust retries, the framework captures the input record at the moment of failure. Query the disposition table to see what data was being processed:
-- View failed records with their input snapshots
SELECT action_name, record_id, reason, input_snapshot
FROM record_disposition
WHERE disposition = 'failed' AND input_snapshot IS NOT NULL;
-- View exhausted records (retries exceeded)
SELECT action_name, record_id, reason, input_snapshot
FROM record_disposition
WHERE disposition = 'exhausted' AND input_snapshot IS NOT NULL;
Debugging Agentic Workflows
When an error occurs, resist the urge to start changing code immediately. Follow this systematic approach to understand what went wrong before fixing it.
Step 1: Parse the Error
Extract key information from the error message. Agent Actions provides structured error context—use it:
- What type?
SchemaValidationError,ProcessingError, etc. - Which field? Look at
error_path - What value? Look at
failed_value - What expected? Look at
schema_constraint - Which action? Look at
functionoragent_name
Step 2: Find the Source Record
Use source_guid to trace the record. Each action's output is a JSON array stored in the data column of the target_data table, keyed by action_name. Use json_each(data) to scan records and json_extract to filter by source_guid:
# Find every action that produced a record with this source_guid
sqlite3 agent_io/store/<workflow>.db "
SELECT DISTINCT t.action_name
FROM target_data t, json_each(t.data) r
WHERE json_extract(r.value, '\$.source_guid') = '37812c37-80a2-596b-8747-8f93e7a34e7f'
"
Step 3: Check Node Outputs
Compare data at each stage. Filter the JSON blob in the data column by source_guid to inspect the same record at each step:
# Input to failing action — fetch the matching record
sqlite3 agent_io/store/<workflow>.db "
SELECT json_extract(r.value, '\$')
FROM target_data t, json_each(t.data) r
WHERE t.action_name = '<failing_action>'
AND json_extract(r.value, '\$.source_guid') = '37812c37-80a2-596b-8747-8f93e7a34e7f'
"
# Previous action's output for the same record
sqlite3 agent_io/store/<workflow>.db "
SELECT json_extract(r.value, '\$')
FROM target_data t, json_each(t.data) r
WHERE t.action_name = '<previous_action>'
AND json_extract(r.value, '\$.source_guid') = '37812c37-80a2-596b-8747-8f93e7a34e7f'
"
Step 4: Enable Debug Mode
Get detailed tracebacks:
AGENT_ACTIONS_LOG_LEVEL=DEBUG agac run -a my_workflow
Step 5: Enable Prompt Debug
See rendered prompts for LLM actions:
- name: my_action
prompt: $workflow.My_Prompt
prompt_debug: true # Logs full prompt
Data Lineage
Here's where it gets interesting: every record in Agent Actions maintains tracking fields that let you trace its journey through the agentic workflow. This lineage data is invaluable for debugging.
source_guid
Original content UUID. Never changes through the agentic workflow.
"source_guid": "37812c37-80a2-596b-8747-8f93e7a34e7f"
Use this to trace any record back to its source, regardless of how many actions it has passed through.
lineage
Array of all node_ids visited. Grows at each node.
"lineage": [
"node_0_693094fb-53d1-48d6-bdc9-781a4989d35c",
"node_1_361c54c6-7080-4527-9a00-aaeccfd0e6ba_0",
"node_2_e546c260-de10-4f20-8950-e09f01ea468f"
]
The _0 suffix indicates this was the first record from a flattening operation.
node_id
Identifies the specific processing node:
node_0_693094fb-... # Single output
node_1_361c54c6-..._0 # Flattened, index 0
node_1_361c54c6-..._1 # Flattened, index 1
Action Output Storage
Action outputs are stored as JSON arrays in the target_data table of agent_io/store/<workflow>.db, keyed by (action_name, relative_path). List what each action produced:
sqlite3 agent_io/store/<workflow>.db "
SELECT action_name, relative_path, record_count
FROM target_data
ORDER BY action_name, relative_path
"
Typical layout for a multi-action workflow:
target_data
├── action_name='node_0_extract_raw_qa' relative_path='input.json' record_count=42
├── action_name='node_1_flatten_questions' relative_path='input.json' record_count=128
├── action_name='node_2_classify_type' relative_path='input.json' record_count=128
└── action_name='final_workflow_output' relative_path='input.json' record_count=128
TypedDict Best Practices
Use total=False for Optional Fields
class MyInput(TypedDict, total=False):
required_field: str # Still works, just not enforced
optional_field: str
Use Plain dict for Mixed Types
# When dict values can be int, string, or other types
metadata: dict # Not Dict[str, int]
Use Union for Polymorphic Fields
# When a field can be different types
answer_text: Union[str, List[str]]
Access Namespaced Fields
@udf_tool()
def safe_function(data: dict) -> dict:
value = data["upstream_action"]["field"]
items = data["upstream_action"]["items"]
return {'result': value}
Reprompting
What happens when an LLM returns invalid JSON? Rather than failing immediately, Agent Actions can automatically retry with feedback about what went wrong. This is reprompting.
Configuration
Reprompt requires explicit configuration:
reprompt:
max_attempts: 3 # Number of retry attempts
on_exhausted: return_last # return_last | raise
To disable: reprompt: false
Configuration Options
| Option | Description |
|---|---|
max_attempts | Maximum retry attempts (default: 2) |
on_exhausted | Behavior when exhausted: return_last, raise |
When to Use
Consider what your agentic workflow needs:
- Simple schemas — Low
max_attempts(2-3) - Complex schemas — Higher
max_attempts(4-5) - Critical outputs — Maximum attempts,
on_exhausted: raise
Reprompting adds latency and token cost. For high-volume agentic workflows, consider fixing schema issues at the source rather than relying on retries.
Log Analysis
Log Location
agent_io/logs/events.json
Search Patterns
Events are JSON Lines. Structured fields live under the .data object
(e.g. .data.action_name); message and level are top-level.
# Find all schema validation errors
jq -c 'select(.message | test("SchemaValidationError"))' agent_io/logs/events.json
# Find events for a specific action
jq -c 'select(.data.action_name == "add_answer_text")' agent_io/logs/events.json
# Find events that mention a specific field
jq -c 'select(.message | test("answer_text"))' agent_io/logs/events.json
Execution History
Check artefact/runs.json for execution metrics:
# Count failed runs
grep '"status": "FAILED"' artefact/runs.json | wc -l
# Find recent failures
grep -A5 '"status": "FAILED"' artefact/runs.json | tail -20
Quick Reference
These commands and patterns are your debugging toolkit.
Debug Commands
# Debug mode (full tracebacks)
AGENT_ACTIONS_LOG_LEVEL=DEBUG agac run -a workflow
Common Fixes Cheatsheet
| Error Pattern | Quick Fix |
|---|---|
X is not of type 'string' | str(value) or Union[int, str] |
X is not of type 'array' | [value] if isinstance(value, str) else value |
X was unexpected | Add field to TypedDict |
X is a required property | Return field from tool or use reprompt |
Dict[str, int] fails | Use plain dict |
Debug Checklist
When debugging agentic workflow errors, work through this checklist:
- Read full error message, note
error_pathandfailed_value - Find record by
source_guidin node outputs - Compare data before/after failing action
- Check TypedDict matches actual data shape
- Enable
prompt_debug: truefor LLM actions - Run with
AGENT_ACTIONS_LOG_LEVEL=DEBUGfor tracebacks - Consider enabling reprompt with
max_attempts: 3for LLM schema failures
Most errors fall into one of two categories: schema mismatches (the data structure doesn't match expectations) or missing fields (required data wasn't provided). The checklist above helps you identify which category you're dealing with.
Dependency Patterns
Understanding Fan-in vs Parallel
When using multiple dependencies, understanding the pattern detection is crucial:
# Pattern 1: Parallel Branches (MERGE)
dependencies: [classify_1, classify_2, classify_3]
# Same base name "classify" → outputs are merged
# Execution count: N (from merged outputs)
# Pattern 2: Fan-in (PRIMARY + CONTEXT)
dependencies: [extract, enrich, validate]
# Different actions → first is primary, others via context
# Execution count: N (from extract only)
# Pattern 3: Aggregation (MERGE with reduce_key)
dependencies: [validator_A, validator_B, validator_C]
reduce_key: parent_id
# reduce_key set → all outputs merged and grouped by key
Missing Context Data in Fan-in
Symptom: Action only sees data from first dependency, not others.
Cause: Fan-in pattern requires context sources to be in context_scope:
# WRONG - enrich and validate data not accessible
- name: generate_report
dependencies: [extract, enrich, validate]
# CORRECT - all dependencies in context_scope
- name: generate_report
dependencies: [extract, enrich, validate]
context_scope:
observe:
- extract.*
- enrich.* # Loaded from record namespaces
- validate.* # Loaded from record namespaces
Unexpected Execution Count
Symptom: Action executes more/fewer times than expected.
Debug:
- Check if dependencies are parallel branches (same base name) or different actions
- For fan-in: first dependency determines execution count
- For parallel: merged outputs determine execution count
- For aggregation:
reduce_keygroups determine execution count