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.

python
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.

Diagram

Configuration Sources

📝 Explicit
Agent(memory=MemoryConfig(…))

🌍 Environment
PRAISONAI_PLUGINS=true

📄 Config Files
global → ancestors → project
(deep-merged)

⚙️ Defaults
Built-in values

🤖 Agent

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.

Diagram
Resolved ConfigDeep Merge[global, ancestor1, …, project]Discover (walk-up)cwd (project)Resolved ConfigDeep Merge[global, ancestor1, …, project]Discover (walk-up)cwd (project)locate config filesglobal → ancestors → nearestfold left → rightnearest key wins, rest falls throughmerged config

Precedence (highest to lowest):

  1. Explicit parameters in code
  2. Environment variables
  3. Nearest project config (deepest ancestor of cwd)
  4. Ancestor project configs (root → parent-of-cwd)
  5. User global (~/.praisonai/config.*)
  6. Built-in defaults

Quick Start

  1. Create Config File

    Create .praisonai/config.toml in your project:

    toml
    [defaults]
    model = "gpt-4o-mini"
    
    [defaults.memory]
    enabled = true
    backend = "sqlite"
  2. 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):

OrderLocationScope
1 (lowest)~/.praisonai/config.toml · .yaml · .ymlUser global — defaults for every project
2 … n-1.praisonai/config. · praisonai. in each ancestor of cwdWalked root → cwd
n (highest).praisonai/config. · praisonai. nearest to cwdNearest 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.yml

Deep Merge Semantics

Config files merge recursively — a project override for one key keeps every other key from the layers below.

Value shapeBehaviour
Both sides are a mapping (dict)Recurse key-by-key
Scalar, list, or NoneThe 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().

yaml
# ~/.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 global

Lists replace, they never concatenate:

yaml
# 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.

yaml
# .praisonai/config.yaml
defaults:
  model: gpt-4o            # primary — used by your agents
  small_model: gpt-4o-mini # cheap auxiliary — internal calls

When unset, behaviour is byte-identical to today: internal calls fall back to gpt-4o-mini.

Resolution precedence (highest first):

Diagram

defaults.small_model

primary_model
(running agent’s model)

defaults.model

gpt-4o-mini
(built-in fallback)

