Config Enhancer Semantic Guardrails
Date: 2026-07-23 Scope: services/agent Status: implemented locally; not deployed
Date: 2026-07-23 Scope:
services/agentStatus: implemented locally; not deployed
Incident
Online session 84664120166982656 used chatbot
69ff0f7c7eef888daa8369d8 and config hash
8b59ff1880c515850498c5e5abb4e0f5.
The cached enhanced config exposed three independent problems:
- The generated
instructionsstring contained a serialized copy of sibling fields such astimers,constraints, andskills. - The source sequence “after 20 minutes, then after another 10 minutes” became
two independent idle timers with delays
1200and600. Idle timers share the latest-user-activity baseline, so the closing message would run before the first follow-up. - Skill enhancement rewrote runtime instructions as well as routing descriptions. Several configured conditions were shortened or lost, and a concrete repeated-charge complaint was routed to a generic loan-consultation skill.
The final answer also treated its self-reported evidence value as sufficient
support. A general instruction was used to infer a specific merchant
relationship and charge cause that were not established by the available
instructions, skill, Knowledge Base material, or tool result.
Production Scope
A six-hour online log sample contained:
| Measure | Count |
|---|---|
| Prompt contexts | 812 |
| Chatbots | 20 |
| Config hashes | 23 |
| Sessions | 319 |
| Embedded sibling-JSON occurrences | 115 |
All 115 occurrences belonged to chatbot
69ff0f7c7eef888daa8369d8 and config hash
8b59ff1880c515850498c5e5abb4e0f5. Every observed prompt for that config was
affected; the other 22 sampled config hashes had no matching occurrence. This
was therefore deterministic for one persisted enhanced config, not a
platform-wide prompt-logging behavior.
Enhanced Config Comparison
The cached config was inspected through:
https://omni-flux.ycloud.com/api/v1/config/69ff0f7c7eef888daa8369d8
| Area | Raw configuration | Enhanced configuration | Assessment |
|---|---|---|---|
| Main instructions | 2,968 characters | 4,809 characters | The clean 2,868-character prefix equals the raw text after removing the 100-character timeout section; the remaining tail is unrelated sibling JSON |
| Timeout sequence | Follow up after 20 minutes; close after another 10 minutes | Independent delays 1200 and 600 |
Semantic inversion; runtime would close first |
| Consultation-fee policy | Broad rule that all products include a consultation fee | Preserved unchanged | The risky policy originated in user configuration, not enhancement |
| Constraints | Present in the SOP | Copied to top-level constraints and retained in the SOP | Matches the current copy-only contract |
| Skills | Seven raw skills with detailed execution rules | Seven skills with rewritten descriptions and instructions | Membership and tools remained, but several execution details were shortened or changed |
Skill Semantic Review
| Skill | Enhancement result | Risk |
|---|---|---|
| Own bank account / IBAN | Core rule retained; post-thanks continuation and several explicit prohibitions removed | Medium |
| Amount / borrowing intent | Amount range retained; boundary with generic consultation remained weak and explicit forbidden-output details were reduced | Medium |
| Website / application entry | Conditional button/card capability became unconditional | High |
| Loan rejection | Core behavior retained | Low |
| Proxy application | Core behavior retained | Low |
| Page/upload failure | “Website or contact information” condition was narrowed to “link” | Medium |
| Generic loan consultation | Repayment option and useful routing distinctions were removed; selected for a concrete repeated-charge complaint | High |
The agreed platform boundary is not to rewrite these customer-owned business rules. Skills are outside enhancement and pass through exactly as configured.
Case Root Cause
The observed answer was produced by a combined failure chain:
- The user supplied transaction notifications and later reported that the charge had happened three times.
- The skill router selected the generic loan-consultation skill with confidence
0.85, despite the latest message describing a concrete payment complaint. - The customer-authored SOP contained a broad consultation-fee rule.
- No trusted source established the specific merchant relationship or charge cause.
- The model marked
evidence=instruction, but the value was self-reported and did not perform claim-level validation. - The first final envelope was invalid. During envelope retry, the model added a new fee explanation instead of limiting the retry to JSON repair.
The embedded sibling JSON was a separate config-integrity defect. It made the prompt confusing and oversized, but it was not the direct source of the fee claim.
Agreed Scope
- Keep the existing full-config JSON response contract for
ConfigEnhancer. - Assign retained SOP text to
instructionsand keep every sibling output in its declared top-level field. - Model
idleandfixedtimer baselines explicitly. Chained relative waits sharing a mode and baseline are cumulative; independent actions keep their source delays. - Exclude skills from enhancement input and output; preserve raw skills exactly.
- Strengthen answer grounding so an evidence label cannot authorize a claim and a broad policy cannot establish a specific entity, event, transaction, or causal relationship.
- Preserve supported answer meaning during final-envelope repair; a JSON retry must not introduce new business facts.
- Remove the redundant
original_configcache field. Build the performance evaluation'soriginalAgentConfigview frombasic_settings.instructionand top-level skills when requested. - Bump
CONFIG_CACHE_VERSIONtoconfig_enhance_v2, causing old L1/L2 entries to miss and be enhanced again.
Non-Goals
- No customer-specific SOP or skill rewrite.
- No incident-specific production matching branch.
- No extraction-plan/source-span architecture.
- No change to timer runtime scheduling.
Prompt Design
Production wording uses three general semantic invariants:
- Field ownership: each generated value belongs to its declared schema field.
- Time semantics: timer mode determines the baseline, and chained relative waits on the same baseline are cumulative.
- Claim grounding: trusted support entails the complete business claim.
The production prompts contain no chatbot, merchant, fee, language, or incident-specific examples. Positive statements define the target result; negative wording remains limited to existing security and transport boundaries where omission would weaken the contract.
Implementation
Config parser
The parser prompt must state:
instructionscontains the retained SOP text.- Sibling fields appear exactly once at the top level.
- The response ends after the single top-level JSON object.
idlecounts from the latest user activity and resets on new activity.fixedcounts from initial session timer registration and retains its schedule across user activity.- A chained “another N” delay is cumulative only when actions share the same mode and baseline.
Skills are omitted from parser input and response schema. Runtime assembly keeps the raw skill array without LLM rewriting or parser post-processing.
Main-agent grounding
Before emitting a business fact, the model must check that a trusted source
entails the complete claim: subject, relationship, condition, scope, and, when
present, cause. User text and the evidence enum do not add authority.
When final JSON parsing requires a retry, the retry instruction requires the model to retain already-supported meaning and fix the envelope without adding facts.
Cache invalidation
CONFIG_CACHE_VERSION = "config_enhance_v2" invalidates incompatible L1 and L2
results through the existing version-miss path. Re-enhancement remains lazy and
coalesced per (tenant_id, chatbot_id, config_hash) inside each process.
Redundant config removal
original_config.instructions duplicated basic_settings.instruction, while
original_config.skills duplicated top-level runtime skills after skill
enhancement was removed. Agent storage and config/observability APIs therefore
omit original_config. The performance API derives originalAgentConfig from
those existing fields so evaluation context remains complete without duplicate
Mongo storage.
Existing Mongo documents remain readable because cache deserialization selects
declared fields. Agent database initialization automatically applies a batched,
restartable migration that removes the legacy field with $unset; it does not
write empty or null values. This storage-only cleanup does not bump the cache
version or trigger re-enhancement.
Database migrations use one document in the versions collection:
{"version": N}. A missing document means version 0. Code runs ordered,
idempotent migrations above the stored version and advances the value only
after each migration succeeds. MongoDB owns the document _id; migration code
does not assign or depend on it. Every Mongo-backed Agent instance checks the
version before service initialization. Migration failure is logged without
advancing the version or blocking service startup; the next restart retries
from the last completed version.
Acceptance Criteria
- Config parser prompt assigns every output to its declared field.
- Config parser prompt defines both timer modes and same-baseline cumulative delays.
- Skills are absent from parser input/output and remain identical to raw config.
- Prompt grounding states that
evidenceis a reporting label, not permission. - Final-envelope retry cannot add new business claims.
- Runtime config and observability contain no persisted
original_config; performance derives its source-config view from existing fields. - Agent startup automatically runs pending migrations in bounded batches.
- A failed migration does not advance the version or block startup, and the next restart retries it.
- A current database version does not scan the business collection.
- Old cache versions miss and successful enhancement persists
config_enhance_v2. - Focused prompt, config service, and workflow-agent tests pass.
Implemented Changes
| File | Change |
|---|---|
services/agent/api/services/config/enhancer.py |
Added instructions field boundaries, mode-aware cumulative timer semantics, and raw skill passthrough |
services/agent/bu_agent_sdk/prompts/templates.py |
Added claim-entailment grounding, made evidence non-authorizing, and prohibited new facts during final-envelope retry |
services/agent/api/services/config/service.py |
Bumped cache version to config_enhance_v2 |
| Agent config model, repository, and query services | Removed redundant original-config persistence and response fields |
| Agent/omni-base performance contract | Derives source instruction and skills without persisted duplication |
| Agent database infrastructure | Added startup-driven, ordered, versioned, restartable batched Mongo migrations |
docs/agent/configs/config-enhance.md |
Updated the authoritative config-enhance contract |
| Focused test files | Added prompt, schema, passthrough, grounding, retry, and cache-version regression coverage |
Verification
Executed:
cd services/agent
PYTHONPATH=. uv run pytest \
tests/prompts/test_config_enhancer.py \
tests/prompts/test_prompt_builder.py \
tests/runtime/test_workflow_agent.py \
tests/config/test_config_service.py \
tests/api/internal/test_observability_api.py \
tests/api/internal/test_performance_api.py \
tests/api/v1/test_session_api.py \
tests/session/test_session_management.py \
tests/services/database/test_migrations.py \
tests/test_container_database_startup.py -q
Result: 398 passed in 3.91s.
omni-base performance contract:
cd services/omni-base
uv run pytest \
tests/unit/test_agent_runtime_performance_client.py \
tests/unit/test_ai_agent_performance_signals.py -q
Result: 51 passed in 0.45s.
Repository quick verification also passed with the project Python:
PATH="$PWD/.venv/bin:$PATH" bash scripts/verify.sh
The unqualified system python3 is 3.9.6 and cannot compile the project's
Python 3.11+ match syntax.
Local startup verification:
- First startup applied migration v1, removed five local legacy fields, wrote
database version 1, and returned HTTP 200 from
/api/v1/health. - Second startup read database version 1 without applying the migration and
returned HTTP 200 from
/api/v1/health. - Both locally started Agent processes shut down cleanly after verification.
Real-provider config enhancement and production deployment were not performed as part of this local bugfix.
Evidence
- Session log:
services/agent/automation_test/case/online/0723/session-84664120166982656.log - Sampled production logs showed the embedded sibling JSON on every observed prompt for this config hash and not on the other sampled config hashes.
- Runtime timer registration starts all configured timers independently;
reset_idle()restarts onlyidletimers and leavesfixedtimers on their original plan inservices/agent/api/services/timer/manager.py.