Skip to content

Dynamic Agent Config Developer Guide ​

What Changed and Why ​

Previously, all agent configuration (presets, voices, languages, features) lived in a static shared/agent_config.json file. Every change required editing the file and redeploying. This migration moves everything into MongoDB, making config dynamic, org-scoped, versioned, and manageable via API.

Before vs After ​

AspectBeforeAfter
Config storageshared/agent_config.json (static file)MongoDB SystemConfig collection
VoicesEmbedded in each preset (duplicated)Separate Voice collection, referenced by ID
Agent fieldsCopied provider fields from preset on createStores only preset_id + optional language, resolved at dispatch time
Config changesEdit JSON → redeployAPI call → instant, versioned
Version historyNoneFull snapshots with diff and rollback
Customer visibilityAll fields exposedProvider fields stripped for customer role
PermissionsNoneOrg-level edit_system_config + role check
Multi-languageOne provider stack per presetPer-language LLM/TTS/STT combinations (max 20) with canonical language keys
Provider catalogNone (free-text)ProviderModel_Registry all providers/models must be registered first, with language key→value mappings
Language handlingRaw BCP-47 codes (inconsistent across providers)Canonical language keys as source of truth, resolved to provider-specific codes at dispatch time

How Config is Managed Step by Step ​

1. Default Config (Superadmin-Managed) ​

There is a single platform-wide default config stored with orgUID = "__default__". It is:

  • Auto-seeded from agent_config.json on the first request from any org without its own config
  • Managed by superadmin via the System Config page in the superadmin dashboard
  • Used by all orgs that don't have edit_system_config enabled

2. Org-Level Config (Admin-Managed) ​

When an org has edit_system_config enabled in their plan features:

  • The org admin sees a Config Management page in their dashboard
  • Initially shows "Using platform default" with a "Customize Config" button
  • Clicking "Customize" forks (copies) the default config into an org-specific config
  • The org can then CRUD their own presets independently
  • They can "Revert to Default" at any time to delete their custom config

3. Config Resolution Flow ​

Request for org config
    │
    ├── Org has own SystemConfig? → Use it
    │
    └── No → Use __default__ SystemConfig
              └── __default__ doesn't exist? → Auto-seed from agent_config.json

4. After Seed JSON File is Ignored ​

Once the __default__ SystemConfig exists in MongoDB, the JSON file is never read again. All changes happen through the UI or API.


Architecture Flow ​

