Per-user configuration¶
Per-user data lives in three DB tables and (optionally) the user's Nextcloud workspace:
- DB tables (authoritative)
user_profiles— display_name, timezone, channels, worker overrides, email lists, trusted senders, disabled_skills, disabled_modules, delivery routing (default_destination+ a purpose-keyedroutingtable)user_resources— resources (calendar, folder, todo_file, notes_folder, email_folder, reminders_file). TheextrasJSON column carries resource-type-specific config.briefing_configs— briefing schedules.enabled=0mutes a briefing without deletion.secrets— Fernet-encrypted credentials (Karakeep, Monarch, Tumblr, Overland ingest token, ntfy, etc.). See credentials for the full per-user inventory.[users.alice]block in mainconfig/config.toml(the docker entrypoint path) — DB rows still win at config-load time.- User workspace files in Nextcloud (
PERSONA.md,BRIEFINGS.md,CRON.md,HEARTBEAT.md,TASKS.md,USER.md).
The legacy
config/users/{user_id}.tomlfile (and its.user.jsonoverlay) was retired with the OIDC retirement / Phase 7 sweep. Theconfig/users/directory is gone, Ansible no longer renders per-user TOML, andConfig.users_dir/load_user_configs()no longer exist.
The DB rows are populated four ways:
- Ansible:
istota user|resource|briefing|secret ensure …— each idempotent and printsSTATE: created|updated|noopforchanged_whensemantics. - Web UI:
/istota/settings(Profile + Connected services + module pages) and the per-feature settings under/istota/{feeds,money,location}/settings. - Auto-seed: on first OAuth login the profile row is created from the Nextcloud display_name and any
[users.X]block. Subsequent logins do not overwrite values the user has edited. - TOML migration: on scheduler startup,
import_from_user_configs(one each for profiles / resources / briefings) seeds DB rows from any remaining[users.X]block whose natural key isn't already present.
Per-user TOML settings¶
# main config.toml: [users.alice]
display_name = "Alice"
email_addresses = ["alice@example.com", "alice.work@company.com"]
timezone = "America/New_York"
# Per-user worker limits (0 = use global default)
max_foreground_workers = 2
max_background_workers = 1
# Skills to exclude for this user
disabled_skills = ["markets"]
# Verbose tool logging to a dedicated Talk room
log_channel = "room456"
# Talk room for confirmations and security alerts
alerts_channel = "room789"
# Trusted email senders (bypass confirmation gate, supports fnmatch patterns)
trusted_email_senders = ["*@company.com", "boss@other.com"]
# Static website at /~alice/
site_enabled = true
# Modules to opt out of (default-on otherwise)
disabled_modules = ["money"]
# Default delivery surface for results/notifications when nothing else applies
default_destination = "talk" # talk | email | ntfy | web | surface:channel | comma list
# Where replies to inbound email threads are delivered
email_reply_routing = "origin+thread" # origin+thread (default) | origin | thread
# Purpose-keyed routing table — overrides default_destination per purpose.
# Purposes: reply, alert, log, briefing, notification
[users.alice.routing]
alert = "ntfy" # heartbeat + security alerts go to ntfy
log = "web:<room-token>" # verbose execution log streamed to a web chat room
ntfy push notifications are not a profile field. They live in the encrypted
secretstable — provision via the web UI (/istota/settings→ Connected services → ntfy push) oristota secret ensure --user alice --service ntfy --key topic --value ….
Resources¶
Resources define what data the bot can access for this user. Provision via Ansible (istota resource ensure --user alice --type calendar --path /Personal --name Personal) or the web UI (/istota/settings). The [[users.X.resources]] TOML block is also accepted as a docker-entrypoint shortcut.
[[users.alice.resources]]
type = "reminders_file"
path = "/Users/alice/shared/Notes/_REMINDERS.md"
name = "Reminders"
[[users.alice.resources]]
type = "calendar"
path = "Personal"
name = "Personal"
Resource types: calendar, folder, todo_file, email_folder, shared_file, reminders_file, notes_folder.
Modules vs resources vs connected services. The retired
feeds/money/monarch/karakeep/overlandresource types were split apart in the modules / connected services refactor: - Modules (feeds,money,location) are on by default; opt out per user viadisabled_modules. Module-owned secrets (Tumblr API key, Monarch session, Overland ingest token) live on the per-module settings page. - Connected services (karakeep,google_workspace) are external API credentials in the encryptedsecretstable. - The scheduler auto-cleans the obsolete resource types fromuser_resourceson startup; their TOML extras are migrated intosecretsviasecrets_store.import_from_user_configs.
CalDAV calendars are auto-discovered from Nextcloud and don't need to be configured as resources.
Briefings¶
Provision via istota briefing ensure --user alice --name morning --cron "0 6 * * *" --conversation-token room123 --output both --component calendar=true --component todos=true or the web UI (/istota/settings → Briefings). The [[users.X.briefings]] TOML block is a docker-entrypoint shortcut.
[[users.alice.briefings]]
name = "morning"
cron = "0 6 * * *"
conversation_token = "room123"
output = "both"
[users.alice.briefings.components]
calendar = true
todos = true
markets = true
news = true
See briefings for details.
Delivery routing¶
Each user has a default delivery surface (default_destination, defaults to talk) plus an optional purpose-keyed routing table that overrides it per purpose. The purposes are reply, alert, log, briefing, and notification; each maps to an output_target descriptor (talk, email, ntfy, web, surface:channel, or a comma list). Routing notifications by purpose (e.g. alert = "ntfy") is what reroutes heartbeat and security alerts off Talk; the log purpose drives the verbose per-task execution log to any user-routable surface (it supersedes the legacy log_channel shorthand). web is a routable delivery surface — alerts, the execution log, and notifications routed to it land in a web chat room as system messages.
Provision via the CLI:
istota user ensure -u alice \
--default-destination email \
--route alert=ntfy \
--route log=web:<room-token>
--route is repeatable and validates the purpose against the allowed set. The web Preferences card surfaces default_destination, the alert route, and the log route; CLI-set routes for the other purposes are preserved on round-trip.
User workspace files¶
These files live in the user's Nextcloud folder at /Users/{user_id}/{bot_dir}/config/ and can be edited through the Nextcloud web UI:
| File | Purpose | See |
|---|---|---|
USER.md |
Persistent memory (auto-loaded into prompts) | Memory |
TASKS.md |
File-based task queue with status markers | Scheduling |
PERSONA.md |
Personality customization (overrides global) | Persona |
BRIEFINGS.md |
Briefing schedule (overrides TOML config) | Briefings |
CRON.md |
Scheduled jobs (markdown + TOML) | Scheduling |
HEARTBEAT.md |
Health monitoring checks | Heartbeat |
TASKS.md format¶
# Tasks
- [ ] Send email to john about the meeting tomorrow
- [~] Checking calendar for tomorrow's schedule...
- [x] 2025-01-26 12:34 | Summarized report | Result: Summary saved to exports/
- [!] 2025-01-26 12:35 | Failed task | Error: timeout (attempt 2/3)
Status markers: [ ] pending, [~] in progress, [x] completed, [!] failed.
Admin vs non-admin¶
Admin users are listed in /etc/istota/admins. Empty file = all users are admin.
Non-admin restrictions:
- Scoped mount path (
/Users/{user_id}only) - No DB access (no
ISTOTA_DB_PATH, no sqlite3 tool) - No subtask creation
admin_onlyskills filtered out (e.g., tasks, schedules)