Skip to content

Managing Operational Settings

Eric Fitzgerald edited this page May 22, 2026 · 1 revision

Managing Operational Settings

Operational settings are stored in the system_settings database table, seeded from built-in registry defaults on first run, and fully manageable at runtime through the admin API. You do not need to restart the server to change them (with the exception of a small number of static settings — see Hot vs. Static Settings below).

This page covers the runtime workflow: reading and writing settings through the admin API, understanding what takes effect immediately, reconfiguring the Timmy AI subsystem and content sources without a restart, migrating YAML config into the database, and rotating encryption keys.

Out of scope: Bootstrap settings (database connection, JWT secret, server port, TLS, logging, etc.) are resolved from config files and environment variables at startup and require a restart to change. They are never stored in system_settings. See Configuration-Model for the full bootstrap/operational split, and Bootstrapping-Production for bootstrap setup.


The Admin Settings API

All endpoints require the administrator role. Send a Bearer JWT in the Authorization header.

Method Endpoint Purpose
GET /admin/settings List all settings with their effective values and source
GET /admin/settings/{key} Get one setting and its effective source
PUT /admin/settings/{key} Create or update a setting in the database
DELETE /admin/settings/{key} Delete a setting from the database
POST /admin/settings/reencrypt Re-encrypt all stored secrets under the current key

The source field

Every GET response includes a source field that tells you where the effective value comes from:

Source Meaning
database Value was written to system_settings via the API or dbtool
config Value came from a config file at startup (unusual for operational settings)
environment Value came from a TMI_* environment variable

TMI applies a still-live environment > config > database precedence to operational settings, not just to bootstrap settings. For a fully operational setting, you should see database after initial seeding. If you see config or environment, a config-file or environment-variable value for that same operational key is overriding the database row — its read value comes from env/config rather than the DB.

When a key is config- or environment-controlled, it cannot be edited through the admin API. PUT /admin/settings/{key} returns 409 Conflict with a message like Setting '<key>' is controlled by <source> and cannot be modified via the API. To make the key editable again, remove the overriding environment variable or config-file entry for that key; once it falls back to the database, the DB value becomes editable via the API.

Secret masking

Settings that are marked as secrets (API keys, client secrets, etc.) are masked in all API responses. The response body will contain a redacted placeholder, not the live value. Use POST /admin/settings/reencrypt to rotate keys, not GET to retrieve them.

Empty-string values are rejected

A PUT request with an empty string value for a string setting is rejected with a 400 error. An empty string would be stored as a NULL CLOB on Oracle, violating the column's NOT NULL constraint. If you need to effectively disable a setting, delete it (DELETE /admin/settings/{key}) to fall back to the registry default, or set it to a meaningful sentinel value.

Examples

List all settings:

curl -s http://localhost:8080/admin/settings \
  -H "Authorization: Bearer $TOKEN" | jq .

Read one setting:

curl -s http://localhost:8080/admin/settings/timmy.enabled \
  -H "Authorization: Bearer $TOKEN" | jq .

Update a setting:

curl -s -X PUT http://localhost:8080/admin/settings/timmy.enabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "true"}'

Delete a setting (reverts to registry default on next read):

curl -s -X DELETE http://localhost:8080/admin/settings/timmy.llm_provider \
  -H "Authorization: Bearer $TOKEN"

Hot vs. Static Settings

Operational settings have a mutability property:

Mutability Behavior after PUT /admin/settings/{key}
hot The new value is picked up at the next use. No restart required.
static The database row is updated immediately, but the running server keeps the value it read at boot until you restart.

When you update a static setting, TMI writes the new value to the database and returns success — but the change does not take effect in the running process. Restart the server after editing a static setting to activate it.

See Configuration-Reference for the mutability column of each setting.


Runtime Reconfiguration Without a Restart

Most operational settings are hot. This section covers the subsystems that benefit most from live reconfiguration.

Timmy AI subsystem

The entire Timmy AI assistant is DB-backed and hot. Every aspect of Timmy's configuration — whether it is on or off, which model and provider it uses, its API keys, and its tuning knobs — can be changed through the admin API with no restart.

Toggling Timmy on or off:

# Disable
curl -s -X PUT http://localhost:8080/admin/settings/timmy.enabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "false"}'

# Re-enable
curl -s -X PUT http://localhost:8080/admin/settings/timmy.enabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "true"}'

Switching model or provider:

curl -s -X PUT http://localhost:8080/admin/settings/timmy.llm_provider \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "openai"}'

curl -s -X PUT http://localhost:8080/admin/settings/timmy.llm_model \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "gpt-4o"}'

When you change the provider, model, API key, or base URL, Timmy rebuilds its internal client on the next incoming request — no restart, no dropped connections.

Rotating a Timmy API key:

curl -s -X PUT http://localhost:8080/admin/settings/timmy.llm_api_key \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "sk-new-key-value"}'

The key is encrypted at rest. After writing, call POST /admin/settings/reencrypt if you are also rotating the encryption key (see Rotating the Encryption Key).

Adjusting tuning knobs (top-k, timeouts, rate limits, history depth, embedding dimension) works the same way — each knob is a separate key under timmy.*. Tuning changes are read live per request; there is no rebuild step.

Content sources

The content-source registry and access poller are also DB-backed and runtime-tunable. You can enable or disable individual content sources and toggle the access poller without restarting the server. Use GET /admin/settings to identify the relevant content.* keys, then PUT /admin/settings/{key} to change them.


Migrating Config from YAML to the Database

If you are upgrading from a pre-1.4.0 deployment whose operational settings were in a YAML config file, use tmi-dbtool to import those settings into the database. After migration, manage them at runtime via the admin API.

Build the tool

make build-dbtool          # PostgreSQL
make build-dbtool-oci      # Oracle ADB

Import a legacy config file (--import-legacy)

Use this when your existing YAML file contains operational settings mixed in with bootstrap settings (the pre-1.4.0 layout).

1. Preview what will happen:

tmi-dbtool --import-legacy \
  --input-file config-production.yml \
  --config config-production.yml \
  --dry-run

The dry-run output shows which settings would be written to system_settings and which are bootstrap-only (and will stay in the file).

2. Run the import:

tmi-dbtool --import-legacy \
  --input-file config-production.yml \
  --config config-production.yml

By default the tool:

  • Writes a timestamped backup of the source file before modifying it.
  • Rewrites the source file in place, leaving only bootstrap settings.
  • Writes operational settings to the database; existing DB rows for those keys are skipped (not overwritten) to protect any runtime edits you have already made via the API.

Preserve the original file unchanged (write a sibling instead):

tmi-dbtool --import-legacy \
  --input-file config-production.yml \
  --config config-production.yml \
  --no-rewrite

This writes config-production-migrated.yml alongside the original and leaves config-production.yml untouched. The two flags --no-rewrite and --output are mutually exclusive.

Skip the automatic backup:

tmi-dbtool --import-legacy \
  --input-file config-production.yml \
  --config config-production.yml \
  --no-backup

Only use this if you are managing your own backup strategy.

Import a clean config file (--import-config)

Use this to bulk-load settings from any YAML file into the database — for example, to seed a new environment from a reference config or to restore settings from a snapshot.

# Dry run first
tmi-dbtool --import-config \
  --input-file settings-snapshot.yml \
  --config config-production.yml \
  --dry-run

# Write to DB (skip existing rows by default)
tmi-dbtool --import-config \
  --input-file settings-snapshot.yml \
  --config config-production.yml \
  --output settings-snapshot-migrated.yml

Overwrite behavior: by default, a key that already exists in system_settings is left unchanged. This protects any runtime edits made via the admin API. Pass --overwrite to replace existing values with the file values:

tmi-dbtool --import-config \
  --input-file settings-snapshot.yml \
  --config config-production.yml \
  --overwrite

Use --overwrite deliberately — it will discard any changes operators have made since the last config file snapshot.

Encryption during import

If your bootstrap config has a secrets.* provider configured, tmi-dbtool initializes the encryptor automatically during import. Settings marked as secrets are encrypted at rest when written to system_settings. No extra flags are needed; the encryptor picks up its configuration from the file passed to --config.

Common flags

Flag Purpose
-c, --import-config Import a config file into the database
-l, --import-legacy Import a pre-1.4.0 legacy config file
-f, --input-file FILE Source YAML file
--config FILE Bootstrap config file providing the DB connection (database.url)
--output FILE Write the post-migration config to this path (with -c or -l)
--dry-run Show what would happen without writing anything
--overwrite Replace existing DB rows (with -c); default is to skip them
--no-backup Skip the timestamped backup of the source file (with -l)
--no-rewrite Write a sibling *-migrated.yml and leave the source unchanged (with -l)
-v, --verbose Verbose output

Rotating the Encryption Key

Secret operational settings (API keys, OAuth client secrets, etc.) are encrypted at rest in system_settings. When you rotate the encryption key in your secrets provider, re-encrypt the stored values with the new key:

curl -s -X POST http://localhost:8080/admin/settings/reencrypt \
  -H "Authorization: Bearer $TOKEN"

This re-reads every secret setting, decrypts it with the old key, and re-encrypts it under the current key (as configured in the running server's secrets.* bootstrap config). The operation is atomic per setting. If it fails partway through, repeat the call — settings that were already re-encrypted under the new key will be skipped or safely overwritten.

Update your secrets provider with the new key before calling reencrypt, not after.


See Also

Home

Releases


Getting Started

Deployment

Operation

Troubleshooting

Development

Integrations

Tools

API Reference

Reference

Clone this wiki locally