┌─────────────────────────────────────────────────────────┐
│                    Frontend (oktivo_app)                  │
│                                                          │
│  useAgentConfig hook ──→ GET /agent-config               │
│  configService       ──→ /api/v2/config/* (admin CRUD)   │
└──────────────────────────┬───────────────────────────────┘
                           │
┌──────────────────────────▼───────────────────────────────┐
│                  Node.js Backend                          │
│                                                          │
│  config.controller.ts                                    │
│    └─ getAgentConfig()                                   │
│       ├─ configService.getConfigForOrg(orgUID)           │
│       │   └─ auto-seeds from JSON if no config exists    │
│       ├─ presetResolver.resolveAllForDisplay(orgUID)     │
│       │   └─ populates voice details from Voice collection│
│       └─ sanitizeConfigResponse middleware                │
│           └─ strips provider fields for customer role     │
│                                                          │
│  agent.controller.ts                                     │
│    ├─ createAgent: validates preset_id via configService  │
│    ├─ updateAgent: validates new preset_id if changed     │
│    └─ testAgent/testAgentWeb: uses presetResolver         │
│       └─ resolveForDispatch() builds agent_config         │
│          with provider fields + system_prompt + metadata  │
│                                                          │
│  systemConfig.controller.ts (NEW)                        │
│    └─ Admin CRUD for presets, voices, versions            │
│       └─ Permission-gated: edit_system_config + role      │
└──────────────────────────┬───────────────────────────────┘
                           │
┌──────────────────────────▼───────────────────────────────┐
│                     MongoDB                               │
│                                                          │
│  ProviderModel_Registry ─ Catalog of LLM/TTS/STT models   │
│  SystemConfig  ─ One per org, presets + language_combos    │
│  Voice         ─ Deduplicated, requires tts_provider+model │
│  ConfigVersion ─ Immutable snapshots for audit/rollback   │
│  Agent         ─ Stores preset_id + language (dispatch key)│
└──────────────────────────────────────────────────────────┘
                           │
                           ▼
┌──────────────────────────────────────────────────────────┐
│              Python Agent Server (LiveKit)                 │
│                                                          │
│  Receives fully resolved agent_config in dispatch metadata│
│  No changes needed extra="allow" on MetadataSchema      │
│  New fields: system_prompt, metadata (passthrough)        │
│  Voice behavior: backchannels, exit_phrases_agent,        │
│    exit_phrases_user, pronunciation_dict (top-level +     │
│    inside agent_config)                                   │
└──────────────────────────────────────────────────────────┘

File Changes Summary ​

New Files Created ​

FilePurpose
src/models/Voice.tsVoice MongoDB model deduplicated voice entries (requires tts_provider + tts_model)
src/models/SystemConfig.tsSystemConfig model org-scoped config with presets + language_combinations
src/models/ConfigVersion.tsConfigVersion model immutable version snapshots
src/models/ProviderModelRegistry.tsProvider/model catalog with language key-value mappings and multilingual flag
src/utils/preset.resolver.tsPresetResolver resolves preset_id + language to full dispatch config
src/utils/preset.utils.tsUtility functions incl. deriveSupportedLanguages
src/services/config.service.tsConfigService all config CRUD, versioning, auto-seed
src/services/providerModelRegistry.service.tsRegistry CRUD with deletion guards
src/services/languageCombination.validator.tsValidates language_combinations arrays
src/services/cascadeConsistency.service.tsDetects consistency issues on voice/registry changes
src/data/language-keys.tsCanonical language key definitions (Hindi, English, Telugu, etc.) with validation
src/middleware/sanitize.middleware.tsStrips provider fields for customer role
src/middleware/configPermission.middleware.tsPermission gate for config mutations
src/controllers/systemConfig.controller.tsAdmin CRUD API handlers
src/routes/systemConfig.routes.tsAdmin CRUD routes at /api/v2/config
src/routes/providerModelRegistry.routes.tsProvider Registry CRUD routes (superadmin only)
src/scripts/migrate-language-combinations.tsMigration: seeds registry, adds language_combinations, populates Voice.tts_model
src/scripts/migrate-config-to-mongodb.tsOne-time migration script (original JSON → MongoDB)

Modified Files ​

FileWhat Changed
src/models/Agent.tsRemoved 7 provider fields, made preset_id required
src/types/index.tsUpdated IAgent interface to match
src/controllers/config.controller.tsReads from MongoDB instead of JSON, uses PresetResolver
src/controllers/agent.controller.tsRemoved static JSON loading, uses ConfigService + PresetResolver for create/update/dispatch. Test call dispatch uses shared dispatch.utils.ts helpers.
src/utils/preset.utils.tsReads preset IDs from MongoDB instead of JSON
src/controllers/admin.controller.tsUpdated validatePresetIds calls (now async with orgUID)
src/controllers/superadmin.controller.tsUpdated validatePresetIds calls (now async with orgUID)
src/routes/config.routes.tsAdded sanitizeConfigResponse middleware
src/app.tsMounted systemConfigRoutes at /api/v2/config
oktivo_app/src/hooks/useAgentConfig.tsProvider fields optional, new response parsing, language_key voice matching
oktivo_app/src/services/config.service.tsAdded admin CRUD methods, updated response type
oktivo_app/src/pages/superadmin/ProviderRegistryPanel.tsxLanguage key-value pair UI, multilingual LLM toggle
oktivo_app/src/pages/superadmin/LanguageCombinationEditor.tsxSimplified to language key + voice only, inherits providers from preset, multilingual LLM exclusion
oktivo_app/src/components/features/SystemConfigPanel.tsxLanguage key labels, preset update impact guard, voice language_key dropdown
oktivo_app/src/components/features/LanguageSelector.tsxLanguage key labels with flag emojis
oktivo_app/src/lib/languageKeys.ts(NEW) Client-side language key definitions and helpers

Follow-up Fix: Voice uid vs Provider voice_id ​

Problem: The original Voice model enforced unique: true directly on voice_id. Since voice_id was also the TTS provider's own voice identifier, admins could not add the same provider voice for a second language or model (some providers, e.g. Azure/Google, reuse one voice identifier across locales).

Fix: Introduced a system-generated uid field as the sole internal reference key, decoupled from the provider's voice_id.

FileWhat Changed
src/models/Voice.tsAdded required unique uid field. Removed unique: true from voice_id. Added compound unique index (voice_id, tts_provider, tts_model, language_key) so the same provider voice_id can be reused across different languages/models, while still preventing exact duplicates.
src/services/config.service.tscreateVoice generates uid, validates the compound key instead of raw voice_id. updateVoice/deleteVoice now key off uid. autoPopulateCombinationVoices and the JSON auto-seed path (seedDefaultFromJson) build/store uid references in presets.
src/utils/preset.resolver.tsresolveAllForDisplay/resolveForDisplay/populateVoices now look up voices by uid. Added resolveVoiceUidToProviderId() resolves a preset's stored voice uid to the provider voice_id at dispatch time (resolveForDispatch).
src/utils/dispatch.utils.tsAdded resolveAgentVoiceForDispatch(ttsVoice) resolves an agent's tts_voice (a uid) to the provider voice_id before building the dispatch payload. buildAgentConfig accepts an optional pre-resolved resolvedAgentVoice.
src/controllers/agent.controller.ts, src/controllers/landing.controller.ts, src/services/health.service.ts, src/services/calling.service.ts, src/services/crmApi.service.tsAll dispatch call sites (test calls, web calls, campaign dispatch, CRM-triggered calls, landing page demo calls) now call resolveAgentVoiceForDispatch() before sending tts_voice to the Agent Server.
src/services/cascadeConsistency.service.ts, src/services/languageCombination.validator.tsVoice consistency checks now look up voices by uid (with legacy voice_id fallback).
src/controllers/systemConfig.controller.tsVoice CRUD routes (:voiceId param), upload-sample, and stream-sample handlers now treat :voiceId as the voice uid.
src/middleware/sanitize.middleware.tsVoice sanitization now also strips tts_model (previously missed) and preserves uid, which customers need to reference a voice.
src/scripts/migrate-voice-uid.ts(NEW) One-time, idempotent migration: backfills uid on existing Voice documents, rewrites SystemConfig preset voice references and Agent.tts_voice from old voice_id → new uid, and drops the old unique index on voice_id. Runnable standalone (npx tsx src/scripts/migrate-voice-uid.ts) or via API.
src/routes/script.routes.tsAdded POST /internal/scripts/migrate-voice-uid (protected by verifyWebhookSecret / x-api-secret), following the same pattern as other internal migration endpoints.
src/scripts/migrate-config-to-mongodb.ts, src/scripts/migrate-voice-language-keys.ts, src/controllers/language-combination-migration.controller.tsUpdated pre-existing migration scripts to remain correct under the new schema: generate/preserve uid, look up voices by uid (with voice_id fallback), and target updates by _id instead of the now-non-unique voice_id.
oktivo_app/src/hooks/useAgentConfig.ts, .../components/features/VoiceGrid.tsx, .../components/features/SystemConfigPanel.tsx, .../pages/superadmin/LanguageCombinationEditor.tsx, .../pages/customer/AgentEditorPage.tsx, .../components/features/AgentFormDialog.tsx, .../components/shared/DataTable.tsx, .../services/config.service.tsVoice selection, matching, and CRUD across the customer voice picker and superadmin System Config / Language Combination editors now use uid as the selection/reference key. The "Add Voice" dialog's Voice ID field is now labeled "Provider Voice ID" and is a required, freely reusable field (no longer auto-generated/unique).

Migrating existing data: Run the migration once per environment before relying on the new behavior:

bash
# Standalone
npx tsx src/scripts/migrate-voice-uid.ts

# Or via API (idempotent safe to re-run)
curl -X POST -H "x-api-secret: <SUPERADMIN_API_SECRET>" \
  http://localhost:3000/internal/scripts/migrate-voice-uid

This is safe to skip on brand-new deployments (no existing Voice documents), since createVoice always generates a uid going forward.


Key Concepts ​

Preset-by-Reference ​

Agents store only preset_id and an optional language field. When you fetch an agent or dispatch a call, PresetResolver looks up the preset from SystemConfig and resolves the full provider/model fields. This means:

  • Updating a preset instantly affects all agents using it
  • No need to update individual agent documents when preset config changes
  • The language field controls which language_combination is used at dispatch time

Multi-Language Provider System ​

Each preset can define up to 20 language_combinations per-language LLM/TTS/STT provider/model/voice configurations. At dispatch time:

  1. Agent has preset_id + language (e.g., "hindi")
  2. PresetResolver.resolveForDispatch(orgUID, presetId, language) looks up the preset
  3. If a language_combination matches (case-insensitive on the key), its providers are used
  4. For TTS: if the row has a non-empty tts_language override, that key is used for TTS resolution; otherwise language_code is used
  5. For STT: if the row has a non-empty stt_language override, that key is used for STT resolution; otherwise language_code is used
  6. Each language key is resolved to provider-specific codes via Provider Registry language_mappings
  7. If no match, flat fallback fields are used (backward-compatible)
  8. Output is always a flat IDispatchConfig Agent Server sees no difference

Workflow:

Provider Registry (key→value mappings) → Preset (language_combinations with keys) → Voice (language_key) → Agent (selects key) → Dispatch (resolves to provider codes)

Key rules:

  • Providers and models must be registered in ProviderModel_Registry first. The preset editor uses registry-driven dropdowns no free-text entry.
  • Language combinations in presets use canonical language keys (e.g., "hindi") not raw codes.
  • Available languages for a preset = intersection of TTS languages ∩ STT languages ∩ LLM languages. If the selected LLM is marked is_multilingual, it's excluded from this intersection (most LLMs handle all languages natively).
  • Each language combination requires an accent (voice). A preset cannot be saved without a voice for each language.
  • Each language combination can optionally override TTS and/or STT language resolution via tts_language and stt_language fields. When set, they decouple TTS/STT language from the row's primary language_code.

Auto-derivation: When a preset is saved with language_combinations, the supported_languages array is automatically derived (union of all language_code values). This feeds the customer-facing language dropdown.

Preset Update Guard: When editing a preset that would remove languages used by existing agents, the system warns the superadmin with an impact assessment (affected agent count, removed languages) and suggests creating a new preset instead. The update can still proceed with explicit confirmation.

Provider Model Registry ​

A catalog of all available LLM/TTS/STT provider-model combinations:

  • Superadmin manages via /superadmin/provider-registry UI
  • Each entry defines: provider_type, provider_name, model_name, display_name, language_mappings, is_multilingual, is_active
  • Language Key-Value Mappings: Instead of raw BCP-47 codes, each provider entry maps canonical language keys (e.g., "hindi", "english") to provider-specific codes (e.g., "hi", "hi-IN"). This solves the problem where different TTS/STT providers use different codes for the same language.
  • Multi Language flag (LLM only): Most LLMs support all languages natively. When is_multilingual: true, the entry doesn't need language mappings and is excluded from the language intersection calculation (presets only consider TTS ∩ STT for available languages).
  • Preset editor dropdowns are populated exclusively from active registry entries
  • Deletion is blocked if any preset references the entry
  • Deactivation blocks new references but existing presets continue to resolve

Language Key System ​

The language key is the single source of truth for language identification across the entire platform:

LayerUses Language Key
Provider RegistryMaps key → provider-specific value (e.g., "hindi" → "hi" for Deepgram, "hi-IN" for ElevenLabs)
System Config Presetslanguage_combinations[].language_code stores the key
Voice collectionlanguage_key field identifies which language the voice belongs to
Customer DashboardLanguage dropdown shows human-readable labels derived from keys
Dispatch (Agent Server)PresetResolver resolves key → actual provider code at call time

Predefined keys are in src/data/language-keys.ts. Custom keys can be added via the "Other" option in the UI.

Dispatch-Time Language Resolution ​

When a call is dispatched with a language key (e.g., "hindi"):

  1. PresetResolver finds the matching language_combination in the preset
  2. Determines TTS language key: uses tts_language override if set, otherwise falls back to language_code
  3. Determines STT language key: uses stt_language override if set, otherwise falls back to language_code
  4. Looks up the TTS registry entry for that provider+model, finds language_mappings entry matching the TTS key → gets provider-specific code
  5. Does the same for STT with the STT key
  6. If no mapping is found for a key, falls back to the raw key itself
  7. Passes the resolved provider-specific codes to the Agent Server

This means the same preset+language works correctly across providers that use different codes, and TTS/STT can be independently overridden to different languages than the row's primary language.

Voice Separation ​

Voices are stored in a separate Voice collection. Presets, agents, and all internal references use a system-generated uid (e.g., vce_abc123...) not the raw provider voice_id. This distinction exists because TTS providers often reuse the same voice_id across different languages or models (e.g., Azure/Google voices that share an identifier but differ by locale). Enforcing uniqueness directly on voice_id made it impossible to add such variants.

FieldMeaningUniqueness
uidSystem-generated internal identifier. Used in preset.supported_voices, preset.default_voice, language_combinations[].default_voice/supported_voices, and Agent.tts_voice.Globally unique (unique: true index)
voice_idThe TTS provider's own voice identifier (UUID, name, etc.), passed to the TTS SDK at dispatch time.Unique only per (voice_id, tts_provider, tts_model, language_key) compound index the same voice_id can be reused across different languages/models/providers

Key rules:

  • Same voice used across multiple presets without duplication
  • Deleting a voice (by uid) cascades removes it from all presets' supported_voices, default_voice, and language_combinations
  • Each voice has a language_key field (canonical key) alongside the raw language field (provider-specific code)
  • Voice matching uses language_key (primary) with fallback to language for backward compatibility
  • Dispatch-time resolution: PresetResolver.resolveForDispatch() and resolveAgentVoiceForDispatch() (in dispatch.utils.ts) resolve a voice uid → provider voice_id right before building the dispatch payload. The Python Agent Server only ever receives the resolved provider voice_id as tts_voice it has no awareness of uid.
  • All Voice CRUD API routes (/api/v2/config/voices/:voiceId, .../upload-sample, .../sample) take the voice uid as the :voiceId route param, not the provider voice_id.
  • Legacy data written before this distinction existed is handled by fallback lookups (uid first, then voice_id) in the resolver helpers, and normalized permanently by the migrate-voice-uid one-time migration script (see Migration Scripts section below).

System Prompt Fallback ​

When dispatching, the system prompt is resolved with this priority:

  1. Preset's system_prompt (if non-empty)
  2. SystemConfig's default_system_prompt (if non-empty)
  3. Empty string

Frontend Sanitization ​

The sanitizeConfigResponse middleware intercepts res.json() and strips internal fields for customer role users.

Stripped from presets: llm_provider, llm_model, tts_provider, tts_model, stt_provider, stt_model, realtime_model, system_prompt, metadata

Stripped from voice objects inside supported_voices: _id, __v, createdAt, updatedAt, orgUID, tts_provider, tts_model

Customers only see: preset name, description, cost, show flag, tts_speed, supported languages, default voice/language, and voice details (uid, voice_id, name, sample_url, description, language, gender). The uid field is required by the customer dashboard to select/persist a voice on an agent it is not stripped.

Admin/owner/superadmin see everything.

Important for new developers: Any new endpoint that returns preset data to customers MUST apply sanitizeConfigResponse middleware. See the Developer Guide section 8 for details.

Versioning ​

Every config mutation (create/update/delete preset) automatically:

  1. Increments the version counter on SystemConfig
  2. Creates a ConfigVersion snapshot with the full config state
  3. Records who made the change and a description

You can view history, diff between versions, and rollback to any previous version.


API Endpoints ​

Existing (Modified) ​

GET /agent-config  Returns config from MongoDB (auto-seeds if needed)
                     Sanitized for customer role

New Admin CRUD (all require JWT + edit_system_config permission) ​

GET    /api/v2/config/presets                     List all presets
POST   /api/v2/config/presets                     Create preset
PUT    /api/v2/config/presets/:presetId            Update preset
DELETE /api/v2/config/presets/:presetId            Delete preset (body: { replacement_preset_id })

GET    /api/v2/config/voices                      List voices (optional filters)
POST   /api/v2/config/voices                      Create voice (body: { voice_id, tts_provider, tts_model, language_key, ... })
                                                      voice_id is the provider's own identifier the same voice_id
                                                      can be reused across different providers/models/languages.
                                                      A system-generated `uid` is always returned; use it for all
                                                      subsequent references (preset supported_voices, agent tts_voice, etc.)
PUT    /api/v2/config/voices/:voiceId              Update voice (:voiceId param is the voice's `uid`, not provider voice_id)
DELETE /api/v2/config/voices/:voiceId              Delete voice by uid (cascades to presets)
POST   /api/v2/config/voices/:voiceId/upload-sample Upload audio sample (:voiceId param is the voice's `uid`)
GET    /api/v2/config/voices/:voiceId/sample       Stream/redirect audio sample (:voiceId param is the voice's `uid`)

GET    /api/v2/config/versions                    Version history (paginated)
GET    /api/v2/config/versions/diff?from=X&to=Y   Diff between versions
POST   /api/v2/config/versions/:versionNumber/rollback Rollback to version

Provider Registry API (superadmin only) ​

GET    /api/config/provider-registry              List entries (filter: provider_type, provider_name, language, page, limit)
GET    /api/config/provider-registry/language-keys Get predefined language key definitions (for dropdown population)
POST   /api/config/provider-registry              Create entry (requires unique type+provider+model, with language_mappings or is_multilingual)
GET    /api/config/provider-registry/:id          Get single entry
PUT    /api/config/provider-registry/:id          Update entry (merge semantics)
DELETE /api/config/provider-registry/:id          Delete entry (blocked if referenced by presets)

How to Verify Step by Step ​

Pre-requisite: Start both servers ​

  1. Start the backend: cd OP_oktivo_backend/client_backend && npm run dev
  2. Start the frontend: cd oktivo_app && npm run dev
  3. Watch the backend console on the first API call from any org, you should see:
    ⚙️ No SystemConfig found for org <orgUID> auto-seeding from agent_config.json
    ✅ Seeded X voices
    ✅ Auto-seeded SystemConfig for org <orgUID> (N presets, v1)

Test 1: Superadmin Grant config edit permission to an org ​

  1. Login as superadmin
  2. Go to Organization Management page
  3. Click the ⋮ menu on any org → Edit
  4. In the Features checkbox grid, you'll see "edit system config" check it
  5. Click Update
  6. The org now has the edit_system_config permission

Test 2: Login as customer verify presets load ​

  1. Login as a customer user (belonging to the org from Test 1)
  2. Navigate to Agents page (/agents)
  3. Verify: The page loads without errors. Existing agents show their preset name
  4. Open browser DevTools → Network tab → find the agent-config request
  5. Verify response shape: { success: true, data: { presets: [...], features, languages, default_cost_per_min }, allowedPresets }
  6. Verify: For customer role, presets should NOT contain llm_provider, llm_model, tts_provider, tts_model, stt_provider, stt_model, realtime_model

Test 3: Login as admin verify full data visible ​

  1. Login as an admin user
  2. Navigate to Agents page
  3. Open DevTools → Network → check the agent-config response
  4. Verify: Presets SHOULD contain all provider fields (llm_provider, tts_provider, etc.)
  5. Compare with the customer response from Test 2 customer should be missing those fields

Test 4: Create a new agent ​

  1. Login as a customer
  2. Go to Agents → New Agent → pick a template → Agent Editor
  3. Step 1 (Agent Setup): Fill in agent name, prompt, first message
  4. Step 2 (Voice & Settings):
    • Verify: Voice Plan selector shows all visible presets (cards with name, description)
    • Select a preset → language dropdown updates to that preset's supported languages
    • Select a language → voice grid updates to show voices for that language
    • Each voice card shows: name, gender icon, description, audio sample player
    • Select a voice → adjust TTS speed slider
  5. Step 3 (Analysis): Pick an analysis schema
  6. Click Create Agent
  7. Verify: Agent created successfully (toast notification)

Test 5: Edit an agent change preset ​

  1. Go to Agents → click edit on an existing agent
  2. In Step 2, change the Voice Plan to a different preset
  3. Verify: Language dropdown resets to the new preset's default language
  4. Verify: Voice grid updates to show the new preset's voices
  5. Verify: If your previously selected voice isn't in the new preset, it auto-selects the default voice
  6. Save the agent → verify success

Test 6: Make a test call ​

  1. Go to Agents → click the test call button on any agent
  2. Enter a phone number → click Call
  3. Verify: Call dispatches successfully
  4. This confirms presetResolver.resolveForDispatch() works resolves provider/model fields at dispatch time

Test 7: Make a web test call ​

  1. Go to Agents → click the web test button (if available)
  2. Verify: Web call connects and you can talk to the agent

Test 8: Verify preset changes propagate instantly (via API) ​

  1. Using curl or Postman (as admin with edit_system_config):
    bash
    curl -X PUT -H "Authorization: Bearer <admin-jwt>" -H "Content-Type: application/json" \
      http://localhost:3000/api/v2/config/presets/<preset_id> \
      -d '{"description":"Updated via API"}'
  2. Refresh the Agent Editor page in the browser
  3. Verify: The preset card now shows "Updated via API" as the description
  4. No agent documents were modified the change propagated at read time

Test 9: Verify version history (via API) ​

After the update in Test 8:

bash
curl -H "Authorization: Bearer <admin-jwt>" http://localhost:3000/api/v2/config/versions

Verify: Shows version 2 with the update description and version 1 from the auto-seed.


Troubleshooting ​

IssueCauseFix
404: No configuration found and seed file unavailableagent_config.json not found and no SystemConfig in DBEnsure shared/agent_config.json exists, or run the migration script
503: Service temporarily unavailableMongoDB connection issueCheck MongoDB connectivity
403: Organization does not have permissionOrg missing edit_system_config in plan.featuresSuperadmin → Org Management → Edit org → check "edit system config"
403: Insufficient role for config managementUser is not admin/owner/superadminUse an admin account
400: Preset not found in organization configurationInvalid preset_id on agent create/updateCheck the org's SystemConfig for valid preset_ids
Agent test call fails with missing provider fieldsAgent was created before migrationRun the migration script to clean up agent documents
Frontend shows empty presetsResponse parsing mismatchClear browser cache, verify backend returns new response shape
409: A voice with this ID already exists when adding a voice for a different language/model than an existing entryEnvironment hasn't run migrate-voice-uid yet, so voice_id still has the old unique: true index at the DB level even though the app code no longer expects itRun npx tsx src/scripts/migrate-voice-uid.ts (or POST /internal/scripts/migrate-voice-uid) to backfill uid and drop the stale unique index on voice_id
Voice selection in agent editor shows "voice not found" or resets to preset default unexpectedlyFrontend/backend version mismatch one side still expects voice_id as the selection key instead of uidEnsure both app/ and backend/client_backend/ are on the version that uses uid for voice references; clear browser cache
Dispatched call uses the wrong/garbled voice with the TTS providertts_voice sent to the Agent Server is a uid instead of the resolved provider voice_id (a dispatch call site is missing resolveAgentVoiceForDispatch)Confirm the dispatch call site resolves agentDoc.tts_voice via resolveAgentVoiceForDispatch() before calling buildAgentConfig()

FAQ ​

Q: Do I need to run the migration script? A: Not for new deployments. The auto-seed handles it. Only run the migration script if you have existing agents with old denormalized provider fields that need cleanup.

Q: Can I still edit agent_config.json? A: You can, but it only affects the initial seed. Once a SystemConfig exists in MongoDB, the JSON file is ignored. Edit config through the API instead.

Q: What happens if I delete agent_config.json? A: Nothing, as long as SystemConfig already exists in MongoDB. If you delete it before any org has been seeded, new orgs will get a 404 until you either restore the file or create a SystemConfig manually.

Q: How do I add a new preset? A: POST /api/v2/config/presets with the preset data. Requires admin role + edit_system_config permission.

Q: How do I rollback a bad config change? A: POST /api/v2/config/versions/:versionNumber/rollback. Every mutation creates a version snapshot, so you can always go back.

Q: Can I reuse the same provider voice_id for a different language or model now? A: Yes. voice_id (the TTS provider's own identifier) is no longer required to be globally unique. Uniqueness is only enforced per (voice_id, tts_provider, tts_model, language_key) combination so the same voice_id can appear multiple times as long as the provider, model, or language differs. Every Voice document also gets a system-generated uid, which is what's actually referenced by presets and agents; you never need to manage it manually.

Q: I'm on an environment created before the voice uid change do I need to do anything? A: Run the one-time migration once: npx tsx src/scripts/migrate-voice-uid.ts or POST /internal/scripts/migrate-voice-uid (with x-api-secret). It backfills uid on existing voices, updates preset and agent references, and drops the old unique index on voice_id. It's idempotent safe to re-run or run on an already-migrated environment (it will just report zero changes).

Last updated: