configuration-and-data.md 13 KB


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, how src/config_watch.rs reloads them live every 2 seconds, 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 release profile, and operational and security caveats. tags: [configuration, toml, data-directory, sqlite, security, soul-md, live-reload] sources:

  • id: openwiki-source-ea70eb6c04 resource: repo://.gitignore
  • id: openwiki-source-651d1fb6c9 resource: repo://Cargo.toml
  • id: openwiki-source-0d5b861992 resource: repo://config.toml.global.template
  • id: openwiki-source-1b05507d70 resource: repo://src/ai.rs
  • id: openwiki-source-9fec9d1af4 resource: repo://src/app.rs
  • id: openwiki-source-d6b2055bbb resource: repo://src/config_watch.rs
  • id: openwiki-source-2a737474d8 resource: repo://src/config.rs
  • id: openwiki-source-b55a21a31e resource: repo://src/main.rs generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" } verified:
  • by: openwiki/0.7.2 at: 2026-10-23T21:26:24.156Z ---

Configuration and data files

All runtime settings come from files in the data directory, loaded 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). The files are loaded at startup and reloaded automatically when they change (see Live reload); only web_port needs a restart.

Data directory

The data directory is fixed: <platform config dir>/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) session_db_path, src/config.rs:325-331
history.db Local messages, chats, contacts, per-chat autoreply flags, the global autoreply setting and image descriptions (History store) history_db_path, src/config.rs:333-339
ai_requests.log Optional AI request log, written only when ai_log = true (AI client) ai_log_path, src/config.rs:341-347

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 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 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<u32>, src/config.rs:121-127), so a negative value is rejected as an invalid value type. web_port is Option<u16> (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, AI client and Web control panel.

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 <path>), 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). 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); 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 uses its built-in Italian description request. The vision model receives only this request and the image, never the conversation, so tone and reply style belong in SOUL.md. The file was previously named vision.md: on Linux, where names are case-sensitive, an old lowercase file is no longer read and must be renamed.

Loading rules for config.toml

  • Missing file → all defaults (read_config_file, src/config.rs:196-210).
  • Unreadable file → cannot read config file <path>.
  • Invalid TOML or wrong value type → invalid config file <path> 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); during a live reload the same errors only keep the current configuration.

Live reload

main starts config_watch::spawn(config.source_files(), app_tx). Config::source_files (src/config.rs:317-323) lists config.toml, SOUL.md and VISION.md inside the data directory. The watcher (src/config_watch.rs:15-52) polls their modification times every 2 seconds (POLL_INTERVAL_SECONDS) instead of using file notifications, so it needs no extra dependency and behaves the same on Windows and Linux. A file whose metadata cannot be read counts as None, so creating or deleting a file is also a change. On any change it runs Config::load again and sends AppEvent::ConfigReloaded with the new Config or the error text; the task ends when the application receiver is dropped.

App then:

  • on success replaces its Config, rebuilds the AI client (replies already in flight keep the old one) and shows Configurazione ricaricata, or AI non configurata: ... / AI non disponibile: ... when the new files leave the AI unusable;
  • on error keeps the current configuration and shows Configurazione non ricaricata: <error>.

New values of the AI keys, SOUL.md, VISION.md, ai_log, reply_delay_seconds, catch_up_seconds and ai_retries therefore apply to the next replies without restarting. web_port does not: the web panel is bound once at startup. Saving a file in several steps can trigger more than one reload; each one reloads the full set of files.

Build version and profile

wh --version (or -V) prints wh <version> (<short git commit>) 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).

The release profile only strips symbols (Cargo.toml:31-33); LTO and a single codegen unit are no longer used, which keeps release rebuilds fast. TLS for the AI client uses ring only: reqwest is built with rustls-no-provider and the ring provider is installed when the AI client is built, so the large aws-lc-sys C build is not needed, which matters for the Linux musl cross-compile with cargo-zigbuild described in README.md.

Security and operational notes

  • The API key is never printed: Config's Debug shows *** or <unset> (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, quit the bot, unlink the WhatsApp device and, while pairing, scan the QR code. 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 <port>.
  • Keep config.toml, SOUL.md and VISION.md in the data directory, outside the repository; the two prompt files are personal. .gitignore ignores a root-level /config.toml, .env, any SOUL.md, any VISION.md and *.log files (.gitignore:4-19), 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. Copying it to another machine reuses the linked session, but never run both copies at the same time. To pair again without restarting, press F8 twice or use the web panel's "Nuovo QR": the device is logged out and session.db is deleted or emptied in place (see WhatsApp relink and pairing). 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:350+ 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). src/config_watch.rs checks that modification-time snapshots change when a watched file is created and deleted. See Unit tests.