Skip to main content
An agent’s behaviour, language, models, and knowledge attachment all live in one nested config object on the agent document. This page documents every field of it: type, default, bounds, and what the runtime actually does with it. Field names, defaults, and bounds come from AgentConfigPayload and its nested models in apps/api/app/models/schemas.py. What the runtime reads comes from apps/runtime/services/pipecat/config.py and its neighbours.
config is validated on both POST /api/v1/agents and PATCH /api/v1/agents/{agent_id} by validate_agent_config() in apps/api/app/services/agent_config_validation.py. A failure returns 422. The whole page is also live on a running API at /docs.

The config object

AgentConfigPayload has seven fields. behaviour and knowledge_base may be omitted entirely and take their model defaults. prompts, language, and models are required.

prompts

AgentPrompts. Two fields. validate_agent_config() strips whitespace from greeting_message and rejects an empty result with prompts.greeting_message is required. Both strings pass through variable substitution — see Custom variables.

behaviour

AgentBehaviour. Every field is optional and every field has a default, so "behaviour": {} is valid. The description= column is the text from the model itself.

How the runtime reads them

The Pydantic defaults are not always the effective defaults. pipeline_config_from_behaviour() and online_detection_from_behaviour() coerce nulls when they build the pipeline: The idle timeout the pipeline uses is user_online_detection_seconds when online detection is enabled, and user_silence_hangup_seconds otherwise (PipelineConfig.user_idle_timeout). The two settings share one timer — you cannot have both. ignore_user_speech_before_greeting installs Pipecat’s MuteUntilFirstBotCompleteUserMuteStrategy. interruption_min_words above zero installs a MinWordsUserTurnStartStrategy. Hold messages need both halves: hold_from_behaviour() returns nothing unless hold_message_timeout_seconds is non-null and greater than zero and hold_messages contains at least one non-blank string. One message is chosen at random per inference, and only one is played per turn. Online detection speaks user_online_detection_message up to user_online_detection_repeats times; on the next idle it speaks user_online_detection_closing_message and ends the call. With detection disabled, a single idle timeout speaks the closing message and ends the call directly. automatic_call_ending registers an end_conversation function tool on the LLM context, but only when both enabled and graceful_llm_call_ending are true — _call_ending_enabled() in apps/runtime/services/pipecat/call_ending.py requires the pair. The tool ends the call when the model calls it.
call_timeout_seconds is accepted, validated, and stored, but nothing in apps/runtime reads it. There is no hard call-duration limit in the pipeline. Enforce a ceiling at your telephony provider if you need one.

Example from the model

AgentBehaviour carries a json_schema_extra example, reproduced here verbatim from apps/api/app/models/schemas.py:

language

AgentLanguage. Two fields. Valid ids come from GET /api/v1/languages, which returns the canonical id → label map the agent builder uses. The per-provider language filter is GET /api/v1/configuration/stt?languages= and its TTS equivalent.
secondary is stored on the agent and returned by the API, but no code reads it. There is no mid-call language switching in VoicEra: nothing in apps/runtime inspects language.secondary, and no processor swaps the STT or TTS language once a session is running. The language a call runs in is whatever the stt_config and tts_config were built with. Treat secondary as documentation of intent, not as behaviour.

models

AgentModels. Three required objects, one per pipeline stage. Two rules apply to all three, enforced by validate_persisted_model_config():
  1. provider is required and must be registered. It is matched against the provider registry for that kind; an unknown id fails validation with the registry’s own error. Enumerate the valid ids with GET /api/v1/configuration/stt, /tts, and /llm, and fetch one provider’s setting schema from GET /api/v1/configuration/{kind}/setting/{provider}.
  2. Non-secret settings only. Every auth and secret field name declared by the provider’s config class is forbidden. Sending one fails with {kind}_config must not include secret/auth fields: …. API keys live in ProviderAuth, stored once per organisation through POST /api/v1/auth and Fernet-encrypted at rest. See Provider credentials (ProviderAuth).
The remaining fields are whatever that provider’s config class declares — model name, voice, language, speed, base URL and so on. Validation runs the payload through the provider’s own Pydantic model, so an unknown or malformed field is rejected there. The stored result is the validated dump with secrets excluded and null values dropped.

knowledge_base

AgentKnowledgeBase. Optional; the default is disabled. When enabled is true, validation requires at least one non-blank document_ids entry, and — when the request carries an organisation — asserts every named document is ready, not still processing or failed. mode: "tool" additionally requires an LLM provider that supports function calling. The allowed set is KB_TOOL_LLM_PROVIDERS in apps/api/app/services/agent_config_validation.py: anthropic, azure_openai, groq, openai. Any other provider is rejected. The runtime re-parses this blob in apps/runtime/services/knowledge/config.py and is more forgiving than the API: an unrecognised mode falls back to context, and top_k is clamped into 110. Knowledge is skipped entirely when enabled is false or document_ids is empty. See Knowledge base (RAG).

Custom variables and prompt substitution

custom_variables is a free-form object of named defaults. Keys must be non-empty strings; values are arbitrary. At call time the runtime merges two layers, with the call winning:
That merge is resolve_custom_variables() in apps/runtime/services/pipecat/audio.py. Per-call values arrive from the custom_variables field on POST /api/v1/calls/outbound, or from a campaign contact row’s context_variables. Substitution is applied to both system_prompt and greeting_message before the pipeline starts. The syntax is {{variable_name}}, matched by the regular expression \{\{(\w+)\}\} — word characters only, so no dots, dashes, or spaces in a name.
Two rules from substitute_variables(), both covered by apps/runtime/tests/test_prompt_substitution.py:
  • A missing key becomes an empty string, not the literal placeholder. "Hi {{name}}" with no name renders as "Hi ". Declare a default in custom_variables for every placeholder you use.
  • Non-string values are coerced with str(). Numbers and booleans render as their Python representation.
Only the two prompt strings are substituted. Hold messages, online-detection messages, and the closing message are used verbatim.

A complete example

The example below is AgentCreateRequest.config as declared in apps/api/app/models/schemas.py, unaltered:
Wrap it in a create request and the provider credentials must already be stored: