Skip to content

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.

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.

{
"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"
}
}

~/.bestie/.env contains secret values:

Terminal window
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.primary and llm.fallbacks[] are canonical provider/model refs. llm.profiles stores endpoint/auth metadata. llm.modelCatalog maps model refs to profiles.

Rules:

  • apiKeyEnv is 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:

Terminal window
bestie llm providers
bestie llm setup --provider openai --set-default
bestie llm setup --provider gemini --set-default
bestie llm models --provider openai
bestie llm test --model openai/gpt-4o-mini
bestie llm profiles list
bestie llm fallbacks add --model anthropic/claude-sonnet-4-5
{
"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:

  • full keeps broad approved-memory injection.
  • governed labels or prioritizes memories by governance metadata so stale, low-confidence, expired, or superseded records are handled more carefully.

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:

Terminal window
bestie memory graph status
bestie memory graph search Bestie
bestie memory graph analyze --json
bestie memory graph review --limit 5
bestie memory graph pending --limit 10
{
"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.

Installed skills live under:

~/.bestie/skills/<skill-name>/SKILL.md

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

{
"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.

{
"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:

Terminal window
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 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.

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

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.

Terminal window
BESTIE_NO_BANNER=1 bestie status
BESTIE_BANNER=static bestie status
NO_COLOR=1 bestie doctor

Machine-readable commands such as bestie doctor --json and bestie memory export should avoid decorative output.