Configuration
Two files, and the split is strict.
.nouride/config.toml | everything structural. Safe to commit, print and render in a browser |
.env | credentials and per-host values. 0600 |
No credentials in config.toml. Where one is needed, the setting names a secret, and the
value is resolved from the environment or the secret store at the moment it is used:
api_key = "nougate" → $NOUGATE → $NOURIDE_SECRET_NOUGATE → data/secrets.jsonEnvironment wins over the store, so a container platform can always pin a value. The store is what the dashboard writes to, which is why adding a bot token does not need a redeploy.
Every value in config.example.toml matches a built-in default, so a missing file boots the same
daemon. Delete anything you are not changing.
nouride config check [file] # would this configuration start? Nothing is launchednouride config schema [section] # every valid key, as JSON Schemanouride config explain <path> # e.g. security.web.search_endpointAn annotated copy ships beside the binary as config.example.toml, and it is the fullest reference
there is — this page is the map.
[daemon]
name = "nouride"log_level = "info" # debug | info | warn | errorlog_pretty = false # human-readable console output — dev onlydata_dir = "./.nouride/data"agents_dir = "./.nouride/agents"workspace_dir = "./.nouride/workspace"shutdown_timeout_ms = 30000timezone = "UTC"backup_keep = 7# default_agent = "nouva"Three paths rather than one root, because the three hold different kinds of thing: daemon state,
identity worth committing, and output that can grow large. workspace_dir on another disk is a
supported arrangement.
backup_keep is dated copies of the persona packs under data/backups/agents/. MEMORY.md is the
only thing in an install that is not in git and cannot be rebuilt.
default_agent answers when a message mentions nobody, arrives on a gateway with no owner, and
matches no channel binding.
[agents]
mentions_enabled = truemention_max_hops = 5job_wake_max_chain = 100groups_require_mention = falsegroup_context_messages = 20What each of these does in practice →
[providers.<name>]
[providers.nougate-anthropic]kind = "anthropic" # the wire format, not the vendorbase_url = "https://nougate.nouverse.tech/anthropic/v1"api_key = "nougate" # a secret NAME# headers = { "HTTP-Referer" = "https://nouverse.tech" }No profiles exist by default beyond anthropic and openai, which point at the real APIs and name
the conventional environment variables — so a minimal setup defines no profiles at all.
Provider profiles →
[gateways.<platform>.<name>]
[gateways.telegram.nouva]enabled = truetoken = "telegram-nouva" # a secret NAMEagent = "nouva"mode = "polling" # webhook is not implemented
[gateways.discord.main]enabled = truetoken = "discord-main"
[gateways.discord.main.cache]messages = 0 # per channelusers = 0 # globalmembers = 0 # per guild
[gateways.whatsapp.main]enabled = falseencryption_key = "wa-encryption-key"session_dir = "./data/whatsapp"bridge_transport = "stdio" # stdio | ws | inprocNone are defined by default, so a fresh install brings up the dashboard and nothing else rather than retry-looping against a platform it has no token for. Gateways →
[security]
approvals = "risky" # normal | risky | nonetool_timeout_ms = 30000tool_max_output_bytes = 10485760approval_timeout_ms = 300000 # unanswered requests expire as DENIALSmax_conversation_turns = 200sender_allowlist = []credentials_dir = "" # empty = <data_dir>/credentials, created 0700credentials_dir is deliberately not a workspace root: file tools cannot reach it, only a
command can, through $NOURIDE_CREDENTIALS_DIR. The daemon refuses to start if it lands inside any
readable root.
[security.access]
mode = "pair" # pair | allowlist | openallowlist = []code_ttl_ms = 900000notify_cooldown_ms = 300000[security.exec]
mode = "full" # deny | allowlist | fullask = "on-miss" # off | on-miss | alwaysask_fallback = "deny" # deny | allowlist — when nobody can be reachedallowlist = ["ls", "pwd", "cat", "…"]blocked = []max_memory_mb = 0 # 0 = unlimited. Linux only, via prlimitmax_cpu_seconds = 0[security.web]
allow_private_urls = falsesearch_kind = "brave" # brave | searxng | duckduckgosearch_api_key = "brave_search_api_key"search_endpoint = ""fallback_enabled = truefallback_backends = ["duckduckgo"][security.rate_limit]
messages_per_minute = 10 # per sender, per gatewaycooldown_reply = "Please slow down."[media]
image_model = "" # empty = the agent's own modelread_images = truemax_image_bytes = 8388608Documents are not sent to a model at all. They land in the agent’s workspace and it opens them
with read or a command, so a PDF costs nothing until the agent looks.
[alerts]
gateway = "" # e.g. "telegram:nouva"chat_id = ""Where the daemon reports on itself — a gateway that gave up reconnecting, a session that needs relinking. Boot warns when chat gateways are enabled and this is empty.
[dashboard] and [control]
[dashboard]enabled = truehost = "127.0.0.1" # $HOST overrides. The container image sets 0.0.0.0port = 18254 # $PORT overridespublic_url = "" # empty rather than a guess
[control]enabled = truechat = true # registers the in-process gateway `nouride chat` uses[llm.defaults], [llm.limits], [llm.streaming], [llm.context]
[llm.defaults]provider = ""model = ""max_tokens = 8192temperature = 0.7
[llm.limits]max_tokens_per_turn = 16384max_tool_iterations = 25max_turn_duration_ms = 300000max_daily_cost_usd = 0 # 0 = offcost_alert_threshold_pct = 80
[llm.streaming]enabled = truetyping_indicator = trueprogress_in_chat = true
[llm.context]max_context_ratio = 0.8truncation_strategy = "summary" # summary | sliding_windowpreserve_recent_turns = 4compact_at_ratio = 0.8compact_at_tokens = 120000preserve_recent_tool_results = 3# summary_model = "claude-haiku-4-5-20251001"[queue]
max_concurrent_per_agent = 1 # per chat. The loader refuses anything highermax_queue_depth = 20max_concurrent_turns = 8 # across every conversation. 0 lifts the ceilingqueue_timeout_ms = 300000dedup_window_ms = 1000[attachments] and [retention]
[attachments]max_size_mb = 50
[retention]session_days = 30 # the conversation AND the files that belong to itaudit_days = 30job_log_days = 14tool_output_days = 2scratchpad_days = 3How long a file is kept is not in [attachments]. It is session_days, the same window as the
transcript that mentions it — a file is part of what was said. It used to have its own key, seven
days against a thirty-day transcript, and the result was a conversation that mentioned a screenshot
for another twenty-three days after the screenshot was deleted.
0 on any of these keeps everything: the off position, not “delete all”.
[session_memory]
active_dir = "./.nouride/memories"messages = 2000retention_days = 3
[session_memory.reset]mode = "none" # none | daily | idle[cron]
enabled = truetick_interval_ms = 1000min_interval_ms = 60000max_jobs = 100run_retention_days = 14catchup_window_ms = 3600000fail_streak_limit = 10unattended_approval = "auto" # auto | denycommand_timeout_ms = 600000[health]
heartbeat_interval_ms = 60000disk_warn_threshold_mb = 1024wal_warn_threshold_mb = 100[skills] and [rsi]
[skills]off = [] # names nobody loads, whatever their own skills.toml says
[rsi]mode = "require_approval" # off | require_approval | always_allowmin_tool_failures = 3reflect_after = 8rapid_follow_up_ms = 180000model = ""cooldown_ms = 60000max_pending = 20notify_chat = trueAnything unrecognised in rsi.mode is read as require_approval, never as always_allow: a bad
value should cost an approval, not grant autonomy. Skills & learning →
[nougate]
in_process = falseport = 18256in_process = true needs a binary built with --bundle-nougate.
Nougate →
Environment variables
Only these override the config file. Everything else structural belongs in config.toml.
HOST | dashboard bind address. Required as 0.0.0.0 in a container |
PORT | dashboard port |
LOG_LEVEL | debug | info | warn | error |
TZ | IANA timezone for the agent’s runtime context |
NOURIDE_DATA_DIR | overrides [daemon] data_dir |
NOURIDE_AGENTS_DIR | overrides [daemon] agents_dir |
NOURIDE_CONFIG | where to find the config file |
INITIAL_ADMIN_USERNAME / INITIAL_ADMIN_PASSWORD | the bootstrap account, read only while the user table is empty. Both, or neither |
SESSION_SECRET | generated into data/.session-secret if unset |
NOURIDE_URL / NOURIDE_TOKEN | point the CLI at a remote daemon |
NOURIDE_TYPECHECK_JOBS | development only — cap the typecheck’s parallelism |
NOURIDE_WA_LOG_LEVEL | turn the WhatsApp bridge’s logging up |
Everything else in .env is a credential, named by your own config:
# [providers.nougate-anthropic] api_key = "nougate" →NOUGATE=
# [gateways.telegram.nouva] token = "telegram-nouva" →TELEGRAM_NOUVA=
# The two built-in profiles name these:ANTHROPIC_API_KEY=OPENAI_API_KEY=
# 64 hex characters — openssl rand -hex 32WA_ENCRYPTION_KEY=
# Without this, web_search is not offered at all, rather than offered and always failing.BRAVE_SEARCH_API_KEY=There is no fixed list. Every variable exists because something in your config.toml names it —
delete the ones your config does not reference.
If a name is too generic for your environment, NOURIDE_SECRET_<NAME> is read as well.
Per-agent config
.nouride/agents/<id>/config.toml and skills.toml.
Agents & persona packs →