--- type: operations title: Configuration and data files description: How src/config.rs loads config.toml, the system prompt from SOUL.md and the optional image description request from vision.md, the supported keys and defaults (reply_delay_seconds, catch_up_seconds, ai_retries, web_port), the template files, where the data directory and databases live, the build version, and operational and security caveats. tags: [configuration, toml, data-directory, sqlite, security, soul-md] verified: - by: openwiki/0.7.2 at: 2026-10-21T21:49:12.291Z sources: - id: openwiki-source-ea70eb6c045047448e446296 resource: repo://.gitignore - id: openwiki-source-0d5b861992295d2e1663735c resource: repo://config.toml.global.template - id: openwiki-source-1b05507d7085e8e99a9b64f1 resource: repo://src/ai.rs - id: openwiki-source-2a737474d86fc75cc9d9694f resource: repo://src/config.rs generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" } --- # Configuration and data files All runtime settings come from files in the data directory, loaded once at startup by `Config::load` (`src/config.rs:265-281`): `config.toml` for the settings, `SOUL.md` for the system prompt and the optional `vision.md` for the image description request. No environment variable is ever read (`src/config.rs:1-25`). Editing any of them requires restarting `wh`. ## Data directory The data directory is fixed: `/wh`, resolved with `dirs::config_dir()` (`src/config.rs:160-168`). On Windows this is `%APPDATA%\wh`, on Linux `~/.config/wh` (file names are case-sensitive there). It cannot be changed from the config file. At startup `ensure_data_dir` creates it if missing (`src/config.rs:308-315`). | File | Purpose | Defined by | |---|---|---| | `config.toml` | Optional settings | `CONFIG_FILE`, `src/config.rs:54` | | `SOUL.md` | System prompt: the responder's identity and rules; required for the AI | `SOUL_FILE`, `src/config.rs:56` | | `vision.md` | Optional request sent with each image to obtain its description | `VISION_FILE`, `src/config.rs:58` | | `session.db` | WhatsApp session store used by `whatsapp-rust` ([WhatsApp client](../integrations/whatsapp-client.md)) | `session_db_path`, `src/config.rs:317-323` | | `history.db` | Local messages, chats, contacts, per-chat autoreply flags, the global autoreply setting and image descriptions ([History store](../concepts/history-store.md)) | `history_db_path`, `src/config.rs:325-331` | | `ai_requests.log` | Optional AI request log, written only when `ai_log = true` ([AI client](../integrations/ai-client.md)) | `ai_log_path`, `src/config.rs:333-339` | The two databases are separate by design: `session.db` belongs to the WhatsApp library (linked-device session and keys, schema owned by `whatsapp-rust`), while `history.db` belongs to the app. ## Keys in `config.toml` | Key | Purpose | Default when missing or blank | |---|---|---| | `openai_api_key` | Bearer token for the AI endpoint (trimmed) | none — the [AI client](../integrations/ai-client.md) cannot be built | | `openai_base_url` | Base URL of the OpenAI-compatible endpoint (trimmed, trailing `/` removed) | `https://ollama.com/v1` | | `openai_model` | Model for text replies (trimmed) | `deepseek-v4.1-flash` | | `openai_vision_model` | Model that describes images (trimmed) | none — image understanding disabled | | `ai_log` | Boolean; when `true` every request sent to the AI model is appended to `ai_requests.log` | `false` | | `reply_delay_seconds` | Seconds the autoreply waits after the first unanswered message of a chat; messages received meanwhile are answered in the same reply; `0` answers at once | `30` | | `catch_up_seconds` | After a restart, the last unanswered message of each private chat is answered once if it is at most this old; `0` disables the catch-up | `7200` | | `ai_retries` | How many times a failed AI request is retried, waiting 2 s, 4 s, …; HTTP 4xx other than 429 is never retried; `0` disables retrying | `2` | | `web_port` | TCP port of the [web control panel](../integrations/web-control-panel.md) on `0.0.0.0`, no authentication; valid range 1–65535 | unset — panel disabled | Mapping and defaults: `Config::from_file` (`src/config.rs:284-306`) and the constants at `src/config.rs:34-43`. `reply_delay_seconds`, `catch_up_seconds` and `ai_retries` are unsigned integers in the file (`Option`, `src/config.rs:121-127`), so a negative value is rejected as an invalid value type. `web_port` is `Option` (`src/config.rs:128-129`), so negatives and values above 65535 fail TOML parsing, and `resolve_web_port` (`src/config.rs:254-263`) additionally rejects `0`. Their use is described in [AI autoreply](../workflows/ai-autoreply.md), [AI client](../integrations/ai-client.md) and [Web control panel](../integrations/web-control-panel.md). Unknown keys are ignored. In particular the keys of older versions are no longer read: `system_prompt` (move the prompt to `SOUL.md`) and `cooldown_seconds` / `group_cooldown_seconds` (replaced by `reply_delay_seconds`); a leftover value is silently ignored, not an error. `config.toml.global.template` in the repository root lists every key with its default and a comment (with `web_port` commented out) and points to `SOUL.md` for the prompt; copy it to the data directory as `config.toml` and fill in `openai_api_key`. ## Prompt files: `SOUL.md` and `vision.md` `read_prompt_file` (`src/config.rs:212-223`) reads both files: - the content is kept **as written** (not trimmed, multiline, Markdown allowed); only a leading UTF-8 BOM is removed; - a missing file or one that is blank (only whitespace) yields `None`; - an unreadable or non-UTF-8 file is an error naming the path (`cannot read prompt file `), which aborts startup. `SOUL.md` becomes `Config::system_prompt`. The binary embeds no prompt text: without `SOUL.md` (or with a blank one) the AI is not configured, every autoreply path stops and the status line says `AI non configurata: imposta openai_api_key in config.toml e il prompt in SOUL.md` (see [Application state](../architecture/app-state.md)). `soul.md.template` in the repository root is a minimal starting point. The app appends only factual context to the prompt (chat kind and name, who writes the `user` and `assistant` messages, see [AI autoreply](../workflows/ai-autoreply.md)); behaviour — including when to call the `no_reply` tool and stay silent — belongs in `SOUL.md`. `vision.md` becomes `Config::image_description_prompt`; when it is missing or blank the [AI client](../integrations/ai-client.md) uses its built-in Italian description request. ## Loading rules for `config.toml` - Missing file → all defaults (`read_config_file`, `src/config.rs:196-210`). - Unreadable file → `cannot read config file `. - Invalid TOML or wrong value type → `invalid config file ` with the cause `invalid TOML syntax or value type at line L, column C`; the error never echoes file content (`parse_config_file`, `src/config.rs:181-194`). This also covers negative or non-integer `reply_delay_seconds`, `catch_up_seconds` and `ai_retries`, a non-boolean `ai_log` and an out-of-range `web_port`. - A leading UTF-8 BOM (common with Windows editors) and CRLF line endings are tolerated. - `openai_base_url` consisting only of slashes is rejected with `openai_base_url in config.toml must not be empty or only slashes` (`src/config.rs:241-252`). - `web_port = 0` is rejected with `web_port in config.toml must be between 1 and 65535`. Startup aborts with `cannot load configuration` on any of these errors ([Runtime](../architecture/runtime-and-event-loop.md)). ## Build version `wh --version` (or `-V`) prints `wh ()` and exits without reading any file; the same text is shown at the bottom right of the main screen. The commit hash is embedded at build time by `build.rs` and is `unknown` when built without git (see [Runtime and event loop](../architecture/runtime-and-event-loop.md)). ## Security and operational notes - The API key is never printed: `Config`'s `Debug` shows `***` or `` (`src/config.rs:132-158`), and the raw `FileConfig` has no `Debug` impl at all (`src/config.rs:107-130`). The AI client redacts it too. - `ai_requests.log` never contains the API key and replaces image data with a size placeholder, but it does store the full request (system prompt and chat context) and the reply in clear text, one record per attempt including retries, and it is only appended to, never rotated. Enable `ai_log` only for diagnosis and delete the file afterwards. - `web_port` exposes an unauthenticated HTTP panel on every network interface: anyone who can reach the port can see chat names, toggle autoreplies and quit the bot. Set it only on a trusted network or firewall the port. A port already in use aborts startup with `cannot start web server on port `. - Keep `config.toml` and `SOUL.md` in the data directory, outside the repository. `.gitignore` ignores a root-level `/config.toml`, `.env`, any `SOUL.md` and `*.log` files (`.gitignore:4-18`), as well as SQLite journal/WAL/SHM files; note that the `*.db` pattern is commented out (`.gitignore:7`), so databases copied into the repo would not be ignored. - `session.db` holds the linked-device credentials: deleting it forces a new QR pairing (copying it to another machine reuses the linked session). `history.db` can be deleted to reset local history, image descriptions and the per-chat and global autoreply flags; it is recreated on start. - Images downloaded for description are kept in memory only and never written to the data directory; only their text description is stored in `history.db`. ## Tests `src/config.rs:342+` covers defaults, full files, the ignored legacy `system_prompt` key, blank values, trimming, base URL normalisation and errors, `Debug` masking, vision model, DB and AI log paths, `ai_log`, `reply_delay_seconds` (default, custom values including `0`, negatives rejected, legacy cooldown keys ignored), catch-up and `ai_retries` defaults and negatives, `web_port` (unset, valid, `0` and out-of-range rejected), unknown keys, BOM/CRLF, non-leaking parse errors, missing, existing, invalid and unreadable files, and `SOUL.md` reading (missing, blank, kept as written without BOM, non-UTF-8 error naming the path). See [Unit tests](../testing/unit-tests.md).