АРЧ АП11 - Добавить builder смешанных AGENT-прогонов и починить meta-memory recall в turnaround 11

This commit is contained in:
dctouch 2026-04-17 12:54:47 +03:00
parent f7edf6aacb
commit 9f0f7f3e79
22 changed files with 5765 additions and 65 deletions

View File

@ -0,0 +1,342 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase7_meta_domain_mix",
"domain": "address_phase7_meta_domain_mix",
"title": "Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"description": "Mixed AGENT replay for turnaround 11. The pack interleaves counterparty documents, inventory root and selected-object continuity, meta-space interruptions, memory recap, receivables to inventory same-date pivot, and an account 60 tail check.",
"bindings": {},
"steps": [
{
"step_id": "step_01_smalltalk",
"title": "Casual chat stays human and non-technical",
"question": "привет, как дела?",
"required_answer_patterns_any": [
"(?i)привет|дела|помочь|норм"
],
"forbidden_answer_patterns": [
"(?i)tool_gate_reason",
"(?i)address_mode",
"(?i)hard_meta_mode",
"(?i)living_reason"
],
"criticality": "info",
"semantic_tags": [
"meta_smalltalk"
],
"notes": "[mixed_pack_slot=slot_01_smalltalk source=address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk]"
},
{
"step_id": "step_01_counterparty_documents",
"title": "Counterparty documents use the normalized legal name",
"question": "покажи все документы по чепурнову",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"list_documents_by_counterparty"
],
"required_direct_answer_patterns_any": [
"(?i)контрагент:",
"(?i)чепурнов"
],
"criticality": "critical",
"semantic_tags": [
"counterparty_documents"
],
"notes": "[mixed_pack_slot=slot_03_counterparty_documents source=address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents]"
},
{
"step_id": "step_02_counterparty_shipments_or_fallback",
"title": "Supplier shipment question stays human even when exact supply rows are absent",
"question": "что нам отгружал чепурнов, какой товар или услугу?",
"allowed_reply_types": [
"factual",
"partial_coverage"
],
"expected_intents": [
"list_documents_by_counterparty"
],
"required_direct_answer_patterns_any": [
"(?i)чепурнов",
"(?i)постав",
"(?i)оплат|возврат|товар|услуг"
],
"forbidden_direct_answer_patterns": [
"(?i)^сейчас не дам прямой адресный ответ",
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб"
],
"criticality": "critical",
"semantic_tags": [
"counterparty_shipment_fallback"
],
"notes": "[mixed_pack_slot=slot_04_counterparty_shipment_fallback source=address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback]"
},
{
"step_id": "step_01_inventory_march_2021",
"title": "Inventory root snapshot at March 2021",
"criticality": "critical",
"question": "какие остатки на складе на март 2021",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"inventory_on_hand_as_of_date"
],
"required_filters": {
"as_of_date": "2021-03-31",
"period_from": "2021-03-01",
"period_to": "2021-03-31"
},
"required_direct_answer_patterns_any": [
"31\\.03\\.2021",
"(?i)на складе",
"(?i)столешница 600\\*3050\\*26 альмандин"
],
"semantic_tags": [
"inventory_root"
],
"notes": "[mixed_pack_slot=slot_05_inventory_root source=address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021]"
},
{
"step_id": "step_02_selected_item_supplier",
"title": "Selected-object supplier provenance",
"criticality": "critical",
"question": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"inventory_purchase_provenance_for_item"
],
"required_direct_answer_patterns_any": [
"(?i)столешница 600\\*3050\\*26 альмандин",
"(?i)поставщик|поставил|куплен",
"(?i)союз|торговый дом"
],
"forbidden_direct_answer_patterns": [
"(?i)^на 31\\.03\\.2021 на складе",
"(?i)^сейчас не дам прямой адресный ответ"
],
"semantic_tags": [
"selected_object",
"selected_object_supplier"
],
"notes": "[mixed_pack_slot=slot_06_selected_object_supplier source=address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier]"
},
{
"step_id": "step_05_capability_meta_interrupt",
"title": "Capability meta question does not break the address context",
"question": "что ты умеешь?",
"required_answer_patterns_any": [
"(?i)могу|умею",
"(?i)остатк|документ|контрагент|ндс"
],
"forbidden_answer_patterns": [
"(?i)tool_gate_reason",
"(?i)address_mode"
],
"criticality": "warning",
"semantic_tags": [
"meta_capability"
],
"notes": "[mixed_pack_slot=slot_07_meta_capability source=address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt]"
},
{
"step_id": "step_03_selected_item_documents",
"title": "Selected-object documents stay in the same contour",
"criticality": "critical",
"question": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"inventory_purchase_documents_for_item"
],
"required_direct_answer_patterns_any": [
"(?i)столешница 600\\*3050\\*26 альмандин",
"(?i)документ",
"(?i)союз|торговый дом"
],
"semantic_tags": [
"selected_object",
"selected_object_documents"
],
"notes": "[mixed_pack_slot=slot_08_selected_object_documents source=address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents]"
},
{
"step_id": "step_06_memory_recap_after_interrupts",
"title": "Memory recap still remembers the selected object after meta interruptions",
"question": "а ты помнишь, что мы по этой позиции уже выяснили?",
"required_answer_patterns_any": [
"(?i)помню",
"(?i)столешница 600\\*3050\\*26 альмандин",
"(?i)позици"
],
"forbidden_answer_patterns": [
"(?i)^сейчас не дам прямой адресный ответ",
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб"
],
"criticality": "warning",
"semantic_tags": [
"meta_memory"
],
"required_answer_patterns_all": [
"(?i)помню",
"(?i)столешница 600\\*3050\\*26 альмандин"
],
"notes": "[mixed_pack_slot=slot_09_meta_memory source=address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts]"
},
{
"step_id": "step_04_inventory_same_date_restore",
"title": "Same-date restore returns to the March root snapshot",
"criticality": "critical",
"question": "покажи еще раз остатки на эту же дату",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"inventory_on_hand_as_of_date"
],
"required_filters": {
"as_of_date": "2021-03-31",
"period_from": "2021-03-01",
"period_to": "2021-03-31"
},
"required_direct_answer_patterns_any": [
"31\\.03\\.2021",
"(?i)на складе"
],
"forbidden_direct_answer_patterns": [
"(?i)^сейчас не дам прямой адресный ответ",
"(?i)transition_not_supported_by_capability"
],
"semantic_tags": [
"inventory_root",
"same_date_restore"
],
"notes": "[mixed_pack_slot=slot_10_same_date_restore source=address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore]"
},
{
"step_id": "step_02_receivables_march_2020",
"title": "Receivables at March 2020",
"question": "кто нам должен на март 2020",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"receivables_confirmed_as_of_date"
],
"expected_capability": "confirmed_receivables_as_of_date",
"expected_result_mode": "confirmed_balance",
"required_filters": {
"as_of_date": "2020-03-31",
"period_from": "2020-03-01",
"period_to": "2020-03-31"
},
"required_direct_answer_patterns_any": [
"31\\.03\\.2020",
"(?i)дебиторск"
],
"notes": "Базовый корневой финансовый вопрос должен отработать точно и задать март 2020 как carryover-якорь. [mixed_pack_slot=slot_11_receivables_root source=address_truth_harness_test2:step_02_receivables_march_2020]",
"criticality": "critical",
"semantic_tags": [
"settlements_receivables"
]
},
{
"step_id": "step_03_inventory_same_date",
"title": "Inventory on the same date",
"question": "остатки по складу на эту же дату",
"allowed_reply_types": [
"factual"
],
"expected_intents": [
"inventory_on_hand_as_of_date"
],
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
"expected_result_mode": "confirmed_balance",
"required_filters": {
"as_of_date": "{{step_02_receivables_march_2020.filters.as_of_date}}",
"period_from": "{{step_02_receivables_march_2020.filters.period_from}}",
"period_to": "{{step_02_receivables_march_2020.filters.period_to}}"
},
"required_direct_answer_patterns_any": [
"31\\.03\\.2020",
"(?i)на складе"
],
"notes": "Смена контура receivables -> inventory должна сохранить ту же дату, без дополнительного ручного уточнения. [mixed_pack_slot=slot_12_same_date_pivot source=address_truth_harness_test2:step_03_inventory_same_date]",
"criticality": "critical",
"semantic_tags": [
"inventory_root",
"same_date_pivot"
]
},
{
"step_id": "step_06_historical_capability_followup",
"title": "Historical capability follow-up stays human",
"criticality": "warning",
"question": "а исторические остатки тоже можешь?",
"required_answer_patterns_any": [
"(?i)историческ|история",
"(?i)могу|умею"
],
"forbidden_answer_patterns": [
"(?i)^сейчас не дам прямой адресный ответ",
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб",
"(?i)tool_gate_reason",
"(?i)hard_meta_mode"
],
"semantic_tags": [
"meta_historical_capability",
"inventory_root"
],
"notes": "[mixed_pack_slot=slot_13_meta_historical source=address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup]"
},
{
"step_id": "step_04_open_items_account_60",
"title": "Account 60 tails for August 2022",
"question": "хвосты покажи по счету 60 на август 2022",
"allowed_reply_types": [
"factual",
"factual_with_explanation"
],
"expected_intents": [
"open_items_by_counterparty_or_contract"
],
"required_filters": {
"account": "60",
"period_from": "2022-08-01",
"period_to": "2022-08-31",
"as_of_date": "2022-08-31"
},
"required_direct_answer_patterns_any": [
"(?i)счету 60",
"(?i)хвост"
],
"criticality": "critical",
"semantic_tags": [
"settlements_account_60"
],
"notes": "[mixed_pack_slot=slot_14_account_60 source=address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60]"
},
{
"step_id": "step_02_data_scope_meta",
"title": "Data-scope meta question stays deterministic and non-technical",
"question": "по какой компании мы сейчас работаем?",
"required_answer_patterns_any": [
"(?i)компан|организац|контур",
"(?i)работ"
],
"forbidden_answer_patterns": [
"(?i)tool_gate_reason",
"(?i)hard_meta_mode",
"(?i)living_reason"
],
"criticality": "warning",
"semantic_tags": [
"meta_scope"
],
"notes": "[mixed_pack_slot=slot_15_meta_scope source=address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta]"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,166 @@
# Agent semantic source catalog
- truth_harness_steps_total: `58`
- saved_session_questions_total: `81`
## Reusable truth-harness tags
- `counterparty_documents`: `3`
- `counterparty_shipment_fallback`: `3`
- `inventory_root`: `21`
- `meta_capability`: `3`
- `meta_historical_capability`: `4`
- `meta_memory`: `2`
- `meta_scope`: `4`
- `meta_smalltalk`: `3`
- `same_date_pivot`: `3`
- `same_date_restore`: `4`
- `selected_object`: `11`
- `selected_object_documents`: `3`
- `selected_object_sale`: `1`
- `selected_object_supplier`: `7`
- `settlements_account_60`: `2`
- `settlements_receivables`: `4`
- `vat`: `3`
## Reusable truth-harness steps
- `address_truth_harness_inventory_provenance_restore:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_inventory_provenance_restore:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_inventory_provenance_restore:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_inventory_provenance_restore:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `address_truth_harness_phase4_coverage_evidence_mix:step_03_inventory_reset_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase4_coverage_evidence_mix:step_04_selected_item_supplier_temporal_limit` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase4_coverage_evidence_mix:step_05_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase5_meta_memory_mix:step_01_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase5_meta_memory_mix:step_02_inventory_history_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase5_meta_memory_mix:step_03_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase5_meta_memory_mix:step_04_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase6_provider_axis_mix:step_03_capability_meta` | tags: meta_capability | question: что ты можешь по 1С?
- `address_truth_harness_phase6_provider_axis_mix:step_04_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase6_provider_axis_mix:step_05_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase7_acceptance_gate_mix:step_05_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase7_meta_domain_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase7_meta_domain_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase7_meta_domain_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `address_truth_harness_phase7_meta_domain_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `address_truth_harness_phase7_meta_domain_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase7_meta_domain_mix:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase7_meta_domain_mix:step_05_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase7_meta_domain_mix:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_phase7_meta_domain_mix:step_06_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase7_meta_domain_mix:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase7_meta_domain_mix:step_02_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
- `address_truth_harness_phase7_meta_domain_mix:step_03_inventory_same_date` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_phase7_meta_domain_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase7_meta_domain_mix:step_04_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_targeted_counterparty_tails:step_01_documents_by_counterparty` | tags: counterparty_documents | question: покажи все документы по чапурнову
- `address_truth_harness_targeted_counterparty_tails:step_02_counterparty_item_flow` | tags: counterparty_shipment_fallback | question: что нам отгружал чапурнов? какой товар или услугу?
- `address_truth_harness_targeted_counterparty_tails:step_03_inventory_reset` | tags: inventory_root | question: какие остатки на складе на сегодня?
- `address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_test2:step_01_chat_opening` | tags: meta_smalltalk | question: йо чо как
- `address_truth_harness_test2:step_02_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
- `address_truth_harness_test2:step_03_inventory_same_date` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_test2:step_04_selected_item_seller` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Четки Пост (84*117)": кто продавец
- `address_truth_harness_test2:step_05_selected_item_sale_trace` | tags: selected_object, selected_object_sale | question: По выбранному объекту "Четки Пост (84*117)": кому мы продали эту хуйню
- `address_truth_harness_test2:step_06_selected_item_purchase_followup` | tags: selected_object, selected_object_supplier | question: а купили у кого известно?
- `address_truth_harness_test2:step_07_inventory_july_2019` | tags: inventory_root | question: остатки на июль 2019
- `address_truth_harness_test2:step_08_inventory_september_2019` | tags: inventory_root | question: сентябрь 2019
- `address_truth_harness_test2:step_09_inventory_march_2020` | tags: inventory_root | question: март 2020
- `address_truth_harness_test2:step_10_inventory_same_date_negative_wording` | tags: inventory_root, same_date_pivot | question: остатков на складе нет на эту дату?
- `address_truth_harness_test2:step_11_vat_same_date` | tags: vat | question: ндс какой надо заплатить на эту же дату
- `address_truth_harness_test2:step_12_vat_may_2016` | tags: vat | question: а на май 2016
- `address_truth_harness_test2:step_13_receivables_same_date_after_vat_2016` | tags: settlements_receivables, vat | question: кто нам должен денег на эту дату
- `address_truth_harness_test2:step_14_receivables_today` | tags: settlements_receivables | question: а на сегодня
## Saved session questions
- `assistant_saved_session_20260416175150_gen-mo1s0m9z-ndf56a3:q01` | tags: meta_smalltalk | question: привет как дела
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q01` | tags: meta_smalltalk | question: приветик - че как там дела
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q02` | tags: meta_capability | question: расскажи что можешь интересного
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q03` | tags: inventory_root | question: кайф - что там на складе по остаткам?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q04` | tags: meta_capability, meta_historical_capability, inventory_root | question: а исторические остатки на другие даты умеешь?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q05` | tags: none | question: давай на июль 2017
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q06` | tags: none | question: март 2016
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q07` | tags: selected_object | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q08` | tags: none | question: а кому продали?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q09` | tags: none | question: у тебя написано кто контрагент: рабочая станция - это ошибка?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q10` | tags: vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q11` | tags: vat | question: а какой ндс мы должны сгрузить на март 2020?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q12` | tags: vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q13` | tags: none | question: кто у нас самый доходный клиент за все время
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q14` | tags: settlements_receivables | question: кто нам должен денег на май 2017
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q15` | tags: vat | question: а какой ндс мы должны примерно заплатить за этот период?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q16` | tags: none | question: мы должны комуто денег на сегодня?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q17` | tags: none | question: а нам?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q18` | tags: none | question: какой у нас самый доходный год
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q19` | tags: none | question: а за 2017 мы скок заработали?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q20` | tags: none | question: сколько вообще денег мы заработали за все время?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q21` | tags: meta_capability | question: ты умеешь считать дельту по договорам?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q22` | tags: counterparty_documents | question: по чепурнову покажи все доки
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q23` | tags: none | question: а по свк
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q24` | tags: inventory_root | question: а сейчас у нас есть что на складе?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q25` | tags: counterparty_shipment_fallback | question: что нам отгружать чепурнов? какой товар или услугу?
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q26` | tags: inventory_root | question: какие остатки на складе на сегодня
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q27` | tags: inventory_root | question: остатки на март 2016
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q28` | tags: meta_scope | question: это по общей базе уже нужен вывод не по чепурнову
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q29` | tags: none | question: хвосты покажи по счету 60 на август 2022
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q30` | tags: inventory_root | question: Есть ли остатки товара, которые закупались очень давно
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q31` | tags: inventory_root | question: Какие конкретно номенклатуры формируют остаток по складу на май 2020
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q01` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q02` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q03` | tags: inventory_root | question: какие остатки на складе на сегодня?
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q04` | tags: settlements_account_60 | question: хвосты по счету 60 на август 2022
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q05` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q07` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q08` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q09` | tags: inventory_root | question: какие остатки на складе на март 2016
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q10` | tags: none | question: на июль 2019
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q11` | tags: none | question: на сентябрь
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q12` | tags: none | question: а на март
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q13` | tags: meta_scope | question: это по общей базе
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q01` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q02` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q03` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q04` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q05` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q01` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q02` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q03` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q04` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q05` | tags: meta_capability | question: что ты умеешь?
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q06` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q01` | tags: meta_smalltalk | question: привет, как дела?
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q03` | tags: none | question: что ты можешь по 1С?
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q04` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q05` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q01` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q02` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q03` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q04` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q05` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q06` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q01` | tags: meta_smalltalk | question: привет, как дела?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q03` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q04` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q05` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q07` | tags: meta_capability | question: что ты умеешь?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q08` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q09` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q10` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q11` | tags: settlements_receivables | question: кто нам должен на март 2020
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q12` | tags: inventory_root | question: остатки по складу на эту же дату
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q13` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q14` | tags: none | question: хвосты покажи по счету 60 на август 2022

View File

@ -107,9 +107,14 @@ function createAssistantLivingModePolicy(deps) {
}
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
if (!hasMemoryCue || !hasDiscussionCue) {
const hasExplicitRecapPrompt = samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|напомни\s+что\s+мы|what\s+we\s+already\s+(?:discussed|figured\s+out))/iu.test(sample));
if (!hasMemoryCue || !(hasDiscussionCue || hasExplicitRecapPrompt)) {
return false;
}
if (hasExplicitRecapPrompt) {
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
shouldHandleAsAssistantCapabilityMetaQuery(sample));
}
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
hasDataRetrievalRequestSignal(sample) ||

