Skip to content

Configuring an AI Diffusion Use Case

A use case on the AI Diffusion DPG is a folder of YAML, one file per block. You do not change service code. This page explains how those files are loaded, what goes in each one, and how one phase of the Blue Dot (jobs) voice agent turns into the turn shown on the architecture page. All file paths are in the ai-diffusion-dpg repository.

The examples are the live voice configuration for the jobs domain (Blue Dot), in dev-kit/configs/blue-dots/. Other domains use the same structure: a use case for education or tourism would be its own folder under dev-kit/configs/, with the same block files.

Each block reads two files at startup:

  • Framework defaults, in dev-kit/dpg/<block>.yaml. These hold the values every use case shares: ports, the addresses of the other blocks, timeouts and retries. They exist so your files only declare what is different.
  • Your use case, in dev-kit/configs/<domain>/<block>.yaml. For the jobs use case this is dev-kit/configs/blue-dots/.

The block deep-merges the two, with your values winning. Maps are merged key by key. Any other value, including a list, is replaced whole by yours. The merged result is then checked against the block’s schema. The schema rejects unknown keys at every level, so a misspelt or unsupported key stops the block from starting, rather than being silently ignored during a live call.

flowchart TD
    A["dev-kit/dpg/block.yaml (framework defaults)"] --> C["Mounted as config/dpg.yaml"]
    B["dev-kit/configs/domain/block.yaml (use case)"] --> D["Mounted as config/block.yaml"]
    C --> E["${VAR} expansion (Reach Layer, Action Gateway)"]
    D --> E
    E --> F["Deep merge (use case overrides defaults)"]
    F --> G["Schema validation (an unknown key stops startup)"]
    G --> H["Running block"]

Reach Layer and Action Gateway expand environment variables in string values before the merge: ${VAR} takes the variable’s value, and ${VAR:-default} falls back to the default when the variable is unset. This is how secrets and environment-specific URLs stay out of the YAML. The other blocks do not expand variables.

Each row names the keys you write in dev-kit/configs/blue-dots/<block>.yaml.

BlockWhat you describeKeys in the blue-dots files
Agent CoreThe model, the phases of the conversation and the rules that move between them, how a caller’s turn is understood, the agent’s identity, human handoff, the tools the model may see, and each channel’s output contract.agent (provider, primary_model, fallback_model, max_tool_rounds); agent_workflow.subagents (one entry per phase, each with tools, predispatch, system_prompt, pending and routing); agent_workflow.global_routing; preprocessing.nlu_processor (slots, topics, act_intents, termination_gate); identity; handoff; connectors.read and connectors.write; channels.<channel>, one block per channel the use case answers on (the jobs use case ships with bridge only, for example channels.bridge.output_contract; the optional web, cli and voice channels each need their own block); conversation (fixed lines spoken without the LLM).
Action GatewayHow each tool calls an external system, and how much of the answer comes back.tools[], each with base_url, auth (the key is read from the environment variable named in secret_env), extra_headers, endpoints, and response (projection picks the fields kept, session_mapping copies some into session state, max_size_chars caps the size).
Trust LayerWhat the agent must refuse or escalate, what it must never say, and which words count as a yes or a no to consent.trust.input_rules (blocked_phrases, escalation_topics); trust.output_rules; trust.policy_packs; trust.consent (consent_phrases, decline_phrases); trust.hitl.
Reach LayerWhich channels run, and the channel-specific lines they speak.reach_layer.channels.<channel>, one block per channel. The bridge (OpenAI chat-completions compatible) is the default, and the jobs use case sets its terminal_word, hangup_tool_name and tool_status_phrases. The optional channels are web chat (local/dev only, without a login: web.auth.enabled: false), CLI, voice and MCP; see AI Diffusion Optional Channels.
Knowledge EngineThe documents and glossaries the agent can search.knowledge.blocks.static_knowledge_base (collection_name, sources) and knowledge.blocks.glossary. The jobs use case switches both off, and does not offer the knowledge_retrieval tool.
Memory LayerThe session fields a conversation keeps, and how long they live.state.session.ttl_minutes; state.session.schema (one entry per field, with a type and a default); state.persistent.
Observability LayerThe outcomes and service levels you want to measure.observability.outcomes, observability.sli, observability.audit.

Tools are described in two places on purpose. Agent Core’s connectors say what the model sees: the tool’s description, its input schema and per-turn limits such as max_calls_per_turn. Action Gateway’s tools say how the call is made. The model never sees a URL or a key.

The jobs use case splits a call into phases (called subagents in the YAML): the main path is opening, profile_resolve, job_match, profile_setup and apply_confirm. Other phases handle the exceptions: consent_declined and u18_blocked close the call when consent is refused or the caller is under age, clarification is the fallback when no rule matches, handoff follows a handoff line, confirm_close asks once whether to end the call, and ended ends it. This is a trimmed copy of job_match from dev-kit/configs/blue-dots/agent_core.yaml:

# agent_workflow.subagents[] — trimmed
- id: job_match
description: >
Call fetch_jobs with trade + city. Read out up to 3 best matches
as a compact spoken block.
tools:
- fetch_jobs
predispatch:
- tool: fetch_jobs
unless_fresh: true
args:
query_text:
template: "{trade|stored_trade} jobs in {location|stored_location}"
normalise: { location: city_canonical }
system_prompt: |
You are in the **job_match** phase.
# ... the rest of the prompt: how to read out jobs, hard rules
pending:
- id: submit_confirm
when: [{ field: current_question, operator: contains, value: ["आवेदन भेज दूँ", "अप्लाई कर दूँ"] }]
options_from: { tool: fetch_jobs, fields: [role, company], id_field: item_id }
resolves_to: selected_job_item_id
- id: select_job
options_from: { tool: fetch_jobs, fields: [role, company], id_field: item_id }
resolves_to: selected_job_item_id
routing:
- intent: termination_intent
next_subagent_id: ended
- intent: apply_now
conditions:
- field: profile_item_id
operator: not_eq
value: ""
next_subagent_id: apply_confirm
- intent: apply_now
next_subagent_id: profile_setup
- intent: "*"
next_subagent_id: job_match

Here is the turn from the architecture page, where the caller asks for electrician work in Lucknow, traced through this configuration:

  1. Understanding. The slots in preprocessing.nlu_processor declare trade and location, so the understanding call returns trade = Electrician and location = Lucknow, and both are written to the session.
  2. Routing into the phase. The caller was in profile_resolve. Its rule intent: "*", with trade and location both not_eq "", matches, so the next phase is job_match. A phase’s own rules are tried top to bottom and the first match wins. If none match, agent_workflow.global_routing is tried, then default_fallback_subagent_id.
  3. Pre-dispatch. The predispatch entry fills its template from the session and searches for “Electrician jobs in Lucknow” before the reply call starts. normalise maps the city through the city_canonical table (Bangalore to Bengaluru, for example). unless_fresh skips the search when a fresh result is already saved.
  4. The prompt. system_prompt tells the model how to read out the results. tools lists only fetch_jobs, so in this phase the model cannot save a profile or apply for a job, whatever it writes.
  5. The reply. The output guard applies channels.bridge.output_contract (Devanagari script, numbers as words), and the reply ends on a question, such as asking which job the caller wants to hear more about.

On the caller’s next turn, pending decides which question they are answering. Agent Core takes the first entry whose when conditions hold. After a list of jobs, the last question contains no apply phrase, so select_job is pending. When the caller names a job (“the Flipkart one”), options_from lets the understanding call match the name against the saved fetch_jobs result, and resolves_to writes the job’s item_id to selected_job_item_id. Naming a job maps to the job_pick intent, and the phase’s job_pick rules (not shown in the excerpt) send the caller straight to apply_confirm or profile_setup, with the same profile condition as apply_now. When the agent has instead asked “shall I send the application?”, submit_confirm is pending. An act_intents row maps a yes to that question to apply_now, and the apply_now rule sends the caller to apply_confirm if they already have a live profile, or to profile_setup to create one. A goodbye that the termination gate accepts maps to termination_intent, and the termination_intent rule ends the call.

Consent works through the same phases. In the jobs use case, the write tools, save_profile and apply_job, are listed only in profile_setup and apply_confirm. Those phases come after opening, which settles consent: it asks a new caller the consent question, and lets a returning caller through without asking only when their consent is already on file.

You configure the content of a conversation. The framework owns its mechanics, so every use case gets them without writing them:

  • The turn pipeline. The order of the steps in a turn (load, understand, check, route, pre-dispatch, prompt, reply, guard, check, write) is fixed in Agent Core.
  • The tool guards. Agent Core enforces the per-turn call limits you set and the overall max_tool_rounds. For the parameters you list in grounded_params, it refuses a tool call whose identifier did not come from an earlier tool result.
  • The Trust boundary. The Trust Layer’s input and output checks are steps of the pipeline, not options. If the Trust Layer cannot be reached, the check counts as a block.
  • Termination. Ending a call is decided in code. termination_gate lets you say when a goodbye is accepted, for example only after an application or a closing offer. The framework then ends the session.
  • Streaming. Splitting the reply into sentences, checking them in batches and sending each one as soon as it passes.

The dev-kit (port 8080) is the tool you use to write a use case. It is a web app that interviews you about your use case step by step, with steps such as language, knowledge, memory, trust, tools, workflow and channels, and writes the YAML files to dev-kit/configs/<domain>/. Before it deploys a use case, it validates each block’s merged configuration. Run from its Docker image, it uses the blocks’ runtime schemas, the same ones each block checks at startup. Run directly on your machine, it uses a hand-kept copy of those schemas inside the dev-kit, which can lag behind the runtime. You can also edit the files by hand. A running agent does not depend on the dev-kit.

  • A key that is newer than the image. The schema is built into each block’s image. If your YAML comes from a newer branch than the image you run, a key the image does not know fails validation, and the block stops at startup. Keep the configuration and the image tag from the same commit.
  • Expecting a list to merge. A list in your file replaces the default list whole. To add one item to a default list, copy the whole list into your file.
  • TOOL_RESULT_KEY_SECRET is not set. The Memory Layer needs this secret to save tool results. Without it the block still starts, but logs tool_result_store.disabled and saves no tool results. Later turns then cannot put earlier results back into the prompt as known facts. Set it in the Memory Layer’s environment.
  • An unresolved placeholder in a tool URL. In Action Gateway, a ${VAR} with no default and no value in a tool’s base_url stops the block at startup. The error names the tool and the variable. Set the variable, or write ${VAR:-default}.
  • Pointing at a different Signals cluster. The jobs use case’s tools read SIGNALS_BASE_URL, SIGNALS_SEARCH_URL and SIGNALS_INSTANCE_URL. Change all three together, and change the API keys with them. A key from another cluster returns 401 or 403 instead of a clear error.
  • ${VAR} in other blocks. Only Reach Layer and Action Gateway expand variables. In any other block’s file, ${VAR} is just text.