Configuration Reference¶
This page documents every field in the {config_id}_dag dictionary. Use it alongside the Tutorial and Advanced Patterns for context on how each field is used in practice.
Top-level structure¶
The {config_id}_dag() function (e.g. bella_notte_dag()) returns a dictionary with these top-level keys:
{
"slots": [ ... ], # Required
"tasks": [ ... ], # Required (can be empty list)
"confirm_transition_prefix": [ ... ], # Optional
"steer_back": { ... }, # Optional
}
| Key | Type | Required | Description |
|---|---|---|---|
slots | list[dict] | Yes | Ordered list of slot definitions. Order determines question priority. |
tasks | list[dict] | Yes | Ordered list of task definitions. Order determines firing priority. |
confirm_transition_prefix | list[str] | No | Transition phrases used after readback confirmation. One is picked randomly. |
steer_back | dict | No | 3-tier steer-back config for off-topic conversations (soft directive, hard preempt, escalate). |
Slot configuration¶
Each slot is a dictionary in the slots list. The available fields depend on the slot's source.
Common fields (all slot types)¶
| Field | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Unique identifier. Used in requires, inputs, outputs, and {placeholder} references. |
source | str or list[str] | Yes | Where the value comes from. See source types. |
condition | str | No | Lambda string evaluated each turn. Returns True to activate, False to deactivate. Receives filled dict as argument. Example: "lambda filled: int(filled.get('party_size', 0)) >= 5" |
requires | list[str] | No | Slot names that must be in filled before this slot is asked or its setter is visible. |
Source types¶
| Source | Description | Required fields |
|---|---|---|
"user" | User provides the value via a setter tool | setter, ask |
"task:TaskName" | Value populated when task TaskName succeeds | None (task's outputs maps to this slot) |
"event" | Pre-filled from external event data | event_key |
"announce" | Framework delivers a message, no input needed | message |
["event", "user"] | Try event first, fall back to user if no event data | event_key, setter, ask |
User-sourced slot fields¶
These fields apply when source is "user" or includes "user" in a list.
| Field | Type | Required | Description |
|---|---|---|---|
setter | str | Yes | Tool name the LLM calls to record the value. Must match a tool in the app. |
ask | str | Yes | Question text. Supports {slot_name} placeholders resolved from filled. |
hint | str | No | Short description for tool selection guidance. Helps the LLM pick the right setter when the user provides multiple values. |
requires_readback | bool | No | If True, value goes to pending for confirmation before filled. Default: False. |
readback_fmt | str or dict | No | How to format the value during readback. See readback formats. |
response | list[dict] | No | Rich response parts (payloads, chips) delivered alongside the slot's question. See Rich Response Payloads. |
validation | dict | No | Validation error messages, retry limits, and escalation. See validation config. |
validate_against | dict | No | Cross-slot validation. See cross-slot validation. |
setter_field | str | No | For multi-slot setters. Identifies which field of the multi-slot tool maps to this slot. The tool returns {"values": {...}, "field_errors": {...}} instead of {"value": ...}. |
Event-sourced slot fields¶
These fields apply when source is "event" or includes "event" in a list.
| Field | Type | Required | Description |
|---|---|---|---|
event_key | str | Yes | Key to look up in the event_data dictionary passed via session variables. |
Announce slot fields¶
These fields apply when source is "announce".
| Field | Type | Required | Description |
|---|---|---|---|
message | str | Yes | The message to deliver. Supports {slot_name} placeholders. |
preempt | bool | No | If True, message is delivered verbatim (LLM doesn't run). If False (default), LLM wraps it naturally. |
response | list[dict] | No | Rich response parts (payloads, cards) delivered with the announce message. See Rich Response Payloads. |
Readback formats¶
The readback_fmt field controls how a value is presented during readback confirmation.
Built-in string formats¶
| Value | Description | Example |
|---|---|---|
"date" | Formats YYYY-MM-DD as a human-readable date | "2026-06-19" → "Friday, June 19th" |
"time" | Formats time values | "19:30" → "7:30 PM" |
Dictionary formats¶
| Config | Description | Example |
|---|---|---|
{"type": "plural", "one": "guest", "other": "guests"} | Pluralizes based on numeric value | 4 → "4 guests", 1 → "1 guest" |
{"type": "prefix", "text": "under the name"} | Prepends a label | "Maria" → "under the name Maria" |
{"type": "none_sub", "default": "no special requests"} | Substitutes a default when value is "none" or empty | "none" → "no special requests" |
Validation config¶
The validation field on a slot controls error handling and retries.
"validation": {
"max_retries": 3,
"errors": {
"error_code_1": "Message for this error.",
"error_code_2": "Another message with {slot_name} placeholder.",
},
"on_exhaust": {
"say": "Escalation message to the user.",
"then": "escalate",
},
},
| Field | Type | Required | Description |
|---|---|---|---|
max_retries | int | Yes | Maximum number of validation failures before escalation. |
errors | dict[str, str] | Yes | Maps error codes (returned by setter) to user-facing messages. Supports {slot_name} placeholders. |
error_responses | dict[str, list[dict]] | No | Maps error codes to rich response parts delivered alongside the error message. Keys match errors keys. |
on_exhaust | dict | Yes | Action when retries are exhausted. See escalation config. |
Error code protocol¶
Setters signal errors by returning {"error": True, "error_code": "code_name"}. The code_name must match a key in the errors dictionary. If the code isn't found, the framework uses a generic retry message.
Cross-slot validation¶
The validate_against field on a slot enables validation of one slot's value against data in another filled slot.
"validate_against": {
"response_field": "display_value",
"filled_slot": "available_times",
"error_code": "not_available",
},
| Field | Type | Required | Description |
|---|---|---|---|
response_field | str | Yes | Key in the setter's response dict to validate. |
filled_slot | str | Yes | Name of a filled slot whose value is checked against. |
error_code | str | Yes | Error code to use if validation fails. Must have a corresponding entry in validation.errors. |
The framework checks whether the setter's response_field value appears in the filled slot's value. If not, it triggers the specified error code.
Task configuration¶
Each task is a dictionary in the tasks list.
{
"name": "FindAvailableTimes",
"tool": "find_available_times",
"inputs": ["party_size", "preferred_date"],
"requires": [],
"outputs": {"available_times": "available_times"},
"success_check": "success",
"terminal": False,
"readback_inputs": False,
"then_say": "We have {available_times}. Which time?",
"condition": None,
"on_failure": { ... },
},
| Field | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Unique identifier for the task. |
tool | str | Yes | Name of the executor tool to call. Must match a tool in the app. |
inputs | list[str] | Yes | Slot names whose values are passed as arguments. All must be in filled (or inactive) before the task fires. |
requires | list[str] | No | Slot names that must be in filled (gates) but aren't passed as arguments. Use for gating on announce slots. |
outputs | dict[str, str] | Yes | Maps result keys to slot names. {"result_key": "slot_name"} — when the task succeeds, result["result_key"] is stored in filled["slot_name"]. |
success_check | str | Yes | Key in the task result that must be truthy for the task to count as successful. |
terminal | bool | No | If True, successful completion ends the conversation. Default: False. |
readback_inputs | bool | No | If True, enables deferred readback for this task's input slots. Default: False. |
then_say | str | No | Message shown to the user on success. Supports {slot_name} placeholders resolved from filled and task_results. |
then_response | list[dict] | No | Rich response parts delivered on task success. When present, replaces then_say as the preemption response. Supports {slot_name} variable substitution. |
condition | str | No | Lambda string. Task only fires when condition is True. Receives filled dict. |
channel_responses | dict[str, list[dict]] | No | Channel-specific response overrides. Keys are channel identifiers; values replace the default then_response for that channel. |
on_failure | dict | No | Failure handling config. See task failure config. |
Task failure config¶
The on_failure field on a task controls retry behavior when the task fails.
"on_failure": {
"retry_say": "That didn't work. Want to try a different date?",
"max_retries": 2,
"clear_slots": ["preferred_date"],
"on_exhaust": {
"say": "I can't complete this. Please call us.",
"then": {"tool": "end_session", "args": {"reason": "retry_exhausted"}},
},
},
| Field | Type | Required | Description |
|---|---|---|---|
retry_say | str | Yes | Message shown to the user on each failure. |
max_retries | int | Yes | Maximum retry attempts before escalation. |
clear_slots | list[str] | No | Slots to clear from filled on failure. The framework re-asks for these slots. Omit for same-input retry. |
on_exhaust | dict | Yes | Action when retries are exhausted. See escalation config. |
Retry modes¶
- Re-collect retry (with
clear_slots): Clears named slots and re-asks the user. Good for "no availability" scenarios where different inputs might succeed. - Same-input retry (without
clear_slots): Retries with the same inputs on the next engine pass. Good for transient backend errors.
Escalation config¶
The on_exhaust field appears in slot validation, task failure, and steer-back configs. It always has the same structure:
| Field | Type | Required | Description |
|---|---|---|---|
say | str | Yes | Message delivered to the user. |
then | str or dict | Yes | The escalation action. See below. |
then formats¶
| Format | Description | Example |
|---|---|---|
"escalate" | String tool name. Framework calls this tool. | "escalate" |
{"tool": "...", "args": {...}} | Tool name with arguments. | {"tool": "end_session", "args": {"reason": "retry_exhausted"}} |
Global: steer-back¶
Controls the 3-tier steer-back mechanism for off-topic conversations.
"steer_back": {
"soft_after": 2,
"hard_after": 4,
"escalate_after": 6,
"on_exhaust": {
"say": "Having trouble completing your request.",
"then": {"tool": "end_session", "args": {"reason": "steer_back_exhausted"}},
},
},
| Field | Type | Required | Description |
|---|---|---|---|
soft_after | int | Yes | Consecutive off-topic turns before injecting a steering directive in the system instruction. |
hard_after | int | Yes | Consecutive off-topic turns before preempting with the next question (or readback re-ask). |
escalate_after | int | Yes | Consecutive off-topic turns before escalation. |
on_exhaust | dict | Yes | Action when steer-back reaches the escalate tier. |
How it works:
- Soft (tier 1): After
soft_afterturns, the engine adds a<steer_back>directive to the system instruction. The LLM still runs and can incorporate the guidance. - Hard (tier 2): After
hard_afterturns, the engine preempts — re-asks the readback question or the next collection question. Then yields one turn so the LLM can process the user's response. - Escalate (tier 3): After
escalate_afterturns, fireson_exhaust. Typically ends the session or transfers to a human.
The counter resets to 0 whenever forward progress occurs (new slot filled, value confirmed, etc.).
Global: confirm transition prefix¶
A list of transition phrases. After the user confirms a readback and the engine moves to the next question, it picks one randomly as a prefix: "Wonderful! What date would you like?"
If omitted, no prefix is added — the engine delivers the next question directly.
State dictionary reference¶
All framework state lives in callback_context.state["sm"]. You generally don't modify this directly — the engine and callbacks manage it — but it's useful for debugging and advanced customization.
| Key | Type | Description |
|---|---|---|
filled | dict[str, Any] | Confirmed slot values. |
pending | dict[str, Any] | Values awaiting readback confirmation. |
deferred | dict[str, Any] | Values held for grouped readback (deferred mode). |
task_results | dict[str, dict] | Full response from each completed task. |
_retries | dict[str, int] | Retry counters. Keys: "TaskName" (task), "slot:name" (validation). |
_slot_errors | list[dict] | Validation errors from the current turn's setter calls. |
_steer_back_turns | int | Consecutive off-topic turns since last forward progress. Drives the 3-tier steer-back mechanism. |
_system_message | str | Next message for the LLM (set by engine, consumed by callback). |
_pending_payloads | list[dict] | Announce-slot response parts stashed for after_model_callback injection. |
_pending_question_payloads | dict | Question-slot response parts with slot name for conditional injection. |
_debug_mode | bool | Opt-in debug logging. Set to True in session state to enable. |
_debug_log | list[str] | Debug log entries (visible in simulator trace). Populated when _debug_mode is True. |
status | str | "in_progress", "complete", or "escalated". |
Setter tool protocol¶
Every setter tool follows the same return protocol:
Success response¶
The after_tool_callback reads "value" and routes it to pending (or deferred).
Error response¶
The after_tool_callback appends the error to _slot_errors. The engine looks up the code in the slot's validation.errors config and delivers the message.
With cross-slot validation¶
When a slot has validate_against, include the validation field in the success response:
The after_tool_callback checks response["display_value"] against filled["available_times"].
Multi-slot success response¶
When a slot has setter_field in the config, the after_tool_callback reads the matching field from values. Each field is routed independently — valid fields go to pending even if other fields have errors:
return {"stored": True, "values": {"party_size": 4}, "field_errors": {"preferred_date": "past_date"}}
Executor tool protocol¶
Executor tools (called by tasks) return a result dictionary. The framework checks the success_check key:
Success response¶
The after_tool_callback maps outputs keys from the result to filled slots.
Failure response¶
The engine follows the task's on_failure config.
Quick-start template¶
Copy this template to start a new slot-filling agent. Replace the slot and task definitions with your own.
from typing import Any
def my_agent_dag() -> dict[str, Any]:
"""Return the slot-filling DAG configuration."""
return {
"slots": [
{
"name": "slot_1",
"source": "user",
"setter": "set_slot_1",
"ask": "What is value 1?",
},
{
"name": "slot_2",
"source": "user",
"setter": "set_slot_2",
"ask": "What is value 2?",
},
{
"name": "result",
"source": "task:MyTask",
},
],
"tasks": [
{
"name": "MyTask",
"tool": "my_backend_tool",
"inputs": ["slot_1", "slot_2"],
"outputs": {"result": "result"},
"success_check": "success",
"terminal": True,
"then_say": "Done! Result: {result}",
"on_failure": {
"retry_say": "That didn't work. Try different values?",
"max_retries": 1,
"clear_slots": ["slot_1"],
"on_exhaust": {
"say": "Unable to complete. Please try again later.",
"then": {"tool": "end_session", "args": {"reason": "retry_exhausted"}},
},
},
},
],
}