Configuration File
Configure agent defaults with a TOML config file
Set default values for all Agent parameters using a configuration file. When you pass True to a feature, it uses your configured defaults.
from praisonaiagents import Agent
agent = Agent(name="configured", instructions="Use defaults from config.toml.")
agent.start("Hello — apply my saved model and memory settings.")The user sets defaults in .praisonai/config.toml; explicit Agent parameters still override the file. Config files are deep-merged across the whole precedence chain — your global ~/.praisonai/config.* supplies defaults, and every project config down to your current directory overrides only the keys it sets.
How It Works
PraisonAI discovers every config file from your global home down to your current directory, then deep-merges them so unset keys fall through and the nearest project file wins for keys it sets.
Precedence (highest to lowest):
- Explicit parameters in code
- Environment variables
- Nearest project config (deepest ancestor of
cwd) - Ancestor project configs (root → parent-of-
cwd) - User global (
~/.praisonai/config.*) - Built-in defaults
.praisonai/config.toml nearest to cwd — wins for keys it sets
Any .praisonai/config.* in a parent directory — merged underneath
~/.praisonai/config.toml — lowest, supplies defaults for all
PRAISONAI_* env vars beat every config file value
Quick Start
- Create Config File
Create
.praisonai/config.tomlin your project:toml [defaults] model = "gpt-4o-mini" [defaults.memory] enabled = true backend = "sqlite" - Use in Code
python from praisonaiagents import Agent # memory=True now uses your config defaults (sqlite backend) agent = Agent( name="Assistant", instructions="Help users", memory=True # Uses [defaults.memory] settings )
Config File Locations
Every config file found from your home directory down to cwd is deep-merged. Unset keys fall through to a lower layer; the nearest project file wins for the keys it sets.
Discovery order (lowest → highest precedence):
| Order | Location | Scope |
|---|---|---|
| 1 (lowest) | ~/.praisonai/config.toml · .yaml · .yml | User global — defaults for every project |
| 2 … n-1 | .praisonai/config. · praisonai. in each ancestor of cwd | Walked root → cwd |
| n (highest) | .praisonai/config. · praisonai. nearest to cwd | Nearest project file wins |
Within a single directory, the first matching filename supplies that layer, in this order:
.praisonai/config.toml → .praisonai/config.yaml → .praisonai/config.yml
→ praisonai.toml → praisonai.yaml → praisonai.ymlDeep Merge Semantics
Config files merge recursively — a project override for one key keeps every other key from the layers below.
| Value shape | Behaviour |
|---|---|
Both sides are a mapping (dict) | Recurse key-by-key |
Scalar, list, or None | The higher layer replaces the lower — no list concatenation |
Merge inputs are never mutated, so loading is safe to repeat and caching still works via clear_config_cache().
# ~/.praisonai/config.yaml (global)
defaults:
model: anthropic/claude-3-5-sonnet-latest
small_model: anthropic/claude-3-5-haiku-latest
plugins:
pii_guardrail:
redact: [email, phone]
# .praisonai/config.yaml (project) — one key
plugins:
enabled: true
# merged result
defaults:
model: anthropic/claude-3-5-sonnet-latest # kept from global
small_model: anthropic/claude-3-5-haiku-latest
plugins:
enabled: true # added by project
pii_guardrail:
redact: [email, phone] # kept from globalLists replace, they never concatenate:
# global
plugins:
enabled: [a, b]
# project
plugins:
enabled: [c]
# merged → [c], NOT [a, b, c]Ancestor Discovery
Project configs are discovered by walking up from Path.cwd() through every parent to the filesystem root. Each directory contributes its own .praisonai/config. (or root-level praisonai.), and the nearest one to cwd wins for the keys it sets. The global ~/.praisonai/config.* always sits underneath.
Running praisonai run … from ~/work/monorepo/apps/frontend/:
~/.praisonai/config.yaml ← global (lowest)
~/work/monorepo/.praisonai/config.yaml ← ancestor (middle)
~/work/monorepo/apps/frontend/.praisonai/config.yaml ← nearest (highest, wins)All three are deep-merged. A repo-root config sets defaults for every package; a package-level config overrides just the keys it names (e.g. model); the global user config still supplies everything neither one sets.
Small Model for Cheap Internal Calls
small_model routes internal, non-user-facing LLM calls — session-title generation, context compaction/summarisation, and LLM guardrail validation — to a cheaper model than your primary model.
# .praisonai/config.yaml
defaults:
model: gpt-4o # primary — used by your agents
small_model: gpt-4o-mini # cheap auxiliary — internal callsWhen unset, behaviour is byte-identical to today: internal calls fall back to gpt-4o-mini.
Resolution precedence (highest first):
| Field | Type | Default | Description |
|---|---|---|---|
small_model | str | None (falls back to gpt-4o-mini) | Cheap auxiliary model for internal LLM calls. Falls back to primary_model, then defaults.model, then gpt-4o-mini. |
Full Configuration Reference
Plugins
[plugins]
# Enable all plugins: true
# Enable specific: ["logging", "metrics"]
# Disable: false
enabled = false
# Auto-discover from default directories
auto_discover = true
# Plugin directories to scan
directories = [
"./.praisonai/plugins/",
"~/.praisonai/plugins/"
]plugins:
enabled: true
auto_discover: true
directories:
- ./.praisonai/plugins/
- ~/.praisonai/plugins/
pii_guardrail: # per-plugin option block
redact: [email, phone]
memory_watchdog:
interval: 30
enabled: false # disable just this pluginLLM Defaults
[defaults]
# Primary LLM model — used by every agent unless overridden
model = "gpt-4o"
# Cheap/fast auxiliary model for internal LLM calls
# (session-title generation, context compaction/summarisation,
# and LLM guardrail validation). Falls back to `model` when unset,
# then to a built-in default.
small_model = "gpt-4o-mini"
# Custom endpoint (for local LLMs)
# base_url = "http://localhost:11434/v1"
# Feature flags
allow_delegation = false
allow_code_execution = false
code_execution_mode = "safe"defaults:
model: gpt-4o
small_model: gpt-4o-mini
# base_url: http://localhost:11434/v1Memory
[defaults.memory]
enabled = false
backend = "file" # file, sqlite, chroma, mem0, mongodb, dakera, in_memory
auto_memory = false
history = false
history_limit = 10
[defaults.memory.learn]
enabled = false
persona = true
insights = true
patterns = falseKnowledge
[defaults.knowledge]
enabled = false
embedder = "openai"
chunking_strategy = "semantic"
chunk_size = 1000
chunk_overlap = 200
retrieval_k = 5
rerank = falsePlanning & Reflection
[defaults.planning]
enabled = false
reasoning = false
auto_approve = false
[defaults.reflection]
enabled = false
min_iterations = 1
max_iterations = 3Output & Execution
[defaults.output]
preset = "silent" # silent, actions, verbose, json
verbose = false
stream = false
metrics = false
[defaults.execution]
max_iter = 20
max_retry_limit = 2
# retry_initial_delay = 1.0
# retry_backoff_factor = 2.0
# retry_jitter = 0.1Guardrails
[defaults.guardrails]
enabled = true
max_retries = 3
on_fail = "retry" # "retry" | "skip" | "raise"
# llm_validator = "Response must be professional and under 200 words"Retry
[defaults.retry]
enabled = true
max_attempts = 3
initial_delay = 1.0
backoff_factor = 2.0
jitter = 0.1Cheap auxiliary model for internal calls
small_model sets a cheap/fast model for PraisonAI’s internal LLM calls — session-title generation, context compaction/summarisation, and LLM guardrail validation — independent of your agent’s primary model.
| Field | Type | Default | Description |
|---|---|---|---|
defaults.small_model | str (optional) | None (falls back to primary model, then gpt-4o-mini) | Cheap/fast auxiliary model used for internal LLM calls. Honoured under both [defaults] (typed loader) and [agent] (CLI/JSON-schema) namespaces of the same config file. |
Resolution order for get_small_model(primary_model, fallback):
defaults.small_model(oragent.small_modelCLI namespace) — if set.primary_model— the running agent’s model, if any.defaults.model(oragent.model) — if set.fallback— preserves today’s hardcodedgpt-4o-miniwhen nothing is configured.
Env-var fallback (new in PR #4812)
Call sites outside the config-file loader (memory quality scoring, context compaction/summarisation, lite helpers, workflow routing, task callbacks, learn-manager extraction) resolve their auxiliary model through a parallel env-var ladder. This is what lets you run fully locally without having to set defaults.small_model in a config file.
| Precedence | Source | Set via |
|---|---|---|
| 1 | Explicit llm=… argument on the call site | Code — e.g. Memory(llm="ollama/qwen3:0.6b") |
| 2 | PRAISONAI_AUXILIARY_MODEL env var | export PRAISONAI_AUXILIARY_MODEL=ollama/qwen3:0.6b |
| 3 | OPENAI_MODEL_NAME env var | export OPENAI_MODEL_NAME=ollama/llama3.3:70b |
| 4 | "gpt-4o-mini" (unchanged legacy default) | — |
Why a dedicated variable? A local setup usually wants a smaller model for internal helper calls than for the agent itself. Pointing OPENAI_MODEL_NAME at a 70B local model should not make every internal summarisation call use it:
# unset: -> gpt-4o-mini
export OPENAI_MODEL_NAME=ollama/llama3.3:70b # -> ollama/llama3.3:70b for everything
export PRAISONAI_AUXILIARY_MODEL=ollama/qwen3:0.6b # -> agent stays on 70B; helpers use 0.6bEmpty or whitespace-only env values are ignored, so OPENAI_MODEL_NAME=" " does not override the default.
The env-var resolver covers 8 modules: memory/memory.py, memory/learn/manager.py, context/compressor.py, context/optimizer.py, workflows/workflows.py, lite/__init__.py, task/task.py, and session/title.py.
Deliberately not routed — cost tables (utils/cost_utils.py, llm/_cost.py, where the name selects a price, not a model), docstring examples, and public dataclass field defaults. Setting PRAISONAI_AUXILIARY_MODEL does not change pricing behaviour.
Session-title generation is the one call site that combines both ladders: config-file defaults.small_model still wins over env vars there (its fallback is now PRAISONAI_AUXILIARY_MODEL → OPENAI_MODEL_NAME → "gpt-4o-mini" instead of a hardcoded literal).
Usage Examples
Guardrail safety net for the whole project
Set a config-wide guardrail so every Agent gets it unless it passes its own.
# .praisonai/config.toml
[defaults.guardrails]
llm_validator = "Response must be professional and contain no PII"
max_retries = 3from praisonaiagents import Agent
# Every Agent gets the config-wide guardrail unless it passes its own.
agent = Agent(name="Assistant", instructions="Help users")
agent.start("Draft a customer-facing reply")Cheap auxiliary model for internal calls
Use a cheap/fast model for internal work (session titles, compaction/summarisation, LLM guardrails) while keeping a powerful primary model for the agent itself.
# .praisonai/config.toml
[defaults]
model = "gpt-4o" # primary — used for the agent
small_model = "gpt-4o-mini" # cheap auxiliary — used for internal callsfrom praisonaiagents import Agent
# Agent runs on gpt-4o; session titles use gpt-4o-mini automatically.
agent = Agent(
name="Assistant",
instructions="Help users"
)Single-provider setups work the same way:
# Anthropic-only setup — no OpenAI calls anywhere
[defaults]
model = "anthropic/claude-3-5-sonnet-latest"
small_model = "anthropic/claude-3-5-haiku-latest"# Ollama-only setup — everything runs locally
[defaults]
model = "ollama/llama3.1:70b"
small_model = "ollama/llama3.1:8b"
base_url = "http://localhost:11434/v1"Explicit Agent(...) kwargs still override the config file, same as model.
Cheap validator for LLM guardrails
small_model also routes LLM-based guardrail validation. A guardrail expressed as a natural-language string uses the auxiliary model automatically — no code change on the agent.
# .praisonai/config.toml
[defaults]
model = "gpt-4o" # primary agent model
small_model = "gpt-4o-mini" # cheap validator for guardrails, titles, compactionfrom praisonaiagents import Agent
# Guardrail validation runs on gpt-4o-mini; the agent still runs on gpt-4o.
agent = Agent(
name="Writer",
instructions="Write professional articles",
guardrails="Ensure the response is professional, accurate, and at least 150 words",
)
agent.start("Write a launch announcement")An explicit llm_instance passed to GuardrailConfig (with its own api_key, base_url, or client) always wins — the reroute only applies when the guardrail would otherwise fall back to the bare primary model-name string.
Memory with PostgreSQL
Postgres and Redis are not memory backends — pass a live store with db(database_url=...) (Postgres) or db(state_url=...) (Redis). Set durable defaults (like learn) in the config file, then attach the store in code.
# .praisonai/config.toml
[defaults.memory]
enabled = true
[defaults.memory.learn]
enabled = true
persona = true
insights = truefrom praisonaiagents import Agent, MemoryConfig, db
# Redis/Postgres go through db(database_url=...), not backend="postgres"
agent = Agent(
name="Assistant",
instructions="Help users",
memory=MemoryConfig(db=db(database_url="postgresql://localhost:5432/praisonai")),
)Knowledge with Reranking
# .praisonai/config.toml
[defaults.knowledge]
enabled = true
embedder = "openai"
retrieval_k = 10
rerank = true
rerank_model = "cohere"from praisonaiagents import Agent
# knowledge=True uses reranking
agent = Agent(
name="Researcher",
instructions="Research topics",
knowledge=True
)Verbose Output Mode
# .praisonai/config.toml
[defaults.output]
preset = "verbose"
verbose = true
markdown = true
stream = truefrom praisonaiagents import Agent
# All agents now use verbose output by default
agent = Agent(
name="Assistant",
instructions="Help users"
)Local LLM (Ollama)
# .praisonai/config.toml
[defaults]
model = "llama3"
base_url = "http://localhost:11434/v1"from praisonaiagents import Agent
# Uses local Ollama by default
agent = Agent(
name="Local Assistant",
instructions="Help users"
)Programmatic Access
get_config() and get_default() return the merged view across global, ancestor, and project files.
set_plugin_enabled(name, enabled) writes to the highest-precedence existing file (the one that actually wins the merge) — or creates a project-local .praisonai/config.yaml when none exist — so CLI writes always land in the same file the runtime reads. It seeds from the target file only (not the merged view), so writes round-trip cleanly without persisting inherited keys.
from praisonaiagents.config.loader import (
get_config,
get_default,
get_small_model, # NEW
get_plugins_config,
get_plugin_options,
is_plugins_enabled,
set_plugin_enabled, # writes to the highest-precedence file
)
# Get entire config (merged across global → ancestors → project)
config = get_config()
print(config.plugins.enabled)
print(config.defaults.model)
# Get specific default with fallback
model = get_default("model", "gpt-4o-mini")
memory_backend = get_default("memory.backend", "file")
# Config-wide guardrails / retry defaults (loaded dicts, or None)
guardrails = get_default("guardrails", None)
retry = get_default("retry", None)
# Resolve the cheap auxiliary model with fallback chain:
# defaults.small_model -> primary_model -> defaults.model -> fallback
small = get_small_model(primary_model="gpt-4o", fallback="gpt-4o-mini")
print(small)
# Check plugins status
if is_plugins_enabled():
print("Plugins are enabled")
# Read per-plugin option maps ({plugin_name: options_dict})
options = get_plugin_options()
print(options.get("pii_guardrail", {}))
# Persist a plugin's enabled state to the highest-precedence file
# (or create .praisonai/config.yaml if none exists yet)
target = set_plugin_enabled("pii_guardrail", True)
print(f"Wrote to {target}")Auxiliary / Small Model Resolution
small_model routes PraisonAI’s internal LLM calls — session-title generation, context compaction/summarisation, and LLM guardrail validation — to a cheap, fast, or local model without changing your agent’s primary model.
# .praisonai/config.yaml
defaults:
model: gpt-4o # user-facing quality
small_model: gpt-4o-mini # cheap background work: titles, compaction, guardrailsSet small_model to a local model to keep background calls off third-party APIs:
defaults:
model: gpt-4o
small_model: ollama/llama3
base_url: http://localhost:11434/v1The resolver picks the first available source top to bottom:
Precedence: explicit llm_model > defaults.small_model > primary_model > defaults.model > built-in gpt-4o-mini.
Resolve the auxiliary model programmatically:
from praisonaiagents.config.loader import get_small_model
# Resolves according to config + primary hint + fallback
model = get_small_model(primary_model="gpt-4o", fallback="gpt-4o-mini")The CLI/schema namespace agent.small_model reaches the same resolver, so both [defaults] and [agent] in the same config file work.
What small_model drives
The resolved small_model is used for cheap, non-user-facing LLM calls:
- Session titles — generating short titles for conversations
- Context summarisation / compaction — the
ContextCompactorLLM-summarize path (Agent._create_llm_summarize_fn) - LLM guardrail validation —
Agent(guardrail=...)andTask(guardrail=...)when the guardrail is a natural-language string (no explicitLLMinstance passed)
An explicit LLM instance or per-call model override always wins over small_model. Leaving small_model unset resolves to the primary model — single-provider setups (Anthropic-only, Ollama, on-prem) make zero unexpected third-party calls.
Config Validation
from praisonaiagents import validate_config, ConfigValidationError
# Validate a config dict
config = {
"plugins": {"enabled": True},
"defaults": {"modell": "gpt-4o"} # Typo!
}
errors = validate_config(config)
# Output: ["[defaults] Unknown key 'modell'. Did you mean 'model'?"]
# Or raise on error
try:
validate_config(config, raise_on_error=True)
except ConfigValidationError as e:
print(e.errors)Override Precedence
Explicit parameters always override config defaults:
from praisonaiagents import Agent, MemoryConfig
# Config file has: backend = "sqlite"
# But explicit config wins:
agent = Agent(
name="Assistant",
instructions="Help users",
memory=MemoryConfig(backend="chroma") # Uses chroma, not sqlite
)The three config-file layers (nearest → ancestor → global) are deep-merged, not first-wins: a key set only in the global file still applies when no project file overrides it.
Sample Config File
Complete config.toml template
# PraisonAI Agents Configuration
# Copy to .praisonai/config.toml
[plugins]
enabled = false
auto_discover = true
directories = ["./.praisonai/plugins/", "~/.praisonai/plugins/"]
[defaults]
model = "gpt-4o"
small_model = "gpt-4o-mini" # cheap auxiliary model; falls back to `model` when unset
allow_delegation = false
allow_code_execution = false
code_execution_mode = "safe"
[defaults.memory]
enabled = false
backend = "file"
auto_memory = false
history = false
history_limit = 10
[defaults.memory.learn]
enabled = false
persona = true
insights = true
patterns = false
[defaults.knowledge]
enabled = false
embedder = "openai"
chunking_strategy = "semantic"
chunk_size = 1000
retrieval_k = 5
rerank = false
[defaults.planning]
enabled = false
reasoning = false
auto_approve = false
[defaults.reflection]
enabled = false
min_iterations = 1
max_iterations = 3
[defaults.web]
enabled = false
search = true
fetch = true
search_provider = "duckduckgo"
max_results = 5
[defaults.output]
preset = "silent"
verbose = false
stream = false
metrics = false
[defaults.execution]
max_iter = 20
max_retry_limit = 2
# retry_initial_delay = 1.0
# retry_backoff_factor = 2.0
# retry_jitter = 0.1
[defaults.guardrails]
enabled = true
max_retries = 3
on_fail = "retry" # "retry" | "skip" | "raise"
# llm_validator = "Response must be professional and under 200 words"
[defaults.retry]
enabled = true
max_attempts = 3
initial_delay = 1.0
backoff_factor = 2.0
jitter = 0.1
[defaults.caching]
enabled = true
prompt_caching = false
[defaults.autonomy]
level = "suggest"
escalation_enabled = true
doom_loop_detection = trueBest Practices
Set org-wide defaults, override per agent
Put values every project should share (model, memory backend, output preset) in .praisonai/config.toml, and let explicit Agent(...) parameters override them where a specific agent needs something different. Explicit parameters always win over the file, so the config is a floor, not a cage.
Remember the precedence order
Configuration resolves as Explicit Agent parameter > Environment variable > Nearest project config > Ancestor configs > User global > Built-in default. The config-file layers are deep-merged, so when a setting seems ignored, check for a higher-precedence source (an env var like PRAISONAI_PLUGINS=true, an explicit kwarg, or a nearer project file) before editing a lower layer.
Split defaults across global and project
Put reusable defaults — model, small_model, plugin options, telemetry — in ~/.praisonai/config.yaml, and keep only project-specific overrides in .praisonai/config.yaml. Deep-merge folds them together, so the project file stays tiny.
# ~/.praisonai/config.yaml (global — reusable defaults)
defaults:
model: anthropic/claude-3-5-sonnet-latest
small_model: anthropic/claude-3-5-haiku-latest
# .praisonai/config.yaml (project — just the override)
plugins:
enabled: trueCLI writes land in the winning file
set_plugin_enabled() (and the CLI plugin toggles) write to the highest-precedence existing config file — the one that actually wins the merge — or create .praisonai/config.yaml when none exist. This avoids editing a lower-precedence global whose change would be silently overridden on reload.
Keep the file in version control
Commit .praisonai/config.toml so every teammate and CI run starts from the same defaults. Keep secrets (API keys) in environment variables, not the config file, so the file stays safe to share.
Enable features conservatively
Most feature defaults ship disabled (enabled = false) for a reason — turning on memory, knowledge, or reflection globally adds latency and cost to every agent. Enable them in the config only when the whole project needs them; otherwise flip them on per agent.