View File

@ -27,6 +27,9 @@ function collectMessageSamples(input) {
function hasSignalAcrossSamples(samples, detector) {
return samples.some((sample) => detector(sample));
}
function hasExplicitRecapPromptSignal(samples) {
return samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test(sample));
}
function findLastGroundedInventoryAddressDebug(items) {
if (!Array.isArray(items)) {
return null;
@ -182,6 +185,7 @@ function createAssistantMemoryRecapPolicy(deps) {
const samples = collectMessageSamples(input);
const historicalCapabilitySignal = hasSignalAcrossSamples(samples, deps.hasHistoricalCapabilityFollowupSignal);
const memoryRecapSignal = hasSignalAcrossSamples(samples, deps.hasConversationMemoryRecallFollowupSignal);
const explicitRecapPromptSignal = hasExplicitRecapPromptSignal(samples);
return {
contextualHistoricalCapabilityFollowupDetected: Boolean(input.capabilityMetaQuery &&
!input.dataScopeMetaQuery &&
@ -190,10 +194,9 @@ function createAssistantMemoryRecapPolicy(deps) {
deps.isGroundedInventoryContextDebug(input.lastGroundedAddressDebug)),
contextualMemoryRecapFollowupDetected: Boolean(!input.dataScopeMetaQuery &&
!input.capabilityMetaQuery &&
!input.dataRetrievalSignal &&
!input.strongDataSignal &&
!input.aggregateBusinessAnalyticsSignal &&
memoryRecapSignal &&
(explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) &&
input.hasPriorAddressDebug)
};
}

View File

@ -304,36 +304,36 @@ function createAssistantRoutePolicy(deps) {
}
};
}
if (nonDomainQueryIndexed) {
if (contextualMemoryRecapFollowupDetected) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",
toolGateReason: "memory_recap_followup_detected",
livingMode: "chat",
livingReason: "memory_recap_followup_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "non_domain",
provider_execution: providerExecution,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
unsupported_address_intent_fallback_to_deep: false,
final_decision: {
run_address_lane: false,
tool_gate_decision: "skip_address_lane",
tool_gate_reason: "memory_recap_followup_detected",
living_mode: "chat",
living_reason: "memory_recap_followup_detected"
}
if (contextualMemoryRecapFollowupDetected) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",
toolGateReason: "memory_recap_followup_detected",
livingMode: "chat",
livingReason: "memory_recap_followup_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: nonDomainQueryIndexed ? "non_domain" : null,
provider_execution: providerExecution,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug || lastAddressAssistantDebug),
unsupported_address_intent_fallback_to_deep: false,
final_decision: {
run_address_lane: false,
tool_gate_decision: "skip_address_lane",
tool_gate_reason: "memory_recap_followup_detected",
living_mode: "chat",
living_reason: "memory_recap_followup_detected"
}
};
}
}
};
}
if (nonDomainQueryIndexed) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",

View File

@ -171,9 +171,14 @@ export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyD
}
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
if (!hasMemoryCue || !hasDiscussionCue) {
const hasExplicitRecapPrompt = samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|напомни\s+что\s+мы|what\s+we\s+already\s+(?:discussed|figured\s+out))/iu.test(sample));
if (!hasMemoryCue || !(hasDiscussionCue || hasExplicitRecapPrompt)) {
return false;
}
if (hasExplicitRecapPrompt) {
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
shouldHandleAsAssistantCapabilityMetaQuery(sample));
}
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
hasDataRetrievalRequestSignal(sample) ||

View File

@ -69,6 +69,14 @@ function hasSignalAcrossSamples(
return samples.some((sample) => detector(sample));
}
function hasExplicitRecapPromptSignal(samples: string[]): boolean {
return samples.some((sample) =>
/(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test(
sample
)
);
}
function findLastGroundedInventoryAddressDebug(items: unknown[]): Record<string, unknown> | null {
if (!Array.isArray(items)) {
return null;
@ -269,6 +277,7 @@ export function createAssistantMemoryRecapPolicy(
samples,
deps.hasConversationMemoryRecallFollowupSignal
);
const explicitRecapPromptSignal = hasExplicitRecapPromptSignal(samples);
return {
contextualHistoricalCapabilityFollowupDetected: Boolean(
input.capabilityMetaQuery &&
@ -280,10 +289,9 @@ export function createAssistantMemoryRecapPolicy(
contextualMemoryRecapFollowupDetected: Boolean(
!input.dataScopeMetaQuery &&
!input.capabilityMetaQuery &&
!input.dataRetrievalSignal &&
!input.strongDataSignal &&
!input.aggregateBusinessAnalyticsSignal &&
memoryRecapSignal &&
(explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) &&
input.hasPriorAddressDebug
)
};

View File

@ -338,36 +338,36 @@ export function createAssistantRoutePolicy(deps) {
}
};
}
if (nonDomainQueryIndexed) {
if (contextualMemoryRecapFollowupDetected) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",
toolGateReason: "memory_recap_followup_detected",
livingMode: "chat",
livingReason: "memory_recap_followup_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "non_domain",
provider_execution: providerExecution,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
unsupported_address_intent_fallback_to_deep: false,
final_decision: {
run_address_lane: false,
tool_gate_decision: "skip_address_lane",
tool_gate_reason: "memory_recap_followup_detected",
living_mode: "chat",
living_reason: "memory_recap_followup_detected"
}
if (contextualMemoryRecapFollowupDetected) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",
toolGateReason: "memory_recap_followup_detected",
livingMode: "chat",
livingReason: "memory_recap_followup_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: nonDomainQueryIndexed ? "non_domain" : null,
provider_execution: providerExecution,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug || lastAddressAssistantDebug),
unsupported_address_intent_fallback_to_deep: false,
final_decision: {
run_address_lane: false,
tool_gate_decision: "skip_address_lane",
tool_gate_reason: "memory_recap_followup_detected",
living_mode: "chat",
living_reason: "memory_recap_followup_detected"
}
};
}
}
};
}
if (nonDomainQueryIndexed) {
return {
runAddressLane: false,
toolGateDecision: "skip_address_lane",

View File

@ -34,6 +34,14 @@ function buildPolicy() {
}
describe("assistantLivingModePolicy", () => {
it("detects explicit recap wording as memory signal even when selected-object words are present", () => {
const policy = buildPolicy();
expect(
policy.hasConversationMemoryRecallFollowupSignal("а ты помнишь, что мы по этой позиции уже выяснили?")
).toBe(true);
});
it("routes casual small-talk to chat mode", () => {
const policy = buildPolicy();

View File

@ -55,6 +55,25 @@ describe("assistantMemoryRecapPolicy", () => {
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("treats explicit recap wording over selected-object phrasing as memory follow-up even when data cues are present", () => {
const signals = policy.resolveRouteMemorySignals({
rawUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "",
repairedEffectiveAddressUserMessage: "",
dataScopeMetaQuery: false,
capabilityMetaQuery: false,
dataRetrievalSignal: true,
strongDataSignal: true,
aggregateBusinessAnalyticsSignal: false,
lastGroundedAddressDebug: null,
hasPriorAddressDebug: true
});
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(false);
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("builds deterministic recap from prior selected object context", () => {
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "memory_recap_followup_detected",

View File

@ -187,6 +187,43 @@ describe("assistantRoutePolicy", () => {
expect(decision.livingReason).toBe("memory_recap_followup_detected");
});
it("routes explicit recap wording with selected-object phrasing to chat even when address-like cues exist", () => {
const policy = buildPolicy({
hasStrongDataIntentSignal: () => true,
hasDataRetrievalRequestSignal: () => true,
resolveRouteMemorySignals: () => ({
contextualHistoricalCapabilityFollowupDetected: false,
contextualMemoryRecapFollowupDetected: true
}),
findLastGroundedAddressAnswerDebug: () => ({
execution_lane: "address_query",
detected_intent: "inventory_purchase_provenance_for_item"
})
});
const decision = policy.resolveAssistantOrchestrationDecision({
rawUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
effectiveAddressUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
}
},
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateReason).toBe("memory_recap_followup_detected");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("memory_recap_followup_detected");
});
it("does not force unsupported-intent fallback when predecompose runtime is unavailable", () => {
const policy = buildPolicy({
hasStrongDataIntentSignal: () => true,

View File

@ -1,4 +1,88 @@
[
{
"generation_id": "gen-ag04170941-87680e",
"created_at": "2026-04-17T09:41:32+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"count": 14,
"domain": "address_phase7_meta_domain_mix",
"questions": [
"привет, как дела?",
"по какой компании мы сейчас работаем?",
"покажи все документы по чепурнову",
"что нам отгружал чепурнов, какой товар или услугу?",
"какие остатки на складе на март 2021",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"что ты умеешь?",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"а ты помнишь, что мы по этой позиции уже выяснили?",
"покажи еще раз остатки на эту же дату",
"кто нам должен на март 2020",
"остатки по складу на эту же дату",
"а исторические остатки тоже можешь?",
"хвосты покажи по счету 60 на август 2022"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260417094132_gen-ag04170941-87680e.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260417094132_gen-ag04170941-87680e.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
}
},
{
"generation_id": "gen-ag04170931-6bb7e5",
"created_at": "2026-04-17T09:31:44+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"count": 14,
"domain": "address_phase7_meta_domain_mix",
"questions": [
"привет, как дела?",
"по какой компании мы сейчас работаем?",
"покажи все документы по чепурнову",
"что нам отгружал чепурнов, какой товар или услугу?",
"какие остатки на складе на март 2021",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"что ты умеешь?",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"а ты помнишь, что мы по этой позиции уже выяснили?",
"покажи еще раз остатки на эту же дату",
"кто нам должен на март 2020",
"остатки по складу на эту же дату",
"а исторические остатки тоже можешь?",
"хвосты покажи по счету 60 на август 2022"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260417093144_gen-ag04170931-6bb7e5.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
}
},
{
"generation_id": "gen-ag04170911-ff51e1",
"created_at": "2026-04-17T09:11:27+00:00",

View File

@ -0,0 +1,173 @@
{
"saved_at": "2026-04-17T09:31:44+00:00",
"generation_id": "gen-ag04170931-6bb7e5",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"agent_run": true,
"questions": [
"привет, как дела?",
"по какой компании мы сейчас работаем?",
"покажи все документы по чепурнову",
"что нам отгружал чепурнов, какой товар или услугу?",
"какие остатки на складе на март 2021",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"что ты умеешь?",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"а ты помнишь, что мы по этой позиции уже выяснили?",
"покажи еще раз остатки на эту же дату",
"кто нам должен на март 2020",
"остатки по складу на эту же дату",
"а исторические остатки тоже можешь?",
"хвосты покажи по счету 60 на август 2022"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "привет, как дела?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "по какой компании мы сейчас работаем?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "покажи все документы по чепурнову",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "что нам отгружал чепурнов, какой товар или услугу?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "какие остатки на складе на март 2021",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "что ты умеешь?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "а ты помнишь, что мы по этой позиции уже выяснили?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "покажи еще раз остатки на эту же дату",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "кто нам должен на март 2020",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "остатки по складу на эту же дату",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "а исторические остатки тоже можешь?",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "хвосты покажи по счету 60 на август 2022",
"created_at": "2026-04-17T09:31:44+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
}
}
}

View File

@ -0,0 +1,173 @@
{
"saved_at": "2026-04-17T09:41:32+00:00",
"generation_id": "gen-ag04170941-87680e",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"agent_run": true,
"questions": [
"привет, как дела?",
"по какой компании мы сейчас работаем?",
"покажи все документы по чепурнову",
"что нам отгружал чепурнов, какой товар или услугу?",
"какие остатки на складе на март 2021",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"что ты умеешь?",
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"а ты помнишь, что мы по этой позиции уже выяснили?",
"покажи еще раз остатки на эту же дату",
"кто нам должен на март 2020",
"остатки по складу на эту же дату",
"а исторические остатки тоже можешь?",
"хвосты покажи по счету 60 на август 2022"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "привет, как дела?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "по какой компании мы сейчас работаем?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "покажи все документы по чепурнову",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "что нам отгружал чепурнов, какой товар или услугу?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "какие остатки на складе на март 2021",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "что ты умеешь?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "а ты помнишь, что мы по этой позиции уже выяснили?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "покажи еще раз остатки на эту же дату",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "кто нам должен на март 2020",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "остатки по складу на эту же дату",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "а исторические остатки тоже можешь?",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "хвосты покажи по счету 60 на август 2022",
"created_at": "2026-04-17T09:41:32+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
"architecture_phase": "turnaround_11_phase7",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
}
}
}

View File

@ -0,0 +1,67 @@
{
"suite_id": "assistant_saved_session_gen-ag04170931-6bb7e5",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-17T09:31:44+00:00",
"generation_id": "gen-ag04170931-6bb7e5",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"domain": "address_phase7_meta_domain_mix",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "привет, как дела?"
},
{
"user_message": "по какой компании мы сейчас работаем?"
},
{
"user_message": "покажи все документы по чепурнову"
},
{
"user_message": "что нам отгружал чепурнов, какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на март 2021"
},
{
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
},
{
"user_message": "что ты умеешь?"
},
{
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции"
},
{
"user_message": "а ты помнишь, что мы по этой позиции уже выяснили?"
},
{
"user_message": "покажи еще раз остатки на эту же дату"
},
{
"user_message": "кто нам должен на март 2020"
},
{
"user_message": "остатки по складу на эту же дату"
},
{
"user_message": "а исторические остатки тоже можешь?"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
}
]
}
]
}

View File

@ -0,0 +1,67 @@
{
"suite_id": "assistant_saved_session_gen-ag04170941-87680e",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-17T09:41:32+00:00",
"generation_id": "gen-ag04170941-87680e",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"domain": "address_phase7_meta_domain_mix",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "привет, как дела?"
},
{
"user_message": "по какой компании мы сейчас работаем?"
},
{
"user_message": "покажи все документы по чепурнову"
},
{
"user_message": "что нам отгружал чепурнов, какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на март 2021"
},
{
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
},
{
"user_message": "что ты умеешь?"
},
{
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции"
},
{
"user_message": "а ты помнишь, что мы по этой позиции уже выяснили?"
},
{
"user_message": "покажи еще раз остатки на эту же дату"
},
{
"user_message": "кто нам должен на март 2020"
},
{
"user_message": "остатки по складу на эту же дату"
},
{
"user_message": "а исторические остатки тоже можешь?"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
}
]
}
]
}

View File

@ -0,0 +1,505 @@
from __future__ import annotations
import argparse
import json
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parent.parent
ORCHESTRATION_DIR = REPO_ROOT / "docs" / "orchestration"
SAVED_SESSIONS_DIR = REPO_ROOT / "llm_normalizer" / "data" / "autorun_generators" / "saved_sessions"
TRUTH_HARNESS_GLOB = "address_truth_harness*.json"
CATALOG_SCHEMA_VERSION = "agent_semantic_source_catalog_v1"
INVENTORY_KEYWORDS = ("остатк", "склад", "41 счет")
META_SCOPE_KEYWORDS = ("по какой компании", "по какой организации", "по общей базе", "по какой базе")
META_CAPABILITY_KEYWORDS = ("что ты умеешь", "что можешь", "умеешь")
META_MEMORY_KEYWORDS = ("ты помнишь", "что мы уже выяснили", "по этой позиции уже")
META_HISTORICAL_KEYWORDS = ("историческ",)
SMALLTALK_KEYWORDS = ("привет", "как дела", "йо", "че как", "приветик")
COUNTERPARTY_DOCS_KEYWORDS = ("покажи все документы", "покажи все доки")
COUNTERPARTY_SHIPMENT_KEYWORDS = ("что нам отгружал", "какой товар или услугу")
ACCOUNT_60_KEYWORDS = ("хвосты по счету 60",)
RECEIVABLES_KEYWORDS = ("кто нам должен",)
VAT_KEYWORDS = ("ндс",)
RECIPE_LIBRARY: dict[str, dict[str, Any]] = {
"turnaround_11_phase7_meta_domain_mix": {
"scenario_id": "address_truth_harness_phase7_meta_domain_mix",
"domain": "address_phase7_meta_domain_mix",
"title": "Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
"description": (
"Mixed AGENT replay for turnaround 11. The pack interleaves counterparty documents, "
"inventory root and selected-object continuity, meta-space interruptions, memory recap, "
"receivables to inventory same-date pivot, and an account 60 tail check."
),
"bindings": {},
"step_plan": [
{
"slot_id": "slot_01_smalltalk",
"criticality": "info",
"preferred_candidate_ids": [
"address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk",
"address_truth_harness_test2:step_01_chat_opening",
],
"required_tags": ["meta_smalltalk"],
},
{
"slot_id": "slot_03_counterparty_documents",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents",
"address_truth_harness_targeted_counterparty_tails:step_01_documents_by_counterparty",
],
"required_tags": ["counterparty_documents"],
},
{
"slot_id": "slot_04_counterparty_shipment_fallback",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback",
"address_truth_harness_targeted_counterparty_tails:step_02_counterparty_item_flow",
],
"required_tags": ["counterparty_shipment_fallback"],
},
{
"slot_id": "slot_05_inventory_root",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021",
"address_truth_harness_phase5_meta_memory_mix:step_01_inventory_root_march_2021",
],
"required_tags": ["inventory_root"],
},
{
"slot_id": "slot_06_selected_object_supplier",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier",
"address_truth_harness_inventory_provenance_restore:step_02_selected_item_supplier",
],
"required_tags": ["selected_object_supplier"],
},
{
"slot_id": "slot_07_meta_capability",
"criticality": "warning",
"preferred_candidate_ids": [
"address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt",
"address_truth_harness_phase6_provider_axis_mix:step_03_capability_meta",
],
"required_tags": ["meta_capability"],
},
{
"slot_id": "slot_08_selected_object_documents",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents",
"address_truth_harness_inventory_provenance_restore:step_03_selected_item_documents",
],
"required_tags": ["selected_object_documents"],
},
{
"slot_id": "slot_09_meta_memory",
"criticality": "warning",
"preferred_candidate_ids": [
"address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts",
],
"required_tags": ["meta_memory"],
"override_fields": {
"required_answer_patterns_all": [
"(?i)помню",
"(?i)столешница 600\\*3050\\*26 альмандин"
]
},
},
{
"slot_id": "slot_10_same_date_restore",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore",
"address_truth_harness_phase4_coverage_evidence_mix:step_05_inventory_same_date_restore",
],
"required_tags": ["same_date_restore"],
},
{
"slot_id": "slot_11_receivables_root",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_test2:step_02_receivables_march_2020",
],
"required_tags": ["settlements_receivables"],
},
{
"slot_id": "slot_12_same_date_pivot",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_test2:step_03_inventory_same_date",
],
"required_tags": ["same_date_pivot"],
},
{
"slot_id": "slot_13_meta_historical",
"criticality": "warning",
"preferred_candidate_ids": [
"address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup",
"address_truth_harness_phase5_meta_memory_mix:step_02_inventory_history_capability_followup",
],
"required_tags": ["meta_historical_capability"],
},
{
"slot_id": "slot_14_account_60",
"criticality": "critical",
"preferred_candidate_ids": [
"address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60",
],
"required_tags": ["settlements_account_60"],
},
{
"slot_id": "slot_15_meta_scope",
"criticality": "warning",
"preferred_candidate_ids": [
"address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta",
"address_truth_harness_phase5_meta_memory_mix:step_03_data_scope_meta_interrupt",
],
"required_tags": ["meta_scope"],
},
],
}
}
def read_json(path: Path) -> Any:
return json.loads(path.read_text(encoding="utf-8-sig"))
def write_json(path: Path, payload: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
def write_text(path: Path, content: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8", newline="\n")
def _now_iso() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
def _normalize_tags(raw_tags: list[str]) -> list[str]:
ordered: list[str] = []
seen: set[str] = set()
for raw_tag in raw_tags:
tag = str(raw_tag or "").strip().lower()
if not tag or tag in seen:
continue
seen.add(tag)
ordered.append(tag)
return ordered
def _contains_any(text: str, needles: tuple[str, ...]) -> bool:
normalized = text.casefold()
return any(needle.casefold() in normalized for needle in needles)
def _base_step_tags(question: str, step_id: str, title: str, expected_intents: list[str]) -> list[str]:
haystack = " | ".join([question, step_id, title, " ".join(expected_intents)])
tags: list[str] = []
if _contains_any(haystack, SMALLTALK_KEYWORDS):
tags.append("meta_smalltalk")
if _contains_any(haystack, META_SCOPE_KEYWORDS) or "data_scope" in step_id:
tags.append("meta_scope")
if _contains_any(haystack, META_CAPABILITY_KEYWORDS) or "capability_meta" in step_id:
tags.append("meta_capability")
if _contains_any(haystack, META_MEMORY_KEYWORDS) or "memory" in step_id:
tags.append("meta_memory")
if _contains_any(haystack, META_HISTORICAL_KEYWORDS) or "historical" in step_id:
tags.append("meta_historical_capability")
if _contains_any(haystack, COUNTERPARTY_DOCS_KEYWORDS) or "counterparty_documents" in step_id or "documents_by_counterparty" in step_id:
tags.append("counterparty_documents")
if _contains_any(haystack, COUNTERPARTY_SHIPMENT_KEYWORDS) or "counterparty_shipments" in step_id or "counterparty_item_flow" in step_id:
tags.append("counterparty_shipment_fallback")
if any(intent == "inventory_on_hand_as_of_date" for intent in expected_intents) or _contains_any(haystack, INVENTORY_KEYWORDS):
tags.append("inventory_root")
if "same_date_restore" in step_id or "same_date_restore" in title.casefold():
tags.append("same_date_restore")
if "inventory_same_date" in step_id and "restore" not in step_id:
tags.append("same_date_pivot")
if any(intent == "receivables_confirmed_as_of_date" for intent in expected_intents) or _contains_any(haystack, RECEIVABLES_KEYWORDS):
tags.append("settlements_receivables")
if "open_items_account_60" in step_id or _contains_any(haystack, ACCOUNT_60_KEYWORDS):
tags.append("settlements_account_60")
if "vat" in step_id or _contains_any(haystack, VAT_KEYWORDS):
tags.append("vat")
if any(intent == "inventory_purchase_provenance_for_item" for intent in expected_intents):
tags.extend(["selected_object", "selected_object_supplier"])
if any(intent == "inventory_purchase_documents_for_item" for intent in expected_intents):
tags.extend(["selected_object", "selected_object_documents"])
if any(intent == "inventory_sale_trace_for_item" for intent in expected_intents):
tags.extend(["selected_object", "selected_object_sale"])
if "selected_item" in step_id or "по выбранному объекту" in question.casefold():
tags.append("selected_object")
return _normalize_tags(tags)
def classify_truth_harness_step(spec_path: Path, spec: dict[str, Any], step: dict[str, Any]) -> dict[str, Any]:
question = str(step.get("question") or "").strip()
step_id = str(step.get("step_id") or "").strip()
title = str(step.get("title") or step_id).strip() or step_id
expected_intents = [str(item).strip() for item in (step.get("expected_intents") or []) if str(item).strip()]
semantic_tags = _normalize_tags(
[*step.get("semantic_tags", []), *_base_step_tags(question, step_id, title, expected_intents)]
)
return {
"entry_id": f"{spec_path.stem}:{step_id}",
"source_type": "truth_harness_step",
"source_file": str(spec_path.relative_to(REPO_ROOT)).replace("\\", "/"),
"source_title": spec.get("title"),
"scenario_id": spec.get("scenario_id"),
"domain": spec.get("domain"),
"reusable_in_agent_pack": True,
"step_id": step_id,
"title": title,
"question": question,
"criticality": str(step.get("criticality") or "critical"),
"expected_intents": expected_intents,
"semantic_tags": semantic_tags,
"step_payload": step,
}
def classify_saved_session_question(session_path: Path, session: dict[str, Any], question: str, index: int) -> dict[str, Any]:
tags = _base_step_tags(
question=question,
step_id=f"saved_session_q{index:02d}",
title=str(session.get("title") or ""),
expected_intents=[],
)
return {
"entry_id": f"{session_path.stem}:q{index:02d}",
"source_type": "saved_session_question",
"source_file": str(session_path.relative_to(REPO_ROOT)).replace("\\", "/"),
"source_title": session.get("title"),
"scenario_id": session.get("generation_id"),
"domain": session.get("domain"),
"reusable_in_agent_pack": False,
"step_id": f"saved_session_q{index:02d}",
"title": f"Saved session question {index}",
"question": question,
"criticality": "info",
"expected_intents": [],
"semantic_tags": tags,
"session_mode": session.get("mode"),
"agent_run": bool(session.get("agent_run") or (session.get("context") or {}).get("agent_run")),
}
def build_source_catalog() -> dict[str, Any]:
truth_harness_entries: list[dict[str, Any]] = []
saved_session_entries: list[dict[str, Any]] = []
for spec_path in sorted(ORCHESTRATION_DIR.glob(TRUTH_HARNESS_GLOB)):
spec = read_json(spec_path)
for step in spec.get("steps") or []:
if isinstance(step, dict) and step.get("question"):
truth_harness_entries.append(classify_truth_harness_step(spec_path, spec, step))
for session_path in sorted(SAVED_SESSIONS_DIR.glob("*.json")):
session = read_json(session_path)
for index, question in enumerate(session.get("questions") or [], start=1):
text = str(question or "").strip()
if text:
saved_session_entries.append(classify_saved_session_question(session_path, session, text, index))
tag_counter: Counter[str] = Counter()
reusable_tag_counter: Counter[str] = Counter()
for entry in [*truth_harness_entries, *saved_session_entries]:
for tag in entry.get("semantic_tags") or []:
tag_counter[tag] += 1
if entry.get("reusable_in_agent_pack"):
reusable_tag_counter[tag] += 1
return {
"schema_version": CATALOG_SCHEMA_VERSION,
"generated_at": _now_iso(),
"summary": {
"truth_harness_steps_total": len(truth_harness_entries),
"saved_session_questions_total": len(saved_session_entries),
"reusable_truth_harness_tags": dict(sorted(reusable_tag_counter.items())),
"all_tags": dict(sorted(tag_counter.items())),
},
"truth_harness_entries": truth_harness_entries,
"saved_session_entries": saved_session_entries,
}
def _catalog_markdown(catalog: dict[str, Any]) -> str:
summary = catalog.get("summary") if isinstance(catalog.get("summary"), dict) else {}
lines = [
"# Agent semantic source catalog",
"",
f"- truth_harness_steps_total: `{summary.get('truth_harness_steps_total', 0)}`",
f"- saved_session_questions_total: `{summary.get('saved_session_questions_total', 0)}`",
"",
"## Reusable truth-harness tags",
]
reusable_tags = summary.get("reusable_truth_harness_tags") or {}
for tag, count in reusable_tags.items():
lines.append(f"- `{tag}`: `{count}`")
lines.extend(["", "## Reusable truth-harness steps"])
for entry in catalog.get("truth_harness_entries") or []:
tags = ", ".join(entry.get("semantic_tags") or []) or "none"
lines.append(
f"- `{entry.get('entry_id')}` | tags: {tags} | question: {entry.get('question')}"
)
lines.extend(["", "## Saved session questions"])
for entry in catalog.get("saved_session_entries") or []:
tags = ", ".join(entry.get("semantic_tags") or []) or "none"
lines.append(
f"- `{entry.get('entry_id')}` | tags: {tags} | question: {entry.get('question')}"
)
return "\n".join(lines).strip() + "\n"
def _find_catalog_entry(catalog: dict[str, Any], entry_id: str) -> dict[str, Any] | None:
for entry in catalog.get("truth_harness_entries") or []:
if entry.get("entry_id") == entry_id:
return entry
return None
def _entry_matches_tags(entry: dict[str, Any], required_tags: list[str]) -> bool:
entry_tags = set(entry.get("semantic_tags") or [])
return all(tag in entry_tags for tag in required_tags)
def _select_recipe_entry(
catalog: dict[str, Any],
slot: dict[str, Any],
used_entry_ids: set[str],
) -> dict[str, Any]:
preferred_ids = [str(item).strip() for item in (slot.get("preferred_candidate_ids") or []) if str(item).strip()]
required_tags = [str(item).strip().lower() for item in (slot.get("required_tags") or []) if str(item).strip()]
for entry_id in preferred_ids:
entry = _find_catalog_entry(catalog, entry_id)
if entry and entry.get("reusable_in_agent_pack") and entry_id not in used_entry_ids:
if not required_tags or _entry_matches_tags(entry, required_tags):
return entry
candidates = [
entry
for entry in catalog.get("truth_harness_entries") or []
if entry.get("reusable_in_agent_pack")
and entry.get("entry_id") not in used_entry_ids
and _entry_matches_tags(entry, required_tags)
]
if not candidates:
raise RuntimeError(
f"Could not resolve slot `{slot.get('slot_id')}` with tags {required_tags or ['<none>']}"
)
return candidates[0]
def build_recipe_spec(catalog: dict[str, Any], recipe_name: str) -> dict[str, Any]:
recipe = RECIPE_LIBRARY.get(recipe_name)
if recipe is None:
supported = ", ".join(sorted(RECIPE_LIBRARY))
raise RuntimeError(f"Unknown recipe `{recipe_name}`. Supported recipes: {supported}")
used_entry_ids: set[str] = set()
steps: list[dict[str, Any]] = []
for slot_index, slot in enumerate(recipe.get("step_plan") or [], start=1):
entry = _select_recipe_entry(catalog, slot, used_entry_ids)
used_entry_ids.add(str(entry["entry_id"]))
payload = dict(entry["step_payload"])
payload["criticality"] = str(slot.get("criticality") or payload.get("criticality") or "critical")
payload["semantic_tags"] = _normalize_tags(
[*payload.get("semantic_tags", []), *(entry.get("semantic_tags") or [])]
)
override_fields = slot.get("override_fields")
if isinstance(override_fields, dict):
payload.update(override_fields)
payload["notes"] = (
f"{str(payload.get('notes') or '').strip()} "
f"[mixed_pack_slot={slot.get('slot_id')} source={entry.get('entry_id')}]"
).strip()
steps.append(payload)
return {
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": recipe["scenario_id"],
"domain": recipe["domain"],
"title": recipe["title"],
"description": recipe["description"],
"bindings": recipe.get("bindings") or {},
"steps": steps,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Inventory reusable semantic sources and build mixed AGENT replay packs.")
subparsers = parser.add_subparsers(dest="command", required=True)
inventory_parser = subparsers.add_parser("inventory", help="Build a source catalog from harness specs and saved sessions.")
inventory_parser.add_argument(
"--output-json",
default="docs/orchestration/agent_semantic_source_catalog.json",
help="Where to write the catalog JSON.",
)
inventory_parser.add_argument(
"--output-md",
default="docs/orchestration/agent_semantic_source_catalog.md",
help="Where to write the catalog markdown summary.",
)
build_parser = subparsers.add_parser("build-pack", help="Build a mixed truth-harness pack from catalogued sources.")
build_parser.add_argument("--recipe", required=True, help="Recipe name to build.")
build_parser.add_argument(
"--output-spec",
default="docs/orchestration/address_truth_harness_phase7_meta_domain_mix.json",
help="Where to write the generated truth-harness spec.",
)
return parser.parse_args()
def _resolve_repo_path(raw_path: str) -> Path:
path = Path(raw_path)
return path if path.is_absolute() else (REPO_ROOT / path).resolve()
def run_inventory(args: argparse.Namespace) -> int:
catalog = build_source_catalog()
output_json = _resolve_repo_path(args.output_json)
output_md = _resolve_repo_path(args.output_md)
write_json(output_json, catalog)
write_text(output_md, _catalog_markdown(catalog))
print(f"Catalog written to {output_json}")
print(f"Summary written to {output_md}")
print(
"Reusable truth-harness tags:",
", ".join(sorted((catalog.get("summary") or {}).get("reusable_truth_harness_tags", {}).keys())),
)
return 0
def run_build_pack(args: argparse.Namespace) -> int:
catalog = build_source_catalog()
spec = build_recipe_spec(catalog, args.recipe)
output_spec = _resolve_repo_path(args.output_spec)
write_json(output_spec, spec)
print(f"Mixed pack written to {output_spec}")
print(f"scenario_id={spec['scenario_id']}")
print(f"steps={len(spec['steps'])}")
return 0
def main() -> int:
args = parse_args()
if args.command == "inventory":
return run_inventory(args)
if args.command == "build-pack":
return run_build_pack(args)
raise RuntimeError(f"Unsupported command: {args.command}")
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -84,6 +84,7 @@ def normalize_step_spec(index: int, raw_step: Any) -> dict[str, Any]:
normalized_step = dcl.normalize_step_definition(index, raw_step)
step = raw_step if isinstance(raw_step, dict) else {}
normalized_step["criticality"] = normalize_criticality(step.get("criticality"))
normalized_step["semantic_tags"] = dcl.normalize_string_list(step.get("semantic_tags"))
normalized_step["allowed_reply_types"] = normalize_pattern_list(step.get("allowed_reply_types"))
normalized_step["allowed_limited_reason_categories"] = normalize_pattern_list(
step.get("allowed_limited_reason_categories")
@ -219,6 +220,7 @@ def build_generated_manifest(spec: dict[str, Any]) -> dict[str, Any]:
}
for field_name in TECHNICAL_QUESTION_FIELDS:
manifest_step[field_name] = step.get(field_name)
manifest_step["semantic_tags"] = step.get("semantic_tags") or []
manifest_steps.append(manifest_step)
previous_step_id = step["step_id"]
return {

View File

@ -21,6 +21,13 @@ SELECTED_OBJECT_INTENTS = {
"inventory_profitability_for_item",
"inventory_purchase_to_sale_chain",
}
META_CONTEXT_TAGS = {
"meta_smalltalk",
"meta_scope",
"meta_capability",
"meta_memory",
"meta_historical_capability",
}
def _now_iso() -> str:
@ -44,18 +51,28 @@ def _priority_from_finding(finding: dict[str, Any]) -> str:
def _highest_priority(findings: list[dict[str, Any]]) -> str:
if not findings:
return "none"
return "none"
priorities = [_priority_from_finding(item) for item in findings]
return sorted(priorities, key=lambda item: PRIORITY_RANK.get(item, 99))[0]
def _normalize_semantic_tags(step: dict[str, Any]) -> set[str]:
raw = step.get("semantic_tags")
if not isinstance(raw, list):
return set()
return {str(item).strip().lower() for item in raw if str(item).strip()}
def _has_selected_object_signal(step: dict[str, Any]) -> bool:
question = str(step.get("question_template") or "").lower()
semantic_tags = _normalize_semantic_tags(step)
expected_intents = {
str(item).strip()
for item in (step.get("expected_intents") or [])
if str(item).strip()
}
if "selected_object" in semantic_tags:
return True
if expected_intents & SELECTED_OBJECT_INTENTS:
return True
return any(
@ -70,6 +87,19 @@ def _has_selected_object_signal(step: dict[str, Any]) -> bool:
)
def _has_meta_context_signal(step: dict[str, Any]) -> bool:
semantic_tags = _normalize_semantic_tags(step)
if semantic_tags & META_CONTEXT_TAGS:
return True
title = str(step.get("title") or "").lower()
step_id = str(step.get("step_id") or "").lower()
question = str(step.get("question_template") or "").lower()
return any(
marker in f"{step_id} {title} {question}"
for marker in ("meta", "memory", "smalltalk", "историческ", "что ты умеешь", "что можешь", "по какой компании", "по какой базе")
)
def _is_direct_answer_code(code: str) -> bool:
return code.startswith("required_direct_answer_") or code.startswith("forbidden_direct_answer_")
@ -105,15 +135,26 @@ def _is_human_answer_quality_code(code: str) -> bool:
}
def _is_meta_context_code(code: str) -> bool:
return (
_is_route_code(code)
or _is_truth_gate_code(code)
or _is_human_answer_quality_code(code)
or _is_direct_answer_code(code)
)
def _derive_step_invariant_failures(step: dict[str, Any], findings: list[dict[str, Any]]) -> dict[str, bool]:
codes = [str(item.get("code") or "").strip() for item in findings]
selected_object_step = _has_selected_object_signal(step)
meta_context_step = _has_meta_context_signal(step)
return {
"direct_answer": any(_is_direct_answer_code(code) for code in codes),
"temporal_honesty": any(_is_temporal_code(code) for code in codes),
"selected_object_continuity": selected_object_step and any(_is_route_code(code) for code in codes),
"truth_gate": any(_is_truth_gate_code(code) for code in codes),
"human_answer_quality": any(_is_human_answer_quality_code(code) for code in codes),
"meta_context_integrity": meta_context_step and any(_is_meta_context_code(code) for code in codes),
}
@ -129,6 +170,7 @@ def build_scenario_acceptance_matrix(
"selected_object_continuity": 0,
"truth_gate": 0,
"human_answer_quality": 0,
"meta_context_integrity": 0,
}
for index, step in enumerate(spec.get("steps") or [], start=1):
@ -151,11 +193,13 @@ def build_scenario_acceptance_matrix(
"title": step.get("title"),
"question": step.get("question_template"),
"criticality": str(step.get("criticality") or "critical"),
"semantic_tags": sorted(_normalize_semantic_tags(step)),
"review_status": str(step_state.get("review_status") or "unknown"),
"reply_type": step_state.get("reply_type"),
"detected_intent": step_state.get("detected_intent"),
"capability_id": step_state.get("capability_id"),
"selected_object_step": _has_selected_object_signal(step),
"meta_context_step": _has_meta_context_signal(step),
"highest_unresolved_priority": highest_priority,
"unresolved_findings_count": len(findings),
"invariant_failures": [name for name, failed in invariant_failures.items() if failed],
@ -169,6 +213,7 @@ def build_scenario_acceptance_matrix(
"selected_object_continuity_ok": invariant_failure_counts["selected_object_continuity"] == 0,
"truth_gate_ok": invariant_failure_counts["truth_gate"] == 0,
"human_answer_quality_ok": invariant_failure_counts["human_answer_quality"] == 0,
"meta_context_integrity_ok": invariant_failure_counts["meta_context_integrity"] == 0,
}
critical_rows = [row for row in rows if row["criticality"] == "critical"]
critical_path_green = bool(critical_rows) and all(row["review_status"] == "pass" for row in critical_rows)
@ -274,6 +319,7 @@ def build_scenario_acceptance_matrix_markdown(acceptance_matrix: dict[str, Any])
f"- selected_object_continuity_ok: `{invariants.get('selected_object_continuity_ok')}`",
f"- truth_gate_ok: `{invariants.get('truth_gate_ok')}`",
f"- human_answer_quality_ok: `{invariants.get('human_answer_quality_ok')}`",
f"- meta_context_integrity_ok: `{invariants.get('meta_context_integrity_ok')}`",
"",
"## Steps",
]
@ -283,8 +329,10 @@ def build_scenario_acceptance_matrix_markdown(acceptance_matrix: dict[str, Any])
f"- `{row.get('step_id')}`",
f" review_status: `{row.get('review_status')}`",
f" criticality: `{row.get('criticality')}`",
f" semantic_tags: {', '.join(row.get('semantic_tags') or []) or 'none'}",
f" highest_unresolved_priority: `{row.get('highest_unresolved_priority')}`",
f" selected_object_step: `{row.get('selected_object_step')}`",
f" meta_context_step: `{row.get('meta_context_step')}`",
f" invariant_failures: {', '.join(row.get('invariant_failures') or []) or 'none'}",
]
)
@ -303,4 +351,6 @@ def build_truth_harness_final_status_markdown(pack_state: dict[str, Any]) -> str
f"- temporal_honesty_ok: `{invariants.get('temporal_honesty_ok')}`\n"
f"- selected_object_continuity_ok: `{invariants.get('selected_object_continuity_ok')}`\n"
f"- truth_gate_ok: `{invariants.get('truth_gate_ok')}`\n"
f"- human_answer_quality_ok: `{invariants.get('human_answer_quality_ok')}`\n"
f"- meta_context_integrity_ok: `{invariants.get('meta_context_integrity_ok')}`\n"
)

View File

@ -0,0 +1,38 @@
from __future__ import annotations
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import agent_semantic_pack_builder as builder
class AgentSemanticPackBuilderTests(unittest.TestCase):
def test_build_source_catalog_finds_reusable_meta_and_selected_object_tags(self) -> None:
catalog = builder.build_source_catalog()
summary = catalog["summary"]
self.assertGreater(summary["truth_harness_steps_total"], 0)
self.assertIn("meta_scope", summary["reusable_truth_harness_tags"])
self.assertIn("selected_object_supplier", summary["reusable_truth_harness_tags"])
self.assertIn("counterparty_documents", summary["reusable_truth_harness_tags"])
def test_build_recipe_spec_creates_mixed_phase7_pack(self) -> None:
catalog = builder.build_source_catalog()
spec = builder.build_recipe_spec(catalog, "turnaround_11_phase7_meta_domain_mix")
self.assertEqual(spec["scenario_id"], "address_truth_harness_phase7_meta_domain_mix")
self.assertGreaterEqual(len(spec["steps"]), 10)
all_tags = {tag for step in spec["steps"] for tag in step.get("semantic_tags", [])}
self.assertIn("meta_scope", all_tags)
self.assertIn("meta_capability", all_tags)
self.assertIn("selected_object_supplier", all_tags)
self.assertIn("same_date_restore", all_tags)
self.assertIn("settlements_receivables", all_tags)
if __name__ == "__main__":
unittest.main()

View File

@ -1,6 +1,11 @@
from __future__ import annotations
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import scenario_acceptance_policy as sap
@ -18,6 +23,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
"question_template": 'По выбранному объекту "Стол": кто поставил?',
"criticality": "critical",
"expected_intents": ["inventory_purchase_provenance_for_item"],
"semantic_tags": ["selected_object"],
}
],
}
@ -52,6 +58,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
self.assertFalse(pack_state["invariants"]["selected_object_continuity_ok"])
self.assertFalse(pack_state["invariants"]["temporal_honesty_ok"])
self.assertEqual(pack_state["unresolved_p0_count"], 2)
self.assertTrue(pack_state["invariants"]["meta_context_integrity_ok"])
def test_accepts_when_all_review_and_acceptance_invariants_are_green(self) -> None:
spec = {
@ -65,6 +72,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
"question_template": "какие остатки на складе на март 2021",
"criticality": "critical",
"expected_intents": ["inventory_on_hand_as_of_date"],
"semantic_tags": ["inventory_root"],
}
],
}
@ -97,6 +105,52 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
self.assertTrue(pack_state["critical_path_green"])
self.assertTrue(all(pack_state["invariants"].values()))
def test_flags_meta_context_integrity_when_meta_step_leaks_technical_answer_shape(self) -> None:
spec = {
"scenario_id": "demo_phase7_meta",
"domain": "inventory_demo",
"title": "Demo meta",
"steps": [
{
"step_id": "step_meta",
"title": "Capability meta",
"question_template": "что ты умеешь?",
"criticality": "warning",
"semantic_tags": ["meta_capability"],
}
],
}
scenario_state = {
"session_id": "asst-meta",
"step_outputs": {
"step_meta": {
"review_status": "warning",
"reply_type": "factual_with_explanation",
"detected_intent": None,
"capability_id": None,
"review_findings": [
{"code": "forbidden_answer_pattern_hit", "severity": "warning"},
],
}
},
}
review_summary = {
"review_source": "live_strict_replay",
"overall_status": "warning",
"steps_total": 1,
"steps_passed": 0,
"steps_with_warning": 1,
"steps_failed": 0,
}
acceptance_matrix = sap.build_scenario_acceptance_matrix(spec, scenario_state, review_summary)
pack_state = sap.derive_truth_harness_pack_state(spec, scenario_state, review_summary, acceptance_matrix)
self.assertFalse(pack_state["invariants"]["meta_context_integrity_ok"])
row = acceptance_matrix["rows"][0]
self.assertTrue(row["meta_context_step"])
self.assertIn("meta_context_integrity", row["invariant_failures"])
if __name__ == "__main__":
unittest.main()