Configuration reference
Bestie stores non-secret settings in ~/.bestie/config.json. Secrets live in ~/.bestie/.env and must never be printed, logged, committed, or pasted into support requests.
Runtime paths
Section titled “Runtime paths”Normal installed usage stores runtime data under:
~/.bestie/ config.json .env data/ logs/ skills/ workspace/Use ~/.bestie/workspace for ad hoc agent-created files and channel attachments so project directories do not become a junk drawer with TypeScript trauma.
Minimal config
Section titled “Minimal config”{ "version": 2, "agent": { "name": "Bestie", "ownerName": "Owner", "language": "vi", "timeZone": "Asia/Bangkok", "toneIntensity": 7, "emojiLevel": "light" }, "llm": { "primary": "openai/gpt-4o-mini", "fallbacks": ["anthropic/claude-sonnet-4-5"], "authProfile": "openai:api-key", "profiles": { "openai:api-key": { "provider": "openai", "mode": "api-key", "baseUrl": "https://api.openai.com/v1", "apiKeyEnv": "OPENAI_API_KEY" }, "anthropic:api-key": { "provider": "anthropic", "mode": "api-key", "baseUrl": "https://api.anthropic.com/v1", "apiKeyEnv": "ANTHROPIC_API_KEY" }, "gemini:api-key": { "provider": "gemini", "mode": "api-key", "apiKeyEnv": "GEMINI_API_KEY" } }, "modelCatalog": { "openai/gpt-4o-mini": { "profile": "openai:api-key" }, "anthropic/claude-sonnet-4-5": { "profile": "anthropic:api-key" }, "gemini/gemini-2.5-flash": { "profile": "gemini:api-key" } }, "timeoutMs": 300000, "maxRetries": 1, "retryDelayMs": 500 }, "memory": { "provider": "sqlite", "writePolicy": "ask", "deletePolicy": "ask", "retrievalPolicy": "full" }}Environment file
Section titled “Environment file”~/.bestie/.env contains secret values:
OPENAI_API_KEY=ANTHROPIC_API_KEY=GEMINI_API_KEY=GROQ_API_KEY=OPENROUTER_API_KEY=BESTIE_TELEGRAM_BOT_TOKEN=BESTIE_ZALO_BOT_TOKEN=BESTIE_TRANSCRIPTION_API_KEY=ELEVENLABS_API_KEY=COMPOSIO_CONSUMER_API_KEY=ZEP_API_KEY=Only add secrets for features you enable. Basic setup needs only the API key named by the active LLM profile’s apiKeyEnv.
LLM profiles
Section titled “LLM profiles”llm.primary and llm.fallbacks[] are canonical provider/model refs. llm.profiles stores endpoint/auth metadata. llm.modelCatalog maps model refs to profiles.
Rules:
apiKeyEnvis an environment variable name, not the secret value.- HTTP providers store
baseUrl. - Native Gemini API-key profiles intentionally omit
baseUrl. - Local Ollama profiles use local endpoint config and do not need an API key.
- Fallbacks should reference models already present in
llm.modelCatalog.
Use CLI commands when possible:
bestie llm providersbestie llm setup --provider openai --set-defaultbestie llm setup --provider gemini --set-defaultbestie llm models --provider openaibestie llm test --model openai/gpt-4o-minibestie llm profiles listbestie llm fallbacks add --model anthropic/claude-sonnet-4-5Memory
Section titled “Memory”{ "memory": { "provider": "sqlite", "writePolicy": "ask", "deletePolicy": "ask", "retrievalPolicy": "full", "sqlitePath": "~/.bestie/data/memory.sqlite", "zepEnabled": false, "zepApiKeyEnv": "ZEP_API_KEY" }}memory.writePolicy controls model-requested memory and knowledge writes:
| Policy | Behavior |
|---|---|
allow |
Store allowed non-secret items directly. |
ask |
Queue allowed non-secret items for owner approval. |
deny |
Reject writes. |
memory.deletePolicy controls cleanup, deletion, supersession, graph merge/update/forget, and other memory-governance mutations.
memory.retrievalPolicy controls prompt retrieval behavior:
fullkeeps broad approved-memory injection.governedlabels or prioritizes memories by governance metadata so stale, low-confidence, expired, or superseded records are handled more carefully.
Knowledge graph
Section titled “Knowledge graph”The local knowledge graph lives in the same SQLite memory store. It stores entities, relations, pending graph writes, and audit events.
Graph tools and commands follow memory policies. Secret-like payloads are rejected and should not be stored as graph evidence.
Useful commands:
bestie memory graph statusbestie memory graph search Bestiebestie memory graph analyze --jsonbestie memory graph review --limit 5bestie memory graph pending --limit 10Workspace
Section titled “Workspace”{ "workspace": { "defaultPath": "~/.bestie/workspace", "externalPaths": [] }}Relative write/edit/exec paths land in workspace.defaultPath. Absolute paths outside the runtime workspace or active project need explicit allowlisting through workspace.externalPaths.
Skills
Section titled “Skills”Installed skills live under:
~/.bestie/skills/<skill-name>/SKILL.mdEach non-empty skill file is loaded in directory-name order and injected into chat prompts within a byte budget. Keep skills focused and never store secrets in them.
Internal tools
Section titled “Internal tools”{ "internalTools": { "policies": { "internal.read_url": "ask", "internal.write_file": "ask", "internal.edit_file": "ask", "internal.apply_patch": "ask", "internal.exec": "ask", "internal.list_processes": "allow", "internal.spawn_subagent": "ask" }, "exec": { "timeoutMs": 120000 } }}Policies use allow, ask, or deny. Local read tools are normally safe to allow. Writes, patches, exec, process inspection, web reads, and subagents should stay permission-gated unless the owner explicitly trusts the workflow.
Telegram
Section titled “Telegram”{ "channels": { "telegram": { "enabled": true, "botTokenEnv": "BESTIE_TELEGRAM_BOT_TOKEN", "ownerUserId": "123456789", "attachments": { "downloadPolicy": "allow", "maxBytes": 20971520, "previewMaxBytes": 16384, "parseMaxBytes": 5242880, "visionPolicy": "deny", "visionMaxBytes": 4194304, "transcriptionPolicy": "deny", "transcriptionMaxBytes": 10485760, "deleteAfterProcessingKinds": [] } } }}Run setup with:
bestie channels telegram setup{ "channels": { "zalo": { "enabled": true, "botTokenEnv": "BESTIE_ZALO_BOT_TOKEN", "ownerUserId": "zalo-owner-1", "pollingTimeoutSeconds": 25 } }}Zalo is currently owner-only and text-only.
Cron destinations
Section titled “Cron destinations”Cron schedules can report to specific destinations:
telegram:<userId>zalo:<userId>Schedule prompts are stored locally and run later. Keep them narrow and never put secrets in them.
Transcription
Section titled “Transcription”OpenAI-compatible transcription:
{ "transcription": { "provider": "openai-compatible", "baseUrl": "https://api.openai.com/v1", "model": "whisper-1", "apiKeyEnv": "BESTIE_TRANSCRIPTION_API_KEY", "timeoutMs": 60000 }}Local whisper transcription:
{ "transcription": { "provider": "local-whisper", "command": "~/.bestie/tools/local-whisper-transcribe.sh", "args": ["{modelPath}", "{audioPath}", "-l", "vi"], "modelPath": "~/.bestie/models/ggml-small.bin", "timeoutMs": 120000 }}The local command runs without a shell. {audioPath} and {modelPath} are replaced by Bestie.
Speech replies
Section titled “Speech replies”Speech output is a shared capability. Telegram can use it for voice replies when configured:
{ "speech": { "provider": "elevenlabs", "apiKeyEnv": "ELEVENLABS_API_KEY", "voiceId": "voice-id-here", "modelId": "eleven_v3", "languageCode": "en", "outputFormat": "mp3_44100_128", "timeoutMs": 60000 }, "channels": { "telegram": { "voiceReplyPolicy": "voice-input-only" } }}{ "mcp": { "servers": [ { "name": "remote-docs", "enabled": true, "transport": "streamable-http", "url": "https://example.com/mcp", "tools": [ { "name": "read_file", "category": "read" } ] } ] }}A top-level mcpServers object is also accepted and normalized to mcp.servers.
OAuth-backed servers should store authorization values in env variables, not raw config. Use bestie mcp login <server> to generate the full provider login URL.
Local UI
Section titled “Local UI”bestie ui serves a local web console against the same runtime files. UI APIs should expose secret presence and env var names only. Raw .env values must not be returned.
Output controls
Section titled “Output controls”BESTIE_NO_BANNER=1 bestie statusBESTIE_BANNER=static bestie statusNO_COLOR=1 bestie doctorMachine-readable commands such as bestie doctor --json and bestie memory export should avoid decorative output.
