Repository navigation
Expand file tree
/
Copy pathconfig.example.yaml
More file actions
407 lines (386 loc) · 21.2 KB
/
Copy pathconfig.example.yaml
File metadata and controls
407 lines (386 loc) · 21.2 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# Nerve — Personal AI Assistant Configuration
# Copy to config.yaml and customize. Secrets go in config.local.yaml (gitignored).
#
# Any string value may reference an environment variable — the recommended way
# to keep secrets out of committed/shared config:
# key: ${MY_VAR} # required — load fails if MY_VAR is unset
# key: ${MY_VAR:-default} # optional — uses default when unset/empty
# Only the braced ${...} form is interpolated; a bare "$" is left untouched
# (so bcrypt hashes / jwt secrets are safe). Use $$ for a literal "$".
# Core
workspace: ~/nerve-workspace # Path to workspace with SOUL.md, MEMORY.md, etc.
timezone: America/New_York
# Setup page — whether it may store the credentials it is given (an API key,
# a bot token, a Telegram sign-in) on this machine. On an instance whose
# connections the operator provides (a model credential in the service
# environment, Slack through a channel gateway), local_secrets: false closes
# the Memory, Channels, Sources and Agents steps ("Not available on this
# instance") and the Model step accepts only Bedrock or the credential from
# the environment. Usually set in the tracked workspace/config/settings.yaml;
# NERVE_SETUP_LOCAL_SECRETS=0 in the service environment forces it off
# (one-way, like NERVE_LOCKDOWN). See docs/setup.md.
# setup:
# local_secrets: true
# Provider — how Nerve connects to Claude (default: anthropic)
# provider:
# type: bedrock # "anthropic" or "bedrock"
# aws_region: us-east-1 # AWS region for Bedrock
# # aws_profile: "" # Optional: AWS SSO profile name
# Local proxy (CLIProxyAPI) — optional. Routes Anthropic API calls through
# Claude Code OAuth, and is the Anthropic↔OpenAI translation layer that local
# Ollama models are reached through. Required for the ollama block below.
# proxy:
# enabled: false
# port: 8317
# host: 127.0.0.1
# Anthropic-compatible endpoint (optional), for example an LLM gateway. Every
# model request goes here in place of api.anthropic.com: the Claude CLI, the
# direct SDK calls and memU. Not with proxy.enabled or Bedrock.
# anthropic_base_url: https://llm-gateway.example.com
# Local Ollama — expose locally-installed Ollama models as selectable chat
# models in the web composer's model picker. Ollama speaks an OpenAI-compatible
# API, so this requires proxy.enabled: true (the proxy translates the
# Anthropic requests the SDK emits). Models are auto-discovered at runtime
# from the running Ollama server (GET /api/tags) — whatever you've pulled
# locally shows up automatically, no need to list models here.
# ollama:
# enabled: false
# host: 127.0.0.1
# port: 11434
# Agent
agent:
model: claude-opus-5-5 # Primary model for conversations (Nerve's default)
# Use claude-fable-5-1 for Anthropic's newest generation (Mythos-class).
# Note: ~2x the cost of Opus ($10/$50 per MTok in/out).
cron_model: claude-sonnet-5-5 # Cheaper model for cron jobs
title_model: claude-haiku-5-5 # Session title generation
# Model-alias remapping for the CLI: short aliases ("opus") used in
# Agent/Workflow tool model options and skill frontmatter resolve to the
# mapped ID. Supported: opus, sonnet, haiku, fable. opus → claude-opus-5-5
# and haiku → claude-haiku-5-5 are the built-in defaults (not applied on
# Bedrock); "" unsets an alias. The CLI also uses the haiku model for its
# background calls.
# model_aliases:
# opus: claude-opus-5-5
# haiku: claude-haiku-5-5
# Claude models selectable in the web composer's model picker. The
# configured `model` above is always offered first; entries here extend
# the list. Unset → the models the Anthropic Models API reports for your
# credentials (see model_discovery below), falling back to a built-in
# current-generation list (opus / fable / sonnet / haiku). On Bedrock only
# models named in config are offered (Bedrock IDs are region-prefixed, so
# neither discovery nor the bare built-ins would resolve).
# models:
# - claude-opus-5-5
# - claude-sonnet-5-5
# Ask the Anthropic Models API (GET /v1/models) at startup which models the
# configured credentials' catalog endpoint advertises, so a newly released
# model shows up in the picker without a config edit. Catalog membership is
# not a serving guarantee — an advertised ID can still fail on send, and
# discovery runs no serving probe (see model_discovery_excluded_models to
# prune such entries). Ignored when `models` above is set, on Bedrock, or
# without an API key. false → always use the built-in list.
model_discovery: true
max_turns: 50 # Max agentic turns per request
max_concurrent: 32 # Max concurrent agent sessions
# Keep the appended system prompt byte-identical across sessions so the
# exact-prefix prompt cache is shared between them: the session id and the
# pre-recalled memories travel in a <session-context> block at the top of
# each session's first message instead. false → legacy shape (id + recall
# inline in the system prompt; every session start re-writes the cache).
static_system_prompt: true
background_agent_permissions: true # Background sub-agents (Agent run_in_background) get the same tool permissions as foreground; false denies their Write/Edit/Bash
# Agent teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS). The CLI gates the
# SendMessage tool behind this flag while the Agent tool advertises it
# regardless ("use SendMessage with to: '<id>' to continue this agent"), so
# with it off the model is told to resume sub-agents with a tool that does
# not exist. On: sub-agents are resumable with their context intact, and
# teammates can message each other. Teammates stay opt-in per turn and cost
# a full context window each; the CLI cannot restore in-process teammates
# when a session's client is recycled (idle timeout, restart, crash retry).
agent_teams: true
# First-prompt rewrite — the web UI can refine the opening message of a
# new chat with a fast model, preview it, and send only after approval.
prompt_rewrite:
enabled: true # Master switch (per-user toggle is in the UI)
model: "" # Empty → falls back to agent.model (chat model)
max_tokens: 1024
timeout_seconds: 45
# For Bedrock, use model IDs like:
# model: us.anthropic.claude-opus-5-5
# cron_model: us.anthropic.claude-sonnet-5-5
# title_model: us.anthropic.claude-haiku-5-5
# Gateway
gateway:
host: 0.0.0.0
port: 8900
ssl:
cert: ~/.nerve/certs/cert.pem
key: ~/.nerve/certs/key.pem
# Channels
telegram:
enabled: true
# Live-message authorization in private and group chats:
# pairing — only users in allowed_users may talk to the bot. New users
# authorize with a one-time code: run `nerve pair` on the
# server, then send the bot `/pair <code>` in a private chat.
# Paired IDs are persisted to config.local.yaml automatically.
# open — anyone can talk to the bot (dangerous: full agent access).
dm_policy: pairing
# allowed_users: [123456789] # users allowed to use the bot (or pair instead)
stream_mode: partial # "partial" (edit messages) or "full" (wait for complete)
#
# Feed the source inbox from watched group chats, reaching it as the source
# "telegram:observed". This grant is independent of allowed_users: a sender
# who cannot use the bot may still reach the source. A live-routed message is
# not collected unless include_handled_messages is true. Only numeric IDs may
# grant; titles and @usernames can only deny. Private chats, other bots, and
# /pair commands are never collected. With privacy mode enabled, a non-admin
# bot does not receive ordinary group messages. Make it an admin or disable
# privacy mode with BotFather.
# See docs/sources.md before enabling.
# source:
# enabled: true
# allow_chats: ["-1001234567890"]
# deny_senders: ["12345"]
# include_handled_messages: false # true also collects live-routed messages
# Notifications — where the notify tool and async questions/approvals are delivered.
# By default they go to the owner's DM. Point telegram_chat_id at a dedicated group
# to collect them there instead; enable delivery_only_sink to make that group one-way
# (inbound messages and reactions there never start an agent turn). Default: off.
# notifications:
# telegram_chat_id: -1000000000000
# delivery_only_sink: false
slack:
# Omitting this key leaves Slack off until both tokens below are set, so an
# install that does not use Slack never has to say so. Set it to true to be
# told about a missing token instead of having the channel stay down.
enabled: false
# "socket" (default): this process holds the tokens below. "hosted": a
# shared channel gateway holds them and streams events to this process;
# no token is needed, and channels.hosted below must name the gateway's
# public key file and this agent. See docs/config.md#hosted-mode.
# mode: socket
# Socket Mode needs both tokens in config.local.yaml; no public URL is needed:
# bot_token: xoxb-… (OAuth & Permissions → Bot User OAuth Token)
# app_token: xapp-… (Basic Information → App-Level Tokens, connections:write)
#
# Access patterns match Slack IDs or names, case-insensitively with globs.
# Deny wins, DMs require explicit opt-in, and no allow grant refuses everyone.
# Grant users by member ID, handle, or email only: a member edits their own
# display and full name, so allow_users never matches those.
# allow_users: ["U0123ABC", "alice.example"]
# deny_users: ["*-bot"]
allow_direct_messages: false
# allow_channels: ["eng-*", "C0456DEF"]
# deny_channels: ["*-random", "*-social"]
#
# Let an agent post to a conversation it names (send_channel_message),
# including from a cron run with no chat attached. Off by default and
# separate from the read grant above: allow_channels is set by nearly every
# deployment for inbound access, so deriving writes from it alone would open
# unprompted posting on upgrade. Turning this on never widens where writes
# may go — allow_channels still bounds that, and DMs are always refused.
# While this is off, the send_channel_message tool is not offered at all.
# allow_outbound: false
#
# Every shared-channel thread is its own session; DMs use one conversation.
#
# How a reply appears while the agent works. "partial" posts a placeholder
# and edits it as tokens and tool labels arrive, so a long turn shows
# progress; the finished reply is then posted as a new message. "full" waits
# and posts once. Prefer "full" in a busy channel, where the placeholder is
# visible to everyone, or where the edits crowd Slack's rate limit.
stream_mode: partial # "partial" (edit messages) or "full" (wait for complete)
# `/nerve` defaults to new, stop, star, unstar, and reply.
# omitted — new, stop, star, unstar, reply
# [] — no slash commands at all
# [all] — everything, including doctor and restart
# doctor/restart affect the host; sessions lists other channels. Opt in.
# commands: [sessions, new, stop, reply]
#
# Feed the source inbox from watched channels, reaching it as the source
# "slack:observed", where poll_source and the `messages` cron gate read it.
# Nothing here starts an agent turn.
#
# A SEPARATE grant from the access rules above, decided independently of
# them: those say who may drive the agent, this says whose traffic may reach
# its inbox. So an empty allow_channels collects NOTHING rather than
# everything. A mention from someone access refuses is still collected; a
# live-routed message is not collected unless include_handled_messages is true.
# DMs (group DMs included), other apps, and the bot's own posts are never
# collected.
#
# allow_channels takes a channel ID or a channel name, with globs and a
# leading "#" tolerated; deny_channels the same. allow_senders takes a
# member ID, handle, or email — a display name can only ever deny, since
# its owner picks it. Prefer uppercase IDs: everything else uses cached Slack
# API lookups.
#
# Everything collected is untrusted input: the grant decides whose messages
# are kept, not whether their contents can be believed. See docs/sources.md.
# source:
# enabled: true
# allow_channels: ["C0456DEF", "eng-*"]
# deny_channels: ["*-social"]
# deny_senders: ["*-bot"]
# include_handled_messages: false # true also collects live-routed messages
# schedule: "*/5 * * * *" # how often the buffer drains into the inbox
# Streams from a shared channel gateway, for a provider in hosted mode
# (slack.mode: hosted). A stream opens only with a token that the gateway
# signs for this exact tenant and agent. gateway_jwks_file holds the
# gateway's public keys only; Nerve fetches no keys over the network.
# channels:
# hosted:
# gateway_jwks_file: /etc/nerve/gateway-jwks.json
# issuer: nerve-gateway
# audience: nerve-channel
# tenant_id: 00000000-0000-0000-0000-000000000000
# agent_id: 00000000-0000-0000-0000-000000000000
# max_streams: 8
# Where notify, ask_user, and propose_action deliver. The list replaces the
# default rather than adding to it, so name every transport you want. A
# transport that is off costs nothing here.
notifications:
channels: [web, telegram, slack]
# Target conversation for Slack cards. Without this, the first literal ID
# in slack.allow_channels is used; names and globs are not resolved.
# slack_channel_id: "C0456DEF"
# Quiet hours (local timezone). Optional: set both to "" for none, which is
# what a worker gets by default.
quiet_start: "02:00"
quiet_end: "12:00"
# Sync sources
sync:
telegram:
enabled: true
monitored_folders: [Work, Friends]
gmail:
enabled: true
accounts: [] # Set in config.local.yaml
schedule: "*/15 * * * *"
# Plain IMAP mailboxes — for providers the Gmail source can't reach.
# One source per account. Passwords go in config.local.yaml under
# sync.imap.passwords, keyed by username. See docs/sources.md for the
# optional pass that reads mail whose payload is only legible as an image.
# imap:
# enabled: false
# accounts:
# - host: imap.example.net
# username: me@example.net
# label: mailbox # source name becomes imap:mailbox
# schedule: "*/30 * * * *"
github:
enabled: true
schedule: "*/15 * * * *"
# Guardrails (case-insensitive globs; deny wins; non-empty allow is
# fail-closed). allow_* = allowlist (empty = all pass).
# repo_name — the notification's repo full_name ("ClickHouse/nerve").
# actors — every login involved (author, assignees, commenters).
# reason — GitHub's notification reason: mention, author,
# review_requested, assign, team_mention, comment,
# subscribed, ci_activity, state_change, ...
# ci_branch — branch of a CI run (empty for everything else), so this
# one is deny-only: an allow list would fail closed and
# drop every non-CI notification.
allow_repos: [] # Example: ["ClickHouse/*", "myorg/myrepo"]
deny_repos: []
allow_actors: []
deny_actors: []
allow_reasons: []
deny_reasons: [] # Example: ["comment", "subscribed"] — mute
# follow-up churn on threads you only
# commented on / watch; keep mentions,
# review requests, and your own PRs.
deny_ci_branches: [] # Example: ["main", "master"] — keep CI
# failures on your own PR branches, drop
# default-branch syncs and scheduled runs.
github_repos: # Monitor a set of repos for NEW issues & PRs
enabled: false
schedule: "*/15 * * * *"
repos: [] # Required: ["ClickHouse/clickhouse-go", "owner/repo"]
batch_size: 50
# Memory (memU)
memory:
recall_model: claude-sonnet-5-5
# embed_model: text-embedding-3-small # Only needed with openai_api_key
# Cron — no path keys needed. Cron config lives in <workspace>/config/cron/
# (system.yaml, jobs.yaml, gates/) and is resolved from the workspace, with a
# fallback to the legacy ~/.nerve/cron for un-migrated installs.
#
# Do NOT pin system_file/jobs_file here unless you really mean it: an explicit
# value wins outright, so it disables both the workspace location and the
# fallback, and `nerve init` will keep regenerating a system.yaml nothing reads.
#
# Editing the cron files does not apply itself: run `nerve reload`, or let the
# next workspace sync do it.
# Workflow runs — budget-capped multi-agent jobs (Claude Workflow tool or
# Codex Ultracode) in dedicated tracked sessions. Nerve meters real dollar
# spend, warns at 80% of budget, and kills the run at 100% (scoped to the
# run's own session). Runs do not survive a daemon restart. Enabled by
# default; values below are the defaults. See docs/workflow-runs.md.
# workflows:
# enabled: true
# runs_dir: ~/.nerve/workflow-runs # <runs_dir>/<run-id>/{run.json,events.ndjson,result.md}
# poll_interval_seconds: 60 # budget monitor cadence (re-meters spend)
# warn_fraction: 0.8 # one-time warning at this fraction of budget
# kill_grace_seconds: 30 # graceful stop -> grace -> force kill
# max_concurrent_runs: 2 # excess runs queue as 'pending'; keep well below agent.max_concurrent
# allow_unbudgeted: false # require budget_usd on every run
#
# # Review loops — deterministic implement→verify cycles built from
# # workflow runs: an implementer leg works toward a Goal prompt, an
# # independent verifier leg judges the workspace against Verifier
# # criteria (structured, evidence-backed verdict), and the server
# # iterates until the criteria pass or a cap fires. Cross-vendor legs
# # (Claude implements, Codex verifies) are the default when Codex is
# # configured. See docs/review-loops.md.
# review_loop:
# enabled: true
# implementer: {engine: claude-workflow, model: "", effort: ""} # "" = backend default
# verifier: {engine: codex-ultracode, model: "", effort: ""} # cross-vendor default
# max_iterations: 3 # per-loop default; hard ceiling 8
# default_budget_usd: 10.0 # total loop budget (all legs)
# min_leg_budget_usd: 0.5 # never start a leg with less
# verifier_reserve_fraction: 0.15 # held back so the last attempt is always verified
# criteria_adoption: "no" # no | ask | auto — verifier-proposed criteria policy
# max_new_criteria_per_iteration: 3 # auto mode adoption caps
# max_discovered_criteria: 12
# discovery_grace_rounds: 2 # discovery-only rounds before escalating "scope keeps growing"
# auto_reissue_implementer: false # restart recovery: re-run interrupted implementer legs
# escalation_reproposals: 2 # re-file expired decision cards this many times
# verifier_sandbox: workspace-write # codex verifier legs (isolation vs test-writes)
# reconcile_interval_seconds: 600 # card-liveness + loop budget-warn tick
# Backup (optional) — scheduled, verified snapshots of Nerve state into a
# single portable bundle (nerve-backup-<host>-<ts>.tar.zst). Off by default.
# Point target_dir at an external mount or a synced directory — the OFF-BOX
# copy is what protects against a disk failure. The SQLite DBs are snapshotted
# with the online-backup API, so bundles are consistent even while Nerve runs.
# Manual use: `nerve backup` / `nerve restore BUNDLE` (no running server needed).
# backup:
# enabled: false
# target_dir: "" # e.g. /mnt/backup/nerve (REQUIRED when enabled)
# interval_hours: 24 # scheduled cadence (an hourly tick fires when due)
# retention_count: 7 # keep the newest N bundles; prune older ones
# include_workspace: true # include the workspace BRAIN (*.md, memory/, scripts/, skills/)
# workspace_excludes: [] # extra glob patterns to skip, e.g. ["*.png", "drafts"]
# notify_on_failure: true # high-priority notify if a scheduled backup fails
# notify_on_success: false # low-priority digest line on success
# External MCP server (optional) — expose Nerve's tool registry to
# external MCP clients (Codex, Claude Code, Cursor) over Streamable HTTP.
# Off by default. Authentication reuses the gateway JWT
# (auth.jwt_secret) — present it as `Authorization: Bearer <jwt>` or
# `?token=<jwt>`. Connect a client with the URL "https://<host>:8900/mcp/v1/".
# mcp_endpoint:
# enabled: false
# path: /mcp/v1
# include_hoa: false # Expose the deprecated houseofagents stub tools externally (default off)
# Observability (optional) — Langfuse tracing for the agent loop.
# Set keys in config.local.yaml. Without keys, the integration is a no-op.
# See docs/observability.md for setup details.
# langfuse:
# public_key: pk-lf-... # from your Langfuse project (set in config.local.yaml)
# secret_key: sk-lf-... # from your Langfuse project (set in config.local.yaml)
# host: https://cloud.langfuse.com
# # redact_patterns: # optional — strip these regexes from spans
# # - "sk-ant-[A-Za-z0-9_\\-]{20,}"