By Sagar Shankaran, Founder of CallSphere
Compare YAML, TOML, and Python-based configuration patterns for AI agents. Learn config file design, schema validation, safe loading, and default merging strategies.
Key takeaways
Storing agent configuration in code — version-controlled config files rather than database rows or UI settings — brings the full power of software engineering to agent management. You get git history showing who changed what, pull request reviews for configuration changes, automated validation in CI, and deterministic deployments where the same commit always produces the same agent behavior.
The question is which format to use. YAML, TOML, and Python each have distinct tradeoffs for agent configuration.
YAML is the most common format in the cloud-native ecosystem. Its strength is readability and support for complex nested structures.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
# agent_config.yaml loaded by the application
YAML_EXAMPLE = """
agent:
name: support-agent
model: gpt-4o
temperature: 0.7
max_tokens: 2048
system_prompt: |
You are a customer support agent for Acme Corp.
Always be polite and professional.
If you cannot resolve an issue, escalate to a human agent.
tools:
- name: search_docs
description: Search the knowledge base
enabled: true
- name: create_ticket
description: Create a support ticket
enabled: true
- name: refund_order
description: Process a refund
enabled: false
requires_approval: true
guardrails:
max_tool_calls_per_turn: 3
block_pii_in_responses: true
escalation_keywords:
- "speak to a human"
- "supervisor"
- "complaint"
"""
import yaml
def load_yaml_config(path: str) -> dict:
with open(path, "r") as f:
config = yaml.safe_load(f)
return config
The critical detail here is yaml.safe_load. Never use yaml.load with untrusted input — it can execute arbitrary Python code. safe_load restricts parsing to basic data types.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
TOML is more explicit than YAML and avoids its indentation pitfalls. It is the standard for Python packaging (pyproject.toml) and has first-class support in Python 3.11 and later via tomllib.
TOML_EXAMPLE = """
[agent]
name = "support-agent"
model = "gpt-4o"
temperature = 0.7
max_tokens = 2048
system_prompt = '''
You are a customer support agent for Acme Corp.
Always be polite and professional.
If you cannot resolve an issue, escalate to a human agent.
'''
[guardrails]
max_tool_calls_per_turn = 3
block_pii_in_responses = true
escalation_keywords = ["speak to a human", "supervisor", "complaint"]
[[tools]]
name = "search_docs"
description = "Search the knowledge base"
enabled = true
[[tools]]
name = "create_ticket"
description = "Create a support ticket"
enabled = true
"""
try:
import tomllib
except ImportError:
import tomli as tomllib
def load_toml_config(path: str) -> dict:
with open(path, "rb") as f:
return tomllib.load(f)
TOML's advantage is unambiguous typing. In YAML, yes, on, true are all boolean true. In TOML, only true is boolean. This eliminates an entire class of subtle configuration bugs.
Python config files offer maximum flexibility. You get type checking, computed values, and validation built into the config definition itself.
from pydantic import BaseModel, field_validator
from typing import Optional
class ToolConfig(BaseModel):
name: str
description: str
enabled: bool = True
requires_approval: bool = False
class GuardrailConfig(BaseModel):
max_tool_calls_per_turn: int = 3
block_pii_in_responses: bool = True
escalation_keywords: list[str] = []
@field_validator("max_tool_calls_per_turn")
@classmethod
def validate_max_calls(cls, v: int) -> int:
if not 1 <= v <= 20:
raise ValueError("max_tool_calls_per_turn must be 1-20")
return v
class AgentConfig(BaseModel):
name: str
model: str = "gpt-4o"
temperature: float = 0.7
max_tokens: int = 2048
system_prompt: str
tools: list[ToolConfig] = []
guardrails: GuardrailConfig = GuardrailConfig()
@field_validator("temperature")
@classmethod
def validate_temp(cls, v: float) -> float:
if not 0.0 <= v <= 2.0:
raise ValueError("Temperature must be 0.0-2.0")
return v
A common pattern is merging user-provided config with defaults. The user only specifies what they want to change.
from copy import deepcopy
def deep_merge(base: dict, override: dict) -> dict:
result = deepcopy(base)
for key, value in override.items():
if (
key in result
and isinstance(result[key], dict)
and isinstance(value, dict)
):
result[key] = deep_merge(result[key], value)
else:
result[key] = deepcopy(value)
return result
DEFAULTS = {
"agent": {
"model": "gpt-4o-mini",
"temperature": 0.7,
"max_tokens": 1024,
},
"guardrails": {
"max_tool_calls_per_turn": 3,
"block_pii_in_responses": True,
},
}
def load_with_defaults(config_path: str) -> dict:
user_config = load_toml_config(config_path)
return deep_merge(DEFAULTS, user_config)
In practice, you want a single loader that handles any format and validates the result.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
from pathlib import Path
class ConfigLoader:
LOADERS = {
".yaml": lambda p: yaml.safe_load(open(p)),
".yml": lambda p: yaml.safe_load(open(p)),
".toml": lambda p: tomllib.load(open(p, "rb")),
".json": lambda p: json.load(open(p)),
}
@classmethod
def load(cls, path: str) -> AgentConfig:
p = Path(path)
loader = cls.LOADERS.get(p.suffix)
if not loader:
raise ValueError(f"Unsupported config format: {p.suffix}")
raw = loader(path)
merged = deep_merge(DEFAULTS, raw)
agent_data = merged.get("agent", {})
agent_data["guardrails"] = merged.get("guardrails", {})
agent_data["tools"] = merged.get("tools", [])
return AgentConfig(**agent_data)
Use YAML when your team is already in the Kubernetes ecosystem and familiar with its conventions. Use TOML when you want strict, unambiguous typing and your config is relatively flat. Use Python configs when you need computed values, complex validation, or type safety throughout. For most AI agent projects, TOML combined with Pydantic validation offers the best balance of readability and safety.
TOML supports multi-line strings with triple quotes. Use single-quoted triple quotes (''') for literal strings where backslashes are not interpreted as escapes. This is ideal for system prompts that may contain special characters.
Absolutely. Add a CI step that loads every config file through your validation layer. This catches typos, invalid values, and missing required fields before they reach any environment. The validation step should take less than a second and prevents entire classes of deployment failures.
When configurations change frequently (multiple times per day) and are managed by non-technical users. In that case, a database-backed config with an admin UI is more appropriate. Configuration-as-code works best for settings that change with releases and are managed by the engineering team.
#ConfigurationAsCode #AIAgents #YAML #TOML #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Head-to-head: OpenAI Frontier and Anthropic's managed agent stack — strengths, fit, and what each means for enterprise AI voice and chat deployment.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.