Configuration
Every setting lives in one file: config.yaml. Environment variables
interpolate as ${VAR_NAME}. If no config file is found, TAI uses
defaults (Ollama on localhost, basic tools enabled).
A full annotated reference is
config.example.yaml
in the repo.
Where TAI looks for config
--config <path>if passed to the CLI.$TAI_HOME/config.yamlifTAI_HOMEis set../config.yamlin the cwd.
.env in the same directory is loaded automatically. Its vars are
available for ${VAR} interpolation.
Hot reload
TAI watches config.yaml and reloads on save. Adding or removing
tools, changing models, editing agent instructions: all take effect on
the next message. No restart.
Top-level keys
provider: { ... } # active LLM provider (single)
providers: { ... } # named providers (multi-provider setups)
agent: { ... } # global agent defaults
agents: { ... } # named agent definitions
channels: { ... } # discord, etc.
tools: { ... } # tool enable/disable + per-tool config
custom_tools: { ... } # YAML-defined shell-command tools
commands: { ... } # slash commands
cron: { ... } # scheduled jobs
webhooks: { ... } # incoming webhook config
workflows: { ... } # workflow directory + caps
exploratory: { ... } # global toggle for online-mode worker
memory: { ... } # embeddings + recall settings
plugins: [ ... ] # declarative third-party extension loader
resources: [ ... ] # skills, knowledge files
server: { ... } # HTTP API binding
database: { ... } # SQLite location
Validation warnings print at startup.
Provider
Providers are keyed by registered factory id under providers:, and
agent.defaultProvider selects the active one. The one built-in is
openai_compatible, which covers Ollama, vLLM, LM Studio, llama.cpp's
server, and any other OpenAI-wire endpoint:
providers:
openai_compatible:
baseUrl: http://localhost:11434/v1 # Ollama
defaultModel: llama3.2
# apiKey: ${SOME_KEY} # optional, only sent when set
# name: Ollama # label shown in logs and UIs
agent:
defaultProvider: openai_compatible
Hosted vendors are plugins
openai, anthropic, openrouter, and bedrock register their ids from
plugin packages:
plugins:
- "@tailored-ai/provider-anthropic"
providers:
anthropic:
apiKey: ${ANTHROPIC_API_KEY}
defaultModel: claude-haiku-4-5
agent:
defaultProvider: anthropic
Each value under providers: is an opaque options bag the provider reads
itself; core carries no per-provider schema. See each plugin's page under
Packages for its options.
Custom providers
Provider ids resolve through a registry, so a plugin (or your own code via
registerProviderFactory from @tailored-ai/core) can register any id and
config selects it the same way. See Extending →
Adding a provider.
Multi-provider
Define multiple providers and let agents pick:
providers:
openai_compatible:
baseUrl: http://localhost:11434/v1
defaultModel: devstral-small-2
openai: # via @tailored-ai/provider-openai
apiKey: ${OPENAI_API_KEY}
defaultModel: gpt-5-mini
anthropic: # via @tailored-ai/provider-anthropic
apiKey: ${ANTHROPIC_API_KEY}
defaultModel: claude-haiku-4-5
agent:
defaultProvider: openai_compatible
Per agent:
agents:
researcher:
provider: openai
model: gpt-5-mini
coder:
provider: openai_compatible
model: qwen3-coder:30b
Resilient model selection
For setups where local models may be unreachable, give an agent an ordered priority list. TAI picks the first reachable one:
agents:
default:
models:
- { provider: openai_compatible, model: qwen3-coder:30b }
- { provider: openai, model: gpt-5-mini }
- { provider: anthropic, model: claude-haiku-4-5 }
Agent defaults
agent:
defaultProvider: ollama
extraInstructions: "" # appended to every agent's system prompt
maxHistoryTokens: 16000 # context window budget
temperature: 0.3 # default LLM temperature
maxToolRounds: 10 # safety limit on tool calls per message
Per-agent overrides go under agents.<name>. See Agents.
Channels
See Channels. Discord example:
channels:
discord:
enabled: true
token: ${DISCORD_BOT_TOKEN}
owner: ${DISCORD_OWNER_ID}
respondToDMs: true
respondToMentions: true
allowedGuilds: [] # empty = any
perChannelMapping: {} # channel id → project id
Tools
The full catalog is in Tools. Per-tool config keys vary; the pattern is:
tools:
exec:
enabled: true
allowedCommands: [git, pnpm, node]
write:
enabled: true
allowedPrefixes: [data/, output/]
web_search:
enabled: true
provider: brave
apiKey: ${BRAVE_API_KEY}
browser_mediator:
enabled: true
egressAllowList: [amazon.com]
vaultEnabled: true
discord_dm:
enabled: true
Custom tools
See Custom tools.
custom_tools:
weather:
description: Get weather for a city.
parameters:
city: { type: string, description: City name. }
command: "curl -s wttr.in/{{city}}?format=3"
timeout_ms: 5000
Cron jobs
See Cron jobs.
cron:
enabled: true
jobs:
- name: morning-briefing
schedule: "0 7 * * 1-5"
agent: default
prompt: "Run my morning briefing."
delivery:
channel: discord-dm
Workflows
workflows:
directory: ./workflows # default
maxConcurrent: 4
maxConcurrentByAgent:
coder: 1
_default: 2
retainRuns: 100
Online mode (per-agent)
Online mode is configured per agent, not globally. See
Agents → Online mode. The global toggle
exploratory.enabled turns the worker on or off across all agents.
Webhooks
webhooks:
enabled: true
bind_path: /webhooks # default
hmac_secret: ${WEBHOOK_SECRET} # optional; verified per request
Plugins
The eight extension registries (tool, channel, provider, embedding,
task backend, step executor, trigger, skill) are live. Ship a plugin as
an npm package that calls register*Factory(id, factory) on import, and
declare it in config.yaml:
plugins:
- "@some-author/tai-plugin-slack"
- module: "@me/tai-plugin-todoist"
config:
api_token: ${TODOIST_TOKEN}
The CLI dynamic-imports each entry before constructing the runtime, so
the plugin's import side-effects populate the registries before anything
asks. Per-entry config is reserved for future routing — today plugins
read their config from the normal tools.* / channels.* blocks.
See Plugins for the full recipe.
Resources
Skills and knowledge files installed from local paths, GitHub, or npm:
resources:
- kind: skill
source: github:user/skill-code-review
- kind: knowledge
source: ./data/kb/runbooks.md
See Skills.
Server (HTTP)
server:
port: 3000
host: 127.0.0.1 # bind only locally by default
authToken: ${TAI_AUTH_TOKEN} # required when host is non-loopback
authToken gates every /api/* route — GETs included — behind
Authorization: Bearer <token>. Constant-time comparison.
Set authToken whenever host is anything other than 127.0.0.1,
localhost, or ::1. Without it, every chat history, tool output,
and memory entry is readable by anyone who can route to the port. The
validator emits a startup warning when this condition is detected.
The legacy apiKey field still works but only gates mutating verbs
(POST/PUT/PATCH/DELETE). Prefer authToken for new deployments.
Database
database:
path: ./agent.db
Environment variable interpolation
Anywhere a string appears in config.yaml, write ${VAR_NAME} and TAI
substitutes from .env or the host environment. If the variable is
unset, the substitution is left literal and a startup warning prints.
provider:
type: openai
apiKey: ${OPENAI_API_KEY} # reads from .env or $OPENAI_API_KEY
Per-project overlays
A registered project's .tai.yaml can carry a config: block that
merges over the global config. See
Tasks & projects → Projects.