1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# ─── Brain OS — Configuration ─────────────────────────────────────────────────
# Generated by `brain init`. Edit to customise your setup.
# Env-var override for any key: BRAIN_<SECTION>__<KEY> (e.g. BRAIN_LLM__API_KEY)
# ── LLM Providers ──────────────────────────────────────────────────────────────
# Brain probes each entry at startup, picks the first reachable one, and
# automatically falls over to the next on rate-limit or error.
#
# kind: ollama | groq | openai | openrouter | deepseek | together | gemini-compat
llm:
temperature: 0.7
max_tokens: 4096
providers:
- name: ollama
kind: ollama
base_url: "http://localhost:11434"
model: "qwen2.5-coder:7b"
preferred_models:
# - name: groq
# kind: groq
# api_key: "gsk_..."
# model: "llama-3.3-70b-versatile"
# preferred_models: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant"]
# - name: openrouter
# kind: openrouter
# api_key: "sk-or-..."
# model: "meta-llama/llama-3.1-8b-instruct:free"
# preferred_models: ["meta-llama/llama-3.1-8b-instruct:free"]
# Legacy single-provider fallback — only used when providers list is empty.
provider: "ollama"
model: "qwen2.5-coder:7b"
base_url: "http://localhost:11434"
api_key: ""
# ── Embedding ──────────────────────────────────────────────────────────────────
# Run `ollama pull nomic-embed-text` before starting Brain.
# dimensions must match the model's actual output size exactly.
embedding:
model: "nomic-embed-text"
dimensions: 768
# ── Memory ─────────────────────────────────────────────────────────────────────
memory:
episodic:
semantic:
similarity_threshold: 0.65
max_results: 20
search:
rrf_k: 60 # Reciprocal Rank Fusion constant
pre_fusion_limit: 50 # candidates fetched from each source (BM25, ANN) before fusion
importance_weight: 0.3 # weight for importance in final reranking
recency_weight: 0.2 # weight for recency in final reranking
decay_rate: 0.01 # forgetting-curve decay rate (higher = faster forgetting)
consolidation:
enabled: true
interval_hours: 24
forgetting_threshold: 0.05
# ── Encryption ─────────────────────────────────────────────────────────────────
# Run `brain init --encrypt` to generate a salt and enable at-rest encryption.
encryption:
enabled: false
# ── Security ───────────────────────────────────────────────────────────────────
security:
# Binaries the sandbox is allowed to execute. The list is intentionally
# narrow — read-only inspection plus the toolchain. To run anything else
# (docker, brew, ssh, custom scripts), add it here explicitly.
exec_allowlist:
exec_timeout_seconds: 30
# ── Actions ────────────────────────────────────────────────────────────────────
actions:
web_search:
# On by default. The "duckduckgo" provider is a zero-config built-in
# that works without Docker or an API key — basic quality, but always
# available. Switch to "searxng" (run `brain deps up` first) for the
# best results, or "tavily" with an API key for a hosted option.
enabled: true
provider: "duckduckgo" # duckduckgo | searxng | tavily | custom
endpoint: "http://localhost:8888" # used by searxng/custom only
api_key: "" # required for tavily
timeout_ms: 3000
default_top_k: 5
scheduling:
enabled: false
mode: "persist_only"
messaging:
enabled: false
timeout_ms: 3000
channels:
# Webhook channel example — works for Discord, Telegram, Slack, or any HTTP endpoint.
# Template vars: {{channel}} {{recipient}} {{content}} {{namespace}} {{timestamp}}
#
# discord:
# url: "https://discord.com/api/webhooks/<ID>/<TOKEN>"
# body: '{"content": "{{content}}"}'
# headers: {}
# telegram:
# url: "https://api.telegram.org/bot<TOKEN>/sendMessage"
# body: '{"chat_id": "<CHAT_ID>", "text": "{{content}}", "parse_mode": "Markdown"}'
# headers: {}
resilience:
max_retries: 2
retry_base_ms: 500
circuit_breaker_threshold: 5
circuit_breaker_cooldown_secs: 60
# ── Proactivity ────────────────────────────────────────────────────────────────
proactivity:
enabled: true
max_per_day: 2
min_interval_minutes: 60
quiet_hours:
start: "20:00"
end: "10:00"
timezone: "UTC" # IANA timezone, e.g. "America/New_York"
delivery:
outbox: true
broadcast: true
webhook_channels: # channel keys from actions.messaging.channels
max_outbox_age_days: 7
open_loop:
enabled: true
scan_window_hours: 72
resolution_window_hours: 24
check_interval_minutes: 120
# ── Adapters ───────────────────────────────────────────────────────────────────
adapters:
http:
enabled: true
host: "127.0.0.1"
port: 19789
cors: true
ws:
enabled: true
port: 19790
mcp:
enabled: true
stdio: true
http: true
port: 19791
grpc:
enabled: true
port: 19792
# ── Channel Relays ─────────────────────────────────────────────────────────────
# Bidirectional WebSocket gateways. Unlike webhooks these are long-lived
# connections — approval responses from any relay are correlated automatically.
channel:
relays:
# - id: telegram
# label: "Telegram"
# url: "ws://127.0.0.1:7000/brain"
# namespace: "personal"
# api_key: ""
# initial_backoff_ms: 1000
# max_backoff_ms: 60000
# ── Agents ─────────────────────────────────────────────────────────────────────
# Specialist agents the orchestrator delegates multi-step tasks to.
agents:
delegates:
fallbacks:
retry_on_timeout: true
# Auto-discovery (default ON below) finds well-known CLI agents on $PATH
# without needing manual entries. Use `delegates[]` for bespoke binaries
# or non-standard invocation flags.
# - name: script
# kind: subprocess
# binary: "/usr/local/bin/my-agent"
# args: ["--task", "{task_id}"]
# prompt_via_stdin: true
# tags: ["custom"]
# ── Access ─────────────────────────────────────────────────────────────────────
# A random key is generated on `brain init` and printed once to stdout.
access:
api_keys:
# ── Internal defaults (safe to leave unchanged) ────────────────────────────────
brain:
version: "0.2.0"
data_dir: "~/.brain"
storage:
ruvector_path: "~/.brain/ruvector/"
sqlite_path: "~/.brain/db/brain.db"
hnsw:
ef_construction: 200
m: 16
ef_search: 50