FieldTypeDefaultDescription
small_modelstrNone (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

config.toml
[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/"
]
config.yaml
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 plugin

LLM Defaults

config.toml
[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"
config.yaml
defaults:
  model: gpt-4o
  small_model: gpt-4o-mini
  # base_url: http://localhost:11434/v1

Memory

toml
[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 = false

Knowledge

toml
[defaults.knowledge]
enabled = false
embedder = "openai"
chunking_strategy = "semantic"
chunk_size = 1000
chunk_overlap = 200
retrieval_k = 5
rerank = false

Planning & Reflection

toml
[defaults.planning]
enabled = false
reasoning = false
auto_approve = false

[defaults.reflection]
enabled = false
min_iterations = 1
max_iterations = 3

Output & Execution

toml
[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.1

Guardrails

toml
[defaults.guardrails]
enabled = true
max_retries = 3
on_fail = "retry"          # "retry" | "skip" | "raise"
# llm_validator = "Response must be professional and under 200 words"

Retry

toml
[defaults.retry]
enabled = true
max_attempts = 3
initial_delay = 1.0
backoff_factor = 2.0
jitter = 0.1

Cheap 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.

FieldTypeDefaultDescription
defaults.small_modelstr (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):

  1. defaults.small_model (or agent.small_model CLI namespace) — if set.
  2. primary_model — the running agent’s model, if any.
  3. defaults.model (or agent.model) — if set.
  4. fallback — preserves today’s hardcoded gpt-4o-mini when 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.

PrecedenceSourceSet via
1Explicit llm=… argument on the call siteCode — e.g. Memory(llm="ollama/qwen3:0.6b")
2PRAISONAI_AUXILIARY_MODEL env varexport PRAISONAI_AUXILIARY_MODEL=ollama/qwen3:0.6b
3OPENAI_MODEL_NAME env varexport 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:

bash
# 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.6b

Empty 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.

toml
# .praisonai/config.toml
[defaults.guardrails]
llm_validator = "Response must be professional and contain no PII"
max_retries = 3
python
from 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.

Diagram

small_model resolution

if 1 unset

if 1 & 2 unset

env fallback

env fallback

last resort

last resort

last resort

1️⃣ defaults.small_model

Resolved model

2️⃣ primary_model
(Agent’s model)

3️⃣ defaults.model

4️⃣ PRAISONAI_AUXILIARY_MODEL

5️⃣ OPENAI_MODEL_NAME

6️⃣ gpt-4o-mini

toml
# .praisonai/config.toml
[defaults]
model = "gpt-4o"            # primary — used for the agent
small_model = "gpt-4o-mini" # cheap auxiliary — used for internal calls
python
from 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:

toml
# Anthropic-only setup — no OpenAI calls anywhere
[defaults]
model = "anthropic/claude-3-5-sonnet-latest"
small_model = "anthropic/claude-3-5-haiku-latest"
toml
# 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.

toml
# .praisonai/config.toml
[defaults]
model = "gpt-4o"                 # primary agent model
small_model = "gpt-4o-mini"      # cheap validator for guardrails, titles, compaction
python
from 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.

toml
# .praisonai/config.toml
[defaults.memory]
enabled = true

[defaults.memory.learn]
enabled = true
persona = true
insights = true
python
from 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
toml
# .praisonai/config.toml
[defaults.knowledge]
enabled = true
embedder = "openai"
retrieval_k = 10
rerank = true
rerank_model = "cohere"
python
from praisonaiagents import Agent

# knowledge=True uses reranking
agent = Agent(
    name="Researcher",
    instructions="Research topics",
    knowledge=True
)
Verbose Output Mode
toml
# .praisonai/config.toml
[defaults.output]
preset = "verbose"
verbose = true
markdown = true
stream = true
python
from praisonaiagents import Agent

# All agents now use verbose output by default
agent = Agent(
    name="Assistant",
    instructions="Help users"
)
Local LLM (Ollama)
toml
# .praisonai/config.toml
[defaults]
model = "llama3"
base_url = "http://localhost:11434/v1"
python
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.

python
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.

yaml
# .praisonai/config.yaml
defaults:
  model: gpt-4o          # user-facing quality
  small_model: gpt-4o-mini  # cheap background work: titles, compaction, guardrails

Set small_model to a local model to keep background calls off third-party APIs:

yaml
defaults:
  model: gpt-4o
  small_model: ollama/llama3
  base_url: http://localhost:11434/v1

The resolver picks the first available source top to bottom:

Diagram

yes

no

yes

no

yes

no

yes

no

Internal LLM call needed
title / summary / compaction

explicit llm_model
argument?

defaults.small_model
set in config?

primary_model
available?

defaults.model
set in config?

Use explicit model

Use small_model

Use primary_model

Use defaults.model

Fallback: gpt-4o-mini

Precedence: explicit llm_model > defaults.small_model > primary_model > defaults.model > built-in gpt-4o-mini.

Resolve the auxiliary model programmatically:

python
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 ContextCompactor LLM-summarize path (Agent._create_llm_summarize_fn)
  • LLM guardrail validation — Agent(guardrail=...) and Task(guardrail=...) when the guardrail is a natural-language string (no explicit LLM instance 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

python
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)
Diagram

Validation Flow

Valid

Invalid

📄 Config File

Parse TOML

Validate Schema

✅ Load Config

❌ Error + Suggestion


Override Precedence

Explicit parameters always override config defaults:

python
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
)
Diagram

Resolution Order

Wins

If no explicit

If no env

Falls through

Falls through

Fallback

1️⃣ Explicit Parameter
memory=MemoryConfig(…)

2️⃣ Environment Variable
PRAISONAI_MEMORY_BACKEND

3️⃣ Nearest project config
deepest ancestor of cwd

4️⃣ Ancestor project configs
root → parent-of-cwd

5️⃣ User global
~/.praisonai/config.*

6️⃣ Built-in Default
backend=’file’

Final Value

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
toml
# 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 = true

Best 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.

yaml
# ~/.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: true
CLI 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.

Related